/*! * * 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 AgentSimController.cpp * \author (original) Stephen Quong * \date (original) 24-May-2026 * */ #include "AgentSimController.h" #include /*! * \brief AgentSimController::AgentSimController * \details Constructor. Loads the INI configuration and wires the server newConnection signal. * \param configPath Path to the INI configuration file. * \param parent Optional QObject parent. */ AgentSimController::AgentSimController(const QString &configPath, QObject *parent) : QObject(parent), _settings(configPath, QSettings::IniFormat) { connect(&_server, &QLocalServer::newConnection, this, &AgentSimController::onNewConnection); } /*! * \brief AgentSimController::listen * \details Reads the socket path from the INI configuration, removes any stale * socket file, then starts the server. * \return true on success, false if the server could not bind. */ bool AgentSimController::listen() { const QString socketPath = _settings.value("Socket/LocalSocketName", "/tmp/leahi_rt.sock").toString(); QLocalServer::removeServer(socketPath); if (!_server.listen(socketPath)) { qCritical().noquote() << "AgentSim: failed to listen on" << socketPath << "—" << _server.errorString(); return false; } qInfo().noquote() << "AgentSim: listening on" << socketPath; return true; } /*! * \brief AgentSimController::onNewConnection * \details Accepts the pending connection. If a client is already connected the * new socket is discarded with a warning. */ void AgentSimController::onNewConnection() { if (_client) { qWarning().noquote() << "AgentSim: second connection attempt rejected — already connected"; _server.nextPendingConnection()->deleteLater(); return; } _client = _server.nextPendingConnection(); qInfo().noquote() << "AgentSim: client connected"; connect(_client, &QLocalSocket::readyRead, this, &AgentSimController::onReadyRead); connect(_client, &QLocalSocket::disconnected, this, &AgentSimController::onDisconnected); } /*! * \brief AgentSimController::onDisconnected * \details Releases the client socket and resets inbound parser state. */ void AgentSimController::onDisconnected() { qInfo().noquote() << "AgentSim: client disconnected"; _client->deleteLater(); _client = nullptr; _rxBuf.clear(); _rxMsg.reset(); } /*! * \brief AgentSimController::onReadyRead * \details Appends incoming bytes to the receive buffer and drains it through * the AgentMessage parser. Logs each complete frame and sends an Ack. */ void AgentSimController::onReadyRead() { _rxBuf.append(_client->readAll()); AgentMessage::FeedResult result; do { result = _rxMsg.feed(_rxBuf); switch (result) { case AgentMessage::FeedResult::Complete: logMessage(_rxMsg.msgId(), _rxMsg.sequence(), _rxMsg.payload()); sendAck(_rxMsg.sequence()); _rxMsg.reset(); break; case AgentMessage::FeedResult::HeaderError: qWarning().noquote() << "AgentSim: header CRC error — frame dropped"; break; case AgentMessage::FeedResult::PayloadError: qWarning().noquote() << "AgentSim: payload CRC error — frame dropped"; break; case AgentMessage::FeedResult::Incomplete: break; } } while (result == AgentMessage::FeedResult::Complete && !_rxBuf.isEmpty()); } /*! * \brief AgentSimController::logMessage * \details Logs the message identifier, sequence number, and payload length. * \param msgId Message identifier from the parsed frame. * \param sequence Sequence number from the parsed frame. * \param payload Payload bytes of the parsed frame. */ void AgentSimController::logMessage(AgentMessage::MsgId msgId, quint16 sequence, const QByteArray &payload) { qInfo().noquote() << QString("AgentSim: rx MsgId=0x%1 seq=%2 payloadLen=%3") .arg(static_cast(msgId), 4, 16, QChar('0')) .arg(sequence) .arg(payload.size()); } /*! * \brief AgentSimController::sendAck * \details Builds an Ack frame carrying \p sequence and writes it to the client socket. * \param sequence Sequence number to echo back in the Ack frame. */ void AgentSimController::sendAck(quint16 sequence) { if (!_client || _client->state() != QLocalSocket::ConnectedState) { return; } const QByteArray frame = AgentMessage::build(AgentMessage::MsgId::Ack, sequence); _client->write(frame); _client->flush(); qInfo().noquote() << QString("AgentSim: tx MsgId=Ack seq=%1").arg(sequence); }