/*! * * 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 AgentMessage.h * \author (original) Stephen Quong * \date (original) 24-May-2026 * */ #pragma once #include /*! * \brief LeahiRt to Connectivity Agent message framing * \details Transport-agnostic binary framing. build() makes a wire-ready frame; * feed() parses inbound bytes (see FeedResult). On Complete, read * msgId()/sequence()/payload(), then reset(). * * Frame layout — header only (payload_length == 0): * * Byte: 0 1 2 3 4 5 6-9 10 11 * ┌─────────┬────────┬────────┬───────────┬────────┐ * │ AA 55 │ msg_id │ seq_num│ pay_length│hdr_crc │ * │ sync │uint16BE│uint16BE│ uint32 BE │uint16BE│ * └─────────┴────────┴────────┴───────────┴────────┘ * * Frame layout — with payload (payload_length > 0): * * ┌── 12-byte header ──┬── N bytes payload ──┬── pay_crc (4 B) ──┐ * │ (see above) │ uint8[] │ CRC-32/ISO-HDLC │ * └────────────────────┴─────────────────────┴───────────────────┘ * * Header CRC: CRC-16/CCITT (poly 0x1021, init 0xFFFF, no reflection). * Payload CRC: CRC-32/ISO-HDLC (IEEE 802.3, reflected poly 0xEDB88320). */ class AgentMessage { public: /*! * \brief MQTT topic identifier carried in every frame header * \details The Connectivity Agent uses this value to determine the MQTT topic. */ enum class MsgId : quint16 { ClinicalData = 0x0001, Diagnostic = 0x0002, Ack = 0x0003, Alarms = 0x0004, Audit = 0x0005, DeviceLogFile = 0x0006, TreatmentLogFile = 0x0007, CloudSyncLogFile = 0x0008, }; /*! * \brief Result returned by feed() after processing each byte chunk */ enum class FeedResult { Incomplete, ///< More bytes needed — continue feeding. Complete, ///< Full valid frame assembled — read accessors, then call reset(). HeaderError, ///< Header CRC mismatch — frame dropped, state reset automatically. PayloadError, ///< Payload CRC mismatch or oversized payload — frame dropped, state reset automatically. }; static QByteArray build(MsgId msgId, quint16 sequence, const QByteArray &payload = {}); FeedResult feed(QByteArray &bytes); /*! * \brief AgentMessage::msgId * \details Message identifier of the last complete frame. * Valid only after feed() returns FeedResult::Complete. * \return MsgId of the last complete frame */ MsgId msgId() const { return _rxMsgId; } /*! * \brief AgentMessage::sequence * \details Sequence number of the last complete frame. * Valid only after feed() returns FeedResult::Complete. * \return sequence number of the last complete frame */ quint16 sequence() const { return _rxSequence; } /*! * \brief AgentMessage::payload * \details Payload bytes of the last complete frame. Empty for zero-length frames. * Valid only after feed() returns FeedResult::Complete. * \return payload of the last complete frame */ QByteArray payload() const { return _rxPayload; } void reset(); private: static quint16 crc16ccitt(const quint8 *data, int len); static quint32 crc32isohdlc(const quint8 *data, int len); static constexpr int SYNC_SIZE = 2; static constexpr quint8 SYNC[SYNC_SIZE] = {0xAA, 0x55}; static constexpr int HEADER_SIZE = 12; static constexpr int MSGID_SIZE = 2; static constexpr int SEQUENCE_SIZE = 2; static constexpr int HEADER_CRC_SIZE = 2; static constexpr int PAYLOAD_CRC_SIZE = 4; static constexpr quint32 MAX_PAYLOAD_LEN = 64 * 1024; QByteArray _headerBuf; MsgId _rxMsgId = MsgId::ClinicalData; quint16 _rxSequence = 0; quint32 _rxPayloadLen = 0; QByteArray _rxPayload; };