/*! * * Copyright (c) 2026 Diality Inc. - All Rights Reserved. * \copyright * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * \file MqttPublisher.cpp * \author (original) Stephen Quong * \date (original) 30-Jul-2026 * */ #include #include #include "MqttPublisher.h" Q_LOGGING_CATEGORY(lcMqtt, "cloudconnect.mqtt") /*! * \brief MqttPublisher::MqttPublisher * \details Constructor * \param parent - optional QObject parent */ MqttPublisher::MqttPublisher(QObject *parent) : QObject(parent) { } /*! * \brief MqttPublisher::~MqttPublisher * \details Stops callbacks reaching this object, then closes the session. */ MqttPublisher::~MqttPublisher() { if (_alive != nullptr) { _alive->storeRelease(0); } if (_transport != nullptr) { _transport->clearCallbacks(); _transport->close(); } } /*! * \brief MqttPublisher::init * \details Attaches the transport and installs the callbacks. * \param transport - the MQTT client to publish through * \param config - endpoint and mTLS credentials * \return true if the transport was accepted. * \note Callbacks are installed before any connect so no event can be missed. */ bool MqttPublisher::init(QSharedPointer transport, const MqttTransport::Config &config) { if (transport == nullptr) { qCCritical(lcMqtt) << "init() requires a transport"; return false; } _transport = transport; _config = config; _alive = QSharedPointer>::create(1); const QSharedPointer> alive = _alive; _transport->setCallbacks( [this, alive](bool connected) { if (alive->loadAcquire() == 0) { return; } _connected.storeRelease(connected ? 1 : 0); QMetaObject::invokeMethod( this, [this, connected]() { onTransportState(connected); }, Qt::QueuedConnection); }, [this, alive](qint64 messageId, bool success) { if (alive->loadAcquire() == 0) { return; } QMetaObject::invokeMethod( this, [this, messageId, success]() { onTransportAck(messageId, success); }, Qt::QueuedConnection); } ); _init = true; return true; } /*! * \brief MqttPublisher::init * \details Calls init() then moves this object onto thread. * \param transport - the MQTT client to publish through * \param config - endpoint and mTLS credentials * \param thread - the thread to move this object onto * \return true if the transport was accepted. * \note Must be called from the main thread. */ bool MqttPublisher::init(QSharedPointer transport, const MqttTransport::Config &config, QThread &thread) { if (!init(transport, config)) { return false; } initThread(thread); return true; } /*! * \brief MqttPublisher::isConnected * \details Reports whether the MQTT session is currently established. * \return true while connected. */ bool MqttPublisher::isConnected() const { return _connected.loadAcquire() != 0; } /*! * \brief MqttPublisher::open * \details Starts an MQTT connection attempt. * \return true if the attempt was started. */ bool MqttPublisher::open() { if (!_init || _transport == nullptr) { qCCritical(lcMqtt) << "open() before init()"; return false; } qCInfo(lcMqtt).noquote() << "Connecting to" << _config.endpoint << "port" << _config.port << "as" << _config.clientId; if (!_transport->open(_config)) { qCCritical(lcMqtt).noquote() << "Could not start the connection to" << _config.endpoint; return false; } return true; } /*! * \brief MqttPublisher::close * \details Closes the MQTT session. */ void MqttPublisher::close() { if (_transport != nullptr) { _transport->close(); } } /*! * \brief MqttPublisher::publish * \param topic - fully resolved MQTT topic * \param payload - serialised message bytes * \param messageId - caller token echoed back on acknowledgement, or -1 for none * \return true if the transport accepted the message. */ bool MqttPublisher::publish(const QString &topic, const QByteArray &payload, qint64 messageId) { if (_transport == nullptr || !isConnected()) { return false; } if (!_transport->publish(topic, payload, messageId)) { qCWarning(lcMqtt).noquote() << "Transport rejected a publish to" << topic; return false; } return true; } /*! * \brief MqttPublisher::reconnect * \details Swaps the mTLS credentials and identity, then rebuilds the session. * \param certPath - new client certificate, PEM * \param keyPath - new private key, PEM * \param clientId - MQTT client id for the new session * \return true if the new connection attempt was started. */ bool MqttPublisher::reconnect(const QString &certPath, const QString &keyPath, const QString &clientId) { _config.certPath = certPath; _config.keyPath = keyPath; _config.clientId = clientId; close(); return open(); } /*! * \brief MqttPublisher::quit * \details Moves this object back to the main thread for safe destruction. */ void MqttPublisher::quit() { quitThread(); } /*! * \brief MqttPublisher::initThread * \details Moves this object onto thread and starts it. * \param thread - the thread to move this object onto * \note Must be called from the main thread. */ void MqttPublisher::initThread(QThread &thread) { Q_ASSERT_X(QThread::currentThread() == qApp->thread(), __func__, "MqttPublisher::init must be called from the main thread"); thread.setObjectName(QString("%1_Thread").arg(metaObject()->className())); connect(qApp, &QCoreApplication::aboutToQuit, this, &MqttPublisher::quit); moveToThread(&thread); thread.start(); } /*! * \brief MqttPublisher::quitThread * \details Moves this object back to the main thread. */ void MqttPublisher::quitThread() { if (QThread::currentThread() != qApp->thread()) { moveToThread(qApp->thread()); } } /*! * \brief MqttPublisher::onTransportState * \details Reports a connection transition that has been marshalled onto this thread. * \param connected - true when the session came up */ void MqttPublisher::onTransportState(bool connected) { if (connected) { qCInfo(lcMqtt).noquote() << "Connected to" << _config.endpoint; } else { qCWarning(lcMqtt).noquote() << "Disconnected from" << _config.endpoint; } emit didConnectionChange(connected); } /*! * \brief MqttPublisher::onTransportAck * \details Report a publish outcome. * \param messageId - caller token the message carried, or -1 * \param success - true when the broker acknowledged the message */ void MqttPublisher::onTransportAck(qint64 messageId, bool success) { if (!success) { qCWarning(lcMqtt).noquote() << "Publish failed for message id" << messageId; } emit didPublishAck(messageId, success); }