diff --git a/Source/Components/Interfaces/DebugService/DebugService.cpp b/Source/Components/Interfaces/DebugService/DebugService.cpp index 4151add..0cb2e38 100644 --- a/Source/Components/Interfaces/DebugService/DebugService.cpp +++ b/Source/Components/Interfaces/DebugService/DebugService.cpp @@ -1,698 +1,254 @@ -#include "Atomic.h" #include "BasicTCPSocket.h" -#include "ClassRegistryItem.h" #include "ConfigurationDatabase.h" -#include "DataSourceI.h" -#include "DebugBrokerWrapper.h" #include "DebugService.h" -#include "GAM.h" #include "GlobalObjectsDatabase.h" #include "HighResolutionTimer.h" #include "LoggerService.h" #include "Message.h" -#include "ObjectBuilder.h" #include "ObjectRegistryDatabase.h" -#include "Threads.h" +#include "ReferenceT.h" #include "Sleep.h" #include "StreamString.h" -#include "TimeoutType.h" #include "TcpLogger.h" -#include "TypeConversion.h" -#include "ReferenceT.h" +#include "Threads.h" +#include "TimeoutType.h" namespace MARTe { -// DebugServiceI static members — defined here so no extra .cpp is needed. -DebugServiceI *DebugServiceI::instance = NULL_PTR(DebugServiceI *); - -static void EscapeJson(const char8 *src, StreamString &dst) { - if (src == NULL_PTR(const char8 *)) - return; - while (*src != '\0') { - if (*src == '"') - dst += "\\\""; - else if (*src == '\\') - dst += "\\\\"; - else if (*src == '\n') - dst += "\\n"; - else if (*src == '\r') - dst += "\\r"; - else if (*src == '\t') - dst += "\\t"; - else - dst += *src; - src++; - } -} - -static bool SuffixMatch(const char8 *target, const char8 *pattern) { - uint32 tLen = StringHelper::Length(target); - uint32 pLen = StringHelper::Length(pattern); - if (pLen > tLen) - return false; - const char8 *suffix = target + (tLen - pLen); - if (StringHelper::Compare(suffix, pattern) == 0) { - if (tLen == pLen || *(suffix - 1) == '.') - return true; - } - return false; -} - -static bool FindPathInContainer(ReferenceContainer *container, - const Object *target, StreamString &path) { - if (container == NULL_PTR(ReferenceContainer *)) - return false; - uint32 n = container->Size(); - for (uint32 i = 0u; i < n; i++) { - Reference ref = container->Get(i); - if (ref.IsValid()) { - if (ref.operator->() == target) { - path = ref->GetName(); - return true; - } - ReferenceContainer *sub = - dynamic_cast(ref.operator->()); - if (sub != NULL_PTR(ReferenceContainer *)) { - if (FindPathInContainer(sub, target, path)) { - StreamString full; - full.Printf("%s.%s", ref->GetName(), path.Buffer()); - path = full; - return true; - } - } - } - } - return false; -} - CLASS_REGISTER(DebugService, "1.0") -// Out-of-class definitions required by C++98 when the constants are odr-used -// (i.e. their address is taken or they appear in a context that needs linkage). +// C++98 ODR definitions for static constants const uint32 DebugService::STREAMER_MTU; const uint32 DebugService::STREAMER_BUFFER_SIZE; const uint32 DebugService::CMD_RATE_LIMIT; const uint32 DebugService::CLIENT_IDLE_TIMEOUT_MS; -const uint32 DebugService::GET_VALUE_MAX_ELEMENTS; const uint32 DebugService::INPUT_BUFFER_MAX; +// --------------------------------------------------------------------------- +// Constructor / Destructor +// --------------------------------------------------------------------------- + DebugService::DebugService() - : ReferenceContainer(), EmbeddedServiceMethodBinderI(), + : DebugServiceBase(), + EmbeddedServiceMethodBinderI(), binderServer(this, ServiceBinder::ServerType), binderStreamer(this, ServiceBinder::StreamerType), - threadService(binderServer), streamerService(binderStreamer) { - controlPort = 0; - streamPort = 8081; - logPort = 8082; - streamIP = "127.0.0.1"; - isServer = false; - suppressTimeoutLogs = true; - isPaused = false; - stepRemaining = 0u; - manualConfigSet = false; - activeClient = NULL_PTR(BasicTCPSocket *); - streamerPacketOffset = 0u; - streamerSequenceNumber = 0u; - cmdCountInWindow = 0u; - cmdWindowStartMs = 0u; - lastDataTimeMs = 0u; - inputBuffer = ""; // FIX #10: initialise carry-over buffer + threadService(binderServer), + streamerService(binderStreamer) { + controlPort = 0u; + streamPort = 8081u; + logPort = 8082u; + streamIP = "127.0.0.1"; + isServer = false; + suppressTimeoutLogs = true; + activeClient = NULL_PTR(BasicTCPSocket *); + streamerPacketOffset = 0u; + streamerSequenceNumber = 0u; + cmdCountInWindow = 0u; + cmdWindowStartMs = 0u; + lastDataTimeMs = 0u; + inputBuffer = ""; } DebugService::~DebugService() { - if (DebugServiceI::GetInstance() == this) { - DebugServiceI::SetInstance(NULL_PTR(DebugServiceI *)); - } - threadService.Stop(); - streamerService.Stop(); - tcpServer.Close(); - udpSocket.Close(); - if (activeClient != NULL_PTR(BasicTCPSocket *)) { - activeClient->Close(); - delete activeClient; - } - for (uint32 i = 0; i < signals.Size(); i++) { - delete signals[i]; - } + if (DebugServiceI::GetInstance() == this) { + DebugServiceI::SetInstance(NULL_PTR(DebugServiceI *)); + } + threadService.Stop(); + streamerService.Stop(); + tcpServer.Close(); + udpSocket.Close(); + if (activeClient != NULL_PTR(BasicTCPSocket *)) { + activeClient->Close(); + delete activeClient; + activeClient = NULL_PTR(BasicTCPSocket *); + } + // signals owned by DebugServiceBase destructor } +// --------------------------------------------------------------------------- +// Initialise +// --------------------------------------------------------------------------- + bool DebugService::Initialise(StructuredDataI &data) { - if (!ReferenceContainer::Initialise(data)) - return false; + if (!ReferenceContainer::Initialise(data)) return false; - uint32 port = 0; - if (data.Read("ControlPort", port)) { - controlPort = (uint16)port; - } else { - (void)data.Read("TcpPort", port); - controlPort = (uint16)port; - } + uint32 port = 0u; + if (data.Read("ControlPort", port)) { + controlPort = (uint16)port; + } else { + (void)data.Read("TcpPort", port); + controlPort = (uint16)port; + } - if (controlPort > 0) { - isServer = true; - DebugServiceI::SetInstance(this); - } + if (controlPort > 0u) { + isServer = true; + DebugServiceI::SetInstance(this); + } - port = 8081; - if (data.Read("StreamPort", port)) { - streamPort = (uint16)port; - } else { - (void)data.Read("UdpPort", port); - streamPort = (uint16)port; - } - - port = 8082; - if (data.Read("LogPort", port)) { - logPort = (uint16)port; - } else { - (void)data.Read("TcpLogPort", port); - logPort = (uint16)port; - } + port = 8081u; + if (data.Read("StreamPort", port)) { + streamPort = (uint16)port; + } else { + (void)data.Read("UdpPort", port); + streamPort = (uint16)port; + } - StreamString tempIP; - if (data.Read("StreamIP", tempIP)) { - streamIP = tempIP; - } else { - streamIP = "127.0.0.1"; - } - uint32 suppress = 1; - if (data.Read("SuppressTimeoutLogs", suppress)) { - suppressTimeoutLogs = (suppress == 1); - } + port = 8082u; + if (data.Read("LogPort", port)) { + logPort = (uint16)port; + } else { + (void)data.Read("TcpLogPort", port); + logPort = (uint16)port; + } - // Do NOT call MoveToRoot() on the shared CDB here — that corrupts the - // ReferenceContainer::Initialise() traversal cursor for sibling objects. - // Just capture the local subtree; full config is rebuilt lazily from the - // live ObjectRegistryDatabase when ServeConfig/EnrichWithConfig is called. - (void)data.Copy(fullConfig); + StreamString tempIP; + if (data.Read("StreamIP", tempIP)) { + streamIP = tempIP; + } else { + streamIP = "127.0.0.1"; + } - if (isServer) { - if (!traceBuffer.Init(8 * 1024 * 1024)) - return false; - PatchRegistry(); - ConfigurationDatabase threadData; - threadData.Write("Timeout", (uint32)1000); - threadService.Initialise(threadData); - streamerService.Initialise(threadData); - if (!tcpServer.Open()) - return false; - if (!tcpServer.Listen(controlPort)) - return false; - if (!udpSocket.Open()) - return false; - if (threadService.Start() != ErrorManagement::NoError) - return false; - if (streamerService.Start() != ErrorManagement::NoError) - return false; - } - return true; + uint32 suppress = 1u; + if (data.Read("SuppressTimeoutLogs", suppress)) { + suppressTimeoutLogs = (suppress == 1u); + } + + // Capture only the local subtree — do NOT call MoveToRoot() on the shared CDB. + (void)data.Copy(fullConfig); + + if (isServer) { + if (!traceBuffer.Init(8 * 1024 * 1024)) return false; + PatchRegistry(); + + ConfigurationDatabase threadData; + threadData.Write("Timeout", (uint32)1000); + threadService.Initialise(threadData); + streamerService.Initialise(threadData); + + if (!tcpServer.Open()) return false; + if (!tcpServer.Listen(controlPort)) return false; + if (!udpSocket.Open()) return false; + + if (threadService.Start() != ErrorManagement::NoError) return false; + if (streamerService.Start() != ErrorManagement::NoError) return false; + } + return true; } -void DebugService::InjectTcpLoggerIfNeeded() { - if (logPort == 0u) - return; +// --------------------------------------------------------------------------- +// Transport config hook (called by RebuildConfigFromRegistry in base) +// --------------------------------------------------------------------------- - // Check if the ORD already contains a LoggerService with at least one TcpLogger. - // If so, leave it untouched. - Reference existing = ObjectRegistryDatabase::Instance()->Find("LoggerService"); - if (existing.IsValid()) { - ReferenceContainer *rc = dynamic_cast(existing.operator->()); - if (rc != NULL_PTR(ReferenceContainer *)) { - for (uint32 i = 0u; i < rc->Size(); i++) { - ReferenceT child = rc->Get(i); - if (child.IsValid()) { - printf("[DebugService] Found existing TcpLogger in LoggerService — skipping injection.\n"); - return; +void DebugService::RebuildTransportConfig() { + const char8 *myName = GetName(); + if (myName != NULL_PTR(const char8 *)) { + if (fullConfig.MoveRelative(myName)) { + (void)fullConfig.Write("ControlPort", static_cast(controlPort)); + (void)fullConfig.Write("UdpPort", static_cast(streamPort)); + (void)fullConfig.Write("LogPort", static_cast(logPort)); + if (streamIP.Size() > 0u) + (void)fullConfig.Write("StreamIP", streamIP.Buffer()); + (void)fullConfig.MoveToAncestor(1u); } - } } - } - - // Build a CDB that mirrors the config-file declaration: - // LoggerService node (current position) - // Class = LoggerService - // CPUs = 1 - // DebugConsumer (child) - // Class = TcpLogger - // Port = - ConfigurationDatabase lsCdb; - (void)lsCdb.Write("Class", "LoggerService"); - uint32 cpus = 1u; - (void)lsCdb.Write("CPUs", cpus); - // ReferenceContainer::Initialise only instantiates children whose names - // start with '+' (matching the StandardParser convention). - if (lsCdb.CreateRelative("+DebugConsumer")) { - (void)lsCdb.Write("Class", "TcpLogger"); - uint32 p = static_cast(logPort); - (void)lsCdb.Write("Port", p); - (void)lsCdb.MoveToAncestor(1u); - } - (void)lsCdb.MoveToRoot(); - - ReferenceT ls( - "LoggerService", GlobalObjectsDatabase::Instance()->GetStandardHeap()); - if (!ls.IsValid()) { - printf("[DebugService] Failed to create LoggerService object.\n"); - return; - } - ls->SetName("LoggerService"); - if (!ls->Initialise(lsCdb)) { - printf("[DebugService] LoggerService::Initialise() failed.\n"); - return; - } - - // Insert into the ORD so it is findable by name and cleaned up on shutdown. - if (!ObjectRegistryDatabase::Instance()->Insert(ls)) { - printf("[DebugService] Failed to insert LoggerService into ORD.\n"); - // Keep a local reference anyway so the logger thread stays alive. - } - - // The ORD now holds a reference to ls; the LoggerService stays alive until - // ObjectRegistryDatabase::Purge(). No need to store a local reference. - printf("[DebugService] Auto-injected LoggerService + TcpLogger on port %u.\n", logPort); } -void DebugService::SetFullConfig(ConfigurationDatabase &config) { - config.MoveToRoot(); - config.Copy(fullConfig); - manualConfigSet = true; +// --------------------------------------------------------------------------- +// SERVICE_INFO hook +// --------------------------------------------------------------------------- + +void DebugService::GetServiceInfo(StreamString &out) { + out.Printf("OK SERVICE_INFO TCP_CTRL:%u UDP_STREAM:%u TCP_LOG:%u STATE:%s\n", + controlPort, streamPort, logPort, + isPaused ? "PAUSED" : "RUNNING"); } -static void BuildCDBFromContainer(ReferenceContainer *container, - ConfigurationDatabase &cdb) { - if (container == NULL_PTR(ReferenceContainer *)) - return; - uint32 n = container->Size(); - for (uint32 i = 0u; i < n; i++) { - Reference child = container->Get(i); - if (!child.IsValid()) - continue; - const char8 *name = child->GetName(); - if (name == NULL_PTR(const char8 *)) - continue; - - bool created = cdb.CreateRelative(name); - if (!created) { - if (!cdb.MoveRelative(name)) - continue; - } - - const char8 *className = child->GetClassProperties()->GetName(); - if (className != NULL_PTR(const char8 *)) - (void)cdb.Write("Class", className); - - // Export scalar parameters via a SEPARATE CDB so the live cursor is - // never touched by ExportData. Then copy only top-level LEAF values - // (i.e. scalars where MoveRelative fails) and skip sub-nodes. - // - // This avoids two ExportData pitfalls: - // 1. ReferenceContainer::ExportData writes numeric-indexed child nodes - // (+0, +1, ...) — those are sub-nodes and get filtered out. - // 2. Some DataSource ExportData implementations follow internal - // references and write sibling objects as children — also sub-nodes, - // also filtered out. - // - // Scalar parameters (ControlPort, UdpPort, CPUs, Port, ...) pass through - // because they are leaf values, not sub-nodes. - { - ConfigurationDatabase exportCdb; - if (child->ExportData(exportCdb)) { - exportCdb.MoveToRoot(); - uint32 nExport = exportCdb.GetNumberOfChildren(); - for (uint32 j = 0u; j < nExport; j++) { - const char8 *ek = exportCdb.GetChildName(j); - if (StringHelper::Compare(ek, "Class") == 0 || - StringHelper::Compare(ek, "Name") == 0 || - StringHelper::Compare(ek, "IsContainer") == 0) - continue; - // Sub-node check: MoveRelative succeeds only for nodes, not scalars - if (exportCdb.MoveRelative(ek)) { - exportCdb.MoveToAncestor(1u); - continue; // skip sub-nodes entirely - } - // Leaf scalar — convert to string and write into the main CDB - AnyType at = exportCdb.GetType(ek); - if (at.GetDataPointer() != NULL_PTR(void *)) { - char8 buf[1024]; - AnyType st(CharString, 0u, buf); - st.SetNumberOfElements(0, 1024); - if (TypeConvert(st, at)) { - (void)cdb.Write(ek, buf); - } - } - } - } - } - - ReferenceContainer *sub = - dynamic_cast(child.operator->()); - if (sub != NULL_PTR(ReferenceContainer *)) - BuildCDBFromContainer(sub, cdb); - - (void)cdb.MoveToAncestor(1u); - } -} - -void DebugService::RebuildConfigFromRegistry() { - fullConfig = ConfigurationDatabase(); - BuildCDBFromContainer(ObjectRegistryDatabase::Instance(), fullConfig); - - // ExportData on ReferenceContainer subclasses (including DebugService itself) - // only writes Name/IsContainer/indexed children — it never re-emits the - // config-file parameters that were read in Initialise(). Write them back - // explicitly from the member variables that Initialise() stored. - const char8 *myName = GetName(); - if (myName != NULL_PTR(const char8 *)) { - if (fullConfig.MoveRelative(myName)) { - (void)fullConfig.Write("ControlPort", static_cast(controlPort)); - (void)fullConfig.Write("UdpPort", static_cast(streamPort)); - (void)fullConfig.Write("LogPort", static_cast(logPort)); - if (streamIP.Size() > 0u) - (void)fullConfig.Write("StreamIP", streamIP.Buffer()); - (void)fullConfig.MoveToAncestor(1u); - } - } -} - -static void PatchItemInternal(const char8 *originalName, - ObjectBuilder *debugBuilder) { - ClassRegistryItem *item = - ClassRegistryDatabase::Instance()->Find(originalName); - if (item != NULL_PTR(ClassRegistryItem *)) { - item->SetObjectBuilder(debugBuilder); - } -} - -void DebugService::PatchRegistry() { - DebugMemoryMapInputBrokerBuilder *b1 = new DebugMemoryMapInputBrokerBuilder(); - PatchItemInternal("MemoryMapInputBroker", b1); - DebugMemoryMapOutputBrokerBuilder *b2 = - new DebugMemoryMapOutputBrokerBuilder(); - PatchItemInternal("MemoryMapOutputBroker", b2); - DebugMemoryMapSynchronisedInputBrokerBuilder *b3 = - new DebugMemoryMapSynchronisedInputBrokerBuilder(); - PatchItemInternal("MemoryMapSynchronisedInputBroker", b3); - DebugMemoryMapSynchronisedOutputBrokerBuilder *b4 = - new DebugMemoryMapSynchronisedOutputBrokerBuilder(); - PatchItemInternal("MemoryMapSynchronisedOutputBroker", b4); - DebugMemoryMapInterpolatedInputBrokerBuilder *b5 = - new DebugMemoryMapInterpolatedInputBrokerBuilder(); - PatchItemInternal("MemoryMapInterpolatedInputBroker", b5); - DebugMemoryMapMultiBufferInputBrokerBuilder *b6 = - new DebugMemoryMapMultiBufferInputBrokerBuilder(); - PatchItemInternal("MemoryMapMultiBufferInputBroker", b6); - DebugMemoryMapMultiBufferOutputBrokerBuilder *b7 = - new DebugMemoryMapMultiBufferOutputBrokerBuilder(); - PatchItemInternal("MemoryMapMultiBufferOutputBroker", b7); - DebugMemoryMapSynchronisedMultiBufferInputBrokerBuilder *b8 = - new DebugMemoryMapSynchronisedMultiBufferInputBrokerBuilder(); - PatchItemInternal("MemoryMapSynchronisedMultiBufferInputBroker", b8); - DebugMemoryMapSynchronisedMultiBufferOutputBrokerBuilder *b9 = - new DebugMemoryMapSynchronisedMultiBufferOutputBrokerBuilder(); - PatchItemInternal("MemoryMapSynchronisedMultiBufferOutputBroker", b9); - DebugMemoryMapAsyncOutputBrokerBuilder *b10 = - new DebugMemoryMapAsyncOutputBrokerBuilder(); - PatchItemInternal("MemoryMapAsyncOutputBroker", b10); - DebugMemoryMapAsyncTriggerOutputBrokerBuilder *b11 = - new DebugMemoryMapAsyncTriggerOutputBrokerBuilder(); - PatchItemInternal("MemoryMapAsyncTriggerOutputBroker", b11); -} - -DebugSignalInfo *DebugService::RegisterSignal(void *memoryAddress, - TypeDescriptor type, - const char8 *name, - uint8 numberOfDimensions, - uint32 numberOfElements) { - fprintf(stderr, " RegisterSignal[%p]: %s\n", (void*)this, name); - mutex.FastLock(); - DebugSignalInfo *res = NULL_PTR(DebugSignalInfo *); - uint32 sigIdx = 0xFFFFFFFF; - for (uint32 i = 0; i < signals.Size(); i++) { - if (signals[i]->memoryAddress == memoryAddress) { - res = signals[i]; - sigIdx = i; - break; - } - } - if (res == NULL_PTR(DebugSignalInfo *)) { - sigIdx = signals.Size(); - res = new DebugSignalInfo(); - res->memoryAddress = memoryAddress; - res->type = type; - res->name = name; - res->numberOfDimensions = numberOfDimensions; - res->numberOfElements = numberOfElements; - res->isTracing = false; - res->isForcing = false; - res->internalID = sigIdx; - res->decimationFactor = 1; - res->decimationCounter = 0; - res->breakOp = BREAK_OFF; - res->breakThreshold = 0.0; - signals.Push(res); - } - if (sigIdx != 0xFFFFFFFF) { - bool foundAlias = false; - for (uint32 i = 0; i < aliases.Size(); i++) { - if (aliases[i].name == name) { - foundAlias = true; - break; - } - } - if (!foundAlias) { - SignalAlias a; - a.name = name; - a.signalIndex = sigIdx; - aliases.Push(a); - } - } - mutex.FastUnLock(); - return res; -} - -void DebugService::ProcessSignal(DebugSignalInfo *signalInfo, uint32 size, - uint64 timestamp) { - if (signalInfo == NULL_PTR(DebugSignalInfo *)) - return; - if (signalInfo->isForcing) { - memcpy(signalInfo->memoryAddress, signalInfo->forcedValue, size); - } - if (signalInfo->isTracing) { - // FIX #4: decimationCounter read-modify-write is a data race when multiple - // RT threads call ProcessSignal() concurrently for signals with the same - // DebugSignalInfo (e.g. an output signal read by two GAMs). - // - // Pattern: atomically increment, then claim the "push token" by exchanging - // back to zero only when the counter reaches the decimation factor. - // Only the thread that observes old >= decimationFactor actually pushes; - // all others just increment and return. No separate lock is needed for - // the counter itself. - // - // tracePushMutex still serialises the Push() call (FIX #2) because - // TraceRingBuffer is SPSC and multiple RT threads can be pushing concurrently. - Atomic::Add((volatile int32 *)&signalInfo->decimationCounter, 1); - if ((uint32)signalInfo->decimationCounter >= signalInfo->decimationFactor) { - int32 old = Atomic::Exchange((volatile int32 *)&signalInfo->decimationCounter, 0); - if ((uint32)old >= signalInfo->decimationFactor) { - tracePushMutex.FastLock(); - traceBuffer.Push(signalInfo->internalID, timestamp, - (uint8 *)signalInfo->memoryAddress, size); - tracePushMutex.FastUnLock(); - } - } - } -} - -void DebugService::RegisterBroker(DebugSignalInfo **signalPointers, - uint32 numSignals, MemoryMapBroker *broker, - volatile bool *anyActiveFlag, - Vec *activeIndices, - Vec *activeSizes, - FastPollingMutexSem *activeMutex, - volatile bool *anyBreakFlag, - Vec *breakIndices, - const char8 *gamName, bool isOutput) { - mutex.FastLock(); - BrokerInfo b; - b.signalPointers = signalPointers; - b.numSignals = numSignals; - b.broker = broker; - b.anyActiveFlag = anyActiveFlag; - b.activeIndices = activeIndices; - b.activeSizes = activeSizes; - b.activeMutex = activeMutex; - b.anyBreakFlag = anyBreakFlag; - b.breakIndices = breakIndices; - b.isOutput = isOutput; - if (gamName != NULL_PTR(const char8 *)) - b.gamName = gamName; - brokers.Push(b); - mutex.FastUnLock(); -} - -void DebugService::UpdateBrokersActiveStatus() { - for (uint32 i = 0; i < brokers.Size(); i++) { - // FIX #3: signalPointers may be NULL when numSignals > 0 if the broker was - // registered with a null array (e.g. from unit tests or misconfigured brokers). - // Dereferencing NULL would be UB; skip the broker entirely. - if (brokers[i].signalPointers == NULL_PTR(DebugSignalInfo **)) continue; - - uint32 count = 0; - for (uint32 j = 0; j < brokers[i].numSignals; j++) { - DebugSignalInfo *s = brokers[i].signalPointers[j]; - if (s != NULL_PTR(DebugSignalInfo *) && (s->isTracing || s->isForcing)) { - count++; - } - } - - Vec tempInd; - Vec tempSizes; - for (uint32 j = 0; j < brokers[i].numSignals; j++) { - DebugSignalInfo *s = brokers[i].signalPointers[j]; - if (s != NULL_PTR(DebugSignalInfo *) && (s->isTracing || s->isForcing)) { - tempInd.Push(j); - tempSizes.Push((brokers[i].broker != NULL_PTR(MemoryMapBroker *)) - ? brokers[i].broker->GetCopyByteSize(j) - : 4); - } - } - - if (brokers[i].activeMutex) - brokers[i].activeMutex->FastLock(); - - // FIX #2: Use O(1) Swap instead of operator= (heap alloc + copy) inside - // the critical section. The old arrays are carried out in tempInd/tempSizes - // and freed after the lock is released. - if (brokers[i].activeIndices) - brokers[i].activeIndices->Swap(tempInd); - if (brokers[i].activeSizes) - brokers[i].activeSizes->Swap(tempSizes); - if (brokers[i].anyActiveFlag) - *(brokers[i].anyActiveFlag) = (count > 0); - - if (brokers[i].activeMutex) - brokers[i].activeMutex->FastUnLock(); - // tempInd and tempSizes now hold the old arrays and are freed here, - // outside the lock. - } -} - -void DebugService::UpdateBrokersBreakStatus() { - for (uint32 i = 0; i < brokers.Size(); i++) { - // FIX #3: same null guard as UpdateBrokersActiveStatus. - if (brokers[i].signalPointers == NULL_PTR(DebugSignalInfo **)) continue; - - Vec tempBreak; - uint32 count = 0; - for (uint32 j = 0; j < brokers[i].numSignals; j++) { - DebugSignalInfo *s = brokers[i].signalPointers[j]; - if (s != NULL_PTR(DebugSignalInfo *) && s->breakOp != BREAK_OFF) { - tempBreak.Push(j); - count++; - } - } - if (brokers[i].activeMutex) - brokers[i].activeMutex->FastLock(); - // FIX #2: O(1) Swap — old array freed after the lock is released. - if (brokers[i].breakIndices) - brokers[i].breakIndices->Swap(tempBreak); - if (brokers[i].anyBreakFlag) - *(brokers[i].anyBreakFlag) = (count > 0); - if (brokers[i].activeMutex) - brokers[i].activeMutex->FastUnLock(); - // tempBreak freed here, outside the lock. - } -} +// --------------------------------------------------------------------------- +// Execute / HandleMessage (framework boilerplate) +// --------------------------------------------------------------------------- ErrorManagement::ErrorType DebugService::Execute(ExecutionInfo &info) { - return ErrorManagement::FatalError; + (void)info; + return ErrorManagement::FatalError; } ErrorManagement::ErrorType DebugService::HandleMessage(ReferenceT &data) { - printf(" DebugService received custom message: Function=%s\n", (const char8*)data->GetFunction()); - return ErrorManagement::NoError; + (void)data; + return ErrorManagement::NoError; } -ErrorManagement::ErrorType DebugService::Server(ExecutionInfo &info) { - if (info.GetStage() == ExecutionInfo::TerminationStage) - return ErrorManagement::NoError; - if (info.GetStage() == ExecutionInfo::StartupStage) { - serverThreadId = Threads::Id(); - // Wait for ObjectRegistryDatabase::Initialise() to finish processing all - // sibling objects (LoggerService etc. come after DebugService in the config). - // 500 ms is well above any realistic initialisation time. - Sleep::MSec(500u); - InjectTcpLoggerIfNeeded(); - return ErrorManagement::NoError; - } - // The MARTe2 framework calls Execute() in a loop; each call should do - // one unit of work and return so the framework can check for Stop(). - // This replaces the old internal infinite-while pattern. - // - // FIX #2: activeClient is guarded by clientMutex. - // Rule: lock clientMutex only when ASSIGNING the pointer (including to NULL). - // Do NOT hold it across blocking I/O (Read/Write) — that would stall any future - // thread trying to grab the lock to check whether there is a live client. - // The command handler (HandleCommand) receives the pointer by value so it is - // safe to call without holding the lock. - // Helper: current time in milliseconds (used for rate limiting and idle timeout) - uint64 nowMs = (uint64)((float64)HighResolutionTimer::Counter() * - HighResolutionTimer::Period() * 1000.0); +// --------------------------------------------------------------------------- +// InjectTcpLoggerIfNeeded +// --------------------------------------------------------------------------- - if (activeClient == NULL_PTR(BasicTCPSocket *)) { - // Wait briefly for a new connection; return so the framework loop can - // check if Stop() was requested between calls. - BasicTCPSocket *newClient = tcpServer.WaitConnection(TimeoutType(100)); - if (newClient != NULL_PTR(BasicTCPSocket *)) { - clientMutex.FastLock(); - activeClient = newClient; // publish pointer — visible to other threads after unlock - clientMutex.FastUnLock(); - // FIX #6/#8: reset per-connection state when a new client connects - cmdCountInWindow = 0u; - cmdWindowStartMs = nowMs; - lastDataTimeMs = nowMs; +void DebugService::InjectTcpLoggerIfNeeded() { + if (logPort == 0u) return; + + Reference existing = ObjectRegistryDatabase::Instance()->Find("LoggerService"); + if (existing.IsValid()) { + ReferenceContainer *rc = + dynamic_cast(existing.operator->()); + if (rc != NULL_PTR(ReferenceContainer *)) { + for (uint32 i = 0u; i < rc->Size(); i++) { + ReferenceT child = rc->Get(i); + if (child.IsValid()) return; // already has a TcpLogger + } + } } - } else { - // FIX #8: idle-timeout check — runs every Execute() invocation even when - // no data arrives, so a client that half-sends a command and goes silent - // cannot hold the slot open indefinitely. - if (nowMs - lastDataTimeMs > CLIENT_IDLE_TIMEOUT_MS) { - REPORT_ERROR_STATIC(ErrorManagement::Warning, - "Server: TCP client idle for >%u ms — closing connection.", CLIENT_IDLE_TIMEOUT_MS); - inputBuffer = ""; // FIX #10: discard carry-over on disconnect - clientMutex.FastLock(); - activeClient->Close(); - delete activeClient; - activeClient = NULL_PTR(BasicTCPSocket *); - clientMutex.FastUnLock(); - cmdCountInWindow = 0u; - } else if (!activeClient->IsConnected()) { - // Check if client is still connected - inputBuffer = ""; // FIX #10 - clientMutex.FastLock(); - activeClient->Close(); - delete activeClient; - activeClient = NULL_PTR(BasicTCPSocket *); - clientMutex.FastUnLock(); - } else { - char buffer[1024]; - uint32 size = 1024; - if (activeClient->Read(buffer, size)) { - if (size > 0) { - lastDataTimeMs = nowMs; // FIX #8: refresh idle timestamp - // FIX #6: slide rate-limit window - if (nowMs - cmdWindowStartMs >= 1000u) { - cmdWindowStartMs = nowMs; + ConfigurationDatabase lsCdb; + (void)lsCdb.Write("Class", "LoggerService"); + uint32 cpus = 1u; + (void)lsCdb.Write("CPUs", cpus); + if (lsCdb.CreateRelative("+DebugConsumer")) { + (void)lsCdb.Write("Class", "TcpLogger"); + uint32 p = static_cast(logPort); + (void)lsCdb.Write("Port", p); + (void)lsCdb.MoveToAncestor(1u); + } + (void)lsCdb.MoveToRoot(); + + ReferenceT ls( + "LoggerService", GlobalObjectsDatabase::Instance()->GetStandardHeap()); + if (!ls.IsValid()) return; + ls->SetName("LoggerService"); + if (!ls->Initialise(lsCdb)) return; + (void)ObjectRegistryDatabase::Instance()->Insert(ls); +} + +// --------------------------------------------------------------------------- +// Server thread +// --------------------------------------------------------------------------- + +ErrorManagement::ErrorType DebugService::Server(ExecutionInfo &info) { + if (info.GetStage() == ExecutionInfo::TerminationStage) + return ErrorManagement::NoError; + if (info.GetStage() == ExecutionInfo::StartupStage) { + serverThreadId = Threads::Id(); + Sleep::MSec(500u); + InjectTcpLoggerIfNeeded(); + return ErrorManagement::NoError; + } + + uint64 nowMs = (uint64)((float64)HighResolutionTimer::Counter() * + HighResolutionTimer::Period() * 1000.0); + + if (activeClient == NULL_PTR(BasicTCPSocket *)) { + BasicTCPSocket *newClient = tcpServer.WaitConnection(TimeoutType(100)); + if (newClient != NULL_PTR(BasicTCPSocket *)) { + clientMutex.FastLock(); + activeClient = newClient; + clientMutex.FastUnLock(); cmdCountInWindow = 0u; - } - - // FIX #10: guard against a client that never sends a newline, - // which would grow inputBuffer without bound. - if (inputBuffer.Size() + (uint32)size > INPUT_BUFFER_MAX) { + cmdWindowStartMs = nowMs; + lastDataTimeMs = nowMs; + } + } else { + if (nowMs - lastDataTimeMs > CLIENT_IDLE_TIMEOUT_MS) { REPORT_ERROR_STATIC(ErrorManagement::Warning, - "Server: input buffer overflow (>%u bytes without newline) " - "— disconnecting.", INPUT_BUFFER_MAX); + "Server: TCP client idle for >%u ms — closing connection.", + CLIENT_IDLE_TIMEOUT_MS); inputBuffer = ""; clientMutex.FastLock(); activeClient->Close(); @@ -700,1372 +256,197 @@ ErrorManagement::ErrorType DebugService::Server(ExecutionInfo &info) { activeClient = NULL_PTR(BasicTCPSocket *); clientMutex.FastUnLock(); cmdCountInWindow = 0u; - } else { - // FIX #10: accumulate data; parse only complete (newline-terminated) - // commands so that commands split across TCP segments are assembled - // correctly before dispatch. - (void)inputBuffer.Seek(inputBuffer.Size()); - uint32 writeSize = (uint32)size; - inputBuffer.Write(buffer, writeSize); + } else if (!activeClient->IsConnected()) { + inputBuffer = ""; + clientMutex.FastLock(); + activeClient->Close(); + delete activeClient; + activeClient = NULL_PTR(BasicTCPSocket *); + clientMutex.FastUnLock(); + } else { + char buffer[1024]; + uint32 size = 1024u; + if (activeClient->Read(buffer, size)) { + if (size > 0u) { + lastDataTimeMs = nowMs; - const char8 *raw = inputBuffer.Buffer(); - uint32 total = (uint32)inputBuffer.Size(); - uint32 lineStart = 0u; - bool rateLimitExceeded = false; + if (nowMs - cmdWindowStartMs >= 1000u) { + cmdWindowStartMs = nowMs; + cmdCountInWindow = 0u; + } - for (uint32 pos = 0u; pos < total && !rateLimitExceeded; pos++) { - if (raw[pos] != '\n') continue; + if (inputBuffer.Size() + (uint32)size > INPUT_BUFFER_MAX) { + REPORT_ERROR_STATIC(ErrorManagement::Warning, + "Server: input buffer overflow (>%u bytes without newline) " + "— disconnecting.", INPUT_BUFFER_MAX); + inputBuffer = ""; + clientMutex.FastLock(); + activeClient->Close(); + delete activeClient; + activeClient = NULL_PTR(BasicTCPSocket *); + clientMutex.FastUnLock(); + cmdCountInWindow = 0u; + } else { + (void)inputBuffer.Seek(inputBuffer.Size()); + uint32 writeSize = (uint32)size; + inputBuffer.Write(buffer, writeSize); - uint32 len = pos - lineStart; - // Trim trailing CR - if (len > 0u && raw[lineStart + len - 1u] == '\r') len--; + const char8 *raw = inputBuffer.Buffer(); + uint32 total = (uint32)inputBuffer.Size(); + uint32 lineStart = 0u; + bool rateLimitExceeded = false; - if (len > 0u) { - cmdCountInWindow++; - if (cmdCountInWindow > CMD_RATE_LIMIT) { - REPORT_ERROR_STATIC(ErrorManagement::Warning, - "Server: client exceeded rate limit (%u cmd/s) — disconnecting.", - CMD_RATE_LIMIT); - rateLimitExceeded = true; - break; + for (uint32 pos = 0u; pos < total && !rateLimitExceeded; pos++) { + if (raw[pos] != '\n') continue; + uint32 len = pos - lineStart; + if (len > 0u && raw[lineStart + len - 1u] == '\r') len--; + if (len > 0u) { + cmdCountInWindow++; + if (cmdCountInWindow > CMD_RATE_LIMIT) { + REPORT_ERROR_STATIC(ErrorManagement::Warning, + "Server: client exceeded rate limit (%u cmd/s) " + "— disconnecting.", CMD_RATE_LIMIT); + rateLimitExceeded = true; + break; + } + StreamString command; + uint32 cmdLen = len; + command.Write(raw + lineStart, cmdLen); + + // Dispatch via base HandleCommand, write response to socket + StreamString out; + HandleCommand(command, out); + if (out.Size() > 0u) { + uint32 outSz = out.Size(); + (void)activeClient->Write(out.Buffer(), outSz); + } + } + lineStart = pos + 1u; + } + + if (rateLimitExceeded) { + inputBuffer = ""; + clientMutex.FastLock(); + activeClient->Close(); + delete activeClient; + activeClient = NULL_PTR(BasicTCPSocket *); + clientMutex.FastUnLock(); + cmdCountInWindow = 0u; + } else { + StreamString newInputBuffer; + if (lineStart < total) { + uint32 remLen = total - lineStart; + newInputBuffer.Write(raw + lineStart, remLen); + } + inputBuffer = newInputBuffer; + } + } } - StreamString command; - uint32 cmdLen = len; - command.Write(raw + lineStart, cmdLen); - HandleCommand(command, activeClient); - } - lineStart = pos + 1u; - } - - if (rateLimitExceeded) { - inputBuffer = ""; - clientMutex.FastLock(); - activeClient->Close(); - delete activeClient; - activeClient = NULL_PTR(BasicTCPSocket *); - clientMutex.FastUnLock(); - cmdCountInWindow = 0u; } else { - // Save the incomplete line (bytes after the last '\n') for the - // next Read() — they form the start of the next command. - StreamString newInputBuffer; - if (lineStart < total) { - uint32 remLen = total - lineStart; - newInputBuffer.Write(raw + lineStart, remLen); - } - inputBuffer = newInputBuffer; + inputBuffer = ""; + clientMutex.FastLock(); + activeClient->Close(); + delete activeClient; + activeClient = NULL_PTR(BasicTCPSocket *); + clientMutex.FastUnLock(); } - } } - } else { - // Read failed (client disconnected or error), clean up - inputBuffer = ""; // FIX #10 - clientMutex.FastLock(); - activeClient->Close(); - delete activeClient; - activeClient = NULL_PTR(BasicTCPSocket *); - clientMutex.FastUnLock(); - } } - } - return ErrorManagement::NoError; + return ErrorManagement::NoError; } +// --------------------------------------------------------------------------- +// Streamer thread (UDP binary telemetry) +// --------------------------------------------------------------------------- + ErrorManagement::ErrorType DebugService::Streamer(ExecutionInfo &info) { - if (info.GetStage() == ExecutionInfo::TerminationStage) - return ErrorManagement::NoError; - if (info.GetStage() == ExecutionInfo::StartupStage) { - streamerThreadId = Threads::Id(); - return ErrorManagement::NoError; - } - // Set UDP destination (idempotent, called each Execute() invocation) - InternetHost dest(streamPort, streamIP.Buffer()); - (void)udpSocket.SetDestination(dest); - - // Poll monitored signals - uint64 currentTimeMs = (uint64)((float64)HighResolutionTimer::Counter() * - HighResolutionTimer::Period() * 1000.0); - mutex.FastLock(); - for (uint32 i = 0; i < monitoredSignals.Size(); i++) { - if (currentTimeMs >= (monitoredSignals[i].lastPollTime + monitoredSignals[i].periodMs)) { - monitoredSignals[i].lastPollTime = currentTimeMs; - uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() * - HighResolutionTimer::Period() * 1000000.0); - void *address = NULL_PTR(void *); - if (monitoredSignals[i].dataSource->GetSignalMemoryBuffer(monitoredSignals[i].signalIdx, 0, address)) { - // FIX #11: ProcessSignal() (called from RT broker threads) serialises - // its traceBuffer.Push() under tracePushMutex. The Streamer's monitored- - // signal path previously skipped that lock, creating a multi-producer race - // between the RT threads and the Streamer thread. - // - // Lock order: mutex (always held here) → tracePushMutex. - // RT broker threads only ever acquire tracePushMutex (never mutex), so - // this ordering introduces no deadlock risk. - tracePushMutex.FastLock(); - traceBuffer.Push(monitoredSignals[i].internalID, ts, (uint8 *)address, monitoredSignals[i].size); - tracePushMutex.FastUnLock(); - } - } - } - mutex.FastUnLock(); - - // Drain ring buffer into UDP packet(s). - // - // FIX #1: Two-level bounds checking to prevent buffer overflow. - // - // Level 1 — per-sample maximum size: - // sampleData is SAMPLE_BUF_SIZE bytes. Pop() enforces this via maxSize so - // the local buffer can never be overrun by Pop() itself. - // - // Level 2 — assembly-buffer hard limit: - // After a flush, streamerPacketOffset resets to sizeof(TraceHeader). The - // next sample occupies (sizeof(TraceHeader) + 16 + size) bytes. With - // SAMPLE_BUF_SIZE = 1024 that is at most ~1060 bytes — well within - // STREAMER_BUFFER_SIZE = 4096. The explicit guard below catches the case - // where SAMPLE_BUF_SIZE or STREAMER_MTU are later changed carelessly so - // that a single sample could still overflow the buffer. - static const uint32 SAMPLE_BUF_SIZE = 1024u; - // Compile-time sanity: one full header + per-sample header + max sample must fit - // inside the assembly buffer. If this fires, raise STREAMER_BUFFER_SIZE or - // lower SAMPLE_BUF_SIZE. - // (C++98-compatible static assert via sizeof a negative-size array) - typedef char StaticAssert_StreamerBufferTooSmall[ - (sizeof(TraceHeader) + 16u + SAMPLE_BUF_SIZE <= STREAMER_BUFFER_SIZE) ? 1 : -1]; - (void)sizeof(StaticAssert_StreamerBufferTooSmall); // suppress unused-typedef warning - - uint32 id, size; - uint64 ts; - uint8 sampleData[SAMPLE_BUF_SIZE]; - bool hasData = false; - while (traceBuffer.Pop(id, ts, sampleData, size, SAMPLE_BUF_SIZE)) { - hasData = true; - - // Level 2 guard — should never fire given the static assert above, but - // defends against future changes that widen SAMPLE_BUF_SIZE or shrink - // STREAMER_BUFFER_SIZE without updating the other. - if (size > SAMPLE_BUF_SIZE || streamerPacketOffset + 16u + size > STREAMER_BUFFER_SIZE) { - REPORT_ERROR_STATIC(ErrorManagement::Warning, - "Streamer: sample size %u would overflow assembly buffer (%u bytes used of %u) " - "— sample dropped.", size, streamerPacketOffset, STREAMER_BUFFER_SIZE); - continue; + if (info.GetStage() == ExecutionInfo::TerminationStage) + return ErrorManagement::NoError; + if (info.GetStage() == ExecutionInfo::StartupStage) { + streamerThreadId = Threads::Id(); + return ErrorManagement::NoError; } - if (streamerPacketOffset == 0u) { - TraceHeader header; - header.magic = 0xDA7A57AD; - header.seq = streamerSequenceNumber++; - header.timestamp = HighResolutionTimer::Counter(); - header.count = 0; - memcpy(streamerPacketBuffer, &header, sizeof(TraceHeader)); - streamerPacketOffset = sizeof(TraceHeader); - } - // Flush the current packet when adding this sample would exceed the MTU. - if (streamerPacketOffset + 16u + size > STREAMER_MTU) { - uint32 toWrite = streamerPacketOffset; - (void)udpSocket.Write((char8 *)streamerPacketBuffer, toWrite); - TraceHeader header; - header.magic = 0xDA7A57AD; - header.seq = streamerSequenceNumber++; - header.timestamp = HighResolutionTimer::Counter(); - header.count = 0; - memcpy(streamerPacketBuffer, &header, sizeof(TraceHeader)); - streamerPacketOffset = sizeof(TraceHeader); - } - memcpy(&streamerPacketBuffer[streamerPacketOffset], &id, 4); - memcpy(&streamerPacketBuffer[streamerPacketOffset + 4], &ts, 8); - memcpy(&streamerPacketBuffer[streamerPacketOffset + 12], &size, 4); - memcpy(&streamerPacketBuffer[streamerPacketOffset + 16], sampleData, size); - streamerPacketOffset += (16u + size); - ((TraceHeader *)streamerPacketBuffer)->count++; - } - if (streamerPacketOffset > 0u) { - uint32 toWrite = streamerPacketOffset; - (void)udpSocket.Write((char8 *)streamerPacketBuffer, toWrite); - streamerPacketOffset = 0u; - } - if (!hasData) { - Sleep::MSec(1); - } - return ErrorManagement::NoError; -} + InternetHost dest(streamPort, streamIP.Buffer()); + (void)udpSocket.SetDestination(dest); -bool DebugServiceI::GetFullObjectName(const Object &obj, - StreamString &fullPath) { - fullPath = ""; - if (FindPathInContainer(ObjectRegistryDatabase::Instance(), &obj, fullPath)) { - return true; - } - const char8 *name = obj.GetName(); - if (name != NULL_PTR(const char8 *)) - fullPath = name; - return true; -} - -void DebugService::HandleCommand(StreamString cmd, BasicTCPSocket *client) { - StreamString token; - cmd.Seek(0); - char8 term; - const char8 *delims = " \r\n"; - if (cmd.GetToken(token, delims, term)) { - if (token == "FORCE") { - StreamString name, val; - if (cmd.GetToken(name, delims, term) && cmd.GetToken(val, delims, term)) { - uint32 count = ForceSignal(name.Buffer(), val.Buffer()); - if (client) { - StreamString resp; - resp.Printf("OK FORCE %u\n", count); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); - } - } - } else if (token == "UNFORCE") { - StreamString name; - if (cmd.GetToken(name, delims, term)) { - uint32 count = UnforceSignal(name.Buffer()); - if (client) { - StreamString resp; - resp.Printf("OK UNFORCE %u\n", count); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); - } - } - } else if (token == "TRACE") { - StreamString name, state, decim; - if (cmd.GetToken(name, delims, term) && - cmd.GetToken(state, delims, term)) { - bool enable = (state == "1"); - uint32 d = 1; - if (cmd.GetToken(decim, delims, term)) { - AnyType decimVal(UnsignedInteger32Bit, 0u, &d); - AnyType decimStr(CharString, 0u, decim.Buffer()); - (void)TypeConvert(decimVal, decimStr); - } - uint32 count = TraceSignal(name.Buffer(), enable, d); - if (client) { - StreamString resp; - resp.Printf("OK TRACE %u\n", count); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); - } - } - } else if (token == "STEP") { - StreamString nStr; - uint32 n = 1u; - if (cmd.GetToken(nStr, delims, term)) { - AnyType nVal(UnsignedInteger32Bit, 0u, &n); - AnyType nS(CharString, 0u, nStr.Buffer()); - (void)TypeConvert(nVal, nS); - } - // Optional thread name: STEP [] - StreamString threadStr; - const char8 *threadArg = NULL_PTR(const char8 *); - if (cmd.GetToken(threadStr, delims, term) && threadStr.Size() > 0u) { - threadArg = threadStr.Buffer(); - } - Step(n, threadArg); - if (client) { - StreamString resp; - resp.Printf("OK STEP %u\n", n); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); - } - } else if (token == "STEP_STATUS") { - GetStepStatus(client); - } else if (token == "VALUE") { - StreamString sigName; - if (cmd.GetToken(sigName, delims, term)) { - GetSignalValue(sigName.Buffer(), client); - } else if (client) { - const char8 *errResp = "{\"Error\": \"Missing signal name\"}\nOK VALUE\n"; - uint32 s = StringHelper::Length(errResp); - (void)client->Write(errResp, s); - } - } else if (token == "BREAK") { - // BREAK — set break condition - // BREAK OFF — clear break condition - StreamString name, opStr; - if (cmd.GetToken(name, delims, term) && cmd.GetToken(opStr, delims, term)) { - uint32 count = 0; - if (opStr == "OFF") { - count = ClearBreak(name.Buffer()); - } else { - StreamString threshStr; - if (cmd.GetToken(threshStr, delims, term)) { - uint8 op = BREAK_OFF; - if (opStr == ">") op = BREAK_GT; - else if (opStr == "<") op = BREAK_LT; - else if (opStr == "==") op = BREAK_EQ; - else if (opStr == ">=") op = BREAK_GEQ; - else if (opStr == "<=") op = BREAK_LEQ; - else if (opStr == "!=") op = BREAK_NEQ; - if (op != BREAK_OFF) { - float64 threshold = 0.0; - AnyType thrVal(Float64Bit, 0u, &threshold); - AnyType thrStr(CharString, 0u, threshStr.Buffer()); - (void)TypeConvert(thrVal, thrStr); - count = SetBreak(name.Buffer(), op, threshold); - } - } - } - if (client) { - StreamString resp; - resp.Printf("OK BREAK %u\n", count); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); - } - } - } else if (token == "DISCOVER") - Discover(client); - else if (token == "MSG") { - StreamString dest, func, waitStr; - if (cmd.GetToken(dest, delims, term) && - cmd.GetToken(func, delims, term) && - cmd.GetToken(waitStr, delims, term)) { - bool wait = (waitStr == "1"); - - const char8 *pStart = cmd.Buffer() + cmd.Position(); - StreamString rawPayload = pStart; - - // Decode escaped newlines (\n) - StreamString payload; - rawPayload.Seek(0u); - char8 c; - while (rawPayload.Size() > rawPayload.Position()) { - uint32 readS = 1; - if (rawPayload.Read(&c, readS)) { - if (c == '\\') { - char8 next; - if (rawPayload.Read(&next, readS)) { - if (next == 'n') { - payload += '\n'; - } else { - payload += c; - payload += next; - } - } else { - payload += c; - } - } else { - payload += c; - } - } - } - - ReferenceT msg( - "Message", GlobalObjectsDatabase::Instance()->GetStandardHeap()); - - ConfigurationDatabase msgConfig; - msgConfig.Write("Destination", dest.Buffer()); - msgConfig.Write("Function", func.Buffer()); - if (wait) { - msgConfig.Write("Mode", "ExpectsReply"); - } - - // Parse payload key=value lines into a ConfigurationDatabase. - // ConstantGAM::SetOutput (and similar handlers) expect a - // ReferenceT inserted into the Message's - // reference container — NOT a sub-node of the message config. - ReferenceT paramCdb( - "ConfigurationDatabase", - GlobalObjectsDatabase::Instance()->GetStandardHeap()); - - if (payload.Size() > 0u && paramCdb.IsValid()) { - payload.Seek(0u); - StreamString line; - while (payload.GetToken(line, "\n", term)) { - if (line.Size() > 0u) { - const char8 *eq = StringHelper::SearchChar(line.Buffer(), '='); - if (eq != NULL_PTR(const char8 *)) { - uint32 eqPos = (uint32)(eq - line.Buffer()); - - // FIX #7: Enforce buffer bounds before reading key/value. - // A key longer than keyBuf would be silently truncated by - // Read(), producing a mismatched key (e.g. "LongKe" instead - // of "LongKey") that causes CDB.Write() to inject a garbled - // parameter. Skip the entire line if the key overflows — - // the CDB write would be nonsensical anyway. - // Values are safely capped: the worst case is a truncated - // value, which the target GAM can detect and reject. - static const uint32 KEY_BUF_SIZE = 256u; - static const uint32 VAL_BUF_SIZE = 1024u; - if (eqPos >= KEY_BUF_SIZE) { - REPORT_ERROR_STATIC(ErrorManagement::Warning, - "MSG: key length %u exceeds buffer (%u) — line skipped.", - eqPos, KEY_BUF_SIZE); - line = ""; - continue; - } - - (void)line.Seek(0u); - char8 keyBuf[KEY_BUF_SIZE]; - MemoryOperationsHelper::Set(keyBuf, '\0', KEY_BUF_SIZE); - uint32 keyReadSize = eqPos; - (void)line.Read(keyBuf, keyReadSize); - - (void)line.Seek(eqPos + 1u); - uint32 valLen = (uint32)(line.Size() - eqPos - 1u); - // Truncate silently to VAL_BUF_SIZE - 1 (leave room for '\0') - if (valLen >= VAL_BUF_SIZE) { - REPORT_ERROR_STATIC(ErrorManagement::Warning, - "MSG: value length %u truncated to %u for key '%s'.", - valLen, VAL_BUF_SIZE - 1u, keyBuf); - valLen = VAL_BUF_SIZE - 1u; - } - char8 valBuf[VAL_BUF_SIZE]; - MemoryOperationsHelper::Set(valBuf, '\0', VAL_BUF_SIZE); - (void)line.Read(valBuf, valLen); - - // Trim trailing whitespace from value - for (int32 ti = (int32)valLen - 1; ti >= 0; ti--) { - if (valBuf[ti] == ' ' || valBuf[ti] == '\r' || valBuf[ti] == '\t') - valBuf[ti] = '\0'; - else - break; - } - - StreamString key = keyBuf; - key = key.Buffer(); // trim happens via assignment - // Trim leading whitespace from key - const char8 *kp = keyBuf; - while (*kp == ' ' || *kp == '\t') kp++; - - if (*kp != '\0') { - (void)paramCdb->Write(kp, valBuf); - } - } - } - line = ""; - } - } - - ErrorManagement::ErrorType err = ErrorManagement::ParametersError; - if (msg->Initialise(msgConfig)) { - if (paramCdb.IsValid() && payload.Size() > 0u) { - // Insert the CDB as a ReferenceT parameter - (void)msg->Insert(paramCdb); - } - // Find destination object in the global database - Reference destObj = ObjectRegistryDatabase::Instance()->Find(dest.Buffer()); - if (destObj.IsValid()) { - Object* sender = this; - StreamString myPath; - if (!GetFullObjectName(*this, myPath)) { - sender = NULL_PTR(Object*); - } - - if (wait) { - err = MessageI::WaitForReply(msg, TTInfiniteWait); - } else { - (void)MessageI::SendMessage(msg, sender); - // Fire-and-forget: destination found, message sent. - // Whether the recipient had a matching filter is not - // reported back to the caller — return OK. - err = ErrorManagement::NoError; - } - } else { - printf(" MSG: Destination object %s not found in ORD\n", dest.Buffer()); - } - - if (err != ErrorManagement::NoError) { - printf(" MSG: MessageI dispatch failed.\n"); - } - } else { - printf(" MSG: Message initialization failed\n"); - } - - if (client) { - - if (err == ErrorManagement::NoError) { - uint32 okSize = 7; - (void)client->Write("OK MSG\n", okSize); - } else { - uint32 errSize = 10; - (void)client->Write("ERROR MSG\n", errSize); - } - } - } - } - else if (token == "SERVICE_INFO") { - if (client) { - StreamString resp; - resp.Printf("OK SERVICE_INFO TCP_CTRL:%u UDP_STREAM:%u TCP_LOG:%u STATE:%s\n", - controlPort, streamPort, logPort, isPaused ? "PAUSED" : "RUNNING"); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); - } - } else if (token == "MONITOR") { - StreamString subToken; - if (cmd.GetToken(subToken, delims, term) && subToken == "SIGNAL") { - StreamString name, period; - if (cmd.GetToken(name, delims, term) && cmd.GetToken(period, delims, term)) { - uint32 p = 100; - AnyType pVal(UnsignedInteger32Bit, 0u, &p); - AnyType pStr(CharString, 0u, period.Buffer()); - (void)TypeConvert(pVal, pStr); - uint32 count = RegisterMonitorSignal(name.Buffer(), p); - if (client) { - StreamString resp; - resp.Printf("OK MONITOR %u\n", count); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); - } - } - } - } else if (token == "UNMONITOR") { - StreamString subToken; - if (cmd.GetToken(subToken, delims, term) && subToken == "SIGNAL") { - StreamString name; - if (cmd.GetToken(name, delims, term)) { - uint32 count = UnmonitorSignal(name.Buffer()); - if (client) { - StreamString resp; - resp.Printf("OK UNMONITOR %u\n", count); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); - } - } - } - } else if (token == "CONFIG") - ServeConfig(client); - else if (token == "PAUSE") { - SetPaused(true); - if (client) { - uint32 s = 3; - (void)client->Write("OK\n", s); - } - } else if (token == "RESUME") { - SetPaused(false); - if (client) { - uint32 s = 3; - (void)client->Write("OK\n", s); - } - } else if (token == "TREE") { - StreamString json; - json = "{\"Name\": \"Root\", \"Class\": \"ObjectRegistryDatabase\", " - "\"Children\": [\n"; - (void)ExportTree(ObjectRegistryDatabase::Instance(), json, NULL_PTR(const char8 *)); - json += "\n]}\nOK TREE\n"; - uint32 s = json.Size(); - if (client) - (void)client->Write(json.Buffer(), s); - } else if (token == "INFO") { - StreamString path; - if (cmd.GetToken(path, delims, term)) - InfoNode(path.Buffer(), client); - } else if (token == "LS") { - StreamString path; - if (cmd.GetToken(path, delims, term)) - ListNodes(path.Buffer(), client); - else - ListNodes(NULL_PTR(const char8 *), client); - } - } -} - -void DebugService::EnrichWithConfig(const char8 *path, StreamString &json) { - if (path == NULL_PTR(const char8 *)) - return; - if (!manualConfigSet) { - RebuildConfigFromRegistry(); - } - fullConfig.MoveToRoot(); - - fprintf(stderr, "[EnrichWithConfig] path=%s\n", path); - - const char8 *current = path; - bool ok = true; - while (ok) { - const char8 *nextDot = StringHelper::SearchString(current, "."); - StreamString part; - if (nextDot != NULL_PTR(const char8 *)) { - uint32 len = (uint32)(nextDot - current); - (void)part.Write(current, len); - current = nextDot + 1; - } else { - part = current; - ok = false; - } - - // Normalise short direction names to both forms so we search consistently. - // Paths use "In"/"Out" (alias convention); CDBs may store either form. - bool found = false; - - // 1. Try exact match (bare name - rebuilt-from-registry CDBs use this) - if (fullConfig.MoveRelative(part.Buffer())) { - fprintf(stderr, "[EnrichWithConfig] nav exact '%s' OK\n", part.Buffer()); - found = true; - } - - // 2. Try +name (raw config-file CDBs prefix nodes with '+') - if (!found) { - StreamString prefixed; - prefixed.Printf("+%s", part.Buffer()); - if (fullConfig.MoveRelative(prefixed.Buffer())) { - fprintf(stderr, "[EnrichWithConfig] nav prefixed '%s' OK\n", prefixed.Buffer()); - found = true; - } - } - - // 3. Expand short direction aliases: In -> InputSignals / Out -> OutputSignals - if (!found) { - if (part == "In") { - if (fullConfig.MoveRelative("InputSignals")) { - fprintf(stderr, "[EnrichWithConfig] nav 'In'->InputSignals OK\n"); - found = true; - } else if (fullConfig.MoveRelative("+InputSignals")) { - fprintf(stderr, "[EnrichWithConfig] nav 'In'->+InputSignals OK\n"); - found = true; - } - } else if (part == "Out") { - if (fullConfig.MoveRelative("OutputSignals")) { - found = true; - } else if (fullConfig.MoveRelative("+OutputSignals")) { - found = true; - } - } - } - - if (!found) { - fprintf(stderr, "[EnrichWithConfig] FAILED at part '%s'\n", part.Buffer()); - fullConfig.MoveToRoot(); - return; - } - } - - ConfigurationDatabase db; - fullConfig.Copy(db); - fullConfig.MoveToRoot(); - db.MoveToRoot(); - uint32 n = db.GetNumberOfChildren(); - for (uint32 i = 0u; i < n; i++) { - const char8 *name = db.GetChildName(i); - AnyType at = db.GetType(name); - if (!at.GetTypeDescriptor().isStructuredData) { - json += ", \""; - EscapeJson(name, json); - json += "\": \""; - char8 buf[1024]; - AnyType st(CharString, 0u, buf); - st.SetNumberOfElements(0, 1024); - if (TypeConvert(st, at)) { - EscapeJson(buf, json); - } - json += "\""; - } - } -} - -void DebugService::JsonifyDatabase(ConfigurationDatabase &db, - StreamString &json) { - json += "{"; - uint32 n = db.GetNumberOfChildren(); - for (uint32 i = 0u; i < n; i++) { - const char8 *name = db.GetChildName(i); - json += "\""; - EscapeJson(name, json); - json += "\": "; - if (db.MoveRelative(name)) { - ConfigurationDatabase child; - db.Copy(child); - JsonifyDatabase(child, json); - db.MoveToAncestor(1u); - } else { - AnyType at = db.GetType(name); - char8 buf[1024]; - AnyType st(CharString, 0u, buf); - st.SetNumberOfElements(0, 1024); - if (TypeConvert(st, at)) { - json += "\""; - EscapeJson(buf, json); - json += "\""; - } else { - json += "null"; - } - } - if (i < n - 1) - json += ", "; - } - json += "}"; -} - -void DebugService::ServeConfig(BasicTCPSocket *client) { - if (client == NULL_PTR(BasicTCPSocket *)) - return; - - // If no manual config was injected (test case), rebuild from the live registry. - // In production, Initialise() only captures the DebugService subtree (to avoid - // corrupting the shared CDB cursor), so we always need to rebuild here. - if (!manualConfigSet) { - RebuildConfigFromRegistry(); - } - - StreamString json; - fullConfig.MoveToRoot(); - JsonifyDatabase(fullConfig, json); - json += "\nOK CONFIG\n"; - uint32 s = json.Size(); - (void)client->Write(json.Buffer(), s); -} - -void DebugService::InfoNode(const char8 *path, BasicTCPSocket *client) { - if (!client) - return; - Reference ref = ObjectRegistryDatabase::Instance()->Find(path); - StreamString json = "{"; - if (ref.IsValid()) { - json += "\"Name\": \""; - EscapeJson(ref->GetName(), json); - json += "\", \"Class\": \""; - EscapeJson(ref->GetClassProperties()->GetName(), json); - json += "\""; - ConfigurationDatabase db; - if (ref->ExportData(db)) { - json += ", \"Config\": {"; - db.MoveToRoot(); - uint32 nChildren = db.GetNumberOfChildren(); - for (uint32 i = 0; i < nChildren; i++) { - const char8 *cname = db.GetChildName(i); - AnyType at = db.GetType(cname); - char8 valBuf[1024]; - AnyType strType(CharString, 0u, valBuf); - strType.SetNumberOfElements(0, 1024); - if (TypeConvert(strType, at)) { - json += "\""; - EscapeJson(cname, json); - json += "\": \""; - EscapeJson(valBuf, json); - json += "\""; - if (i < nChildren - 1) - json += ", "; - } - } - json += "}"; - } - EnrichWithConfig(path, json); - } else { - StreamString enrichAlias; + // Poll monitored signals and push to trace ring buffer + uint64 currentTimeMs = (uint64)((float64)HighResolutionTimer::Counter() * + HighResolutionTimer::Period() * 1000.0); mutex.FastLock(); - bool found = false; - for (uint32 i = 0; i < aliases.Size(); i++) { - if (aliases[i].name == path || - SuffixMatch(aliases[i].name.Buffer(), path)) { - DebugSignalInfo *s = signals[aliases[i].signalIndex]; - const char8 *tname = - TypeDescriptor::GetTypeNameFromTypeDescriptor(s->type); - json.Printf("\"Name\": \"%s\", \"Class\": \"Signal\", \"Type\": " - "\"%s\", \"ID\": %d", - s->name.Buffer(), tname ? tname : "Unknown", s->internalID); - enrichAlias = aliases[i].name; - found = true; - break; - } - } - if (!found) { - fprintf(stderr, "[InfoNode][%p] signal '%s' NOT found in %u aliases:\n", (void*)this, path, (uint32)aliases.Size()); - for (uint32 i = 0; i < aliases.Size(); i++) { - fprintf(stderr, " alias[%u] = '%s'\n", i, aliases[i].name.Buffer()); - } + for (uint32 i = 0u; i < monitoredSignals.Size(); i++) { + if (currentTimeMs >= (monitoredSignals[i].lastPollTime + + monitoredSignals[i].periodMs)) { + monitoredSignals[i].lastPollTime = currentTimeMs; + uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() * + HighResolutionTimer::Period() * 1000000.0); + void *address = NULL_PTR(void *); + if (monitoredSignals[i].dataSource->GetSignalMemoryBuffer( + monitoredSignals[i].signalIdx, 0u, address)) { + tracePushMutex.FastLock(); + traceBuffer.Push(monitoredSignals[i].internalID, ts, + (uint8 *)address, monitoredSignals[i].size); + tracePushMutex.FastUnLock(); + } + } } mutex.FastUnLock(); - if (found) - EnrichWithConfig(enrichAlias.Buffer(), json); - else - json += "\"Error\": \"Object not found\""; - } - json += "}\nOK INFO\n"; - uint32 s = json.Size(); - (void)client->Write(json.Buffer(), s); -} -uint32 DebugService::ExportTree(ReferenceContainer *container, - StreamString &json, const char8 *pathPrefix) { - if (container == NULL_PTR(ReferenceContainer *)) - return 0; - uint32 size = container->Size(); - uint32 validCount = 0; - for (uint32 i = 0u; i < size; i++) { - Reference child = container->Get(i); - if (child.IsValid()) { - if (validCount > 0u) - json += ",\n"; - StreamString nodeJson; - const char8 *cname = child->GetName(); - if (cname == NULL_PTR(const char8 *)) - cname = "unnamed"; + // Drain ring buffer into UDP packet(s) + static const uint32 SAMPLE_BUF_SIZE = 1024u; + typedef char StaticAssert_StreamerBufferTooSmall[ + (sizeof(TraceHeader) + 16u + SAMPLE_BUF_SIZE <= STREAMER_BUFFER_SIZE) ? 1 : -1]; + (void)sizeof(StaticAssert_StreamerBufferTooSmall); - StreamString currentPath; - if (pathPrefix != NULL_PTR(const char8 *)) { - currentPath.Printf("%s.%s", pathPrefix, cname); - } else { - currentPath = cname; - } + uint32 id, size; + uint64 ts; + uint8 sampleData[SAMPLE_BUF_SIZE]; + bool hasData = false; - nodeJson += "{\"Name\": \""; - EscapeJson(cname, nodeJson); - nodeJson += "\", \"Class\": \""; - EscapeJson(child->GetClassProperties()->GetName(), nodeJson); - nodeJson += "\""; - ReferenceContainer *inner = - dynamic_cast(child.operator->()); - DataSourceI *ds = dynamic_cast(child.operator->()); - GAM *gam = dynamic_cast(child.operator->()); - if ((inner != NULL_PTR(ReferenceContainer *)) || - (ds != NULL_PTR(DataSourceI *)) || (gam != NULL_PTR(GAM *))) { - nodeJson += ", \"Children\": [\n"; - uint32 subCount = 0u; - if (inner != NULL_PTR(ReferenceContainer *)) - subCount += ExportTree(inner, nodeJson, currentPath.Buffer()); - if (ds != NULL_PTR(DataSourceI *)) { - uint32 nSignals = ds->GetNumberOfSignals(); - for (uint32 j = 0u; j < nSignals; j++) { - if (subCount > 0u) - nodeJson += ",\n"; - subCount++; - StreamString sname; - (void)ds->GetSignalName(j, sname); - const char8 *stype = TypeDescriptor::GetTypeNameFromTypeDescriptor( - ds->GetSignalType(j)); - uint8 dims = 0u; - (void)ds->GetSignalNumberOfDimensions(j, dims); - uint32 elems = 0u; - (void)ds->GetSignalNumberOfElements(j, elems); - - StreamString signalFullPath; - signalFullPath.Printf("%s.%s", currentPath.Buffer(), sname.Buffer()); - bool traceable = false; - bool forcable = false; - (void)IsInstrumented(signalFullPath.Buffer(), traceable, forcable); - - nodeJson += "{\"Name\": \""; - EscapeJson(sname.Buffer(), nodeJson); - nodeJson += "\", \"Class\": \"Signal\", \"Type\": \""; - EscapeJson(stype ? stype : "Unknown", nodeJson); - nodeJson.Printf("\", \"Dimensions\": %d, \"Elements\": %u", dims, - elems); - nodeJson.Printf(", \"IsTraceable\": %s, \"IsForcable\": %s}", - traceable ? "true" : "false", - forcable ? "true" : "false"); - } + while (traceBuffer.Pop(id, ts, sampleData, size, SAMPLE_BUF_SIZE)) { + hasData = true; + if (size > SAMPLE_BUF_SIZE || + streamerPacketOffset + 16u + size > STREAMER_BUFFER_SIZE) { + REPORT_ERROR_STATIC(ErrorManagement::Warning, + "Streamer: sample size %u would overflow assembly buffer " + "(%u bytes used of %u) — sample dropped.", + size, streamerPacketOffset, STREAMER_BUFFER_SIZE); + continue; } - if (gam != NULL_PTR(GAM *)) { - uint32 nIn = gam->GetNumberOfInputSignals(); - for (uint32 j = 0u; j < nIn; j++) { - if (subCount > 0u) - nodeJson += ",\n"; - subCount++; - StreamString sname; - (void)gam->GetSignalName(InputSignals, j, sname); - const char8 *stype = TypeDescriptor::GetTypeNameFromTypeDescriptor( - gam->GetSignalType(InputSignals, j)); - uint32 dims = 0u; - (void)gam->GetSignalNumberOfDimensions(InputSignals, j, dims); - uint32 elems = 0u; - (void)gam->GetSignalNumberOfElements(InputSignals, j, elems); - - StreamString signalFullPath; - signalFullPath.Printf("%s.In.%s", currentPath.Buffer(), - sname.Buffer()); - bool traceable = false; - bool forcable = false; - (void)IsInstrumented(signalFullPath.Buffer(), traceable, forcable); - - nodeJson += "{\"Name\": \"In."; - EscapeJson(sname.Buffer(), nodeJson); - nodeJson += "\", \"Class\": \"InputSignal\", \"Type\": \""; - EscapeJson(stype ? stype : "Unknown", nodeJson); - nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u", dims, - elems); - nodeJson.Printf(", \"IsTraceable\": %s, \"IsForcable\": %s}", - traceable ? "true" : "false", - forcable ? "true" : "false"); - } - uint32 nOut = gam->GetNumberOfOutputSignals(); - for (uint32 j = 0u; j < nOut; j++) { - if (subCount > 0u) - nodeJson += ",\n"; - subCount++; - StreamString sname; - (void)gam->GetSignalName(OutputSignals, j, sname); - const char8 *stype = TypeDescriptor::GetTypeNameFromTypeDescriptor( - gam->GetSignalType(OutputSignals, j)); - uint32 dims = 0u; - (void)gam->GetSignalNumberOfDimensions(OutputSignals, j, dims); - uint32 elems = 0u; - (void)gam->GetSignalNumberOfElements(OutputSignals, j, elems); - - StreamString signalFullPath; - signalFullPath.Printf("%s.Out.%s", currentPath.Buffer(), - sname.Buffer()); - bool traceable = false; - bool forcable = false; - (void)IsInstrumented(signalFullPath.Buffer(), traceable, forcable); - - nodeJson += "{\"Name\": \"Out."; - EscapeJson(sname.Buffer(), nodeJson); - nodeJson += "\", \"Class\": \"OutputSignal\", \"Type\": \""; - EscapeJson(stype ? stype : "Unknown", nodeJson); - nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u", dims, - elems); - nodeJson.Printf(", \"IsTraceable\": %s, \"IsForcable\": %s}", - traceable ? "true" : "false", - forcable ? "true" : "false"); - } + if (streamerPacketOffset == 0u) { + TraceHeader header; + header.magic = 0xDA7A57ADu; + header.seq = streamerSequenceNumber++; + header.timestamp = HighResolutionTimer::Counter(); + header.count = 0u; + memcpy(streamerPacketBuffer, &header, sizeof(TraceHeader)); + streamerPacketOffset = sizeof(TraceHeader); } - nodeJson += "\n]"; - } - nodeJson += "}"; - json += nodeJson; - validCount++; - } - } - return validCount; -} - -uint32 DebugService::ForceSignal(const char8 *name, const char8 *valueStr) { - mutex.FastLock(); - uint32 count = 0; - for (uint32 i = 0; i < aliases.Size(); i++) { - if (aliases[i].name == name || - SuffixMatch(aliases[i].name.Buffer(), name)) { - - DebugSignalInfo *s = signals[aliases[i].signalIndex]; - - // FIX #4: Guard against writing past the end of forcedValue[1024]. - // TypeConvert writes (byteSize * numberOfElements) bytes into forcedValue. - // For large arrays (e.g. 512 float64s = 4096 bytes) this would silently - // overflow the 1024-byte buffer and corrupt adjacent struct members. - // - // TypeDescriptor::numberOfBits is the element size in bits; integer divide - // by 8 gives bytes. For structured/opaque types numberOfBits may be 0 — - // those are also skipped because we cannot determine the layout safely. - uint32 elemBytes = (uint32)(s->type.numberOfBits) / 8u; - uint32 totalBytes = elemBytes * s->numberOfElements; - if (elemBytes == 0u || totalBytes > (uint32)sizeof(s->forcedValue)) { - REPORT_ERROR_STATIC(ErrorManagement::Warning, - "ForceSignal: signal '%s' requires %u bytes but forcedValue buffer is " - "only %u bytes — force request ignored.", - s->name.Buffer(), totalBytes, (uint32)sizeof(s->forcedValue)); - continue; - } - - s->isForcing = true; - AnyType dest(s->type, 0u, s->forcedValue); - AnyType source(CharString, 0u, valueStr); - (void)TypeConvert(dest, source); - count++; - } - } - UpdateBrokersActiveStatus(); - mutex.FastUnLock(); - return count; -} - -uint32 DebugService::UnforceSignal(const char8 *name) { - mutex.FastLock(); - uint32 count = 0; - for (uint32 i = 0; i < aliases.Size(); i++) { - if (aliases[i].name == name || - SuffixMatch(aliases[i].name.Buffer(), name)) { - signals[aliases[i].signalIndex]->isForcing = false; - count++; - } - } - UpdateBrokersActiveStatus(); - mutex.FastUnLock(); - return count; -} - -uint32 DebugService::TraceSignal(const char8 *name, bool enable, - uint32 decimation) { - mutex.FastLock(); - uint32 count = 0; - for (uint32 i = 0; i < aliases.Size(); i++) { - printf("%s\n", aliases[i].name.Buffer()); - if (aliases[i].name == name || - SuffixMatch(aliases[i].name.Buffer(), name)) { - DebugSignalInfo *s = signals[aliases[i].signalIndex]; - s->isTracing = enable; - s->decimationFactor = decimation; - s->decimationCounter = 0; - count++; - } - } - if (count == 0) { - printf(" signal %s not found\n", name); - } - UpdateBrokersActiveStatus(); - mutex.FastUnLock(); - return count; -} - -void DebugService::ConsumeStepIfNeeded(const char8 *gamName, - const char8 *threadName) { - if (stepRemaining == 0u) return; - mutex.FastLock(); - // If a thread filter is set, only the matching OS thread consumes step credits. - if (stepThreadFilter.Size() > 0u && - (threadName == NULL_PTR(const char8 *) || stepThreadFilter != threadName)) { - mutex.FastUnLock(); - return; - } - if (stepRemaining > 0u) { - stepRemaining--; - if (stepRemaining == 0u) { - isPaused = true; - pausedAtGam = (gamName != NULL_PTR(const char8 *)) ? gamName : ""; - } - } - mutex.FastUnLock(); -} - -void DebugService::Step(uint32 n, const char8 *threadName) { - mutex.FastLock(); - stepRemaining = n; - isPaused = false; - stepThreadFilter = (threadName != NULL_PTR(const char8 *)) ? threadName : ""; - mutex.FastUnLock(); -} - -void DebugService::GetStepStatus(BasicTCPSocket *client) { - if (client == NULL_PTR(BasicTCPSocket *)) return; - mutex.FastLock(); - bool paused = isPaused; - uint32 remaining = stepRemaining; - StreamString gam = pausedAtGam; - StreamString threadFilter = stepThreadFilter; - mutex.FastUnLock(); - StreamString resp; - resp.Printf("{\"Paused\": %s, \"PausedAtGam\": \"%s\", \"StepRemaining\": %u, \"StepThread\": \"%s\"}\nOK STEP_STATUS\n", - paused ? "true" : "false", - gam.Buffer(), - remaining, - threadFilter.Buffer()); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); -} - -void DebugService::GetSignalValue(const char8 *name, BasicTCPSocket *client) { - if (client == NULL_PTR(BasicTCPSocket *)) return; - mutex.FastLock(); - DebugSignalInfo *sig = NULL_PTR(DebugSignalInfo *); - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == name || - SuffixMatch(aliases[i].name.Buffer(), name)) { - sig = signals[aliases[i].signalIndex]; - break; - } - } - if (sig == NULL_PTR(DebugSignalInfo *)) { - mutex.FastUnLock(); - StreamString resp; - resp.Printf("{\"Error\": \"Signal not found: %s\"}\nOK VALUE\n", name); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); - return; - } - TypeDescriptor td = sig->type; - uint32 nElem = sig->numberOfElements; - uint32 byteSize = (td.numberOfBits > 0u) ? (td.numberOfBits / 8u) : 1u; - - // FIX #9: cap element count before computing byte totals. - // Without a cap, a 1M-element uint8 signal would produce a multi-MB - // comma-separated response, exhausting heap and blocking the Server thread. - // GET_VALUE_MAX_ELEMENTS = 256 gives a ~4 KB worst-case JSON string. - bool truncated = (nElem > GET_VALUE_MAX_ELEMENTS); - if (truncated) { - nElem = GET_VALUE_MAX_ELEMENTS; - } - uint32 totalBytes = byteSize * nElem; - // Secondary cap: even after the element limit, ensure we never read more - // than 1024 bytes from the RT memory region. - if (totalBytes > 1024u) { totalBytes = 1024u; nElem = totalBytes / byteSize; } - // Copy bytes while holding the mutex to avoid data races with the RT thread - uint8 localBuf[1024]; - memset(localBuf, 0, sizeof(localBuf)); - if (sig->memoryAddress != NULL_PTR(void *)) { - memcpy(localBuf, sig->memoryAddress, totalBytes); - } - mutex.FastUnLock(); - - // Build the value string via CharString TypeConvert — the same path used by - // EnrichWithConfig/JsonifyDatabase, which is known to work for all types. - StreamString valueStr; - if (nElem > 1u) { - // Array or matrix: produce comma-separated text - for (uint32 i = 0u; i < nElem; i++) { - char8 elemBuf[128] = {'\0'}; - AnyType srcElem(td, 0u, (void *)(localBuf + i * byteSize)); - AnyType dstStr(CharString, 0u, elemBuf); - dstStr.SetNumberOfElements(0u, 128u); - (void)TypeConvert(dstStr, srcElem); - if (i > 0u) valueStr += ", "; - valueStr += elemBuf; - } - } else { - char8 elemBuf[256] = {'\0'}; - AnyType srcElem(td, 0u, (void *)localBuf); - AnyType dstStr(CharString, 0u, elemBuf); - dstStr.SetNumberOfElements(0u, 256u); - (void)TypeConvert(dstStr, srcElem); - valueStr = elemBuf; - } - - StreamString resp; - resp += "{\"Name\": \""; - resp += name; - resp += "\", \"Value\": \""; - // Escape the value text (quotes inside a string value) - const char8 *vp = valueStr.Buffer(); - while (vp != NULL_PTR(const char8 *) && *vp != '\0') { - if (*vp == '"') resp += "\\\""; - else if (*vp == '\\') resp += "\\\\"; - else { char8 tmp[2] = { *vp, '\0' }; resp += tmp; } - vp++; - } - resp += "\", \"Elements\": "; - // Include the capped element count and a Truncated flag so the client can - // distinguish "this is the full signal" from "there are more elements". - resp.Printf("%u, \"Truncated\": %s}\nOK VALUE\n", - nElem, truncated ? "true" : "false"); - uint32 s = resp.Size(); - (void)client->Write(resp.Buffer(), s); -} - -uint32 DebugService::SetBreak(const char8 *name, uint8 op, float64 threshold) { - mutex.FastLock(); - uint32 count = 0; - for (uint32 i = 0; i < aliases.Size(); i++) { - if (aliases[i].name == name || - SuffixMatch(aliases[i].name.Buffer(), name)) { - DebugSignalInfo *s = signals[aliases[i].signalIndex]; - s->breakThreshold = threshold; - s->breakOp = op; - count++; - } - } - if (count > 0) - UpdateBrokersBreakStatus(); - mutex.FastUnLock(); - return count; -} - -uint32 DebugService::ClearBreak(const char8 *name) { - mutex.FastLock(); - uint32 count = 0; - for (uint32 i = 0; i < aliases.Size(); i++) { - if (aliases[i].name == name || - SuffixMatch(aliases[i].name.Buffer(), name)) { - signals[aliases[i].signalIndex]->breakOp = BREAK_OFF; - count++; - } - } - if (count > 0) - UpdateBrokersBreakStatus(); - mutex.FastUnLock(); - return count; -} - -bool DebugService::IsInstrumented(const char8 *fullPath, bool &traceable, - bool &forcable) { - mutex.FastLock(); - bool found = false; - for (uint32 i = 0; i < aliases.Size(); i++) { - if (aliases[i].name == fullPath || - SuffixMatch(aliases[i].name.Buffer(), fullPath)) { - found = true; - break; - } - } - mutex.FastUnLock(); - traceable = found; - forcable = found; - return found; -} - -uint32 DebugService::RegisterMonitorSignal(const char8 *path, uint32 periodMs) { - mutex.FastLock(); - uint32 count = 0; - - // Check if already monitored - for (uint32 j = 0; j < monitoredSignals.Size(); j++) { - if (monitoredSignals[j].path == path) { - monitoredSignals[j].periodMs = periodMs; - mutex.FastUnLock(); - return 1; - } - } - - // Path resolution: find the DataSource object - StreamString fullPath = path; - fullPath.Seek(0); - char8 term; - Vec parts; - StreamString token; - while (fullPath.GetToken(token, ".", term)) { - parts.Push(token); - token = ""; - } - - if (parts.Size() >= 2) { - StreamString signalName = parts[parts.Size() - 1u]; - StreamString dsPath; - for (uint32 i = 0; i < parts.Size() - 1u; i++) { - dsPath += parts[i]; - if (i < parts.Size() - 2u) - dsPath += "."; - } - - ReferenceT ds = - ObjectRegistryDatabase::Instance()->Find(dsPath.Buffer()); - if (ds.IsValid()) { - uint32 idx = 0; - if (ds->GetSignalIndex(idx, signalName.Buffer())) { - MonitoredSignal m; - m.dataSource = ds; - m.signalIdx = idx; - m.path = path; - m.periodMs = periodMs; - m.lastPollTime = 0; - m.size = 0; - (void)ds->GetSignalByteSize(idx, m.size); - if (m.size == 0) - m.size = 4; - - // Use high-bit for polled signals to avoid conflict with brokered ones - m.internalID = 0x80000000 | monitoredSignals.Size(); - - // Re-use existing ID if signal is also instrumented via broker - for (uint32 i = 0; i < aliases.Size(); i++) { - if (aliases[i].name == path || - SuffixMatch(aliases[i].name.Buffer(), path)) { - m.internalID = signals[aliases[i].signalIndex]->internalID; - break; - } + if (streamerPacketOffset + 16u + size > STREAMER_MTU) { + uint32 toWrite = streamerPacketOffset; + (void)udpSocket.Write((char8 *)streamerPacketBuffer, toWrite); + TraceHeader header; + header.magic = 0xDA7A57ADu; + header.seq = streamerSequenceNumber++; + header.timestamp = HighResolutionTimer::Counter(); + header.count = 0u; + memcpy(streamerPacketBuffer, &header, sizeof(TraceHeader)); + streamerPacketOffset = sizeof(TraceHeader); } - - monitoredSignals.Push(m); - count = 1; - } + memcpy(&streamerPacketBuffer[streamerPacketOffset], &id, 4u); + memcpy(&streamerPacketBuffer[streamerPacketOffset + 4u], &ts, 8u); + memcpy(&streamerPacketBuffer[streamerPacketOffset + 12u], &size, 4u); + memcpy(&streamerPacketBuffer[streamerPacketOffset + 16u], sampleData, size); + streamerPacketOffset += (16u + size); + ((TraceHeader *)streamerPacketBuffer)->count++; } - } - - mutex.FastUnLock(); - return count; -} - -uint32 DebugService::UnmonitorSignal(const char8 *path) { - mutex.FastLock(); - uint32 count = 0; - for (uint32 i = 0; i < monitoredSignals.Size(); i++) { - if (monitoredSignals[i].path == path || - SuffixMatch(monitoredSignals[i].path.Buffer(), path)) { - (void)monitoredSignals.Remove(i); - i--; - count++; + if (streamerPacketOffset > 0u) { + uint32 toWrite = streamerPacketOffset; + (void)udpSocket.Write((char8 *)streamerPacketBuffer, toWrite); + streamerPacketOffset = 0u; } - } - mutex.FastUnLock(); - return count; -} - -void DebugService::Discover(BasicTCPSocket *client) { - if (client) { - StreamString header = "{\n \"Signals\": [\n"; - uint32 s = header.Size(); - (void)client->Write(header.Buffer(), s); - mutex.FastLock(); - uint32 total = 0; - for (uint32 i = 0; i < aliases.Size(); i++) { - if (total > 0) { - uint32 commaSize = 2; - (void)client->Write(",\n", commaSize); - } - StreamString line; - DebugSignalInfo *sig = signals[aliases[i].signalIndex]; - const char8 *typeName = - TypeDescriptor::GetTypeNameFromTypeDescriptor(sig->type); - line.Printf(" {\"name\": \"%s\", \"id\": %d, \"type\": \"%s\", \"dimensions\": %u, \"elements\": %u", - aliases[i].name.Buffer(), sig->internalID, - typeName ? typeName : "Unknown", sig->numberOfDimensions, sig->numberOfElements); - EnrichWithConfig(aliases[i].name.Buffer(), line); - line += "}"; - s = line.Size(); - (void)client->Write(line.Buffer(), s); - total++; - } - - // Export monitored signals not already in aliases - for (uint32 i = 0; i < monitoredSignals.Size(); i++) { - bool found = false; - for (uint32 j = 0; j < aliases.Size(); j++) { - if (aliases[j].name == monitoredSignals[i].path) { - found = true; - break; - } - } - if (!found) { - if (total > 0) { - uint32 commaSize = 2; - (void)client->Write(",\n", commaSize); - } - StreamString line; - const char8 *typeName = TypeDescriptor::GetTypeNameFromTypeDescriptor( - monitoredSignals[i].dataSource->GetSignalType( - monitoredSignals[i].signalIdx)); - - uint8 dims = 0; - uint32 elems = 1; - (void)monitoredSignals[i].dataSource->GetSignalNumberOfDimensions(monitoredSignals[i].signalIdx, dims); - (void)monitoredSignals[i].dataSource->GetSignalNumberOfElements(monitoredSignals[i].signalIdx, elems); - - line.Printf(" {\"name\": \"%s\", \"id\": %u, \"type\": \"%s\", \"dimensions\": %u, \"elements\": %u", - monitoredSignals[i].path.Buffer(), - monitoredSignals[i].internalID, - typeName ? typeName : "Unknown", dims, elems); - EnrichWithConfig(monitoredSignals[i].path.Buffer(), line); - line += "}"; - s = line.Size(); - (void)client->Write(line.Buffer(), s); - total++; - } - } - mutex.FastUnLock(); - StreamString footer = " ]\n}\nOK DISCOVER\n"; - s = footer.Size(); - (void)client->Write(footer.Buffer(), s); - } -} - -void DebugService::ListNodes(const char8 *path, BasicTCPSocket *client) { - if (!client) - return; - Reference ref = - (path == NULL_PTR(const char8 *) || StringHelper::Length(path) == 0 || - StringHelper::Compare(path, "/") == 0) - ? ObjectRegistryDatabase::Instance() - : ObjectRegistryDatabase::Instance()->Find(path); - if (ref.IsValid()) { - StreamString out; - out.Printf("Nodes under %s:\n", path ? path : "/"); - ReferenceContainer *container = - dynamic_cast(ref.operator->()); - if (container) { - for (uint32 i = 0; i < container->Size(); i++) { - Reference child = container->Get(i); - if (child.IsValid()) - out.Printf(" %s [%s]\n", child->GetName(), - child->GetClassProperties()->GetName()); - } - } - const char *okMsg = "OK LS\n"; - out += okMsg; - uint32 s = out.Size(); - (void)client->Write(out.Buffer(), s); - } else { - const char *msg = "ERROR: Path not found\n"; - uint32 s = StringHelper::Length(msg); - (void)client->Write(msg, s); - } + if (!hasData) Sleep::MSec(1u); + return ErrorManagement::NoError; } } // namespace MARTe diff --git a/Source/Components/Interfaces/DebugService/DebugService.h b/Source/Components/Interfaces/DebugService/DebugService.h index 86b164d..d4bfad7 100644 --- a/Source/Components/Interfaces/DebugService/DebugService.h +++ b/Source/Components/Interfaces/DebugService/DebugService.h @@ -3,293 +3,104 @@ #include "BasicTCPSocket.h" #include "BasicUDPSocket.h" -#include "ConfigurationDatabase.h" -#include "DebugServiceI.h" +#include "DebugServiceBase.h" #include "EmbeddedServiceMethodBinderI.h" #include "MessageI.h" -#include "ReferenceContainer.h" #include "ReferenceT.h" #include "SingleThreadService.h" namespace MARTe { -class DataSourceI; - - /** - * @brief TCP/UDP implementation of DebugServiceI. + * @brief TCP/UDP implementation of DebugServiceI (via DebugServiceBase). * * Provides signal tracing (UDP), forced-value injection, break conditions, * and a text command channel (TCP) with a log-forwarding sidecar (TcpLogger). - * Alternative transports (TTY, WebSocket, …) can be added by implementing - * DebugServiceI without touching this class or any broker wrapper code. + * All signal management and command dispatch logic lives in DebugServiceBase. */ -class DebugService : public ReferenceContainer, +class DebugService : public DebugServiceBase, public MessageI, - public EmbeddedServiceMethodBinderI, - public DebugServiceI { + public EmbeddedServiceMethodBinderI { public: - friend class DebugServiceTest; - CLASS_REGISTER_DECLARATION() + friend class DebugServiceTest; + CLASS_REGISTER_DECLARATION() - DebugService(); - virtual ~DebugService(); + DebugService(); + virtual ~DebugService(); - virtual bool Initialise(StructuredDataI &data); + virtual bool Initialise(StructuredDataI &data); - // DebugServiceI RT-path overrides - virtual DebugSignalInfo *RegisterSignal(void *memoryAddress, TypeDescriptor type, - const char8 *name, - uint8 numberOfDimensions = 0, - uint32 numberOfElements = 1); - virtual void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size, - uint64 timestamp); - virtual void RegisterBroker(DebugSignalInfo **signalPointers, uint32 numSignals, - MemoryMapBroker *broker, volatile bool *anyActiveFlag, - Vec *activeIndices, Vec *activeSizes, - FastPollingMutexSem *activeMutex, - volatile bool *anyBreakFlag, Vec *breakIndices, - const char8 *gamName = NULL_PTR(const char8 *), - bool isOutput = false); - virtual bool IsPaused() const { return isPaused; } - virtual void SetPaused(bool paused) { isPaused = paused; } - virtual bool IsStepPending() const { return stepRemaining > 0u; } - virtual void ConsumeStepIfNeeded(const char8 *gamName, - const char8 *threadName = NULL_PTR(const char8 *)); + virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info); + virtual ErrorManagement::ErrorType HandleMessage(ReferenceT &data); - // DebugServiceI control-path overrides - virtual uint32 ForceSignal (const char8 *name, const char8 *valueStr); - virtual uint32 UnforceSignal(const char8 *name); - virtual uint32 TraceSignal (const char8 *name, bool enable, uint32 decimation = 1); - virtual uint32 SetBreak (const char8 *name, uint8 op, float64 threshold); - virtual uint32 ClearBreak (const char8 *name); - virtual bool IsInstrumented(const char8 *fullPath, bool &traceable, bool &forcable); - virtual uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs); - virtual uint32 UnmonitorSignal (const char8 *path); - - virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info); - virtual ErrorManagement::ErrorType HandleMessage(ReferenceT &data); - - // TCP-transport-specific methods (not part of DebugServiceI) - void GetStepStatus(BasicTCPSocket *client); - void Step(uint32 n, const char8 *threadName = NULL_PTR(const char8 *)); - void GetSignalValue(const char8 *name, BasicTCPSocket *client); - void Discover(BasicTCPSocket *client); - void InfoNode(const char8 *path, BasicTCPSocket *client); - void ListNodes(const char8 *path, BasicTCPSocket *client); - void ServeConfig(BasicTCPSocket *client); - void SetFullConfig(ConfigurationDatabase &config); - void RebuildConfigFromRegistry(); - - struct MonitoredSignal { - ReferenceT dataSource; - uint32 signalIdx; - uint32 internalID; - uint32 periodMs; - uint64 lastPollTime; - uint32 size; - StreamString path; - }; +protected: + // Transport-specific overrides of DebugServiceBase hooks + virtual void GetServiceInfo(StreamString &out); + virtual void RebuildTransportConfig(); private: - void HandleCommand(StreamString cmd, BasicTCPSocket *client); - void UpdateBrokersActiveStatus(); - void UpdateBrokersBreakStatus(); - void InjectTcpLoggerIfNeeded(); + void InjectTcpLoggerIfNeeded(); - uint32 ExportTree(ReferenceContainer *container, StreamString &json, const char8 *pathPrefix); - void PatchRegistry(); + ErrorManagement::ErrorType Server (ExecutionInfo &info); + ErrorManagement::ErrorType Streamer(ExecutionInfo &info); - void EnrichWithConfig(const char8 *path, StreamString &json); - static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json); + // TCP/UDP-specific configuration + uint16 controlPort; + uint16 streamPort; + uint16 logPort; + StreamString streamIP; + bool isServer; + bool suppressTimeoutLogs; - ErrorManagement::ErrorType Server(ExecutionInfo &info); - ErrorManagement::ErrorType Streamer(ExecutionInfo &info); + BasicTCPSocket tcpServer; + BasicUDPSocket udpSocket; - uint16 controlPort; - uint16 streamPort; - uint16 logPort; - StreamString streamIP; - bool isServer; - bool suppressTimeoutLogs; - volatile bool isPaused; - volatile uint32 stepRemaining; - StreamString pausedAtGam; - StreamString stepThreadFilter; // empty = all threads; non-empty = only this OS thread name + class ServiceBinder : public EmbeddedServiceMethodBinderI { + public: + enum ServiceType { ServerType, StreamerType }; + ServiceBinder(DebugService *parent, ServiceType type) + : parent(parent), type(type) {} + virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info) { + if (type == StreamerType) return parent->Streamer(info); + return parent->Server(info); + } + private: + DebugService *parent; + ServiceType type; + }; - BasicTCPSocket tcpServer; - BasicUDPSocket udpSocket; + ServiceBinder binderServer; + ServiceBinder binderStreamer; + SingleThreadService threadService; + SingleThreadService streamerService; - class ServiceBinder : public EmbeddedServiceMethodBinderI { - public: - enum ServiceType { ServerType, StreamerType }; - ServiceBinder(DebugService *parent, ServiceType type) - : parent(parent), type(type) {} - virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info) { - if (type == StreamerType) { - return parent->Streamer(info); - } - return parent->Server(info); - } + ThreadIdentifier serverThreadId; + ThreadIdentifier streamerThreadId; - private: - DebugService *parent; - ServiceType type; - }; + // activeClient — exclusively owned by Server thread + FastPollingMutexSem clientMutex; + BasicTCPSocket *activeClient; - ServiceBinder binderServer; - ServiceBinder binderStreamer; + // Streamer assembly buffer + static const uint32 STREAMER_MTU = 1400u; + static const uint32 STREAMER_BUFFER_SIZE = 4096u; + uint8 streamerPacketBuffer[STREAMER_BUFFER_SIZE]; + uint32 streamerPacketOffset; + uint32 streamerSequenceNumber; - SingleThreadService threadService; - SingleThreadService streamerService; + // Rate-limiting and idle-timeout constants / state + static const uint32 CMD_RATE_LIMIT = 100u; + static const uint32 CLIENT_IDLE_TIMEOUT_MS = 30000u; + static const uint32 INPUT_BUFFER_MAX = 8192u; - ThreadIdentifier serverThreadId; - ThreadIdentifier streamerThreadId; + uint32 cmdCountInWindow; + uint64 cmdWindowStartMs; + uint64 lastDataTimeMs; - Vec signals; - Vec aliases; - Vec brokers; - Vec monitoredSignals; - - FastPollingMutexSem mutex; - TraceRingBuffer traceBuffer; - - /** - * @brief Mutex protecting traceBuffer.Push() from concurrent RT broker threads. - * - * TraceRingBuffer is designed for single-producer / single-consumer access. - * In practice, multiple RT threads (one per RealTimeThread) call ProcessSignal() - * concurrently, making it a multi-producer scenario. Without a lock they can - * both pass the free-space check, both pick the same write index, and corrupt - * each other's samples. This mutex serialises the Push path. - * - * The Streamer thread (sole consumer) never holds this lock, so the lock is - * only contended between RT threads — not between RT and Streamer. - * - * FIX #2 — TraceRingBuffer multi-producer race. - */ - FastPollingMutexSem tracePushMutex; - - /** - * @brief Mutex protecting the activeClient pointer. - * - * activeClient is currently owned exclusively by the Server thread: - * it is created, read, and destroyed only inside Server(). The mutex - * enforces this invariant and makes future cross-thread access safe. - * - * Rules: - * - Hold clientMutex whenever assigning (including to NULL) activeClient. - * - Do NOT hold clientMutex across blocking I/O calls (Read / Write) - * to avoid starving threads that only need the pointer. - * - Any future code that needs to send data to the active client from a - * non-Server thread must lock clientMutex, copy the pointer, release - * the lock, then do the Write. - * - * FIX #2 — activeClient cross-thread safety. - */ - FastPollingMutexSem clientMutex; - - BasicTCPSocket *activeClient; - - /** - * Maximum payload bytes in a single UDP datagram (leave room for IP+UDP headers - * on a standard 1500-byte Ethernet MTU). Used in the Streamer to decide when - * to flush the current packet and start a new one. - * - * FIX #1 — named constant to replace the magic number 1400. - */ - static const uint32 STREAMER_MTU = 1400u; - - /** - * Size of the streamer assembly buffer. Must be >= STREAMER_MTU so at least - * one full UDP payload can be staged before flushing. A sample that on its own - * would exceed (STREAMER_BUFFER_SIZE - sizeof(TraceHeader) - 16) bytes is - * silently dropped and logged — it can never fit in any packet. - * - * FIX #1 — named constant; prevents silent integer arithmetic relying on - * the array bound being "obviously 4096". - */ - static const uint32 STREAMER_BUFFER_SIZE = 4096u; - - // Streamer state persisted across Execute() calls (framework loops Execute) - uint8 streamerPacketBuffer[STREAMER_BUFFER_SIZE]; - uint32 streamerPacketOffset; - uint32 streamerSequenceNumber; - - /** - * @brief Maximum commands allowed per 1-second sliding window per client. - * - * A client sending more than this many commands in one second is disconnected. - * This prevents a tight command loop from monopolising the Server thread and - * starving normal usage. The limit is intentionally generous (100/s) so that - * legitimate scripted tooling is unaffected. - * - * FIX #6 — TCP command rate limiting. - */ - static const uint32 CMD_RATE_LIMIT = 100u; - - /** - * @brief Close the active TCP client if no data is received for this many ms. - * - * A client that sends a partial command and never completes it would otherwise - * hold the single active-client slot open indefinitely, blocking all other - * connections. After CLIENT_IDLE_TIMEOUT_MS of silence the connection is - * closed and the slot freed. - * - * FIX #8 — active-client idle timeout. - */ - static const uint32 CLIENT_IDLE_TIMEOUT_MS = 30000u; - - /** - * @brief Maximum array elements returned by GetSignalValue / VALUE command. - * - * Without a cap, a 1-million-element uint8 array would produce a multi-MB - * response string, exhausting heap and blocking the Server thread. Elements - * beyond this limit are omitted; the response includes a "Truncated" flag. - * - * FIX #9 — GetSignalValue unbounded array output. - */ - static const uint32 GET_VALUE_MAX_ELEMENTS = 256u; - - // Rate-limiter state (per active TCP connection, reset on each new connection) - uint32 cmdCountInWindow; // commands received in the current 1-second window - uint64 cmdWindowStartMs; // start of the current window, in milliseconds - - // Idle-timeout state - uint64 lastDataTimeMs; // last time the active client sent any data (ms) - - /** - * @brief Carry-over buffer for multi-segment TCP commands. - * - * A single TCP Read() call may return only part of a command if the OS - * delivers it in multiple segments. Bytes after the last '\n' in the - * current receive window are saved here and prepended to the next Read(). - * This ensures that "FORCE Signal " arriving in one segment and "123\n" - * arriving in the next are assembled into the single command - * "FORCE Signal 123" before dispatch. - * - * The buffer is bounded by INPUT_BUFFER_MAX (8 KiB). If a client sends - * more than that without a newline it is disconnected. - * - * FIX #10 — incomplete input buffer handling for multi-line commands. - */ - StreamString inputBuffer; - - /** - * @brief Maximum carry-over bytes allowed before a client is disconnected. - * - * A client that sends a very long line without a newline would otherwise - * grow inputBuffer without bound. At 8 KiB the limit is generous enough - * for any legitimate command while still protecting against memory exhaustion. - * - * FIX #10 — DoS guard for the input carry-over buffer. - */ - static const uint32 INPUT_BUFFER_MAX = 8192u; - - ConfigurationDatabase fullConfig; - bool manualConfigSet; + // Carry-over buffer for multi-segment TCP commands + StreamString inputBuffer; }; } // namespace MARTe -#endif +#endif // DEBUGSERVICE_H diff --git a/Source/Components/Interfaces/DebugService/DebugServiceBase.cpp b/Source/Components/Interfaces/DebugService/DebugServiceBase.cpp new file mode 100644 index 0000000..68b6206 --- /dev/null +++ b/Source/Components/Interfaces/DebugService/DebugServiceBase.cpp @@ -0,0 +1,1219 @@ +#include "AdvancedErrorManagement.h" +#include "Atomic.h" +#include "ClassRegistryItem.h" +#include "ConfigurationDatabase.h" +#include "DataSourceI.h" +#include "DebugBrokerWrapper.h" +#include "DebugServiceBase.h" +#include "ErrorManagement.h" +#include "GAM.h" +#include "GlobalObjectsDatabase.h" +#include "HighResolutionTimer.h" +#include "Message.h" +#include "MessageI.h" +#include "ObjectRegistryDatabase.h" +#include "ReferenceT.h" +#include "StreamString.h" +#include "TimeoutType.h" +#include "TypeConversion.h" + +namespace MARTe { + +// DebugServiceI static member — defined here (only once, shared by all transports). +DebugServiceI *DebugServiceI::instance = NULL_PTR(DebugServiceI *); + +// --------------------------------------------------------------------------- +// DebugServiceI::GetFullObjectName — static utility used by broker wrappers +// --------------------------------------------------------------------------- + +bool DebugServiceI::GetFullObjectName(const Object &obj, StreamString &fullPath) { + fullPath = ""; + // FindPathInContainer is a file-scope static below; forward-declare it here. + // The actual definition appears after this function. + extern bool DSB_FindPath(ReferenceContainer *, const Object *, StreamString &); + if (DSB_FindPath(ObjectRegistryDatabase::Instance(), &obj, fullPath)) { + return true; + } + const char8 *name = obj.GetName(); + if (name != NULL_PTR(const char8 *)) + fullPath = name; + return true; +} + +// --------------------------------------------------------------------------- +// File-scope helper functions (NOT exported) +// --------------------------------------------------------------------------- + +static void EscapeJson(const char8 *src, StreamString &dst) { + if (src == NULL_PTR(const char8 *)) return; + while (*src != '\0') { + if (*src == '"') dst += "\\\""; + else if (*src == '\\') dst += "\\\\"; + else if (*src == '\n') dst += "\\n"; + else if (*src == '\r') dst += "\\r"; + else if (*src == '\t') dst += "\\t"; + else dst += *src; + src++; + } +} + +static bool SuffixMatch(const char8 *target, const char8 *pattern) { + uint32 tLen = StringHelper::Length(target); + uint32 pLen = StringHelper::Length(pattern); + if (pLen > tLen) return false; + const char8 *suffix = target + (tLen - pLen); + if (StringHelper::Compare(suffix, pattern) == 0) { + if (tLen == pLen || *(suffix - 1) == '.') return true; + } + return false; +} + +// Named with DSB_ prefix so GetFullObjectName can forward-declare it. +bool DSB_FindPath(ReferenceContainer *container, const Object *target, + StreamString &path) { + if (container == NULL_PTR(ReferenceContainer *)) return false; + uint32 n = container->Size(); + for (uint32 i = 0u; i < n; i++) { + Reference ref = container->Get(i); + if (!ref.IsValid()) continue; + if (ref.operator->() == target) { path = ref->GetName(); return true; } + ReferenceContainer *sub = dynamic_cast(ref.operator->()); + if (sub != NULL_PTR(ReferenceContainer *)) { + if (DSB_FindPath(sub, target, path)) { + StreamString full; + full.Printf("%s.%s", ref->GetName(), path.Buffer()); + path = full; + return true; + } + } + } + return false; +} + +static void BuildCDBFromContainer(ReferenceContainer *container, + ConfigurationDatabase &cdb) { + if (container == NULL_PTR(ReferenceContainer *)) return; + uint32 n = container->Size(); + for (uint32 i = 0u; i < n; i++) { + Reference child = container->Get(i); + if (!child.IsValid()) continue; + const char8 *name = child->GetName(); + if (name == NULL_PTR(const char8 *)) continue; + + bool created = cdb.CreateRelative(name); + if (!created) { if (!cdb.MoveRelative(name)) continue; } + + const char8 *cn = child->GetClassProperties()->GetName(); + if (cn != NULL_PTR(const char8 *)) (void)cdb.Write("Class", cn); + + { + ConfigurationDatabase exp; + if (child->ExportData(exp)) { + exp.MoveToRoot(); + uint32 ne = exp.GetNumberOfChildren(); + for (uint32 j = 0u; j < ne; j++) { + const char8 *ek = exp.GetChildName(j); + if (StringHelper::Compare(ek, "Class") == 0 || + StringHelper::Compare(ek, "Name") == 0 || + StringHelper::Compare(ek, "IsContainer") == 0) continue; + if (exp.MoveRelative(ek)) { exp.MoveToAncestor(1u); continue; } + AnyType at = exp.GetType(ek); + if (at.GetDataPointer() != NULL_PTR(void *)) { + char8 buf[1024]; + AnyType st(CharString, 0u, buf); + st.SetNumberOfElements(0, 1024); + if (TypeConvert(st, at)) (void)cdb.Write(ek, buf); + } + } + } + } + + ReferenceContainer *rc = dynamic_cast(child.operator->()); + if (rc != NULL_PTR(ReferenceContainer *)) BuildCDBFromContainer(rc, cdb); + (void)cdb.MoveToAncestor(1u); + } +} + +static void PatchItemInternal(const char8 *originalName, ObjectBuilder *debugBuilder) { + ClassRegistryItem *item = ClassRegistryDatabase::Instance()->Find(originalName); + if (item != NULL_PTR(ClassRegistryItem *)) { + item->SetObjectBuilder(debugBuilder); + } +} + +// --------------------------------------------------------------------------- +// DebugServiceBase constructor / destructor +// --------------------------------------------------------------------------- + +DebugServiceBase::DebugServiceBase() + : ReferenceContainer() { + isPaused = false; + stepRemaining = 0u; + manualConfigSet = false; +} + +DebugServiceBase::~DebugServiceBase() { + for (uint32 i = 0u; i < signals.Size(); i++) { + if (signals[i] != NULL_PTR(DebugSignalInfo *)) { + delete signals[i]; + } + } +} + +// --------------------------------------------------------------------------- +// PatchRegistry +// --------------------------------------------------------------------------- + +void DebugServiceBase::PatchRegistry() { + PatchItemInternal("MemoryMapInputBroker", + new DebugMemoryMapInputBrokerBuilder()); + PatchItemInternal("MemoryMapOutputBroker", + new DebugMemoryMapOutputBrokerBuilder()); + PatchItemInternal("MemoryMapSynchronisedInputBroker", + new DebugMemoryMapSynchronisedInputBrokerBuilder()); + PatchItemInternal("MemoryMapSynchronisedOutputBroker", + new DebugMemoryMapSynchronisedOutputBrokerBuilder()); + PatchItemInternal("MemoryMapInterpolatedInputBroker", + new DebugMemoryMapInterpolatedInputBrokerBuilder()); + PatchItemInternal("MemoryMapMultiBufferInputBroker", + new DebugMemoryMapMultiBufferInputBrokerBuilder()); + PatchItemInternal("MemoryMapMultiBufferOutputBroker", + new DebugMemoryMapMultiBufferOutputBrokerBuilder()); + PatchItemInternal("MemoryMapSynchronisedMultiBufferInputBroker", + new DebugMemoryMapSynchronisedMultiBufferInputBrokerBuilder()); + PatchItemInternal("MemoryMapSynchronisedMultiBufferOutputBroker", + new DebugMemoryMapSynchronisedMultiBufferOutputBrokerBuilder()); + PatchItemInternal("MemoryMapAsyncOutputBroker", + new DebugMemoryMapAsyncOutputBrokerBuilder()); + PatchItemInternal("MemoryMapAsyncTriggerOutputBroker", + new DebugMemoryMapAsyncTriggerOutputBrokerBuilder()); +} + +// --------------------------------------------------------------------------- +// RT-path implementations +// --------------------------------------------------------------------------- + +DebugSignalInfo *DebugServiceBase::RegisterSignal(void *memoryAddress, + TypeDescriptor type, + const char8 *name, + uint8 numberOfDimensions, + uint32 numberOfElements) { + mutex.FastLock(); + DebugSignalInfo *res = NULL_PTR(DebugSignalInfo *); + uint32 sigIdx = 0xFFFFFFFFu; + for (uint32 i = 0u; i < signals.Size(); i++) { + if (signals[i]->memoryAddress == memoryAddress) { + res = signals[i]; sigIdx = i; break; + } + } + if (res == NULL_PTR(DebugSignalInfo *)) { + sigIdx = signals.Size(); + res = new DebugSignalInfo(); + res->memoryAddress = memoryAddress; + res->type = type; + res->name = name; + res->numberOfDimensions = numberOfDimensions; + res->numberOfElements = numberOfElements; + res->isTracing = false; + res->isForcing = false; + res->internalID = sigIdx; + res->decimationFactor = 1u; + res->decimationCounter = 0; + res->breakOp = BREAK_OFF; + res->breakThreshold = 0.0; + signals.Push(res); + } + if (sigIdx != 0xFFFFFFFFu) { + bool found = false; + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (aliases[i].name == name) { found = true; break; } + } + if (!found) { + SignalAlias a; + a.name = name; + a.signalIndex = sigIdx; + aliases.Push(a); + } + } + mutex.FastUnLock(); + return res; +} + +void DebugServiceBase::ProcessSignal(DebugSignalInfo *signalInfo, + uint32 size, uint64 timestamp) { + if (signalInfo == NULL_PTR(DebugSignalInfo *)) return; + if (signalInfo->isForcing) { + memcpy(signalInfo->memoryAddress, signalInfo->forcedValue, size); + } + if (signalInfo->isTracing) { + Atomic::Add((volatile int32 *)&signalInfo->decimationCounter, 1); + if ((uint32)signalInfo->decimationCounter >= signalInfo->decimationFactor) { + int32 old = Atomic::Exchange((volatile int32 *)&signalInfo->decimationCounter, 0); + if ((uint32)old >= signalInfo->decimationFactor) { + tracePushMutex.FastLock(); + traceBuffer.Push(signalInfo->internalID, timestamp, + (uint8 *)signalInfo->memoryAddress, size); + tracePushMutex.FastUnLock(); + } + } + } +} + +void DebugServiceBase::RegisterBroker(DebugSignalInfo **signalPointers, + uint32 numSignals, + MemoryMapBroker *broker, + volatile bool *anyActiveFlag, + Vec *activeIndices, + Vec *activeSizes, + FastPollingMutexSem *activeMutex, + volatile bool *anyBreakFlag, + Vec *breakIndices, + const char8 *gamName, + bool isOutput) { + mutex.FastLock(); + BrokerInfo b; + b.signalPointers = signalPointers; + b.numSignals = numSignals; + b.broker = broker; + b.anyActiveFlag = anyActiveFlag; + b.activeIndices = activeIndices; + b.activeSizes = activeSizes; + b.activeMutex = activeMutex; + b.anyBreakFlag = anyBreakFlag; + b.breakIndices = breakIndices; + b.isOutput = isOutput; + if (gamName != NULL_PTR(const char8 *)) b.gamName = gamName; + brokers.Push(b); + mutex.FastUnLock(); +} + +void DebugServiceBase::ConsumeStepIfNeeded(const char8 *gamName, + const char8 *threadName) { + if (stepRemaining == 0u) return; + mutex.FastLock(); + if (stepThreadFilter.Size() > 0u && + (threadName == NULL_PTR(const char8 *) || stepThreadFilter != threadName)) { + mutex.FastUnLock(); + return; + } + if (stepRemaining > 0u) { + stepRemaining--; + if (stepRemaining == 0u) { + isPaused = true; + pausedAtGam = (gamName != NULL_PTR(const char8 *)) ? gamName : ""; + } + } + mutex.FastUnLock(); +} + +// --------------------------------------------------------------------------- +// Control-path implementations +// --------------------------------------------------------------------------- + +uint32 DebugServiceBase::ForceSignal(const char8 *name, const char8 *valueStr) { + mutex.FastLock(); + uint32 count = 0u; + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (aliases[i].name == name || SuffixMatch(aliases[i].name.Buffer(), name)) { + DebugSignalInfo *s = signals[aliases[i].signalIndex]; + uint32 elemBytes = (uint32)(s->type.numberOfBits) / 8u; + uint32 totalBytes = elemBytes * s->numberOfElements; + if (elemBytes == 0u || totalBytes > (uint32)sizeof(s->forcedValue)) { + REPORT_ERROR_STATIC(ErrorManagement::Warning, + "ForceSignal: signal '%s' requires %u bytes but forcedValue " + "buffer is only %u bytes — force request ignored.", + s->name.Buffer(), totalBytes, (uint32)sizeof(s->forcedValue)); + continue; + } + s->isForcing = true; + AnyType dest(s->type, 0u, s->forcedValue); + AnyType source(CharString, 0u, valueStr); + (void)TypeConvert(dest, source); + count++; + } + } + UpdateBrokersActiveStatus(); + mutex.FastUnLock(); + return count; +} + +uint32 DebugServiceBase::UnforceSignal(const char8 *name) { + mutex.FastLock(); + uint32 count = 0u; + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (aliases[i].name == name || SuffixMatch(aliases[i].name.Buffer(), name)) { + signals[aliases[i].signalIndex]->isForcing = false; + count++; + } + } + UpdateBrokersActiveStatus(); + mutex.FastUnLock(); + return count; +} + +uint32 DebugServiceBase::TraceSignal(const char8 *name, bool enable, + uint32 decimation) { + mutex.FastLock(); + uint32 count = 0u; + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (aliases[i].name == name || SuffixMatch(aliases[i].name.Buffer(), name)) { + DebugSignalInfo *s = signals[aliases[i].signalIndex]; + s->isTracing = enable; + s->decimationFactor = decimation; + s->decimationCounter = 0; + count++; + } + } + UpdateBrokersActiveStatus(); + mutex.FastUnLock(); + return count; +} + +uint32 DebugServiceBase::SetBreak(const char8 *name, uint8 op, + float64 threshold) { + mutex.FastLock(); + uint32 count = 0u; + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (aliases[i].name == name || SuffixMatch(aliases[i].name.Buffer(), name)) { + DebugSignalInfo *s = signals[aliases[i].signalIndex]; + s->breakThreshold = threshold; + s->breakOp = op; + count++; + } + } + if (count > 0u) UpdateBrokersBreakStatus(); + mutex.FastUnLock(); + return count; +} + +uint32 DebugServiceBase::ClearBreak(const char8 *name) { + mutex.FastLock(); + uint32 count = 0u; + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (aliases[i].name == name || SuffixMatch(aliases[i].name.Buffer(), name)) { + signals[aliases[i].signalIndex]->breakOp = BREAK_OFF; + count++; + } + } + if (count > 0u) UpdateBrokersBreakStatus(); + mutex.FastUnLock(); + return count; +} + +bool DebugServiceBase::IsInstrumented(const char8 *fullPath, + bool &traceable, bool &forcable) { + mutex.FastLock(); + bool found = false; + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (aliases[i].name == fullPath || + SuffixMatch(aliases[i].name.Buffer(), fullPath)) { + found = true; break; + } + } + mutex.FastUnLock(); + traceable = found; + forcable = found; + return found; +} + +uint32 DebugServiceBase::RegisterMonitorSignal(const char8 *path, + uint32 periodMs) { + mutex.FastLock(); + + // If already monitored, just update period + for (uint32 j = 0u; j < monitoredSignals.Size(); j++) { + if (monitoredSignals[j].path == path) { + monitoredSignals[j].periodMs = periodMs; + mutex.FastUnLock(); + return 1u; + } + } + + // Tokenise path to separate DataSource path from signal name + StreamString fullPath = path; + fullPath.Seek(0u); + char8 term; + Vec parts; + StreamString token; + while (fullPath.GetToken(token, ".", term)) { parts.Push(token); token = ""; } + + uint32 count = 0u; + if (parts.Size() >= 2u) { + StreamString signalName = parts[parts.Size() - 1u]; + StreamString dsPath; + for (uint32 i = 0u; i < parts.Size() - 1u; i++) { + dsPath += parts[i]; + if (i < parts.Size() - 2u) dsPath += "."; + } + ReferenceT ds = + ObjectRegistryDatabase::Instance()->Find(dsPath.Buffer()); + if (ds.IsValid()) { + uint32 idx = 0u; + if (ds->GetSignalIndex(idx, signalName.Buffer())) { + MonitoredSignal m; + m.dataSource = ds; + m.signalIdx = idx; + m.path = path; + m.periodMs = periodMs; + m.lastPollTime = 0u; + m.size = 0u; + (void)ds->GetSignalByteSize(idx, m.size); + if (m.size == 0u) m.size = 4u; + m.internalID = 0x80000000u | monitoredSignals.Size(); + // Re-use existing brokered signal ID if available + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (aliases[i].name == path || + SuffixMatch(aliases[i].name.Buffer(), path)) { + m.internalID = signals[aliases[i].signalIndex]->internalID; + break; + } + } + monitoredSignals.Push(m); + count = 1u; + } + } + } + mutex.FastUnLock(); + return count; +} + +uint32 DebugServiceBase::UnmonitorSignal(const char8 *path) { + mutex.FastLock(); + uint32 count = 0u; + for (uint32 i = 0u; i < monitoredSignals.Size(); i++) { + if (monitoredSignals[i].path == path || + SuffixMatch(monitoredSignals[i].path.Buffer(), path)) { + (void)monitoredSignals.Remove(i); i--; count++; + } + } + mutex.FastUnLock(); + return count; +} + +// --------------------------------------------------------------------------- +// Broker index maintenance +// --------------------------------------------------------------------------- + +void DebugServiceBase::UpdateBrokersActiveStatus() { + for (uint32 i = 0u; i < brokers.Size(); i++) { + if (brokers[i].signalPointers == NULL_PTR(DebugSignalInfo **)) continue; + uint32 count = 0u; + Vec tempInd, tempSizes; + for (uint32 j = 0u; j < brokers[i].numSignals; j++) { + DebugSignalInfo *s = brokers[i].signalPointers[j]; + if (s != NULL_PTR(DebugSignalInfo *) && (s->isTracing || s->isForcing)) { + tempInd.Push(j); + tempSizes.Push((brokers[i].broker != NULL_PTR(MemoryMapBroker *)) + ? brokers[i].broker->GetCopyByteSize(j) : 4u); + count++; + } + } + if (brokers[i].activeMutex) brokers[i].activeMutex->FastLock(); + if (brokers[i].activeIndices) brokers[i].activeIndices->Swap(tempInd); + if (brokers[i].activeSizes) brokers[i].activeSizes->Swap(tempSizes); + if (brokers[i].anyActiveFlag) *(brokers[i].anyActiveFlag) = (count > 0u); + if (brokers[i].activeMutex) brokers[i].activeMutex->FastUnLock(); + } +} + +void DebugServiceBase::UpdateBrokersBreakStatus() { + for (uint32 i = 0u; i < brokers.Size(); i++) { + if (brokers[i].signalPointers == NULL_PTR(DebugSignalInfo **)) continue; + Vec tempBreak; + uint32 count = 0u; + for (uint32 j = 0u; j < brokers[i].numSignals; j++) { + DebugSignalInfo *s = brokers[i].signalPointers[j]; + if (s != NULL_PTR(DebugSignalInfo *) && s->breakOp != BREAK_OFF) { + tempBreak.Push(j); count++; + } + } + if (brokers[i].activeMutex) brokers[i].activeMutex->FastLock(); + if (brokers[i].breakIndices) brokers[i].breakIndices->Swap(tempBreak); + if (brokers[i].anyBreakFlag) *(brokers[i].anyBreakFlag) = (count > 0u); + if (brokers[i].activeMutex) brokers[i].activeMutex->FastUnLock(); + } +} + +// --------------------------------------------------------------------------- +// Step control +// --------------------------------------------------------------------------- + +void DebugServiceBase::Step(uint32 n, const char8 *threadName) { + mutex.FastLock(); + stepRemaining = n; + isPaused = false; + stepThreadFilter = (threadName != NULL_PTR(const char8 *)) ? threadName : ""; + mutex.FastUnLock(); +} + +// --------------------------------------------------------------------------- +// Command-channel output helpers +// --------------------------------------------------------------------------- + +void DebugServiceBase::GetStepStatus(StreamString &out) { + mutex.FastLock(); + bool paused = isPaused; + uint32 rem = stepRemaining; + StreamString gam = pausedAtGam; + StreamString thr = stepThreadFilter; + mutex.FastUnLock(); + out.Printf("{\"Paused\":%s,\"PausedAtGam\":\"%s\"," + "\"StepRemaining\":%u,\"StepThread\":\"%s\"}\nOK STEP_STATUS\n", + paused ? "true" : "false", gam.Buffer(), rem, thr.Buffer()); +} + +void DebugServiceBase::GetSignalValue(const char8 *name, StreamString &out) { + mutex.FastLock(); + DebugSignalInfo *sig = NULL_PTR(DebugSignalInfo *); + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (aliases[i].name == name || + SuffixMatch(aliases[i].name.Buffer(), name)) { + sig = signals[aliases[i].signalIndex]; break; + } + } + if (sig == NULL_PTR(DebugSignalInfo *)) { + mutex.FastUnLock(); + out.Printf("{\"Error\":\"Signal not found: %s\"}\nOK VALUE\n", name); + return; + } + TypeDescriptor td = sig->type; + uint32 nElem = sig->numberOfElements; + uint32 bySz = (td.numberOfBits > 0u) ? (td.numberOfBits / 8u) : 1u; + bool trunc = (nElem > GET_VALUE_MAX_ELEMENTS); + if (trunc) nElem = GET_VALUE_MAX_ELEMENTS; + uint32 total = bySz * nElem; + if (total > 1024u) { total = 1024u; nElem = total / bySz; } + uint8 lb[1024]; memset(lb, 0, sizeof(lb)); + if (sig->memoryAddress != NULL_PTR(void *)) memcpy(lb, sig->memoryAddress, total); + mutex.FastUnLock(); + + StreamString valueStr; + if (nElem > 1u) { + for (uint32 i = 0u; i < nElem; i++) { + char8 eb[128] = { '\0' }; + AnyType se(td, 0u, (void *)(lb + i * bySz)); + AnyType ds(CharString, 0u, eb); + ds.SetNumberOfElements(0u, 128u); + (void)TypeConvert(ds, se); + if (i > 0u) valueStr += ", "; + valueStr += eb; + } + } else { + char8 eb[256] = { '\0' }; + AnyType se(td, 0u, (void *)lb); + AnyType ds(CharString, 0u, eb); + ds.SetNumberOfElements(0u, 256u); + (void)TypeConvert(ds, se); + valueStr = eb; + } + + out += "{\"Name\":\""; + out += name; + out += "\",\"Value\":\""; + const char8 *vp = valueStr.Buffer(); + while (vp != NULL_PTR(const char8 *) && *vp != '\0') { + if (*vp == '"') out += "\\\""; + else if (*vp == '\\') out += "\\\\"; + else { char8 tmp[2] = { *vp, '\0' }; out += tmp; } + vp++; + } + out.Printf("\",\"Elements\":%u,\"Truncated\":%s}\nOK VALUE\n", + nElem, trunc ? "true" : "false"); +} + +void DebugServiceBase::Discover(StreamString &out) { + out += "{\"Signals\":[\n"; + mutex.FastLock(); + uint32 total = 0u; + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (total > 0u) out += ",\n"; + DebugSignalInfo *sig = signals[aliases[i].signalIndex]; + const char8 *tn = TypeDescriptor::GetTypeNameFromTypeDescriptor(sig->type); + out.Printf(" {\"name\":\"%s\",\"id\":%u,\"type\":\"%s\"," + "\"dimensions\":%u,\"elements\":%u", + aliases[i].name.Buffer(), sig->internalID, + tn ? tn : "Unknown", sig->numberOfDimensions, sig->numberOfElements); + EnrichWithConfig(aliases[i].name.Buffer(), out); + out += "}"; + total++; + } + for (uint32 i = 0u; i < monitoredSignals.Size(); i++) { + bool found = false; + for (uint32 j = 0u; j < aliases.Size(); j++) { + if (aliases[j].name == monitoredSignals[i].path) { found = true; break; } + } + if (!found) { + if (total > 0u) out += ",\n"; + const char8 *tn = TypeDescriptor::GetTypeNameFromTypeDescriptor( + monitoredSignals[i].dataSource->GetSignalType(monitoredSignals[i].signalIdx)); + uint8 dims = 0u; uint32 elems = 1u; + (void)monitoredSignals[i].dataSource->GetSignalNumberOfDimensions( + monitoredSignals[i].signalIdx, dims); + (void)monitoredSignals[i].dataSource->GetSignalNumberOfElements( + monitoredSignals[i].signalIdx, elems); + out.Printf(" {\"name\":\"%s\",\"id\":%u,\"type\":\"%s\"," + "\"dimensions\":%u,\"elements\":%u", + monitoredSignals[i].path.Buffer(), monitoredSignals[i].internalID, + tn ? tn : "Unknown", dims, elems); + EnrichWithConfig(monitoredSignals[i].path.Buffer(), out); + out += "}"; + total++; + } + } + mutex.FastUnLock(); + out += "\n]}\nOK DISCOVER\n"; +} + +void DebugServiceBase::InfoNode(const char8 *path, StreamString &out) { + Reference ref = ObjectRegistryDatabase::Instance()->Find(path); + out += "{"; + if (ref.IsValid()) { + out += "\"Name\":\""; EscapeJson(ref->GetName(), out); + out += "\",\"Class\":\""; EscapeJson(ref->GetClassProperties()->GetName(), out); + out += "\""; + ConfigurationDatabase db; + if (ref->ExportData(db)) { + out += ",\"Config\":{"; + db.MoveToRoot(); + uint32 nc = db.GetNumberOfChildren(); + for (uint32 i = 0u; i < nc; i++) { + const char8 *cn = db.GetChildName(i); + AnyType at = db.GetType(cn); + char8 buf[1024]; AnyType st(CharString, 0u, buf); st.SetNumberOfElements(0, 1024); + if (TypeConvert(st, at)) { + out += "\""; EscapeJson(cn, out); out += "\":\""; + EscapeJson(buf, out); out += "\""; + if (i < nc - 1u) out += ","; + } + } + out += "}"; + } + EnrichWithConfig(path, out); + } else { + StreamString enrichAlias; + mutex.FastLock(); + bool found = false; + for (uint32 i = 0u; i < aliases.Size(); i++) { + if (aliases[i].name == path || + SuffixMatch(aliases[i].name.Buffer(), path)) { + DebugSignalInfo *s = signals[aliases[i].signalIndex]; + const char8 *tn = TypeDescriptor::GetTypeNameFromTypeDescriptor(s->type); + out.Printf("\"Name\":\"%s\",\"Class\":\"Signal\",\"Type\":\"%s\"," + "\"ID\":%u", + s->name.Buffer(), tn ? tn : "Unknown", s->internalID); + enrichAlias = aliases[i].name; + found = true; break; + } + } + mutex.FastUnLock(); + if (found) EnrichWithConfig(enrichAlias.Buffer(), out); + else out += "\"Error\":\"Object not found\""; + } + out += "}\nOK INFO\n"; +} + +void DebugServiceBase::ListNodes(const char8 *path, StreamString &out) { + Reference ref = + (path == NULL_PTR(const char8 *) || StringHelper::Length(path) == 0 || + StringHelper::Compare(path, "/") == 0) + ? ObjectRegistryDatabase::Instance() + : ObjectRegistryDatabase::Instance()->Find(path); + out.Printf("Nodes under %s:\n", path ? path : "/"); + if (ref.IsValid()) { + ReferenceContainer *rc = dynamic_cast(ref.operator->()); + if (rc != NULL_PTR(ReferenceContainer *)) { + uint32 n = rc->Size(); + for (uint32 i = 0u; i < n; i++) { + Reference c = rc->Get(i); + if (c.IsValid()) { + out.Printf(" %s [%s]\n", c->GetName(), + c->GetClassProperties()->GetName()); + } + } + } + } else { + out += " (not found)\n"; + } + out += "OK LS\n"; +} + +void DebugServiceBase::ServeConfig(StreamString &out) { + if (!manualConfigSet) RebuildConfigFromRegistry(); + fullConfig.MoveToRoot(); + JsonifyDatabase(fullConfig, out); + out += "\nOK CONFIG\n"; +} + +// --------------------------------------------------------------------------- +// Config management +// --------------------------------------------------------------------------- + +void DebugServiceBase::SetFullConfig(ConfigurationDatabase &config) { + config.MoveToRoot(); + config.Copy(fullConfig); + manualConfigSet = true; +} + +void DebugServiceBase::RebuildConfigFromRegistry() { + fullConfig = ConfigurationDatabase(); + BuildCDBFromContainer(ObjectRegistryDatabase::Instance(), fullConfig); + // Let the transport subclass write back its specific port numbers etc. + RebuildTransportConfig(); +} + +// --------------------------------------------------------------------------- +// Tree export +// --------------------------------------------------------------------------- + +uint32 DebugServiceBase::ExportTree(ReferenceContainer *container, + StreamString &json, + const char8 *pathPrefix) { + if (container == NULL_PTR(ReferenceContainer *)) return 0u; + uint32 size = container->Size(); + uint32 valid = 0u; + for (uint32 i = 0u; i < size; i++) { + Reference child = container->Get(i); + if (!child.IsValid()) continue; + if (valid > 0u) json += ",\n"; + const char8 *cname = child->GetName(); + if (cname == NULL_PTR(const char8 *)) cname = "unnamed"; + StreamString cp; + if (pathPrefix != NULL_PTR(const char8 *)) cp.Printf("%s.%s", pathPrefix, cname); + else cp = cname; + + StreamString nj; + nj += "{\"Name\":\""; EscapeJson(cname, nj); + nj += "\",\"Class\":\""; EscapeJson(child->GetClassProperties()->GetName(), nj); + nj += "\""; + + ReferenceContainer *inner = dynamic_cast(child.operator->()); + DataSourceI *ds = dynamic_cast(child.operator->()); + GAM *gam = dynamic_cast(child.operator->()); + + if (inner != NULL_PTR(ReferenceContainer *) || + ds != NULL_PTR(DataSourceI *) || + gam != NULL_PTR(GAM *)) { + nj += ",\"Children\":[\n"; + uint32 sc = 0u; + if (inner != NULL_PTR(ReferenceContainer *)) + sc += ExportTree(inner, nj, cp.Buffer()); + if (ds != NULL_PTR(DataSourceI *)) { + uint32 ns = ds->GetNumberOfSignals(); + for (uint32 j = 0u; j < ns; j++) { + if (sc > 0u) { nj += ",\n"; } sc++; + StreamString sn; (void)ds->GetSignalName(j, sn); + const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(ds->GetSignalType(j)); + uint8 d = 0u; (void)ds->GetSignalNumberOfDimensions(j, d); + uint32 el = 0u; (void)ds->GetSignalNumberOfElements(j, el); + StreamString sfp; sfp.Printf("%s.%s", cp.Buffer(), sn.Buffer()); + bool tr = false, fo = false; (void)IsInstrumented(sfp.Buffer(), tr, fo); + nj += "{\"Name\":\""; EscapeJson(sn.Buffer(), nj); + nj += "\",\"Class\":\"Signal\",\"Type\":\""; EscapeJson(st ? st : "Unknown", nj); + nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u," + "\"IsTraceable\":%s,\"IsForcable\":%s}", + d, el, tr ? "true" : "false", fo ? "true" : "false"); + } + } + if (gam != NULL_PTR(GAM *)) { + uint32 nIn = gam->GetNumberOfInputSignals(); + for (uint32 j = 0u; j < nIn; j++) { + if (sc > 0u) { nj += ",\n"; } sc++; + StreamString sn; (void)gam->GetSignalName(InputSignals, j, sn); + const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor( + gam->GetSignalType(InputSignals, j)); + uint32 d = 0u; (void)gam->GetSignalNumberOfDimensions(InputSignals, j, d); + uint32 el = 0u; (void)gam->GetSignalNumberOfElements(InputSignals, j, el); + StreamString sfp; sfp.Printf("%s.In.%s", cp.Buffer(), sn.Buffer()); + bool tr = false, fo = false; (void)IsInstrumented(sfp.Buffer(), tr, fo); + nj += "{\"Name\":\"In."; EscapeJson(sn.Buffer(), nj); + nj += "\",\"Class\":\"InputSignal\",\"Type\":\""; + EscapeJson(st ? st : "Unknown", nj); + nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u," + "\"IsTraceable\":%s,\"IsForcable\":%s}", + d, el, tr ? "true" : "false", fo ? "true" : "false"); + } + uint32 nOut = gam->GetNumberOfOutputSignals(); + for (uint32 j = 0u; j < nOut; j++) { + if (sc > 0u) { nj += ",\n"; } sc++; + StreamString sn; (void)gam->GetSignalName(OutputSignals, j, sn); + const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor( + gam->GetSignalType(OutputSignals, j)); + uint32 d = 0u; (void)gam->GetSignalNumberOfDimensions(OutputSignals, j, d); + uint32 el = 0u; (void)gam->GetSignalNumberOfElements(OutputSignals, j, el); + StreamString sfp; sfp.Printf("%s.Out.%s", cp.Buffer(), sn.Buffer()); + bool tr = false, fo = false; (void)IsInstrumented(sfp.Buffer(), tr, fo); + nj += "{\"Name\":\"Out."; EscapeJson(sn.Buffer(), nj); + nj += "\",\"Class\":\"OutputSignal\",\"Type\":\""; + EscapeJson(st ? st : "Unknown", nj); + nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u," + "\"IsTraceable\":%s,\"IsForcable\":%s}", + d, el, tr ? "true" : "false", fo ? "true" : "false"); + } + } + nj += "\n]"; + } + nj += "}"; + json += nj; + valid++; + } + return valid; +} + +// --------------------------------------------------------------------------- +// EnrichWithConfig +// --------------------------------------------------------------------------- + +void DebugServiceBase::EnrichWithConfig(const char8 *path, StreamString &json) { + if (path == NULL_PTR(const char8 *)) return; + if (!manualConfigSet) RebuildConfigFromRegistry(); + fullConfig.MoveToRoot(); + + const char8 *current = path; + bool ok = true; + while (ok) { + const char8 *nextDot = StringHelper::SearchString(current, "."); + StreamString part; + if (nextDot != NULL_PTR(const char8 *)) { + uint32 len = (uint32)(nextDot - current); + (void)part.Write(current, len); + current = nextDot + 1; + } else { + part = current; ok = false; + } + bool found = false; + if (fullConfig.MoveRelative(part.Buffer())) { found = true; } + if (!found) { + StreamString pref; pref.Printf("+%s", part.Buffer()); + if (fullConfig.MoveRelative(pref.Buffer())) found = true; + } + if (!found) { + if (part == "In") { + if (fullConfig.MoveRelative("InputSignals")) found = true; + else if (fullConfig.MoveRelative("+InputSignals")) found = true; + } else if (part == "Out") { + if (fullConfig.MoveRelative("OutputSignals")) found = true; + else if (fullConfig.MoveRelative("+OutputSignals")) found = true; + } + } + if (!found) { fullConfig.MoveToRoot(); return; } + } + + ConfigurationDatabase db; + fullConfig.Copy(db); + fullConfig.MoveToRoot(); + db.MoveToRoot(); + uint32 n = db.GetNumberOfChildren(); + for (uint32 i = 0u; i < n; i++) { + const char8 *name = db.GetChildName(i); + AnyType at = db.GetType(name); + if (!at.GetTypeDescriptor().isStructuredData) { + json += ", \""; + EscapeJson(name, json); + json += "\": \""; + char8 buf[1024]; + AnyType st(CharString, 0u, buf); + st.SetNumberOfElements(0, 1024); + if (TypeConvert(st, at)) EscapeJson(buf, json); + json += "\""; + } + } +} + +// --------------------------------------------------------------------------- +// JsonifyDatabase (static) +// --------------------------------------------------------------------------- + +void DebugServiceBase::JsonifyDatabase(ConfigurationDatabase &db, + StreamString &json) { + json += "{"; + uint32 n = db.GetNumberOfChildren(); + bool first = true; + for (uint32 i = 0u; i < n; i++) { + const char8 *key = db.GetChildName(i); + if (key == NULL_PTR(const char8 *)) continue; + if (!first) json += ", "; + first = false; + json += "\""; EscapeJson(key, json); json += "\": "; + if (db.MoveRelative(key)) { + JsonifyDatabase(db, json); + (void)db.MoveToAncestor(1u); + } else { + AnyType at = db.GetType(key); + if (at.GetDataPointer() != NULL_PTR(void *)) { + char8 buf[512] = { '\0' }; + AnyType dst(CharString, 0u, buf); + dst.SetNumberOfElements(0u, 512u); + if (TypeConvert(dst, at)) { + json += "\""; EscapeJson(buf, json); json += "\""; + } else { + json += "null"; + } + } else { + json += "null"; + } + } + } + json += "}"; +} + +// --------------------------------------------------------------------------- +// HandleCommand — unified command dispatcher +// --------------------------------------------------------------------------- + +void DebugServiceBase::HandleCommand(const StreamString &cmdIn, StreamString &out) { + StreamString cmd = cmdIn; + StreamString token; + cmd.Seek(0u); + char8 term; + const char8 *delims = " \r\n"; + if (!cmd.GetToken(token, delims, term)) return; + + if (token == "FORCE") { + StreamString name, val; + if (cmd.GetToken(name, delims, term) && cmd.GetToken(val, delims, term)) { + uint32 c = ForceSignal(name.Buffer(), val.Buffer()); + out.Printf("OK FORCE %u\n", c); + } + } else if (token == "UNFORCE") { + StreamString name; + if (cmd.GetToken(name, delims, term)) { + uint32 c = UnforceSignal(name.Buffer()); + out.Printf("OK UNFORCE %u\n", c); + } + } else if (token == "TRACE") { + StreamString name, state, decim; + if (cmd.GetToken(name, delims, term) && cmd.GetToken(state, delims, term)) { + bool en = (state == "1"); + uint32 d = 1u; + if (cmd.GetToken(decim, delims, term)) { + AnyType dv(UnsignedInteger32Bit, 0u, &d); + AnyType ds(CharString, 0u, decim.Buffer()); + (void)TypeConvert(dv, ds); + } + uint32 c = TraceSignal(name.Buffer(), en, d); + out.Printf("OK TRACE %u\n", c); + } + } else if (token == "BREAK") { + StreamString name, opStr; + if (cmd.GetToken(name, delims, term) && cmd.GetToken(opStr, delims, term)) { + uint32 c = 0u; + if (opStr == "OFF") { + c = ClearBreak(name.Buffer()); + } else { + StreamString thrStr; + if (cmd.GetToken(thrStr, delims, term)) { + uint8 op = BREAK_OFF; + if (opStr == ">") op = BREAK_GT; + else if (opStr == "<") op = BREAK_LT; + else if (opStr == "==") op = BREAK_EQ; + else if (opStr == ">=") op = BREAK_GEQ; + else if (opStr == "<=") op = BREAK_LEQ; + else if (opStr == "!=") op = BREAK_NEQ; + if (op != BREAK_OFF) { + float64 thr = 0.0; + AnyType tv(Float64Bit, 0u, &thr); + AnyType ts(CharString, 0u, thrStr.Buffer()); + (void)TypeConvert(tv, ts); + c = SetBreak(name.Buffer(), op, thr); + } + } + } + out.Printf("OK BREAK %u\n", c); + } + } else if (token == "PAUSE") { + SetPaused(true); + out += "OK\n"; + } else if (token == "RESUME") { + SetPaused(false); + out += "OK\n"; + } else if (token == "STEP") { + StreamString nStr; + uint32 n = 1u; + if (cmd.GetToken(nStr, delims, term)) { + AnyType nv(UnsignedInteger32Bit, 0u, &n); + AnyType ns(CharString, 0u, nStr.Buffer()); + (void)TypeConvert(nv, ns); + } + StreamString thr; + const char8 *ta = NULL_PTR(const char8 *); + if (cmd.GetToken(thr, delims, term) && thr.Size() > 0u) ta = thr.Buffer(); + Step(n, ta); + out.Printf("OK STEP %u\n", n); + } else if (token == "STEP_STATUS") { + GetStepStatus(out); + } else if (token == "VALUE") { + StreamString sn; + if (cmd.GetToken(sn, delims, term)) { + GetSignalValue(sn.Buffer(), out); + } else { + out += "{\"Error\":\"Missing signal name\"}\nOK VALUE\n"; + } + } else if (token == "DISCOVER") { + Discover(out); + } else if (token == "TREE") { + out += "{\"Name\":\"Root\",\"Class\":\"ObjectRegistryDatabase\"," + "\"Children\":[\n"; + (void)ExportTree(ObjectRegistryDatabase::Instance(), out, + NULL_PTR(const char8 *)); + out += "\n]}\nOK TREE\n"; + } else if (token == "INFO") { + StreamString path; + if (cmd.GetToken(path, delims, term)) InfoNode(path.Buffer(), out); + } else if (token == "LS") { + StreamString path; + if (cmd.GetToken(path, delims, term)) ListNodes(path.Buffer(), out); + else ListNodes(NULL_PTR(const char8 *), out); + } else if (token == "CONFIG") { + ServeConfig(out); + } else if (token == "MONITOR") { + StreamString sub, name, period; + if (cmd.GetToken(sub, delims, term) && sub == "SIGNAL" && + cmd.GetToken(name, delims, term) && + cmd.GetToken(period, delims, term)) { + uint32 p = 100u; + AnyType pv(UnsignedInteger32Bit, 0u, &p); + AnyType ps(CharString, 0u, period.Buffer()); + (void)TypeConvert(pv, ps); + uint32 c = RegisterMonitorSignal(name.Buffer(), p); + out.Printf("OK MONITOR %u\n", c); + } + } else if (token == "UNMONITOR") { + StreamString sub, name; + if (cmd.GetToken(sub, delims, term) && sub == "SIGNAL" && + cmd.GetToken(name, delims, term)) { + uint32 c = UnmonitorSignal(name.Buffer()); + out.Printf("OK UNMONITOR %u\n", c); + } + } else if (token == "SERVICE_INFO") { + GetServiceInfo(out); + } else if (token == "MSG") { + // MSG [key=value\nkey=value...] + StreamString dest, func, waitStr; + if (cmd.GetToken(dest, delims, term) && + cmd.GetToken(func, delims, term) && + cmd.GetToken(waitStr, delims, term)) { + bool wait = (waitStr == "1"); + + const char8 *pStart = cmd.Buffer() + cmd.Position(); + StreamString rawPayload = pStart; + + // Decode escaped newlines (\n → real newline) + StreamString payload; + rawPayload.Seek(0u); + char8 c; + while (rawPayload.Size() > rawPayload.Position()) { + uint32 rs = 1u; + if (rawPayload.Read(&c, rs)) { + if (c == '\\') { + char8 next; + if (rawPayload.Read(&next, rs)) { + if (next == 'n') payload += '\n'; + else { payload += c; payload += next; } + } else { + payload += c; + } + } else { + payload += c; + } + } + } + + ReferenceT msg( + "Message", GlobalObjectsDatabase::Instance()->GetStandardHeap()); + ConfigurationDatabase msgConfig; + msgConfig.Write("Destination", dest.Buffer()); + msgConfig.Write("Function", func.Buffer()); + if (wait) msgConfig.Write("Mode", "ExpectsReply"); + + ReferenceT paramCdb( + "ConfigurationDatabase", + GlobalObjectsDatabase::Instance()->GetStandardHeap()); + + if (payload.Size() > 0u && paramCdb.IsValid()) { + payload.Seek(0u); + StreamString line; + while (payload.GetToken(line, "\n", term)) { + if (line.Size() > 0u) { + const char8 *eq = StringHelper::SearchChar(line.Buffer(), '='); + if (eq != NULL_PTR(const char8 *)) { + uint32 eqPos = (uint32)(eq - line.Buffer()); + static const uint32 KEY_BUF_SIZE = 256u; + static const uint32 VAL_BUF_SIZE = 1024u; + if (eqPos >= KEY_BUF_SIZE) { + REPORT_ERROR_STATIC(ErrorManagement::Warning, + "MSG: key length %u exceeds buffer (%u) — line skipped.", + eqPos, KEY_BUF_SIZE); + line = ""; continue; + } + (void)line.Seek(0u); + char8 keyBuf[KEY_BUF_SIZE]; + MemoryOperationsHelper::Set(keyBuf, '\0', KEY_BUF_SIZE); + uint32 keyReadSize = eqPos; + (void)line.Read(keyBuf, keyReadSize); + (void)line.Seek(eqPos + 1u); + uint32 valLen = (uint32)(line.Size() - eqPos - 1u); + if (valLen >= VAL_BUF_SIZE) { + REPORT_ERROR_STATIC(ErrorManagement::Warning, + "MSG: value length %u truncated to %u for key '%s'.", + valLen, VAL_BUF_SIZE - 1u, keyBuf); + valLen = VAL_BUF_SIZE - 1u; + } + char8 valBuf[VAL_BUF_SIZE]; + MemoryOperationsHelper::Set(valBuf, '\0', VAL_BUF_SIZE); + (void)line.Read(valBuf, valLen); + // Trim trailing whitespace from value + for (int32 ti = (int32)valLen - 1; ti >= 0; ti--) { + if (valBuf[ti] == ' ' || valBuf[ti] == '\r' || + valBuf[ti] == '\t') + valBuf[ti] = '\0'; + else break; + } + // Trim leading whitespace from key + const char8 *kp = keyBuf; + while (*kp == ' ' || *kp == '\t') kp++; + if (*kp != '\0') (void)paramCdb->Write(kp, valBuf); + } + line = ""; + } + } + } + + ErrorManagement::ErrorType err = ErrorManagement::ParametersError; + if (msg->Initialise(msgConfig)) { + if (paramCdb.IsValid() && payload.Size() > 0u) { + (void)msg->Insert(paramCdb); + } + Reference destObj = ObjectRegistryDatabase::Instance()->Find(dest.Buffer()); + if (destObj.IsValid()) { + StreamString myPath; + Object *sender = this; + if (!GetFullObjectName(*this, myPath)) { + sender = NULL_PTR(Object *); + } + if (wait) { + err = MessageI::WaitForReply(msg, TTInfiniteWait); + } else { + (void)MessageI::SendMessage(msg, sender); + err = ErrorManagement::NoError; + } + } else { + REPORT_ERROR_STATIC(ErrorManagement::Warning, + "MSG: destination '%s' not found in ORD.", dest.Buffer()); + } + } else { + REPORT_ERROR_STATIC(ErrorManagement::Warning, + "MSG: Message initialisation failed."); + } + + if (err == ErrorManagement::NoError) out += "OK MSG\n"; + else out += "ERROR MSG\n"; + } + } +} + +// --------------------------------------------------------------------------- +// Out-of-class constant definitions (C++98 ODR requirement) +// --------------------------------------------------------------------------- +const uint32 DebugServiceBase::GET_VALUE_MAX_ELEMENTS; + +} // namespace MARTe diff --git a/Source/Components/Interfaces/DebugService/DebugServiceBase.h b/Source/Components/Interfaces/DebugService/DebugServiceBase.h new file mode 100644 index 0000000..8c1c262 --- /dev/null +++ b/Source/Components/Interfaces/DebugService/DebugServiceBase.h @@ -0,0 +1,190 @@ +#ifndef DEBUGSERVICEBASE_H +#define DEBUGSERVICEBASE_H + +/** + * @file DebugServiceBase.h + * @brief Shared base class for DebugService and WebDebugService. + * + * Extracts all signal-management, command-handling and config logic that is + * common to both the TCP/UDP transport (DebugService) and the HTTP/SSE + * transport (WebDebugService), eliminating the previous code duplication. + */ + +#include "ConfigurationDatabase.h" +#include "DataSourceI.h" +#include "DebugServiceI.h" +#include "FastPollingMutexSem.h" +#include "ReferenceContainer.h" +#include "ReferenceT.h" +#include "StreamString.h" + +namespace MARTe { + +/** + * @brief Intermediate base class that holds all shared debugging logic. + * + * Inherits from ReferenceContainer (to be a MARTe2 Object) and DebugServiceI + * (to expose the RT-path / control-path interface). Transport subclasses + * (DebugService, WebDebugService) inherit from this class and add only their + * transport-specific threading and socket logic. + */ +class DebugServiceBase : public ReferenceContainer, public DebugServiceI { +public: + friend class DebugServiceTest; + + DebugServiceBase(); + virtual ~DebugServiceBase(); + + // ========================================================================= + // DebugServiceI RT-path overrides (shared implementation) + // ========================================================================= + + virtual DebugSignalInfo *RegisterSignal(void *memoryAddress, + TypeDescriptor type, + const char8 *name, + uint8 numberOfDimensions = 0, + uint32 numberOfElements = 1); + + virtual void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size, + uint64 timestamp); + + virtual void RegisterBroker(DebugSignalInfo **signalPointers, + uint32 numSignals, + MemoryMapBroker *broker, + volatile bool *anyActiveFlag, + Vec *activeIndices, + Vec *activeSizes, + FastPollingMutexSem *activeMutex, + volatile bool *anyBreakFlag, + Vec *breakIndices, + const char8 *gamName = NULL_PTR(const char8 *), + bool isOutput = false); + + virtual bool IsPaused() const { return isPaused; } + virtual void SetPaused(bool paused) { isPaused = paused; } + virtual bool IsStepPending() const { return stepRemaining > 0u; } + virtual void ConsumeStepIfNeeded(const char8 *gamName, + const char8 *threadName = NULL_PTR(const char8 *)); + + // ========================================================================= + // DebugServiceI control-path overrides (shared implementation) + // ========================================================================= + + virtual uint32 ForceSignal (const char8 *name, const char8 *valueStr); + virtual uint32 UnforceSignal(const char8 *name); + virtual uint32 TraceSignal (const char8 *name, bool enable, uint32 decimation = 1); + virtual uint32 SetBreak (const char8 *name, uint8 op, float64 threshold); + virtual uint32 ClearBreak (const char8 *name); + virtual bool IsInstrumented(const char8 *fullPath, bool &traceable, bool &forcable); + virtual uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs); + virtual uint32 UnmonitorSignal (const char8 *path); + + // ========================================================================= + // Shared control-path methods (used by transport subclasses) + // ========================================================================= + + /** + * @brief Parse and dispatch a debug command; write text response to @p out. + * + * Handles: TRACE, FORCE, UNFORCE, BREAK, PAUSE, RESUME, STEP, STEP_STATUS, + * VALUE, DISCOVER, TREE, INFO, LS, CONFIG, MONITOR, UNMONITOR, + * SERVICE_INFO, MSG. + * + * SERVICE_INFO delegates to GetServiceInfo() so each transport can fill in + * its own port/state information. + */ + void HandleCommand(const StreamString &cmdIn, StreamString &out); + + void GetStepStatus(StreamString &out); + void GetSignalValue(const char8 *name, StreamString &out); + void Discover (StreamString &out); + void InfoNode (const char8 *path, StreamString &out); + void ListNodes (const char8 *path, StreamString &out); + void ServeConfig (StreamString &out); + + void SetFullConfig(ConfigurationDatabase &config); + void RebuildConfigFromRegistry(); + + // ========================================================================= + // Struct shared by both transports + // ========================================================================= + + struct MonitoredSignal { + ReferenceT dataSource; + uint32 signalIdx; + uint32 internalID; + uint32 periodMs; + uint64 lastPollTime; + uint32 size; + StreamString path; + }; + + // ========================================================================= + // Constants + // ========================================================================= + + static const uint32 GET_VALUE_MAX_ELEMENTS = 256u; + +protected: + // ========================================================================= + // Virtual hooks for transport subclasses + // ========================================================================= + + /** + * @brief Fill the SERVICE_INFO response text (transport-specific). + * + * Called by HandleCommand when the SERVICE_INFO command is received. + * The base implementation writes nothing; subclasses override to append + * e.g. "OK SERVICE_INFO TCP_CTRL:8080 UDP_STREAM:8081 STATE:RUNNING\n". + */ + virtual void GetServiceInfo(StreamString &out) = 0; + + /** + * @brief Write back transport-specific config keys after RebuildConfigFromRegistry(). + * + * Called at the end of RebuildConfigFromRegistry() so that port numbers + * and other transport parameters that were read in Initialise() are + * reflected back into fullConfig (ExportData on ReferenceContainers + * doesn't re-emit config-file parameters). + */ + virtual void RebuildTransportConfig() {} + + // ========================================================================= + // Shared protected helpers + // ========================================================================= + + void Step(uint32 n, const char8 *threadName = NULL_PTR(const char8 *)); + void UpdateBrokersActiveStatus(); + void UpdateBrokersBreakStatus(); + void PatchRegistry(); + + uint32 ExportTree(ReferenceContainer *container, StreamString &json, + const char8 *pathPrefix); + void EnrichWithConfig(const char8 *path, StreamString &json); + static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json); + + // ========================================================================= + // Shared data members + // ========================================================================= + + Vec signals; + Vec aliases; + Vec brokers; + Vec monitoredSignals; + + FastPollingMutexSem mutex; + FastPollingMutexSem tracePushMutex; + TraceRingBuffer traceBuffer; + + volatile bool isPaused; + volatile uint32 stepRemaining; + StreamString pausedAtGam; + StreamString stepThreadFilter; + + ConfigurationDatabase fullConfig; + bool manualConfigSet; +}; + +} // namespace MARTe + +#endif // DEBUGSERVICEBASE_H diff --git a/Source/Components/Interfaces/DebugService/Makefile.inc b/Source/Components/Interfaces/DebugService/Makefile.inc index 6083f09..8c2be21 100644 --- a/Source/Components/Interfaces/DebugService/Makefile.inc +++ b/Source/Components/Interfaces/DebugService/Makefile.inc @@ -23,7 +23,7 @@ # $Id: Makefile.inc 3 2012-01-15 16:26:07Z aneto $ # ############################################################# -OBJSX=DebugService.x +OBJSX=DebugService.x DebugServiceBase.x PACKAGE=Components/Interfaces diff --git a/Source/Components/Interfaces/WebDebugService/Makefile.inc b/Source/Components/Interfaces/WebDebugService/Makefile.inc index 7254dee..b19a8e9 100644 --- a/Source/Components/Interfaces/WebDebugService/Makefile.inc +++ b/Source/Components/Interfaces/WebDebugService/Makefile.inc @@ -1,4 +1,4 @@ -OBJSX=WebDebugService.x +OBJSX=WebDebugService.x DebugServiceBase.x PACKAGE=Components/Interfaces @@ -26,9 +26,16 @@ INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L5GAMs INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L1Portability INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L3Streams +DEBUGSERVICE_SRC=$(ROOT_DIR)/Source/Components/Interfaces/DebugService + all: $(OBJS) $(SUBPROJ) \ $(BUILD_DIR)/WebDebugService$(LIBEXT) \ $(BUILD_DIR)/WebDebugService$(DLLEXT) echo $(OBJS) include $(MAKEDEFAULTDIR)/MakeStdLibRules.$(TARGET) + +# Explicit rule to compile DebugServiceBase.cpp from the DebugService directory +$(BUILD_DIR)/DebugServiceBase.o: $(DEBUGSERVICE_SRC)/DebugServiceBase.cpp + $(COMPILER) -c $(OPTIM) $(INCLUDES) $(CPPFLAGS) $(CFLAGSPEC) $(DEBUG) \ + $(DEBUGSERVICE_SRC)/DebugServiceBase.cpp -o $(BUILD_DIR)/DebugServiceBase.o diff --git a/Source/Components/Interfaces/WebDebugService/WebDebugService.cpp b/Source/Components/Interfaces/WebDebugService/WebDebugService.cpp index 9b1f0c9..dcd5953 100644 --- a/Source/Components/Interfaces/WebDebugService/WebDebugService.cpp +++ b/Source/Components/Interfaces/WebDebugService/WebDebugService.cpp @@ -1,12 +1,9 @@ #include "Atomic.h" #include "Logger.h" #include "BasicTCPSocket.h" -#include "ClassRegistryItem.h" #include "ConfigurationDatabase.h" -#include "DataSourceI.h" #include "DebugBrokerWrapper.h" #include "GAM.h" -#include "GlobalObjectsDatabase.h" #include "HighResolutionTimer.h" #include "ObjectRegistryDatabase.h" #include "ReferenceT.h" @@ -21,9 +18,30 @@ namespace MARTe { // --------------------------------------------------------------------------- -// File-scope helpers (same as in DebugService.cpp) +// CLASS_REGISTER // --------------------------------------------------------------------------- +CLASS_REGISTER(WebDebugService, "1.0") + +// --------------------------------------------------------------------------- +// Helper: convert raw bytes to float64 for SSE JSON +// --------------------------------------------------------------------------- + +static float64 WDS_ToFloat64(const uint8 *data, TypeDescriptor td) { + float64 v = 0.0; + if (td == Float32Bit) { float32 f; memcpy(&f, data, 4u); v = (float64)f; } + else if (td == Float64Bit) { memcpy(&v, data, 8u); } + else if (td == SignedInteger8Bit) { int8 x; memcpy(&x, data, 1u); v = (float64)x; } + else if (td == UnsignedInteger8Bit) { uint8 x; memcpy(&x, data, 1u); v = (float64)x; } + else if (td == SignedInteger16Bit) { int16 x; memcpy(&x, data, 2u); v = (float64)x; } + else if (td == UnsignedInteger16Bit){ uint16 x; memcpy(&x, data, 2u); v = (float64)x; } + else if (td == SignedInteger32Bit) { int32 x; memcpy(&x, data, 4u); v = (float64)x; } + else if (td == UnsignedInteger32Bit){ uint32 x; memcpy(&x, data, 4u); v = (float64)x; } + else if (td == SignedInteger64Bit) { int64 x; memcpy(&x, data, 8u); v = (float64)x; } + else if (td == UnsignedInteger64Bit){ uint64 x; memcpy(&x, data, 8u); v = (float64)(int64)x; } + return v; +} + static void WDS_EscapeJson(const char8 *src, StreamString &dst) { if (src == NULL_PTR(const char8 *)) return; while (*src != '\0') { @@ -37,115 +55,20 @@ static void WDS_EscapeJson(const char8 *src, StreamString &dst) { } } -static bool WDS_SuffixMatch(const char8 *target, const char8 *pattern) { - uint32 tLen = StringHelper::Length(target); - uint32 pLen = StringHelper::Length(pattern); - if (pLen > tLen) return false; - const char8 *suffix = target + (tLen - pLen); - if (StringHelper::Compare(suffix, pattern) == 0) { - if (tLen == pLen || *(suffix - 1) == '.') return true; - } - return false; -} - -static bool WDS_FindPath(ReferenceContainer *c, const Object *target, - StreamString &path) { - if (c == NULL_PTR(ReferenceContainer *)) return false; - uint32 n = c->Size(); - for (uint32 i = 0u; i < n; i++) { - Reference ref = c->Get(i); - if (!ref.IsValid()) continue; - if (ref.operator->() == target) { path = ref->GetName(); return true; } - ReferenceContainer *sub = dynamic_cast(ref.operator->()); - if (sub != NULL_PTR(ReferenceContainer *)) { - if (WDS_FindPath(sub, target, path)) { - StreamString full; - full.Printf("%s.%s", ref->GetName(), path.Buffer()); - path = full; - return true; - } - } - } - return false; -} - -static void WDS_BuildCDB(ReferenceContainer *container, ConfigurationDatabase &cdb) { - if (container == NULL_PTR(ReferenceContainer *)) return; - uint32 n = container->Size(); - for (uint32 i = 0u; i < n; i++) { - Reference child = container->Get(i); - if (!child.IsValid()) continue; - const char8 *name = child->GetName(); - if (name == NULL_PTR(const char8 *)) continue; - bool created = cdb.CreateRelative(name); - if (!created) { if (!cdb.MoveRelative(name)) continue; } - const char8 *cn = child->GetClassProperties()->GetName(); - if (cn != NULL_PTR(const char8 *)) (void)cdb.Write("Class", cn); - { - ConfigurationDatabase exp; - if (child->ExportData(exp)) { - exp.MoveToRoot(); - uint32 ne = exp.GetNumberOfChildren(); - for (uint32 j = 0u; j < ne; j++) { - const char8 *ek = exp.GetChildName(j); - if (StringHelper::Compare(ek, "Class") == 0 || - StringHelper::Compare(ek, "Name") == 0 || - StringHelper::Compare(ek, "IsContainer") == 0) continue; - if (exp.MoveRelative(ek)) { exp.MoveToAncestor(1u); continue; } - AnyType at = exp.GetType(ek); - if (at.GetDataPointer() != NULL_PTR(void *)) { - char8 buf[256] = { '\0' }; - AnyType dst(CharString, 0u, buf); - dst.SetNumberOfElements(0u, 256u); - if (TypeConvert(dst, at)) (void)cdb.Write(ek, buf); - } - } - } - } - ReferenceContainer *rc = dynamic_cast(child.operator->()); - if (rc != NULL_PTR(ReferenceContainer *)) WDS_BuildCDB(rc, cdb); - (void)cdb.MoveToAncestor(1u); - } -} - -// --------------------------------------------------------------------------- -// DebugServiceI out-of-line definitions (each transport .so is self-contained) -// --------------------------------------------------------------------------- - -DebugServiceI *DebugServiceI::instance = NULL_PTR(DebugServiceI *); - -bool DebugServiceI::GetFullObjectName(const Object &obj, StreamString &fullPath) { - fullPath = ""; - if (WDS_FindPath(ObjectRegistryDatabase::Instance(), &obj, fullPath)) { - return true; - } - const char8 *name = obj.GetName(); - if (name != NULL_PTR(const char8 *)) - fullPath = name; - return true; -} - -// --------------------------------------------------------------------------- -// CLASS_REGISTER -// --------------------------------------------------------------------------- - -CLASS_REGISTER(WebDebugService, "1.0") - // --------------------------------------------------------------------------- // Constructor / Destructor // --------------------------------------------------------------------------- WebDebugService::WebDebugService() - : binderHttp(this, WdsBinder::Http), + : DebugServiceBase(), + EmbeddedServiceMethodBinderI(), + binderHttp(this, WdsBinder::Http), binderStream(this, WdsBinder::Stream), httpService(binderHttp), streamerService(binderStream) { httpPort = 8090u; - isPaused = false; - stepRemaining = 0u; streamerSeq = 0u; streamerT0Ns = 0u; - manualConfigSet = false; logHistoryWriteIdx = 0u; logHistoryFill = 0u; memset(logHistory, 0, sizeof(logHistory)); @@ -155,7 +78,9 @@ WebDebugService::WebDebugService() } WebDebugService::~WebDebugService() { - DebugServiceI::SetInstance(NULL_PTR(DebugServiceI *)); + if (DebugServiceI::GetInstance() == this) { + DebugServiceI::SetInstance(NULL_PTR(DebugServiceI *)); + } httpService.Stop(); streamerService.Stop(); tcpServer.Close(); @@ -168,13 +93,7 @@ WebDebugService::~WebDebugService() { } } sseMutex.FastUnLock(); - mutex.FastLock(); - for (uint32 i = 0u; i < signals.Size(); i++) { - if (signals[i] != NULL_PTR(DebugSignalInfo *)) { - delete signals[i]; - } - } - mutex.FastUnLock(); + // signals owned by DebugServiceBase destructor } // --------------------------------------------------------------------------- @@ -191,15 +110,14 @@ bool WebDebugService::Initialise(StructuredDataI &data) { if (!traceBuffer.Init(4 * 1024 * 1024)) return false; DebugServiceI::SetInstance(this); - PatchRegistry(); - // Initialize Logger early so REPORT_ERROR calls from application startup - // are captured in the ring buffer before the Streamer thread begins. + // Initialise Logger early so REPORT_ERROR calls are captured. (void)Logger::Instance(); - REPORT_ERROR(ErrorManagement::Information, "WebDebugService initialised on port %d", (int)httpPort); + REPORT_ERROR(ErrorManagement::Information, + "WebDebugService initialised on port %d", (int)httpPort); - // Record time origin for relative SSE timestamps (ms since service init). + // Record time origin for relative SSE timestamps. streamerT0Ns = (uint64)((float64)HighResolutionTimer::Counter() * HighResolutionTimer::Period() * 1.0e9); @@ -208,7 +126,7 @@ bool WebDebugService::Initialise(StructuredDataI &data) { httpService.Initialise(threadData); streamerService.Initialise(threadData); - if (!tcpServer.Open()) return false; + if (!tcpServer.Open()) return false; if (!tcpServer.Listen(httpPort)) return false; if (httpService.Start() != ErrorManagement::NoError) return false; @@ -218,899 +136,29 @@ bool WebDebugService::Initialise(StructuredDataI &data) { } // --------------------------------------------------------------------------- -// PatchRegistry (identical logic to DebugService) +// EmbeddedServiceMethodBinderI — never actually called (sub-binders used) // --------------------------------------------------------------------------- -static void WDS_PatchItem(const char8 *originalName, ObjectBuilder *debugBuilder) { - ClassRegistryItem *item = ClassRegistryDatabase::Instance()->Find(originalName); - if (item != NULL_PTR(ClassRegistryItem *)) { - item->SetObjectBuilder(debugBuilder); - } -} - -void WebDebugService::PatchRegistry() { - WDS_PatchItem("MemoryMapInputBroker", - new DebugMemoryMapInputBrokerBuilder()); - WDS_PatchItem("MemoryMapOutputBroker", - new DebugMemoryMapOutputBrokerBuilder()); - WDS_PatchItem("MemoryMapSynchronisedInputBroker", - new DebugMemoryMapSynchronisedInputBrokerBuilder()); - WDS_PatchItem("MemoryMapSynchronisedOutputBroker", - new DebugMemoryMapSynchronisedOutputBrokerBuilder()); - WDS_PatchItem("MemoryMapInterpolatedInputBroker", - new DebugMemoryMapInterpolatedInputBrokerBuilder()); - WDS_PatchItem("MemoryMapMultiBufferInputBroker", - new DebugMemoryMapMultiBufferInputBrokerBuilder()); - WDS_PatchItem("MemoryMapMultiBufferOutputBroker", - new DebugMemoryMapMultiBufferOutputBrokerBuilder()); - WDS_PatchItem("MemoryMapSynchronisedMultiBufferInputBroker", - new DebugMemoryMapSynchronisedMultiBufferInputBrokerBuilder()); - WDS_PatchItem("MemoryMapSynchronisedMultiBufferOutputBroker", - new DebugMemoryMapSynchronisedMultiBufferOutputBrokerBuilder()); - WDS_PatchItem("MemoryMapAsyncOutputBroker", - new DebugMemoryMapAsyncOutputBrokerBuilder()); - WDS_PatchItem("MemoryMapAsyncTriggerOutputBroker", - new DebugMemoryMapAsyncTriggerOutputBrokerBuilder()); -} - -// --------------------------------------------------------------------------- -// DebugServiceI RT-path -// --------------------------------------------------------------------------- - -DebugSignalInfo *WebDebugService::RegisterSignal(void *memoryAddress, - TypeDescriptor type, - const char8 *name, - uint8 numberOfDimensions, - uint32 numberOfElements) { - mutex.FastLock(); - DebugSignalInfo *res = NULL_PTR(DebugSignalInfo *); - uint32 sigIdx = 0xFFFFFFFFu; - for (uint32 i = 0u; i < signals.Size(); i++) { - if (signals[i]->memoryAddress == memoryAddress) { - res = signals[i]; sigIdx = i; break; - } - } - if (res == NULL_PTR(DebugSignalInfo *)) { - sigIdx = signals.Size(); - res = new DebugSignalInfo(); - res->memoryAddress = memoryAddress; - res->type = type; - res->name = name; - res->numberOfDimensions = numberOfDimensions; - res->numberOfElements = numberOfElements; - res->isTracing = false; - res->isForcing = false; - res->internalID = sigIdx; - res->decimationFactor = 1u; - res->decimationCounter = 0u; - res->breakOp = BREAK_OFF; - res->breakThreshold = 0.0; - signals.Push(res); - } - if (sigIdx != 0xFFFFFFFFu) { - bool found = false; - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == name) { found = true; break; } - } - if (!found) { - SignalAlias a; - a.name = name; - a.signalIndex = sigIdx; - aliases.Push(a); - } - } - mutex.FastUnLock(); - return res; -} - -void WebDebugService::ProcessSignal(DebugSignalInfo *signalInfo, - uint32 size, uint64 timestamp) { - if (signalInfo == NULL_PTR(DebugSignalInfo *)) return; - if (signalInfo->isForcing) { - memcpy(signalInfo->memoryAddress, signalInfo->forcedValue, size); - } - if (signalInfo->isTracing) { - Atomic::Add((volatile int32 *)&signalInfo->decimationCounter, 1); - if ((uint32)signalInfo->decimationCounter >= signalInfo->decimationFactor) { - int32 old = Atomic::Exchange((volatile int32 *)&signalInfo->decimationCounter, 0); - if ((uint32)old >= signalInfo->decimationFactor) { - tracePushMutex.FastLock(); - traceBuffer.Push(signalInfo->internalID, timestamp, - (uint8 *)signalInfo->memoryAddress, size); - tracePushMutex.FastUnLock(); - } - } - } -} - -void WebDebugService::RegisterBroker(DebugSignalInfo **signalPointers, - uint32 numSignals, MemoryMapBroker *broker, - volatile bool *anyActiveFlag, - Vec *activeIndices, - Vec *activeSizes, - FastPollingMutexSem *activeMutex, - volatile bool *anyBreakFlag, - Vec *breakIndices, - const char8 *gamName, bool isOutput) { - mutex.FastLock(); - BrokerInfo b; - b.signalPointers = signalPointers; - b.numSignals = numSignals; - b.broker = broker; - b.anyActiveFlag = anyActiveFlag; - b.activeIndices = activeIndices; - b.activeSizes = activeSizes; - b.activeMutex = activeMutex; - b.anyBreakFlag = anyBreakFlag; - b.breakIndices = breakIndices; - b.isOutput = isOutput; - if (gamName != NULL_PTR(const char8 *)) b.gamName = gamName; - brokers.Push(b); - mutex.FastUnLock(); -} - -void WebDebugService::ConsumeStepIfNeeded(const char8 *gamName, - const char8 *threadName) { - if (stepRemaining == 0u) return; - mutex.FastLock(); - if (stepThreadFilter.Size() > 0u && - (threadName == NULL_PTR(const char8 *) || stepThreadFilter != threadName)) { - mutex.FastUnLock(); - return; - } - if (stepRemaining > 0u) { - stepRemaining--; - if (stepRemaining == 0u) { - isPaused = true; - pausedAtGam = (gamName != NULL_PTR(const char8 *)) ? gamName : ""; - } - } - mutex.FastUnLock(); -} - -// --------------------------------------------------------------------------- -// DebugServiceI control-path -// --------------------------------------------------------------------------- - -uint32 WebDebugService::ForceSignal(const char8 *name, const char8 *valueStr) { - mutex.FastLock(); - uint32 count = 0u; - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) { - DebugSignalInfo *s = signals[aliases[i].signalIndex]; - uint32 elemBytes = (uint32)(s->type.numberOfBits) / 8u; - uint32 totalBytes = elemBytes * s->numberOfElements; - if (elemBytes == 0u || totalBytes > (uint32)sizeof(s->forcedValue)) continue; - s->isForcing = true; - AnyType dest(s->type, 0u, s->forcedValue); - AnyType source(CharString, 0u, valueStr); - (void)TypeConvert(dest, source); - count++; - } - } - UpdateBrokersActiveStatus(); - mutex.FastUnLock(); - return count; -} - -uint32 WebDebugService::UnforceSignal(const char8 *name) { - mutex.FastLock(); - uint32 count = 0u; - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) { - signals[aliases[i].signalIndex]->isForcing = false; - count++; - } - } - UpdateBrokersActiveStatus(); - mutex.FastUnLock(); - return count; -} - -uint32 WebDebugService::TraceSignal(const char8 *name, bool enable, uint32 decimation) { - mutex.FastLock(); - uint32 count = 0u; - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) { - DebugSignalInfo *s = signals[aliases[i].signalIndex]; - s->isTracing = enable; - s->decimationFactor = decimation; - s->decimationCounter = 0u; - count++; - } - } - UpdateBrokersActiveStatus(); - mutex.FastUnLock(); - return count; -} - -uint32 WebDebugService::SetBreak(const char8 *name, uint8 op, float64 threshold) { - mutex.FastLock(); - uint32 count = 0u; - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) { - DebugSignalInfo *s = signals[aliases[i].signalIndex]; - s->breakThreshold = threshold; - s->breakOp = op; - count++; - } - } - if (count > 0u) UpdateBrokersBreakStatus(); - mutex.FastUnLock(); - return count; -} - -uint32 WebDebugService::ClearBreak(const char8 *name) { - mutex.FastLock(); - uint32 count = 0u; - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) { - signals[aliases[i].signalIndex]->breakOp = BREAK_OFF; - count++; - } - } - if (count > 0u) UpdateBrokersBreakStatus(); - mutex.FastUnLock(); - return count; -} - -bool WebDebugService::IsInstrumented(const char8 *fullPath, - bool &traceable, bool &forcable) { - mutex.FastLock(); - bool found = false; - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == fullPath || - WDS_SuffixMatch(aliases[i].name.Buffer(), fullPath)) { - found = true; break; - } - } - mutex.FastUnLock(); - traceable = found; - forcable = found; - return found; -} - -uint32 WebDebugService::RegisterMonitorSignal(const char8 *path, uint32 periodMs) { - mutex.FastLock(); - for (uint32 j = 0u; j < monitoredSignals.Size(); j++) { - if (monitoredSignals[j].path == path) { - monitoredSignals[j].periodMs = periodMs; - mutex.FastUnLock(); - return 1u; - } - } - - StreamString fullPath = path; - fullPath.Seek(0u); - char8 term; - Vec parts; - StreamString token; - while (fullPath.GetToken(token, ".", term)) { parts.Push(token); token = ""; } - - uint32 count = 0u; - if (parts.Size() >= 2u) { - StreamString signalName = parts[parts.Size() - 1u]; - StreamString dsPath; - for (uint32 i = 0u; i < parts.Size() - 1u; i++) { - dsPath += parts[i]; - if (i < parts.Size() - 2u) dsPath += "."; - } - ReferenceT ds = ObjectRegistryDatabase::Instance()->Find(dsPath.Buffer()); - if (ds.IsValid()) { - uint32 idx = 0u; - if (ds->GetSignalIndex(idx, signalName.Buffer())) { - MonitoredSignal m; - m.dataSource = ds; - m.signalIdx = idx; - m.path = path; - m.periodMs = periodMs; - m.lastPollTime = 0u; - m.size = 0u; - (void)ds->GetSignalByteSize(idx, m.size); - if (m.size == 0u) m.size = 4u; - m.internalID = 0x80000000u | monitoredSignals.Size(); - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == path || WDS_SuffixMatch(aliases[i].name.Buffer(), path)) { - m.internalID = signals[aliases[i].signalIndex]->internalID; - break; - } - } - monitoredSignals.Push(m); - count = 1u; - } - } - } - mutex.FastUnLock(); - return count; -} - -uint32 WebDebugService::UnmonitorSignal(const char8 *path) { - mutex.FastLock(); - uint32 count = 0u; - for (uint32 i = 0u; i < monitoredSignals.Size(); i++) { - if (monitoredSignals[i].path == path || - WDS_SuffixMatch(monitoredSignals[i].path.Buffer(), path)) { - (void)monitoredSignals.Remove(i); i--; count++; - } - } - mutex.FastUnLock(); - return count; -} - -// --------------------------------------------------------------------------- -// EmbeddedServiceMethodBinderI — framework dispatches here; we never reach it -// --------------------------------------------------------------------------- ErrorManagement::ErrorType WebDebugService::Execute(ExecutionInfo &info) { (void)info; return ErrorManagement::FatalError; } // --------------------------------------------------------------------------- -// Config helpers +// SERVICE_INFO hook // --------------------------------------------------------------------------- -void WebDebugService::SetFullConfig(ConfigurationDatabase &config) { - config.MoveToRoot(); - config.Copy(fullConfig); - manualConfigSet = true; -} - -void WebDebugService::RebuildConfigFromRegistry() { - fullConfig = ConfigurationDatabase(); - WDS_BuildCDB(ObjectRegistryDatabase::Instance(), fullConfig); - fullConfig.MoveToRoot(); -} - -void WebDebugService::JsonifyDatabase(ConfigurationDatabase &db, - StreamString &json) { - json += "{"; - uint32 n = db.GetNumberOfChildren(); - bool first = true; - for (uint32 i = 0u; i < n; i++) { - const char8 *key = db.GetChildName(i); - if (key == NULL_PTR(const char8 *)) continue; - if (!first) json += ", "; - first = false; - json += "\""; - WDS_EscapeJson(key, json); - json += "\": "; - if (db.MoveRelative(key)) { - JsonifyDatabase(db, json); - (void)db.MoveToAncestor(1u); - } else { - AnyType at = db.GetType(key); - if (at.GetDataPointer() != NULL_PTR(void *)) { - char8 buf[512] = { '\0' }; - AnyType dst(CharString, 0u, buf); - dst.SetNumberOfElements(0u, 512u); - if (TypeConvert(dst, at)) { - json += "\""; - WDS_EscapeJson(buf, json); - json += "\""; - } else { - json += "null"; - } - } else { - json += "null"; - } - } - } - json += "}"; -} - -void WebDebugService::EnrichWithConfig(const char8 *path, StreamString &json) { - if (path == NULL_PTR(const char8 *)) return; - if (!manualConfigSet) RebuildConfigFromRegistry(); - fullConfig.MoveToRoot(); - const char8 *current = path; - bool ok = true; - while (ok) { - const char8 *dot = StringHelper::SearchString(current, "."); - StreamString part; - if (dot != NULL_PTR(const char8 *)) { - uint32 len = (uint32)(dot - current); - (void)part.Write(current, len); - current = dot + 1; - } else { - part = current; ok = false; - } - bool found = false; - if (fullConfig.MoveRelative(part.Buffer())) { found = true; } - if (!found) { - StreamString pref; pref.Printf("+%s", part.Buffer()); - if (fullConfig.MoveRelative(pref.Buffer())) found = true; - } - if (!found) return; - } - uint32 nc = fullConfig.GetNumberOfChildren(); - for (uint32 i = 0u; i < nc; i++) { - const char8 *ek = fullConfig.GetChildName(i); - if (fullConfig.MoveRelative(ek)) { - fullConfig.MoveToAncestor(1u); continue; - } - AnyType at = fullConfig.GetType(ek); - if (at.GetDataPointer() != NULL_PTR(void *)) { - char8 buf[512] = { '\0' }; - AnyType dst(CharString, 0u, buf); - dst.SetNumberOfElements(0u, 512u); - if (TypeConvert(dst, at)) { - json += ", \""; - WDS_EscapeJson(ek, json); - json += "\": \""; - WDS_EscapeJson(buf, json); - json += "\""; - } - } - } -} - -// --------------------------------------------------------------------------- -// Broker index maintenance -// --------------------------------------------------------------------------- - -void WebDebugService::UpdateBrokersActiveStatus() { - for (uint32 i = 0u; i < brokers.Size(); i++) { - if (brokers[i].signalPointers == NULL_PTR(DebugSignalInfo **)) continue; - uint32 count = 0u; - Vec tempInd, tempSizes; - for (uint32 j = 0u; j < brokers[i].numSignals; j++) { - DebugSignalInfo *s = brokers[i].signalPointers[j]; - if (s != NULL_PTR(DebugSignalInfo *) && (s->isTracing || s->isForcing)) { - tempInd.Push(j); - tempSizes.Push((brokers[i].broker != NULL_PTR(MemoryMapBroker *)) - ? brokers[i].broker->GetCopyByteSize(j) : 4u); - count++; - } - } - if (brokers[i].activeMutex) brokers[i].activeMutex->FastLock(); - if (brokers[i].activeIndices) brokers[i].activeIndices->Swap(tempInd); - if (brokers[i].activeSizes) brokers[i].activeSizes->Swap(tempSizes); - if (brokers[i].anyActiveFlag) *(brokers[i].anyActiveFlag) = (count > 0u); - if (brokers[i].activeMutex) brokers[i].activeMutex->FastUnLock(); - } -} - -void WebDebugService::UpdateBrokersBreakStatus() { - for (uint32 i = 0u; i < brokers.Size(); i++) { - if (brokers[i].signalPointers == NULL_PTR(DebugSignalInfo **)) continue; - Vec tempBreak; - uint32 count = 0u; - for (uint32 j = 0u; j < brokers[i].numSignals; j++) { - DebugSignalInfo *s = brokers[i].signalPointers[j]; - if (s != NULL_PTR(DebugSignalInfo *) && s->breakOp != BREAK_OFF) { - tempBreak.Push(j); count++; - } - } - if (brokers[i].activeMutex) brokers[i].activeMutex->FastLock(); - if (brokers[i].breakIndices) brokers[i].breakIndices->Swap(tempBreak); - if (brokers[i].anyBreakFlag) *(brokers[i].anyBreakFlag) = (count > 0u); - if (brokers[i].activeMutex) brokers[i].activeMutex->FastUnLock(); - } -} - -// --------------------------------------------------------------------------- -// Step control -// --------------------------------------------------------------------------- - -void WebDebugService::Step(uint32 n, const char8 *threadName) { - mutex.FastLock(); - stepRemaining = n; - isPaused = false; - stepThreadFilter = (threadName != NULL_PTR(const char8 *)) ? threadName : ""; - mutex.FastUnLock(); -} - -// --------------------------------------------------------------------------- -// Command handler -// --------------------------------------------------------------------------- - -void WebDebugService::GetStepStatus(StreamString &out) { - mutex.FastLock(); - bool paused = isPaused; - uint32 rem = stepRemaining; - StreamString gam = pausedAtGam; - StreamString thr = stepThreadFilter; - mutex.FastUnLock(); - out.Printf("{\"Paused\":%s,\"PausedAtGam\":\"%s\"," - "\"StepRemaining\":%u,\"StepThread\":\"%s\"}\nOK STEP_STATUS\n", - paused ? "true" : "false", gam.Buffer(), rem, thr.Buffer()); -} - -void WebDebugService::GetSignalValue(const char8 *name, StreamString &out) { - mutex.FastLock(); - DebugSignalInfo *sig = NULL_PTR(DebugSignalInfo *); - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) { - sig = signals[aliases[i].signalIndex]; break; - } - } - if (sig == NULL_PTR(DebugSignalInfo *)) { - mutex.FastUnLock(); - out.Printf("{\"Error\":\"Signal not found: %s\"}\nOK VALUE\n", name); - return; - } - TypeDescriptor td = sig->type; - uint32 nElem = sig->numberOfElements; - uint32 bySz = (td.numberOfBits > 0u) ? (td.numberOfBits / 8u) : 1u; - bool trunc = (nElem > GET_VALUE_MAX_ELEMENTS); - if (trunc) nElem = GET_VALUE_MAX_ELEMENTS; - uint32 total = bySz * nElem; - if (total > 1024u) { total = 1024u; nElem = total / bySz; } - uint8 lb[1024]; memset(lb, 0, sizeof(lb)); - if (sig->memoryAddress != NULL_PTR(void *)) memcpy(lb, sig->memoryAddress, total); - mutex.FastUnLock(); - - StreamString valueStr; - if (nElem > 1u) { - for (uint32 i = 0u; i < nElem; i++) { - char8 eb[128] = { '\0' }; - AnyType se(td, 0u, (void *)(lb + i * bySz)); - AnyType ds(CharString, 0u, eb); - ds.SetNumberOfElements(0u, 128u); - (void)TypeConvert(ds, se); - if (i > 0u) valueStr += ", "; - valueStr += eb; - } - } else { - char8 eb[256] = { '\0' }; - AnyType se(td, 0u, (void *)lb); - AnyType ds(CharString, 0u, eb); - ds.SetNumberOfElements(0u, 256u); - (void)TypeConvert(ds, se); - valueStr = eb; - } - out += "{\"Name\":\""; - out += name; - out += "\",\"Value\":\""; - const char8 *vp = valueStr.Buffer(); - while (vp != NULL_PTR(const char8 *) && *vp != '\0') { - if (*vp == '"') out += "\\\""; - else if (*vp == '\\') out += "\\\\"; - else { char8 tmp[2] = { *vp, '\0' }; out += tmp; } - vp++; - } - out.Printf("\",\"Elements\":%u,\"Truncated\":%s}\nOK VALUE\n", - nElem, trunc ? "true" : "false"); -} - -void WebDebugService::ServeConfig(StreamString &out) { - if (!manualConfigSet) RebuildConfigFromRegistry(); - fullConfig.MoveToRoot(); - JsonifyDatabase(fullConfig, out); - out += "\nOK CONFIG\n"; -} - -void WebDebugService::InfoNode(const char8 *path, StreamString &out) { - Reference ref = ObjectRegistryDatabase::Instance()->Find(path); - out += "{"; - if (ref.IsValid()) { - out += "\"Name\":\""; WDS_EscapeJson(ref->GetName(), out); - out += "\",\"Class\":\""; WDS_EscapeJson(ref->GetClassProperties()->GetName(), out); - out += "\""; - ConfigurationDatabase db; - if (ref->ExportData(db)) { - out += ",\"Config\":{"; - db.MoveToRoot(); - uint32 nc = db.GetNumberOfChildren(); - for (uint32 i = 0u; i < nc; i++) { - const char8 *cn = db.GetChildName(i); - AnyType at = db.GetType(cn); - char8 buf[1024]; AnyType st(CharString, 0u, buf); st.SetNumberOfElements(0, 1024); - if (TypeConvert(st, at)) { - out += "\""; WDS_EscapeJson(cn, out); out += "\":\""; - WDS_EscapeJson(buf, out); out += "\""; - if (i < nc - 1u) out += ","; - } - } - out += "}"; - } - EnrichWithConfig(path, out); - } else { - StreamString enrichAlias; - mutex.FastLock(); - bool found = false; - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (aliases[i].name == path || WDS_SuffixMatch(aliases[i].name.Buffer(), path)) { - DebugSignalInfo *s = signals[aliases[i].signalIndex]; - const char8 *tn = TypeDescriptor::GetTypeNameFromTypeDescriptor(s->type); - out.Printf("\"Name\":\"%s\",\"Class\":\"Signal\",\"Type\":\"%s\",\"ID\":%u", - s->name.Buffer(), tn ? tn : "Unknown", s->internalID); - enrichAlias = aliases[i].name; - found = true; break; - } - } - mutex.FastUnLock(); - if (found) EnrichWithConfig(enrichAlias.Buffer(), out); - else out += "\"Error\":\"Object not found\""; - } - out += "}\nOK INFO\n"; -} - -void WebDebugService::ListNodes(const char8 *path, StreamString &out) { - Reference ref = - (path == NULL_PTR(const char8 *) || StringHelper::Length(path) == 0 || - StringHelper::Compare(path, "/") == 0) - ? ObjectRegistryDatabase::Instance() - : ObjectRegistryDatabase::Instance()->Find(path); - out.Printf("Nodes under %s:\n", path ? path : "/"); - if (ref.IsValid()) { - ReferenceContainer *rc = dynamic_cast(ref.operator->()); - if (rc != NULL_PTR(ReferenceContainer *)) { - uint32 n = rc->Size(); - for (uint32 i = 0u; i < n; i++) { - Reference c = rc->Get(i); - if (c.IsValid()) { - out.Printf(" %s [%s]\n", c->GetName(), - c->GetClassProperties()->GetName()); - } - } - } - } else { - out += " (not found)\n"; - } - out += "OK LS\n"; -} - -uint32 WebDebugService::ExportTree(ReferenceContainer *container, - StreamString &json, const char8 *pathPrefix) { - if (container == NULL_PTR(ReferenceContainer *)) return 0u; - uint32 size = container->Size(); - uint32 valid = 0u; - for (uint32 i = 0u; i < size; i++) { - Reference child = container->Get(i); - if (!child.IsValid()) continue; - if (valid > 0u) json += ",\n"; - const char8 *cname = child->GetName(); - if (cname == NULL_PTR(const char8 *)) cname = "unnamed"; - StreamString cp; - if (pathPrefix != NULL_PTR(const char8 *)) cp.Printf("%s.%s", pathPrefix, cname); - else cp = cname; - StreamString nj; - nj += "{\"Name\":\""; WDS_EscapeJson(cname, nj); - nj += "\",\"Class\":\""; WDS_EscapeJson(child->GetClassProperties()->GetName(), nj); - nj += "\""; - ReferenceContainer *inner = dynamic_cast(child.operator->()); - DataSourceI *ds = dynamic_cast(child.operator->()); - GAM *gam = dynamic_cast(child.operator->()); - if (inner != NULL_PTR(ReferenceContainer *) || ds != NULL_PTR(DataSourceI *) || gam != NULL_PTR(GAM *)) { - nj += ",\"Children\":[\n"; - uint32 sc = 0u; - if (inner != NULL_PTR(ReferenceContainer *)) sc += ExportTree(inner, nj, cp.Buffer()); - if (ds != NULL_PTR(DataSourceI *)) { - uint32 ns = ds->GetNumberOfSignals(); - for (uint32 j = 0u; j < ns; j++) { - if (sc > 0u) { nj += ",\n"; } sc++; - StreamString sn; (void)ds->GetSignalName(j, sn); - const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(ds->GetSignalType(j)); - uint8 d = 0u; (void)ds->GetSignalNumberOfDimensions(j, d); - uint32 el = 0u; (void)ds->GetSignalNumberOfElements(j, el); - StreamString sfp; sfp.Printf("%s.%s", cp.Buffer(), sn.Buffer()); - bool tr = false, fo = false; (void)IsInstrumented(sfp.Buffer(), tr, fo); - nj += "{\"Name\":\""; WDS_EscapeJson(sn.Buffer(), nj); - nj += "\",\"Class\":\"Signal\",\"Type\":\""; WDS_EscapeJson(st ? st : "Unknown", nj); - nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u,\"IsTraceable\":%s,\"IsForcable\":%s}", - d, el, tr ? "true" : "false", fo ? "true" : "false"); - } - } - if (gam != NULL_PTR(GAM *)) { - uint32 nIn = gam->GetNumberOfInputSignals(); - for (uint32 j = 0u; j < nIn; j++) { - if (sc > 0u) { nj += ",\n"; } sc++; - StreamString sn; (void)gam->GetSignalName(InputSignals, j, sn); - const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(gam->GetSignalType(InputSignals, j)); - uint32 d = 0u; (void)gam->GetSignalNumberOfDimensions(InputSignals, j, d); - uint32 el = 0u; (void)gam->GetSignalNumberOfElements(InputSignals, j, el); - StreamString sfp; sfp.Printf("%s.In.%s", cp.Buffer(), sn.Buffer()); - bool tr = false, fo = false; (void)IsInstrumented(sfp.Buffer(), tr, fo); - nj += "{\"Name\":\"In."; WDS_EscapeJson(sn.Buffer(), nj); - nj += "\",\"Class\":\"InputSignal\",\"Type\":\""; WDS_EscapeJson(st ? st : "Unknown", nj); - nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u,\"IsTraceable\":%s,\"IsForcable\":%s}", - d, el, tr ? "true" : "false", fo ? "true" : "false"); - } - uint32 nOut = gam->GetNumberOfOutputSignals(); - for (uint32 j = 0u; j < nOut; j++) { - if (sc > 0u) { nj += ",\n"; } sc++; - StreamString sn; (void)gam->GetSignalName(OutputSignals, j, sn); - const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(gam->GetSignalType(OutputSignals, j)); - uint32 d = 0u; (void)gam->GetSignalNumberOfDimensions(OutputSignals, j, d); - uint32 el = 0u; (void)gam->GetSignalNumberOfElements(OutputSignals, j, el); - StreamString sfp; sfp.Printf("%s.Out.%s", cp.Buffer(), sn.Buffer()); - bool tr = false, fo = false; (void)IsInstrumented(sfp.Buffer(), tr, fo); - nj += "{\"Name\":\"Out."; WDS_EscapeJson(sn.Buffer(), nj); - nj += "\",\"Class\":\"OutputSignal\",\"Type\":\""; WDS_EscapeJson(st ? st : "Unknown", nj); - nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u,\"IsTraceable\":%s,\"IsForcable\":%s}", - d, el, tr ? "true" : "false", fo ? "true" : "false"); - } - } - nj += "\n]"; - } - nj += "}"; - json += nj; - valid++; - } - return valid; -} - -void WebDebugService::Discover(StreamString &out) { - out += "{\"Signals\":[\n"; - mutex.FastLock(); - uint32 total = 0u; - for (uint32 i = 0u; i < aliases.Size(); i++) { - if (total > 0u) out += ",\n"; - DebugSignalInfo *sig = signals[aliases[i].signalIndex]; - const char8 *tn = TypeDescriptor::GetTypeNameFromTypeDescriptor(sig->type); - out.Printf(" {\"name\":\"%s\",\"id\":%u,\"type\":\"%s\"," - "\"dimensions\":%u,\"elements\":%u", - aliases[i].name.Buffer(), sig->internalID, - tn ? tn : "Unknown", sig->numberOfDimensions, sig->numberOfElements); - EnrichWithConfig(aliases[i].name.Buffer(), out); - out += "}"; - total++; - } - for (uint32 i = 0u; i < monitoredSignals.Size(); i++) { - bool found = false; - for (uint32 j = 0u; j < aliases.Size(); j++) { - if (aliases[j].name == monitoredSignals[i].path) { found = true; break; } - } - if (!found) { - if (total > 0u) out += ",\n"; - const char8 *tn = TypeDescriptor::GetTypeNameFromTypeDescriptor( - monitoredSignals[i].dataSource->GetSignalType(monitoredSignals[i].signalIdx)); - uint8 dims = 0u; uint32 elems = 1u; - (void)monitoredSignals[i].dataSource->GetSignalNumberOfDimensions(monitoredSignals[i].signalIdx, dims); - (void)monitoredSignals[i].dataSource->GetSignalNumberOfElements(monitoredSignals[i].signalIdx, elems); - out.Printf(" {\"name\":\"%s\",\"id\":%u,\"type\":\"%s\"," - "\"dimensions\":%u,\"elements\":%u", - monitoredSignals[i].path.Buffer(), monitoredSignals[i].internalID, - tn ? tn : "Unknown", dims, elems); - EnrichWithConfig(monitoredSignals[i].path.Buffer(), out); - out += "}"; - total++; - } - } - mutex.FastUnLock(); - out += "\n]}\nOK DISCOVER\n"; -} - -void WebDebugService::HandleCommand(const StreamString &cmdIn, StreamString &out) { - StreamString cmd = cmdIn; - StreamString token; - cmd.Seek(0u); - char8 term; - const char8 *delims = " \r\n"; - if (!cmd.GetToken(token, delims, term)) return; - - if (token == "FORCE") { - StreamString name, val; - if (cmd.GetToken(name, delims, term) && cmd.GetToken(val, delims, term)) { - uint32 c = ForceSignal(name.Buffer(), val.Buffer()); - out.Printf("OK FORCE %u\n", c); - } - } else if (token == "UNFORCE") { - StreamString name; - if (cmd.GetToken(name, delims, term)) { - uint32 c = UnforceSignal(name.Buffer()); - out.Printf("OK UNFORCE %u\n", c); - } - } else if (token == "TRACE") { - StreamString name, state, decim; - if (cmd.GetToken(name, delims, term) && cmd.GetToken(state, delims, term)) { - bool en = (state == "1"); - uint32 d = 1u; - if (cmd.GetToken(decim, delims, term)) { - AnyType dv(UnsignedInteger32Bit, 0u, &d); - AnyType ds(CharString, 0u, decim.Buffer()); - (void)TypeConvert(dv, ds); - } - uint32 c = TraceSignal(name.Buffer(), en, d); - out.Printf("OK TRACE %u\n", c); - } - } else if (token == "BREAK") { - StreamString name, opStr; - if (cmd.GetToken(name, delims, term) && cmd.GetToken(opStr, delims, term)) { - uint32 c = 0u; - if (opStr == "OFF") { - c = ClearBreak(name.Buffer()); - } else { - StreamString thrStr; - if (cmd.GetToken(thrStr, delims, term)) { - uint8 op = BREAK_OFF; - if (opStr == ">") op = BREAK_GT; - else if (opStr == "<") op = BREAK_LT; - else if (opStr == "==") op = BREAK_EQ; - else if (opStr == ">=") op = BREAK_GEQ; - else if (opStr == "<=") op = BREAK_LEQ; - else if (opStr == "!=") op = BREAK_NEQ; - if (op != BREAK_OFF) { - float64 thr = 0.0; - AnyType tv(Float64Bit, 0u, &thr); - AnyType ts(CharString, 0u, thrStr.Buffer()); - (void)TypeConvert(tv, ts); - c = SetBreak(name.Buffer(), op, thr); - } - } - } - out.Printf("OK BREAK %u\n", c); - } - } else if (token == "PAUSE") { - SetPaused(true); - out += "OK\n"; - // broadcast status event - StreamString ev; - ev.Printf("{\"type\":\"status\",\"paused\":true,\"gam\":\"%s\",\"remaining\":%u}", - pausedAtGam.Buffer(), (uint32)stepRemaining); - BroadcastSse("message", ev); - } else if (token == "RESUME") { - SetPaused(false); - out += "OK\n"; - StreamString ev; - ev += "{\"type\":\"status\",\"paused\":false,\"gam\":\"\",\"remaining\":0}"; - BroadcastSse("message", ev); - } else if (token == "STEP") { - StreamString nStr; - uint32 n = 1u; - if (cmd.GetToken(nStr, delims, term)) { - AnyType nv(UnsignedInteger32Bit, 0u, &n); - AnyType ns(CharString, 0u, nStr.Buffer()); - (void)TypeConvert(nv, ns); - } - StreamString thr; - const char8 *ta = NULL_PTR(const char8 *); - if (cmd.GetToken(thr, delims, term) && thr.Size() > 0u) ta = thr.Buffer(); - Step(n, ta); - out.Printf("OK STEP %u\n", n); - } else if (token == "STEP_STATUS") { - GetStepStatus(out); - } else if (token == "VALUE") { - StreamString sn; - if (cmd.GetToken(sn, delims, term)) GetSignalValue(sn.Buffer(), out); - else out += "{\"Error\":\"Missing signal name\"}\nOK VALUE\n"; - } else if (token == "DISCOVER") { - Discover(out); - } else if (token == "TREE") { - out += "{\"Name\":\"Root\",\"Class\":\"ObjectRegistryDatabase\"," - "\"Children\":[\n"; - (void)ExportTree(ObjectRegistryDatabase::Instance(), out, NULL_PTR(const char8 *)); - out += "\n]}\nOK TREE\n"; - } else if (token == "INFO") { - StreamString path; - if (cmd.GetToken(path, delims, term)) InfoNode(path.Buffer(), out); - } else if (token == "LS") { - StreamString path; - if (cmd.GetToken(path, delims, term)) ListNodes(path.Buffer(), out); - else ListNodes(NULL_PTR(const char8 *), out); - } else if (token == "CONFIG") { - ServeConfig(out); - } else if (token == "MONITOR") { - StreamString sub, name, period; - if (cmd.GetToken(sub, delims, term) && sub == "SIGNAL" && - cmd.GetToken(name, delims, term) && cmd.GetToken(period, delims, term)) { - uint32 p = 100u; - AnyType pv(UnsignedInteger32Bit, 0u, &p); - AnyType ps(CharString, 0u, period.Buffer()); - (void)TypeConvert(pv, ps); - uint32 c = RegisterMonitorSignal(name.Buffer(), p); - out.Printf("OK MONITOR %u\n", c); - } - } else if (token == "UNMONITOR") { - StreamString sub, name; - if (cmd.GetToken(sub, delims, term) && sub == "SIGNAL" && - cmd.GetToken(name, delims, term)) { - uint32 c = UnmonitorSignal(name.Buffer()); - out.Printf("OK UNMONITOR %u\n", c); - } - } else if (token == "SERVICE_INFO") { - out.Printf("OK SERVICE_INFO HTTP:%u STATE:%s\n", - (uint32)httpPort, isPaused ? "PAUSED" : "RUNNING"); - } - // MSG command handled separately — needs access to ORD; not yet supported here +void WebDebugService::GetServiceInfo(StreamString &out) { + out.Printf("OK SERVICE_INFO HTTP:%u STATE:%s\n", + (uint32)httpPort, isPaused ? "PAUSED" : "RUNNING"); } // --------------------------------------------------------------------------- // SSE broadcast // --------------------------------------------------------------------------- -void WebDebugService::BroadcastSse(const char8 *eventType, const StreamString &data) { +void WebDebugService::BroadcastSse(const char8 *eventType, + const StreamString &data) { StreamString pkt; pkt.Printf("event: %s\ndata: ", eventType); pkt += data; @@ -1143,7 +191,6 @@ void WebDebugService::RemoveDeadSseClients() { } void WebDebugService::UpgradeToSse(BasicTCPSocket *client) { - // Send SSE upgrade headers const char8 *hdr = "HTTP/1.1 200 OK\r\n" "Content-Type: text/event-stream\r\n" @@ -1154,15 +201,16 @@ void WebDebugService::UpgradeToSse(BasicTCPSocket *client) { uint32 sz = StringHelper::Length(hdr); (void)client->Write(hdr, sz); - // Send an initial status event so the browser knows the connection is alive + // Send an initial status event mutex.FastLock(); bool paused = isPaused; StreamString gam = pausedAtGam; uint32 rem = stepRemaining; mutex.FastUnLock(); StreamString initEv; - initEv.Printf("{\"type\":\"status\",\"paused\":%s,\"gam\":\"%s\",\"remaining\":%u}", - paused ? "true" : "false", gam.Buffer(), rem); + initEv.Printf( + "{\"type\":\"status\",\"paused\":%s,\"gam\":\"%s\",\"remaining\":%u}", + paused ? "true" : "false", gam.Buffer(), rem); StreamString initPkt; initPkt += "event: message\ndata: "; initPkt += initEv; @@ -1202,11 +250,9 @@ void WebDebugService::UpgradeToSse(BasicTCPSocket *client) { } sseMutex.FastUnLock(); if (!registered) { - // No free slot: close immediately client->Close(); delete client; } - // Do NOT close or delete client here — Streamer owns it now } // --------------------------------------------------------------------------- @@ -1216,7 +262,6 @@ void WebDebugService::UpgradeToSse(BasicTCPSocket *client) { bool WebDebugService::ParseRequest(BasicTCPSocket *client, StreamString &method, StreamString &path, StreamString &body) { - // Read until we see the end of HTTP headers (\r\n\r\n) static const uint32 MAX_HEADER = 8192u; char8 buf[MAX_HEADER + 1]; uint32 total = 0u; @@ -1228,9 +273,9 @@ bool WebDebugService::ParseRequest(BasicTCPSocket *client, if (!client->Read(buf + total, sz) || sz == 0u) break; total += sz; buf[total] = '\0'; - // scan for \r\n\r\n for (uint32 i = 0u; i + 3u < total; i++) { - if (buf[i] == '\r' && buf[i+1] == '\n' && buf[i+2] == '\r' && buf[i+3] == '\n') { + if (buf[i] == '\r' && buf[i+1] == '\n' && + buf[i+2] == '\r' && buf[i+3] == '\n') { headerEnd = i + 4u; foundEnd = true; break; @@ -1239,9 +284,8 @@ bool WebDebugService::ParseRequest(BasicTCPSocket *client, } if (!foundEnd) return false; - // Parse request line const char8 *line = buf; - const char8 *sp1 = StringHelper::SearchChar(line, ' '); + const char8 *sp1 = StringHelper::SearchChar(line, ' '); if (sp1 == NULL_PTR(const char8 *)) return false; method = ""; { uint32 mLen = (uint32)(sp1 - line); (void)method.Write(line, mLen); } @@ -1250,7 +294,6 @@ bool WebDebugService::ParseRequest(BasicTCPSocket *client, path = ""; { uint32 pLen = (uint32)(sp2 - sp1 - 1); (void)path.Write(sp1 + 1, pLen); } - // Extract Content-Length for POST body uint32 contentLength = 0u; const char8 *cl = StringHelper::SearchString(buf, "Content-Length:"); if (cl != NULL_PTR(const char8 *)) { @@ -1261,13 +304,10 @@ bool WebDebugService::ParseRequest(BasicTCPSocket *client, (void)TypeConvert(cv, cs); } - // Read body body = ""; if (contentLength > 0u) { uint32 alreadyRead = (total > headerEnd) ? (total - headerEnd) : 0u; - if (alreadyRead > 0u) { - (void)body.Write(buf + headerEnd, alreadyRead); - } + if (alreadyRead > 0u) (void)body.Write(buf + headerEnd, alreadyRead); static const uint32 MAX_BODY = 16384u; if (contentLength > alreadyRead && contentLength <= MAX_BODY) { char8 bodyBuf[MAX_BODY]; @@ -1306,15 +346,13 @@ void WebDebugService::HandleRequest(BasicTCPSocket *client, StreamString html = WEB_UI_HTML; SendHttp(client, 200u, "text/html; charset=utf-8", html); } else if (method == "GET" && path == "/api/events") { - // SSE upgrade — hands off socket ownership; do not close after return UpgradeToSse(client); - return; // caller must not close socket + return; // socket ownership transferred } else if (method == "POST" && path == "/api/command") { StreamString out; HandleCommand(body, out); SendHttp(client, 200u, "text/plain; charset=utf-8", out); } else if (method == "OPTIONS") { - // CORS preflight const char8 *hdr = "HTTP/1.1 204 No Content\r\n" "Access-Control-Allow-Origin: *\r\n" @@ -1350,13 +388,8 @@ ErrorManagement::ErrorType WebDebugService::HttpServer(ExecutionInfo &info) { StreamString method, path, body; if (ParseRequest(client, method, path, body)) { - // Check if SSE upgrade — HandleRequest takes ownership if SSE - bool isSse = (method == "GET" && path == "/api/events"); HandleRequest(client, method, path, body); - if (isSse) { - // Socket ownership transferred to sseClients — don't touch it - } - // For non-SSE, HandleRequest already closed/deleted the client + // For SSE, HandleRequest transferred ownership — don't touch client here } else { client->Close(); delete client; @@ -1365,36 +398,9 @@ ErrorManagement::ErrorType WebDebugService::HttpServer(ExecutionInfo &info) { } // --------------------------------------------------------------------------- -// Streamer thread — reads ring buffer, broadcasts SSE events, polls monitors +// Streamer thread — drains log, polls monitors, drains trace buffer, heartbeat // --------------------------------------------------------------------------- -// Helper: convert raw bytes to float64 for SSE JSON -static float64 WDS_ToFloat64(const uint8 *data, TypeDescriptor td) { - float64 v = 0.0; - if (td == Float32Bit) { - float32 f; memcpy(&f, data, 4u); v = (float64)f; - } else if (td == Float64Bit) { - memcpy(&v, data, 8u); - } else if (td == SignedInteger8Bit) { - int8 x; memcpy(&x, data, 1u); v = (float64)x; - } else if (td == UnsignedInteger8Bit) { - uint8 x; memcpy(&x, data, 1u); v = (float64)x; - } else if (td == SignedInteger16Bit) { - int16 x; memcpy(&x, data, 2u); v = (float64)x; - } else if (td == UnsignedInteger16Bit) { - uint16 x; memcpy(&x, data, 2u); v = (float64)x; - } else if (td == SignedInteger32Bit) { - int32 x; memcpy(&x, data, 4u); v = (float64)x; - } else if (td == UnsignedInteger32Bit) { - uint32 x; memcpy(&x, data, 4u); v = (float64)x; - } else if (td == SignedInteger64Bit) { - int64 x; memcpy(&x, data, 8u); v = (float64)x; - } else if (td == UnsignedInteger64Bit) { - uint64 x; memcpy(&x, data, 8u); v = (float64)(int64)x; - } - return v; -} - ErrorManagement::ErrorType WebDebugService::Streamer(ExecutionInfo &info) { if (info.GetStage() == ExecutionInfo::TerminationStage) return ErrorManagement::NoError; @@ -1403,7 +409,6 @@ ErrorManagement::ErrorType WebDebugService::Streamer(ExecutionInfo &info) { uint64 nowNs = (uint64)((float64)HighResolutionTimer::Counter() * HighResolutionTimer::Period() * 1.0e9); - uint64 nowMs = (nowNs >= streamerT0Ns) ? (nowNs - streamerT0Ns) / 1000000u : 0u; // ----------------------------------------------------------------- @@ -1423,17 +428,19 @@ ErrorManagement::ErrorType WebDebugService::Streamer(ExecutionInfo &info) { ev.Printf("{\"type\":\"log\",\"level\":\"%s\",\"msg\":\"%s\"}", levelStr.Buffer(), msgEsc.Buffer()); Logger::Instance()->ReturnPage(logPage); - // Store in history for late-connecting clients + logHistoryMutex.FastLock(); { uint32 evSz = ev.Size(); if (evSz >= LOG_ENTRY_MAX_SIZE) evSz = LOG_ENTRY_MAX_SIZE - 1u; - (void)StringHelper::CopyN(logHistory[logHistoryWriteIdx], ev.Buffer(), evSz); + (void)StringHelper::CopyN(logHistory[logHistoryWriteIdx], + ev.Buffer(), evSz); logHistory[logHistoryWriteIdx][evSz] = '\0'; logHistoryWriteIdx = (logHistoryWriteIdx + 1u) % LOG_HISTORY_SIZE; if (logHistoryFill < LOG_HISTORY_SIZE) logHistoryFill++; } logHistoryMutex.FastUnLock(); + BroadcastSse("message", ev); logPage = Logger::Instance()->GetLogEntry(); logCount++; @@ -1441,11 +448,12 @@ ErrorManagement::ErrorType WebDebugService::Streamer(ExecutionInfo &info) { } // ----------------------------------------------------------------- - // Poll monitored signals + // Poll monitored signals → SSE monitor events // ----------------------------------------------------------------- mutex.FastLock(); for (uint32 i = 0u; i < monitoredSignals.Size(); i++) { - if (nowMs >= (monitoredSignals[i].lastPollTime + monitoredSignals[i].periodMs)) { + if (nowMs >= (monitoredSignals[i].lastPollTime + + monitoredSignals[i].periodMs)) { monitoredSignals[i].lastPollTime = nowMs; uint32 tsMs = (uint32)nowMs; void *addr = NULL_PTR(void *); @@ -1458,6 +466,7 @@ ErrorManagement::ErrorType WebDebugService::Streamer(ExecutionInfo &info) { TypeDescriptor td = monitoredSignals[i].dataSource->GetSignalType( monitoredSignals[i].signalIdx); float64 val = WDS_ToFloat64(lb, td); + StreamString sigName; for (uint32 j = 0u; j < aliases.Size(); j++) { if (signals[aliases[j].signalIndex]->internalID == @@ -1467,16 +476,18 @@ ErrorManagement::ErrorType WebDebugService::Streamer(ExecutionInfo &info) { } } if (sigName.Size() == 0u) sigName = monitoredSignals[i].path; - // Safely convert float64 value to string via TypeConvert + char8 valBuf[64] = { '\0' }; { - AnyType vdst(CharString, 0u, valBuf); vdst.SetNumberOfElements(0u, 63u); + AnyType vdst(CharString, 0u, valBuf); + vdst.SetNumberOfElements(0u, 63u); AnyType vsrc(Float64Bit, 0u, &val); (void)TypeConvert(vdst, vsrc); } StreamString ev; - ev.Printf("{\"type\":\"monitor\",\"name\":\"%s\",\"ts\":%u,\"value\":%s}", - sigName.Buffer(), tsMs, valBuf); + ev.Printf( + "{\"type\":\"monitor\",\"name\":\"%s\",\"ts\":%u,\"value\":%s}", + sigName.Buffer(), tsMs, valBuf); mutex.FastUnLock(); BroadcastSse("message", ev); mutex.FastLock(); @@ -1486,14 +497,14 @@ ErrorManagement::ErrorType WebDebugService::Streamer(ExecutionInfo &info) { mutex.FastUnLock(); // ----------------------------------------------------------------- - // Drain ring buffer → SSE trace events (capped to limit CPU usage) + // Drain ring buffer → SSE trace events // ----------------------------------------------------------------- - static const uint32 SAMPLE_BUF_SIZE = 1024u; - static const uint32 MAX_TRACE_CYCLE = 50u; // max samples per Streamer cycle + static const uint32 SAMPLE_BUF_SIZE = 1024u; + static const uint32 MAX_TRACE_CYCLE = 50u; uint32 id, size; uint64 sampleTs; - uint8 sampleData[SAMPLE_BUF_SIZE]; - bool hasData = false; + uint8 sampleData[SAMPLE_BUF_SIZE]; + bool hasData = false; uint32 traceCount = 0u; while (traceCount < MAX_TRACE_CYCLE && @@ -1507,19 +518,16 @@ ErrorManagement::ErrorType WebDebugService::Streamer(ExecutionInfo &info) { mutex.FastLock(); for (uint32 i = 0u; i < signals.Size(); i++) { if (signals[i]->internalID == id) { - sigName = signals[i]->name; - sigType = signals[i]->type; + sigName = signals[i]->name; + sigType = signals[i]->type; break; } } mutex.FastUnLock(); - // Convert hardware-ns timestamp to relative ms (uint32) uint32 tsMs = (sampleTs >= streamerT0Ns) ? (uint32)((sampleTs - streamerT0Ns) / 1000000u) : 0u; - float64 val = WDS_ToFloat64(sampleData, sigType); - // Safely convert float64 value to string via TypeConvert char8 valBuf[64] = { '\0' }; { AnyType vdst(CharString, 0u, valBuf); vdst.SetNumberOfElements(0u, 63u); @@ -1544,16 +552,13 @@ ErrorManagement::ErrorType WebDebugService::Streamer(ExecutionInfo &info) { uint32 rem = stepRemaining; mutex.FastUnLock(); StreamString ev; - ev.Printf("{\"type\":\"status\",\"paused\":%s,\"gam\":\"%s\",\"remaining\":%u}", - paused ? "true" : "false", gam.Buffer(), rem); + ev.Printf( + "{\"type\":\"status\",\"paused\":%s,\"gam\":\"%s\",\"remaining\":%u}", + paused ? "true" : "false", gam.Buffer(), rem); BroadcastSse("message", ev); } - // Clean up dead SSE clients RemoveDeadSseClients(); - - // Always yield to prevent the Streamer from monopolising CPU. - // The RT ring buffer at 1000 Hz is drained in ~10 cycles (50 samples/cycle). (void)hasData; Sleep::MSec(10u); return ErrorManagement::NoError; diff --git a/Source/Components/Interfaces/WebDebugService/WebDebugService.h b/Source/Components/Interfaces/WebDebugService/WebDebugService.h index 2682fd8..7d88141 100644 --- a/Source/Components/Interfaces/WebDebugService/WebDebugService.h +++ b/Source/Components/Interfaces/WebDebugService/WebDebugService.h @@ -2,18 +2,15 @@ #define WEBDEBUGSERVICE_H #include "BasicTCPSocket.h" -#include "ConfigurationDatabase.h" -#include "DataSourceI.h" -#include "DebugServiceI.h" +#include "DebugServiceBase.h" #include "EmbeddedServiceMethodBinderI.h" -#include "ReferenceContainer.h" #include "ReferenceT.h" #include "SingleThreadService.h" namespace MARTe { /** - * @brief HTTP/SSE implementation of DebugServiceI. + * @brief HTTP/SSE implementation of DebugServiceI (via DebugServiceBase). * * Serves a single-page web UI on httpPort (default 8090). * Endpoints: @@ -21,20 +18,11 @@ namespace MARTe { * POST /api/command — execute a debug command; returns text response * GET /api/events — Server-Sent Events stream for real-time telemetry * - * SSE event types (JSON payload on each "data:" line): - * {"type":"trace", "name":"…","ts":,"value":} - * {"type":"monitor", "name":"…","ts":,"value":} - * {"type":"log", "level":"…","msg":"…"} - * {"type":"status", "paused":,"gam":"…","remaining":} - * {"type":"discover","signals":[…]} - * {"type":"tree", "tree":{…}} - * - * This is an alternative to DebugService (TCP/UDP) and registers itself as the - * global DebugServiceI singleton on Initialise(). + * All signal management and command dispatch logic lives in DebugServiceBase. + * This class adds only the HTTP/SSE transport. */ -class WebDebugService : public ReferenceContainer, - public EmbeddedServiceMethodBinderI, - public DebugServiceI { +class WebDebugService : public DebugServiceBase, + public EmbeddedServiceMethodBinderI { public: friend class WebDebugServiceTest; CLASS_REGISTER_DECLARATION() @@ -44,60 +32,13 @@ public: virtual bool Initialise(StructuredDataI &data); - // ------------------------------------------------------------------------- - // DebugServiceI RT-path overrides - // ------------------------------------------------------------------------- - virtual DebugSignalInfo *RegisterSignal(void *memoryAddress, - TypeDescriptor type, - const char8 *name, - uint8 numberOfDimensions = 0, - uint32 numberOfElements = 1); - virtual void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size, - uint64 timestamp); - virtual void RegisterBroker(DebugSignalInfo **signalPointers, - uint32 numSignals, MemoryMapBroker *broker, - volatile bool *anyActiveFlag, - Vec *activeIndices, - Vec *activeSizes, - FastPollingMutexSem *activeMutex, - volatile bool *anyBreakFlag, - Vec *breakIndices, - const char8 *gamName = NULL_PTR(const char8 *), - bool isOutput = false); - virtual bool IsPaused() const { return isPaused; } - virtual void SetPaused(bool paused) { isPaused = paused; } - virtual bool IsStepPending() const { return stepRemaining > 0u; } - virtual void ConsumeStepIfNeeded(const char8 *gamName, - const char8 *threadName = NULL_PTR(const char8 *)); - - // ------------------------------------------------------------------------- - // DebugServiceI control-path overrides - // ------------------------------------------------------------------------- - virtual uint32 ForceSignal (const char8 *name, const char8 *valueStr); - virtual uint32 UnforceSignal (const char8 *name); - virtual uint32 TraceSignal (const char8 *name, bool enable, uint32 decimation = 1); - virtual uint32 SetBreak (const char8 *name, uint8 op, float64 threshold); - virtual uint32 ClearBreak (const char8 *name); - virtual bool IsInstrumented(const char8 *fullPath, bool &traceable, bool &forcable); - virtual uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs); - virtual uint32 UnmonitorSignal (const char8 *path); - // EmbeddedServiceMethodBinderI (framework dispatches to sub-binders) virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info); - // Inject the full config CDB (call after ConfigureApplication) - void SetFullConfig(ConfigurationDatabase &config); - void RebuildConfigFromRegistry(); - - struct MonitoredSignal { - ReferenceT dataSource; - uint32 signalIdx; - uint32 internalID; - uint32 periodMs; - uint64 lastPollTime; - uint32 size; - StreamString path; - }; +protected: + // Transport-specific overrides of DebugServiceBase hooks + virtual void GetServiceInfo(StreamString &out); + virtual void RebuildTransportConfig() {} // no transport-specific keys private: // HTTP server helpers @@ -109,29 +50,6 @@ private: const StreamString &path, const StreamString &body); void UpgradeToSse(BasicTCPSocket *client); - // Command handler — writes response to StreamString (not a socket) - void HandleCommand(const StreamString &cmd, StreamString &out); - void Discover (StreamString &out); - void InfoNode (const char8 *path, StreamString &out); - void ListNodes (const char8 *path, StreamString &out); - void GetSignalValue(const char8 *name, StreamString &out); - void ServeConfig (StreamString &out); - void GetStepStatus(StreamString &out); - void Step(uint32 n, const char8 *threadName = NULL_PTR(const char8 *)); - - // Tree / config helpers - uint32 ExportTree(ReferenceContainer *container, StreamString &json, - const char8 *pathPrefix); - void EnrichWithConfig(const char8 *path, StreamString &json); - static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json); - - // Registry patching (same as DebugService) - void PatchRegistry(); - - // Broker index maintenance - void UpdateBrokersActiveStatus(); - void UpdateBrokersBreakStatus(); - // SSE broadcast void BroadcastSse(const char8 *eventType, const StreamString &data); void RemoveDeadSseClients(); @@ -156,27 +74,13 @@ private: uint16 httpPort; - volatile bool isPaused; - volatile uint32 stepRemaining; - StreamString pausedAtGam; - StreamString stepThreadFilter; - BasicTCPSocket tcpServer; - WdsBinder binderHttp; - WdsBinder binderStream; + WdsBinder binderHttp; + WdsBinder binderStream; SingleThreadService httpService; SingleThreadService streamerService; - Vec signals; - Vec aliases; - Vec brokers; - Vec monitoredSignals; - - FastPollingMutexSem mutex; - FastPollingMutexSem tracePushMutex; - TraceRingBuffer traceBuffer; - // SSE client list static const uint32 MAX_SSE_CLIENTS = 8u; BasicTCPSocket *sseClients[MAX_SSE_CLIENTS]; @@ -186,11 +90,6 @@ private: uint32 streamerSeq; uint64 streamerT0Ns; // nanosecond origin for relative timestamps sent over SSE - ConfigurationDatabase fullConfig; - bool manualConfigSet; - - static const uint32 GET_VALUE_MAX_ELEMENTS = 256u; - // Log history buffer — replayed to newly connecting SSE clients static const uint32 LOG_HISTORY_SIZE = 64u; static const uint32 LOG_ENTRY_MAX_SIZE = 512u; diff --git a/Test/UnitTests/UnitTests.cpp b/Test/UnitTests/UnitTests.cpp index 885f43c..00de03a 100644 --- a/Test/UnitTests/UnitTests.cpp +++ b/Test/UnitTests/UnitTests.cpp @@ -48,14 +48,14 @@ public: service.UnforceSignal("Z"); // 2. Commands - service.HandleCommand("TREE", NULL_PTR(BasicTCPSocket*)); - service.HandleCommand("DISCOVER", NULL_PTR(BasicTCPSocket*)); - service.HandleCommand("CONFIG", NULL_PTR(BasicTCPSocket*)); - service.HandleCommand("PAUSE", NULL_PTR(BasicTCPSocket*)); - service.HandleCommand("RESUME", NULL_PTR(BasicTCPSocket*)); - service.HandleCommand("LS /", NULL_PTR(BasicTCPSocket*)); - service.HandleCommand("INFO X.Y.Z", NULL_PTR(BasicTCPSocket*)); - service.HandleCommand("MSG DebugService DummyFunc 0 K=V", NULL_PTR(BasicTCPSocket*)); + { StreamString out; service.HandleCommand("TREE", out); } + { StreamString out; service.HandleCommand("DISCOVER", out); } + { StreamString out; service.HandleCommand("CONFIG", out); } + { StreamString out; service.HandleCommand("PAUSE", out); } + { StreamString out; service.HandleCommand("RESUME", out); } + { StreamString out; service.HandleCommand("LS /", out); } + { StreamString out; service.HandleCommand("INFO X.Y.Z", out); } + { StreamString out; service.HandleCommand("MSG DebugService DummyFunc 0 K=V", out); } // 3. Broker Active Status volatile bool active = false; @@ -111,12 +111,12 @@ public: service.SetPaused(false); service.Step(0u, NULL_PTR(const char8 *)); - // 5. VALUE command (NULL client — smoke test, no crash) - service.HandleCommand("VALUE X.Y.Z", NULL_PTR(BasicTCPSocket*)); - service.HandleCommand("VALUE NoSuchSignal", NULL_PTR(BasicTCPSocket*)); - service.HandleCommand("STEP 1 ThreadA", NULL_PTR(BasicTCPSocket*)); + // 5. VALUE command — smoke test, no crash + { StreamString out; service.HandleCommand("VALUE X.Y.Z", out); } + { StreamString out; service.HandleCommand("VALUE NoSuchSignal", out); } + { StreamString out; service.HandleCommand("STEP 1 ThreadA", out); } assert(service.stepThreadFilter == "ThreadA"); - service.HandleCommand("STEP 3", NULL_PTR(BasicTCPSocket*)); + { StreamString out; service.HandleCommand("STEP 3", out); } assert(service.stepThreadFilter.Size() == 0u); } @@ -282,8 +282,8 @@ public: for (uint32 i = 0; i < 512; i++) bigBuf[i] = (uint8)(i & 0xFF); service.RegisterSignal(bigBuf, UnsignedInteger8Bit, "Big.Signal", 1, 512); - // VALUE command with NULL client is a smoke test — must not crash or loop - service.HandleCommand("VALUE Signal", NULL_PTR(BasicTCPSocket*)); + // VALUE command — smoke test, must not crash or loop + { StreamString out; service.HandleCommand("VALUE Signal", out); } printf(" -> PASS: VALUE on large array completed without hang\n"); // Verify directly: nElem is capped before the byte limit @@ -334,12 +334,11 @@ public: cmd += "=value"; // Must not crash or assert - service.HandleCommand(cmd, NULL_PTR(BasicTCPSocket*)); + { StreamString out; service.HandleCommand(cmd, out); } printf(" -> PASS: oversized key handled without buffer overflow\n"); // A normal-length key should still be accepted (regression check) - service.HandleCommand("MSG DebugService DummyFunc 0 NormalKey=NormalValue", - NULL_PTR(BasicTCPSocket*)); + { StreamString out; service.HandleCommand("MSG DebugService DummyFunc 0 NormalKey=NormalValue", out); } printf(" -> PASS: normal key not rejected by bounds check\n"); }