Add TCPStreamer/TCPStreamerClient (TCP transport behind the UDPSServerI/UDPSClientI seam), an optional single in-process TCPStreamService serving many streams over worker threads, multiplexed SUBSCRIBE (comma list, stream index in fragmentIdx), and TCPStreamJoin, which merges several streams into one sample-and-hold stream paced by a master. StreamHub takes a comma list of streams over one connection. Includes GTests, E2E scenarios s58-s73 and docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
264 lines
12 KiB
C++
264 lines
12 KiB
C++
/**
|
||
* @file UDPSProtocol.h
|
||
* @brief Shared UDPS binary streaming protocol definitions.
|
||
*
|
||
* This header is free of MARTe2-specific types (other than CompilerTypes.h)
|
||
* so it can be included by both UDPStreamer (DataSource) and DebugService
|
||
* (Interface), as well as any future component that needs to produce or
|
||
* consume UDPS packets.
|
||
*
|
||
* Protocol overview
|
||
* -----------------
|
||
* Every packet starts with a 17-byte packed header (UDPSPacketHeader).
|
||
* Large payloads are split into multiple fragments sharing the same counter.
|
||
*
|
||
* Packet types:
|
||
* DATA (0) - signal sample values, Server → Client
|
||
* CONFIG (1) - signal metadata, Server → Client (sent when set changes)
|
||
* ACK (2) - counter acknowledgment, Client → Server (optional)
|
||
* CONNECT (3) - session initiation, Client → Server
|
||
* DISCONNECT (4) - session teardown, Client → Server or Server → Client
|
||
*
|
||
* CONFIG payload:
|
||
* [uint32 numSigs]
|
||
* numSigs × UDPSSignalDescriptor (136 bytes each, packed)
|
||
* [uint8 publishMode] (PublishModeStrict / Accumulate / Decimate)
|
||
* [uint64 hrtFrequency] ticks per second of the producer's HRT
|
||
*
|
||
* Everything after the descriptors is an optional trailer: a receiver must
|
||
* accept a payload that stops early and must ignore bytes it does not know.
|
||
* publishMode defaults to Strict when absent, hrtFrequency to
|
||
* UDPS_HRT_FREQUENCY_UNKNOWN.
|
||
*
|
||
* DATA payload (Strict / Decimate):
|
||
* [uint64 HRT timestamp]
|
||
* per-signal data in CONFIG order (quantised or raw, no padding)
|
||
*
|
||
* DATA payload (Accumulate):
|
||
* [uint64 HRT timestamp of the first slot in the batch]
|
||
* [uint32 numSamples] RT cycles accumulated into this packet
|
||
* for each signal, in CONFIG order: numSamples × NumElements values
|
||
* (signal-major, one full snapshot per accumulated cycle)
|
||
*/
|
||
|
||
#ifndef UDPS_PROTOCOL_H_
|
||
#define UDPS_PROTOCOL_H_
|
||
|
||
#include "CompilerTypes.h"
|
||
|
||
namespace MARTe {
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Magic and packet types */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/** Magic number: ASCII 'UDPS' stored little-endian (0x53504455). */
|
||
static const uint32 UDPS_MAGIC = 0x53504455u;
|
||
|
||
/** Packet type constants — must match the Go-side PktXxx constants. */
|
||
static const uint8 UDPS_TYPE_DATA = 0u; ///< Server → Client: signal data
|
||
static const uint8 UDPS_TYPE_CONFIG = 1u; ///< Server → Client: signal config
|
||
static const uint8 UDPS_TYPE_ACK = 2u; ///< Client → Server: ack counter
|
||
static const uint8 UDPS_TYPE_CONNECT = 3u; ///< Client → Server: connect request
|
||
static const uint8 UDPS_TYPE_DISCONNECT = 4u; ///< Client → Server: disconnect
|
||
static const uint8 UDPS_TYPE_SUBSCRIBE = 5u; ///< Client → Server (TCP stream service only): payload = stream name(s), comma separated
|
||
|
||
/** TCP stream service: most streams one connection may multiplex. On such a
|
||
* connection every server frame carries the stream's position in the SUBSCRIBE
|
||
* list in the header's fragmentIdx field (totalFragments stays 1). */
|
||
static const uint32 UDPS_TCP_MAX_STREAMS = 64u;
|
||
/** TCP stream service: longest SUBSCRIBE payload (comma separated names, each <= 255). */
|
||
static const uint32 UDPS_TCP_MAX_SUBSCRIBE_PAYLOAD = 4096u;
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Wire packet header (17 bytes) */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/**
|
||
* @brief 17-byte packed UDP packet header, little-endian.
|
||
*
|
||
* All fields use unsigned integer types; no padding. Placed at offset 0
|
||
* of every UDPS datagram.
|
||
*/
|
||
#pragma pack(push, 1)
|
||
struct UDPSPacketHeader {
|
||
uint32 magic; ///< Must equal UDPS_MAGIC
|
||
uint8 type; ///< One of UDPS_TYPE_*
|
||
uint32 counter; ///< Per-update sequence counter (same for all fragments)
|
||
uint16 fragmentIdx; ///< 0-based index of this fragment
|
||
uint16 totalFragments; ///< Total fragments for this update (1 = no fragmentation)
|
||
uint32 payloadBytes; ///< Payload bytes immediately following this header
|
||
};
|
||
#pragma pack(pop)
|
||
|
||
static const uint32 UDPS_HEADER_SIZE = 17u; ///< sizeof(UDPSPacketHeader)
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Signal type codes (CONFIG wire format) */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/** Compact type codes stored in UDPSSignalDescriptor::typeCode. */
|
||
static const uint8 UDPS_TYPECODE_UINT8 = 0u;
|
||
static const uint8 UDPS_TYPECODE_INT8 = 1u;
|
||
static const uint8 UDPS_TYPECODE_UINT16 = 2u;
|
||
static const uint8 UDPS_TYPECODE_INT16 = 3u;
|
||
static const uint8 UDPS_TYPECODE_UINT32 = 4u;
|
||
static const uint8 UDPS_TYPECODE_INT32 = 5u;
|
||
static const uint8 UDPS_TYPECODE_UINT64 = 6u;
|
||
static const uint8 UDPS_TYPECODE_INT64 = 7u;
|
||
static const uint8 UDPS_TYPECODE_FLOAT32 = 8u;
|
||
static const uint8 UDPS_TYPECODE_FLOAT64 = 9u;
|
||
static const uint8 UDPS_TYPECODE_UNKNOWN = 255u;
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Quantisation and time-mode codes */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/** Quantisation codes stored in UDPSSignalDescriptor::quantType. */
|
||
static const uint8 UDPS_QUANT_NONE = 0u; ///< No quantisation; raw wire format
|
||
static const uint8 UDPS_QUANT_UINT8 = 1u; ///< Map to uint8 [0, 255]
|
||
static const uint8 UDPS_QUANT_INT8 = 2u; ///< Map to int8 [-127, 127]
|
||
static const uint8 UDPS_QUANT_UINT16 = 3u; ///< Map to uint16 [0, 65535]
|
||
static const uint8 UDPS_QUANT_INT16 = 4u; ///< Map to int16 [-32767, 32767]
|
||
|
||
/** Time-mode codes stored in UDPSSignalDescriptor::timeMode. */
|
||
static const uint8 UDPS_TIMEMODE_PACKET = 0u; ///< Use packet-level HRT timestamp
|
||
static const uint8 UDPS_TIMEMODE_FULL_ARRAY = 1u; ///< TimeSignal has NumElements timestamps
|
||
static const uint8 UDPS_TIMEMODE_FIRST_SAMPLE = 2u; ///< TimeSignal scalar = timestamp of element [0]
|
||
static const uint8 UDPS_TIMEMODE_LAST_SAMPLE = 3u; ///< TimeSignal scalar = timestamp of element [N-1]
|
||
|
||
/** Sentinel value for UDPSSignalDescriptor::timeSignalIdx when no time signal. */
|
||
static const uint32 UDPS_NO_TIME_SIGNAL = 0xFFFFFFFFu;
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Publish mode codes (CONFIG trailing byte) */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
static const uint8 UDPS_PUBLISH_STRICT = 0u; ///< One packet per Synchronise() call
|
||
static const uint8 UDPS_PUBLISH_ACCUMULATE = 1u; ///< Variable batch; flush on size or time
|
||
static const uint8 UDPS_PUBLISH_DECIMATE = 2u; ///< One packet per Ratio calls
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* HRT frequency (CONFIG trailing uint64) */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/**
|
||
* Sentinel for a CONFIG that carries no HRT frequency, either because the
|
||
* trailer is absent (producer older than this field) or because the producer
|
||
* could not determine it. DATA timestamps are raw ticks of the producer's
|
||
* high-resolution timer, so without this a receiver on another host has no
|
||
* way to turn them into seconds and can only fall back to its own timer's
|
||
* frequency — which is right only while the two happen to agree.
|
||
*/
|
||
static const uint64 UDPS_HRT_FREQUENCY_UNKNOWN = 0u;
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* CONFIG payload — per-signal descriptor */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/** Maximum length of signal name and unit strings in the CONFIG payload. */
|
||
static const uint32 UDPS_MAX_SIGNAL_NAME = 64u;
|
||
static const uint32 UDPS_MAX_UNIT_LEN = 32u;
|
||
|
||
/** Byte size of one serialised UDPSSignalDescriptor on the wire. */
|
||
static const uint32 UDPS_SIGNAL_DESC_SIZE = 136u;
|
||
|
||
/**
|
||
* @brief Wire-format per-signal descriptor embedded in the CONFIG payload.
|
||
*
|
||
* Contains only plain C types so it can be memcpy'd directly to/from
|
||
* the network buffer (little-endian on all supported platforms).
|
||
*
|
||
* Size: 64 + 1 + 1 + 1 + 4 + 4 + 8 + 8 + 1 + 8 + 4 + 32 = 136 bytes.
|
||
*/
|
||
#pragma pack(push, 1)
|
||
struct UDPSSignalDescriptor {
|
||
char8 name[UDPS_MAX_SIGNAL_NAME]; ///< Null-terminated signal name
|
||
uint8 typeCode; ///< UDPS_TYPECODE_*
|
||
uint8 quantType; ///< UDPS_QUANT_*
|
||
uint8 numDimensions; ///< 0=scalar, 1=array, 2=matrix
|
||
uint32 numRows; ///< Number of rows (or elements for 1D)
|
||
uint32 numCols; ///< Number of columns (1 for 1D/scalar)
|
||
float64 rangeMin; ///< Physical range minimum (for quantisation)
|
||
float64 rangeMax; ///< Physical range maximum (for quantisation)
|
||
uint8 timeMode; ///< UDPS_TIMEMODE_*
|
||
float64 samplingRate; ///< Hz; used for FirstSample/LastSample modes
|
||
uint32 timeSignalIdx; ///< Index of time-ref signal; UDPS_NO_TIME_SIGNAL if none
|
||
char8 unit[UDPS_MAX_UNIT_LEN]; ///< Null-terminated physical unit string
|
||
};
|
||
#pragma pack(pop)
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* TypeDescriptor → UDPS type-code mapping */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/**
|
||
* @brief Maps a MARTe2 TypeDescriptor to the compact UDPS_TYPECODE_* value.
|
||
*
|
||
* Returns UDPS_TYPECODE_UNKNOWN for types that have no UDPS equivalent.
|
||
* Defined inline to avoid a separate translation unit for this trivial table.
|
||
*/
|
||
inline uint8 UDPSTypeDescriptorToCode(TypeDescriptor td) {
|
||
if (td == UnsignedInteger8Bit) return UDPS_TYPECODE_UINT8;
|
||
if (td == SignedInteger8Bit) return UDPS_TYPECODE_INT8;
|
||
if (td == UnsignedInteger16Bit) return UDPS_TYPECODE_UINT16;
|
||
if (td == SignedInteger16Bit) return UDPS_TYPECODE_INT16;
|
||
if (td == UnsignedInteger32Bit) return UDPS_TYPECODE_UINT32;
|
||
if (td == SignedInteger32Bit) return UDPS_TYPECODE_INT32;
|
||
if (td == UnsignedInteger64Bit) return UDPS_TYPECODE_UINT64;
|
||
if (td == SignedInteger64Bit) return UDPS_TYPECODE_INT64;
|
||
if (td == Float32Bit) return UDPS_TYPECODE_FLOAT32;
|
||
if (td == Float64Bit) return UDPS_TYPECODE_FLOAT64;
|
||
return UDPS_TYPECODE_UNKNOWN;
|
||
}
|
||
|
||
/**
|
||
* @brief Returns the wire byte size of one element for the given type code.
|
||
* Returns 0 for UDPS_TYPECODE_UNKNOWN.
|
||
*/
|
||
inline uint32 UDPSTypeCodeByteSize(uint8 typeCode) {
|
||
switch (typeCode) {
|
||
case UDPS_TYPECODE_UINT8: case UDPS_TYPECODE_INT8: return 1u;
|
||
case UDPS_TYPECODE_UINT16: case UDPS_TYPECODE_INT16: return 2u;
|
||
case UDPS_TYPECODE_UINT32: case UDPS_TYPECODE_INT32:
|
||
case UDPS_TYPECODE_FLOAT32: return 4u;
|
||
case UDPS_TYPECODE_UINT64: case UDPS_TYPECODE_INT64:
|
||
case UDPS_TYPECODE_FLOAT64: return 8u;
|
||
default: return 0u;
|
||
}
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Inline packet-building helpers */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/**
|
||
* @brief Serialise a UDPSPacketHeader into the first 17 bytes of @p buf.
|
||
* @param buf Must have room for at least UDPS_HEADER_SIZE bytes.
|
||
* @param type Packet type (UDPS_TYPE_*).
|
||
* @param counter Per-update sequence counter.
|
||
* @param fragIdx 0-based fragment index.
|
||
* @param totalFrags Total fragments for this update.
|
||
* @param payloadBytes Bytes of payload that follow this header.
|
||
*/
|
||
inline void UDPSBuildHeader(uint8 *buf,
|
||
uint8 type,
|
||
uint32 counter,
|
||
uint16 fragIdx,
|
||
uint16 totalFrags,
|
||
uint32 payloadBytes)
|
||
{
|
||
UDPSPacketHeader hdr;
|
||
hdr.magic = UDPS_MAGIC;
|
||
hdr.type = type;
|
||
hdr.counter = counter;
|
||
hdr.fragmentIdx = fragIdx;
|
||
hdr.totalFragments = totalFrags;
|
||
hdr.payloadBytes = payloadBytes;
|
||
memcpy(buf, &hdr, UDPS_HEADER_SIZE);
|
||
}
|
||
|
||
} /* namespace MARTe */
|
||
|
||
#endif /* UDPS_PROTOCOL_H_ */
|