/*! * * Copyright (c) 2024-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 CloudConnectServer.cpp * \author (original) Stephen Quong * \date (original) 15-Jul-2026 * */ #include "CloudConnectServer.h" #include /*! * \brief CloudConnectServer::CloudConnectServer * \details Constructor * \param parent Optional QObject parent. */ CloudConnectServer::CloudConnectServer(QObject *parent) : QObject(parent) { connect(&_server, &QLocalServer::newConnection, this, &CloudConnectServer::onNewConnection); } /*! * \brief CloudConnectServer::listen * \details Removes any stale socket file, then starts the server. * \param socketPath Path to the Unix domain socket (UDS). * \return true on success, false if the server could not bind. */ bool CloudConnectServer::listen(const QString &socketPath) { QLocalServer::removeServer(socketPath); if (!_server.listen(socketPath)) { qCritical().noquote() << metaObject()->className() << ": failed to listen on" << socketPath << "—" << _server.errorString(); return false; } qInfo().noquote() << metaObject()->className() << ": listening on" << socketPath; return true; } /*! * \brief CloudConnectServer::send * \details Builds a CloudConnectFrame and writes it to the connected client. * \param topic MQTT topic for the frame header. * \param sequence Caller-managed sequence number. * \param payload Message payload; pass empty for zero-length frames. * \return true if written to the socket, false if no client is connected. */ bool CloudConnectServer::send(CloudConnectFrame::Topic topic, quint16 sequence, const QByteArray &payload) { if (_client == nullptr || _client->state() != QLocalSocket::ConnectedState) { return false; } const QByteArray frame = CloudConnectFrame::build(topic, sequence, payload); _client->write(frame); _client->flush(); return true; } /*! * \brief CloudConnectServer::isConnected * \details Reports whether a client is currently connected to the server. * \return true if a client is currently connected. */ bool CloudConnectServer::isConnected() const { return _client != nullptr; } /*! * \brief CloudConnectServer::onNewConnection * \details Accepts the pending connection. If a client is already connected the * new socket is discarded with a warning. */ void CloudConnectServer::onNewConnection() { if (_client) { qWarning().noquote() << metaObject()->className() << ": previous client connected, new client connection refused"; _server.nextPendingConnection()->deleteLater(); return; } _client = _server.nextPendingConnection(); qInfo().noquote() << metaObject()->className() << ": client connected"; connect(_client, &QLocalSocket::readyRead, this, &CloudConnectServer::onReadyRead); connect(_client, &QLocalSocket::disconnected, this, &CloudConnectServer::onDisconnected); emit didConnect(); } /*! * \brief CloudConnectServer::onDisconnected * \details Releases the client socket and resets incoming frame state. */ void CloudConnectServer::onDisconnected() { qInfo().noquote() << metaObject()->className() << ": client disconnected"; _client->deleteLater(); _client = nullptr; _rxBuf.clear(); _rxFrame.reset(); emit didDisconnect(); } /*! * \brief CloudConnectServer::onReadyRead * \details Handler for incoming data from the client. */ void CloudConnectServer::onReadyRead() { _rxBuf.append(_client->readAll()); CloudConnectFrame::ReadState state; do { state = _rxFrame.read(_rxBuf); switch (state) { case CloudConnectFrame::ReadState::Complete: emit didMessageReceive(_rxFrame.topic(), _rxFrame.sequence(), _rxFrame.payload()); _rxFrame.reset(); break; case CloudConnectFrame::ReadState::HeaderError: qWarning().noquote() << metaObject()->className() << ": header CRC error — frame dropped"; break; case CloudConnectFrame::ReadState::PayloadError: qWarning().noquote() << metaObject()->className() << ": payload CRC error — frame dropped"; break; case CloudConnectFrame::ReadState::Incomplete: break; } } while (state == CloudConnectFrame::ReadState::Complete && !_rxBuf.isEmpty()); }