/** * @file UDPStreamerClient.cpp * @brief Source file for class UDPStreamerClient * @date 24/06/2026 * @author Martino Ferrari * * @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and * the Development of Fusion Energy ('Fusion for Energy'). * Licensed under the EUPL, Version 1.1 or - as soon they will be approved * by the European Commission - subsequent versions of the EUPL (the "Licence") * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl * * @warning Unless required by applicable law or agreed to in writing, * software distributed under the Licence is distributed on an "AS IS" * basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the Licence permissions and limitations under the Licence. * * @details This source file contains the definition of all the methods for * the class UDPStreamerClient (public, protected, and private). */ #define DLL_API /*---------------------------------------------------------------------------*/ /* Standard header includes */ /*---------------------------------------------------------------------------*/ #include /*---------------------------------------------------------------------------*/ /* Project header includes */ /*---------------------------------------------------------------------------*/ #include "AdvancedErrorManagement.h" #include "ConfigurationDatabase.h" #include "GlobalObjectsDatabase.h" #include "MemoryOperationsHelper.h" #include "UDPSProtocol.h" #include "UDPStreamerClient.h" /*---------------------------------------------------------------------------*/ /* Static definitions */ /*---------------------------------------------------------------------------*/ namespace MARTe { /** Default server address when none is specified. */ static const char8 *const UDPS_CLIENT_DEFAULT_ADDR = "127.0.0.1"; /** Default port used when none is specified. */ static const uint16 UDPS_CLIENT_DEFAULT_PORT = 44500u; /** Default data-port offset: dataPort = port + this when DataPort is absent. */ static const uint16 UDPS_CLIENT_DEFAULT_DP_OFFSET = 1u; /** Default max payload per UDP datagram (bytes). */ static const uint32 UDPS_CLIENT_DEFAULT_MAX_PAYLOAD = 1400u; /** Bytes prepended to each DATA payload for the HRT packet timestamp. */ static const uint32 UDPS_CLIENT_TIMESTAMP_BYTES = 8u; /*---------------------------------------------------------------------------*/ /* Decode helpers */ /*---------------------------------------------------------------------------*/ /** * @brief Reads one quantised wire element (1 or 2 bytes) as a float64, * mirroring UDPSourceSession::DecodeRawValue for the quantised type codes. */ static float64 DecodeQuantRaw(const uint8 *ptr, uint8 quantType) { float64 raw = 0.0; switch (quantType) { case UDPS_QUANT_UINT8: { uint8 v = 0u; (void) memcpy(&v, ptr, 1u); raw = static_cast(v); break; } case UDPS_QUANT_INT8: { int8 v = 0; (void) memcpy(&v, ptr, 1u); raw = static_cast(v); break; } case UDPS_QUANT_UINT16: { uint16 v = 0u; (void) memcpy(&v, ptr, 2u); raw = static_cast(v); break; } case UDPS_QUANT_INT16: { int16 v = 0; (void) memcpy(&v, ptr, 2u); raw = static_cast(v); break; } default: break; } return raw; } /** * @brief Dequantises a raw value, mirroring UDPSourceSession::DequantizeValue. */ static float64 DequantValue(float64 raw, uint8 quantType, float64 rangeMin, float64 rangeMax) { const float64 range = rangeMax - rangeMin; float64 val; switch (quantType) { case UDPS_QUANT_UINT8: val = rangeMin + (raw / 255.0) * range; break; case UDPS_QUANT_INT8: val = rangeMin + ((raw + 127.0) / 254.0) * range; break; case UDPS_QUANT_UINT16: val = rangeMin + (raw / 65535.0) * range; break; case UDPS_QUANT_INT16: val = rangeMin + ((raw + 32767.0) / 65534.0) * range; break; default: val = raw; break; } return val; } /*---------------------------------------------------------------------------*/ /* Method definitions */ /*---------------------------------------------------------------------------*/ UDPStreamerClient::UDPStreamerClient() : MemoryDataSourceI(), UDPSClientListener(), client() { serverAddress = UDPS_CLIENT_DEFAULT_ADDR; port = UDPS_CLIENT_DEFAULT_PORT; maxPayloadSize = UDPS_CLIENT_DEFAULT_MAX_PAYLOAD; cpuMask = 0xFFFFFFFFu; stackSize = THREADS_DEFAULT_STACKSIZE; dataPort = UDPS_CLIENT_DEFAULT_PORT + UDPS_CLIENT_DEFAULT_DP_OFFSET; useMulticast = false; numSigs = 0u; signalInfos = NULL_PTR(UDPStreamerClientSignal *); totalSrcBytes = 0u; configValidated = false; publishMode = UDPS_PUBLISH_STRICT; receiverUp = false; readyBuffer = NULL_PTR(uint8 *); scratchBuffer = NULL_PTR(uint8 *); newDataReady = false; if (!dataSem.Create()) { REPORT_ERROR(ErrorManagement::FatalError, "Could not create EventSem."); } bufMutex.Create(false); } UDPStreamerClient::~UDPStreamerClient() { (void) dataSem.Post(); if (receiverUp) { (void) client.Stop(); receiverUp = false; } HeapI *heap = GlobalObjectsDatabase::Instance()->GetStandardHeap(); if (signalInfos != NULL_PTR(UDPStreamerClientSignal *)) { delete[] signalInfos; signalInfos = NULL_PTR(UDPStreamerClientSignal *); } if (readyBuffer != NULL_PTR(uint8 *)) { heap->Free(reinterpret_cast(readyBuffer)); } if (scratchBuffer != NULL_PTR(uint8 *)) { heap->Free(reinterpret_cast(scratchBuffer)); } (void) dataSem.Close(); } bool UDPStreamerClient::Initialise(StructuredDataI &data) { bool ok = MemoryDataSourceI::Initialise(data); if (ok) { StreamString addr = ""; if (!data.Read("ServerAddress", addr) || (addr.Size() == 0u)) { addr = UDPS_CLIENT_DEFAULT_ADDR; REPORT_ERROR(ErrorManagement::Information, "ServerAddress not specified; using default %s.", UDPS_CLIENT_DEFAULT_ADDR); } serverAddress = addr; } if (ok) { if (!data.Read("Port", port)) { port = UDPS_CLIENT_DEFAULT_PORT; REPORT_ERROR(ErrorManagement::Information, "Port not specified; using default %u.", static_cast(port)); } } if (ok) { if (!data.Read("MaxPayloadSize", maxPayloadSize)) { maxPayloadSize = UDPS_CLIENT_DEFAULT_MAX_PAYLOAD; } } if (ok) { if (!data.Read("CPUMask", cpuMask)) { cpuMask = 0xFFFFFFFFu; } } if (ok) { if (!data.Read("StackSize", stackSize)) { stackSize = THREADS_DEFAULT_STACKSIZE; } } if (ok) { StreamString mcastStr = ""; (void) data.Read("MulticastGroup", mcastStr); if (mcastStr.Size() > 0u) { multicastGroup = mcastStr; useMulticast = true; uint16 dp = 0u; if (!data.Read("DataPort", dp)) { dp = port + UDPS_CLIENT_DEFAULT_DP_OFFSET; } dataPort = dp; REPORT_ERROR(ErrorManagement::Information, "Multicast mode: group=%s, server=%s, controlPort=%u, dataPort=%u.", multicastGroup.Buffer(), serverAddress.Buffer(), static_cast(port), static_cast(dataPort)); } else { useMulticast = false; } } /* Forward the transport parameters to the shared UDPSClient receiver, * translating the DataSource parameter names to the UDPSClient keys. */ if (ok) { ConfigurationDatabase cdb; ok = cdb.Write("ServerAddr", serverAddress); if (ok) { ok = cdb.Write("Port", static_cast(port)); } if (ok && useMulticast) { ok = cdb.Write("MulticastGroup", multicastGroup); if (ok) { ok = cdb.Write("DataPort", static_cast(dataPort)); } } if (ok) { ok = cdb.Write("MaxPayloadSize", maxPayloadSize); } if (ok) { ok = cdb.Write("CPUMask", cpuMask); } if (ok) { ok = cdb.Write("StackSize", stackSize); } if (ok) { ok = cdb.MoveToRoot(); } if (ok) { ok = client.Initialise(cdb); } if (!ok) { REPORT_ERROR(ErrorManagement::ParametersError, "Could not configure the UDPSClient receiver."); } client.SetListener(this); } return ok; } bool UDPStreamerClient::SetConfiguredDatabase(StructuredDataI &data) { bool ok = MemoryDataSourceI::SetConfiguredDatabase(data); if (!ok) { return false; } numSigs = GetNumberOfSignals(); if (numSigs == 0u) { REPORT_ERROR(ErrorManagement::ParametersError, "At least one signal must be defined."); return false; } signalInfos = new UDPStreamerClientSignal[numSigs]; totalSrcBytes = 0u; for (uint32 i = 0u; (i < numSigs) && ok; i++) { StreamString sigName; ok = GetSignalName(i, sigName); if (!ok) { REPORT_ERROR(ErrorManagement::FatalError, "Could not get name for signal %u.", i); break; } uint32 nelems = 1u; (void) GetSignalNumberOfElements(i, nelems); if (nelems == 0u) { nelems = 1u; } uint32 bsz = 0u; (void) GetSignalByteSize(i, bsz); signalInfos[i].name = sigName; signalInfos[i].type = GetSignalType(i); signalInfos[i].quantType = UDPS_QUANT_NONE; signalInfos[i].numElements = nelems; signalInfos[i].rangeMin = 0.0; signalInfos[i].rangeMax = 1.0; signalInfos[i].srcByteSize = bsz; signalInfos[i].wireElemBytes = (nelems > 0u) ? (bsz / nelems) : bsz; signalInfos[i].bufferOffset = totalSrcBytes; totalSrcBytes += bsz; } return ok; } bool UDPStreamerClient::AllocateMemory() { bool ok = MemoryDataSourceI::AllocateMemory(); if (!ok) { return false; } if (totalSrcBytes == 0u) { totalSrcBytes = stateMemorySize; } HeapI *heap = GlobalObjectsDatabase::Instance()->GetStandardHeap(); readyBuffer = reinterpret_cast(heap->Malloc(totalSrcBytes)); if (readyBuffer == NULL_PTR(uint8 *)) { REPORT_ERROR(ErrorManagement::FatalError, "Could not allocate readyBuffer."); return false; } (void) MemoryOperationsHelper::Set(readyBuffer, 0, totalSrcBytes); scratchBuffer = reinterpret_cast(heap->Malloc(totalSrcBytes)); if (scratchBuffer == NULL_PTR(uint8 *)) { REPORT_ERROR(ErrorManagement::FatalError, "Could not allocate scratchBuffer."); return false; } (void) MemoryOperationsHelper::Set(scratchBuffer, 0, totalSrcBytes); return true; } const char8 *UDPStreamerClient::GetBrokerName(StructuredDataI &data, const SignalDirection direction) { const char8 *brokerName = ""; if (direction == InputSignals) { brokerName = "MemoryMapSynchronisedInputBroker"; } return brokerName; } bool UDPStreamerClient::PrepareNextState(const char8 *const currentStateName, const char8 *const nextStateName) { bool ok = true; if (!receiverUp) { ok = client.Start(); if (ok) { receiverUp = true; } else { REPORT_ERROR(ErrorManagement::FatalError, "Could not start the UDPSClient receiver."); } } return ok; } bool UDPStreamerClient::Synchronise() { ErrorManagement::ErrorType waitErr = dataSem.ResetWait(TimeoutType(10u)); if (waitErr == ErrorManagement::NoError) { bufMutex.FastLock(TTInfiniteWait); (void) MemoryOperationsHelper::Copy(memory, readyBuffer, totalSrcBytes); newDataReady = false; bufMutex.FastUnLock(); } return true; } /*---------------------------------------------------------------------------*/ /* UDPSClientListener callbacks */ /*---------------------------------------------------------------------------*/ void UDPStreamerClient::OnUDPSConfig(const uint8 *payload, uint32 payloadSize) { if (payloadSize < 4u) { REPORT_ERROR(ErrorManagement::ParametersError, "CONFIG payload too small (%u bytes).", payloadSize); return; } uint32 serverNumSigs = 0u; (void) memcpy(&serverNumSigs, payload, 4u); if (serverNumSigs != numSigs) { REPORT_ERROR(ErrorManagement::ParametersError, "CONFIG signal count mismatch: server=%u, client=%u.", serverNumSigs, numSigs); return; } const uint32 needed = 4u + (numSigs * UDPS_SIGNAL_DESC_SIZE); if (payloadSize < needed) { REPORT_ERROR(ErrorManagement::ParametersError, "CONFIG payload truncated (%u < %u bytes).", payloadSize, needed); return; } bool ok = true; for (uint32 i = 0u; (i < numSigs) && ok; i++) { UDPSSignalDescriptor desc; (void) memcpy(&desc, payload + 4u + (i * UDPS_SIGNAL_DESC_SIZE), UDPS_SIGNAL_DESC_SIZE); char8 nameBuf[UDPS_MAX_SIGNAL_NAME + 1u]; (void) memcpy(nameBuf, desc.name, UDPS_MAX_SIGNAL_NAME); nameBuf[UDPS_MAX_SIGNAL_NAME] = '\0'; StreamString serverName = nameBuf; if (signalInfos[i].name != serverName) { REPORT_ERROR(ErrorManagement::ParametersError, "CONFIG signal %u name mismatch: server=%s, client=%s.", i, serverName.Buffer(), signalInfos[i].name.Buffer()); ok = false; break; } uint32 serverElems = desc.numRows * desc.numCols; if (serverElems == 0u) { serverElems = 1u; } if (serverElems != signalInfos[i].numElements) { REPORT_ERROR(ErrorManagement::ParametersError, "CONFIG signal %u element count mismatch: server=%u, client=%u.", i, serverElems, signalInfos[i].numElements); ok = false; break; } signalInfos[i].quantType = desc.quantType; signalInfos[i].rangeMin = desc.rangeMin; signalInfos[i].rangeMax = desc.rangeMax; if (desc.quantType != UDPS_QUANT_NONE) { uint32 q = 0u; if ((desc.quantType == UDPS_QUANT_UINT8) || (desc.quantType == UDPS_QUANT_INT8)) { q = 1u; } else if ((desc.quantType == UDPS_QUANT_UINT16) || (desc.quantType == UDPS_QUANT_INT16)) { q = 2u; } signalInfos[i].wireElemBytes = q; } } if (ok) { const uint32 pmOffset = needed; publishMode = (payloadSize > pmOffset) ? payload[pmOffset] : UDPS_PUBLISH_STRICT; bufMutex.FastLock(TTInfiniteWait); configValidated = true; bufMutex.FastUnLock(); REPORT_ERROR(ErrorManagement::Information, "CONFIG validated: %u signals, publishMode=%u, totalSrcBytes=%u.", numSigs, static_cast(publishMode), totalSrcBytes); } } void UDPStreamerClient::OnUDPSData(const uint8 *payload, uint32 payloadSize) { if (!configValidated) { return; } if (payloadSize < UDPS_CLIENT_TIMESTAMP_BYTES) { return; } (void) MemoryOperationsHelper::Set(scratchBuffer, 0, totalSrcBytes); DecodeSnapshot(payload, payloadSize, scratchBuffer); bufMutex.FastLock(TTInfiniteWait); (void) MemoryOperationsHelper::Copy(readyBuffer, scratchBuffer, totalSrcBytes); newDataReady = true; bufMutex.FastUnLock(); (void) dataSem.Post(); } void UDPStreamerClient::OnUDPSConnected() { REPORT_ERROR(ErrorManagement::Information, "UDPStreamerClient connected to %s:%u.", serverAddress.Buffer(), static_cast(port)); } void UDPStreamerClient::OnUDPSDisconnected() { bufMutex.FastLock(TTInfiniteWait); configValidated = false; bufMutex.FastUnLock(); REPORT_ERROR(ErrorManagement::Information, "UDPStreamerClient disconnected."); } /*---------------------------------------------------------------------------*/ /* Decode logic */ /*---------------------------------------------------------------------------*/ void UDPStreamerClient::DecodeSnapshot(const uint8 *payload, uint32 size, uint8 *dst) { uint32 offset = UDPS_CLIENT_TIMESTAMP_BYTES; uint32 numSamples = 1u; if (publishMode == UDPS_PUBLISH_ACCUMULATE) { if ((offset + 4u) > size) { return; } (void) memcpy(&numSamples, payload + offset, 4u); offset += 4u; if (numSamples == 0u) { numSamples = 1u; } } uint32 off = offset; for (uint32 s = 0u; s < numSigs; s++) { const UDPStreamerClientSignal &info = signalInfos[s]; const uint32 wireElemBytes = info.wireElemBytes; if (wireElemBytes == 0u) { return; } const uint32 ne = info.numElements; const bool accScalar = (publishMode == UDPS_PUBLISH_ACCUMULATE) && (ne == 1u); const uint32 elemsToRead = accScalar ? numSamples : ne; /* HI-1: 64-bit bounds check to prevent uint32 multiply overflow */ uint64 bytesNeeded = static_cast(off) + static_cast(elemsToRead) * static_cast(wireElemBytes); if (bytesNeeded > static_cast(size)) { return; } uint8 *d = dst + info.bufferOffset; if (info.quantType == UDPS_QUANT_NONE) { if (accScalar) { /* Publish the most recent accumulated sample. */ const uint8 *srcLast = payload + off + ((numSamples - 1u) * wireElemBytes); (void) memcpy(d, srcLast, wireElemBytes); } else { (void) memcpy(d, payload + off, ne * wireElemBytes); } } else { const bool isF32 = (info.type == Float32Bit); const uint32 startElem = accScalar ? (numSamples - 1u) : 0u; const uint32 count = accScalar ? 1u : ne; for (uint32 e = 0u; e < count; e++) { const uint8 *ptr = payload + off + ((startElem + e) * wireElemBytes); const float64 raw = DecodeQuantRaw(ptr, info.quantType); const float64 val = DequantValue(raw, info.quantType, info.rangeMin, info.rangeMax); if (isF32) { float32 f32 = static_cast(val); (void) memcpy(d, &f32, 4u); d += 4u; } else { float64 f64 = val; (void) memcpy(d, &f64, 8u); d += 8u; } } } off += elemsToRead * wireElemBytes; } } CLASS_REGISTER(UDPStreamerClient, "1.0") } /* namespace MARTe */