#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" #include "Sleep.h" #include "StreamString.h" #include "Threads.h" #include "TimeoutType.h" #include "TypeConversion.h" #include "WebDebugService.h" #include "WebUI.h" namespace MARTe { // --------------------------------------------------------------------------- // File-scope helpers (same as in DebugService.cpp) // --------------------------------------------------------------------------- static void WDS_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 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), 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)); for (uint32 i = 0u; i < MAX_SSE_CLIENTS; i++) { sseClients[i] = NULL_PTR(BasicTCPSocket *); } } WebDebugService::~WebDebugService() { DebugServiceI::SetInstance(NULL_PTR(DebugServiceI *)); httpService.Stop(); streamerService.Stop(); tcpServer.Close(); sseMutex.FastLock(); for (uint32 i = 0u; i < MAX_SSE_CLIENTS; i++) { if (sseClients[i] != NULL_PTR(BasicTCPSocket *)) { sseClients[i]->Close(); delete sseClients[i]; sseClients[i] = NULL_PTR(BasicTCPSocket *); } } sseMutex.FastUnLock(); mutex.FastLock(); for (uint32 i = 0u; i < signals.Size(); i++) { if (signals[i] != NULL_PTR(DebugSignalInfo *)) { delete signals[i]; } } mutex.FastUnLock(); } // --------------------------------------------------------------------------- // Initialise // --------------------------------------------------------------------------- bool WebDebugService::Initialise(StructuredDataI &data) { if (!ReferenceContainer::Initialise(data)) return false; uint32 port = 8090u; (void)data.Read("HttpPort", port); httpPort = (uint16)port; 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. (void)Logger::Instance(); REPORT_ERROR(ErrorManagement::Information, "WebDebugService initialised on port %d", (int)httpPort); // Record time origin for relative SSE timestamps (ms since service init). streamerT0Ns = (uint64)((float64)HighResolutionTimer::Counter() * HighResolutionTimer::Period() * 1.0e9); ConfigurationDatabase threadData; threadData.Write("Timeout", (uint32)1000); httpService.Initialise(threadData); streamerService.Initialise(threadData); if (!tcpServer.Open()) return false; if (!tcpServer.Listen(httpPort)) return false; if (httpService.Start() != ErrorManagement::NoError) return false; if (streamerService.Start() != ErrorManagement::NoError) return false; return true; } // --------------------------------------------------------------------------- // PatchRegistry (identical logic to DebugService) // --------------------------------------------------------------------------- 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 // --------------------------------------------------------------------------- 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 } // --------------------------------------------------------------------------- // SSE broadcast // --------------------------------------------------------------------------- void WebDebugService::BroadcastSse(const char8 *eventType, const StreamString &data) { StreamString pkt; pkt.Printf("event: %s\ndata: ", eventType); pkt += data; pkt += "\n\n"; sseMutex.FastLock(); for (uint32 i = 0u; i < MAX_SSE_CLIENTS; i++) { if (sseClients[i] != NULL_PTR(BasicTCPSocket *)) { uint32 sz = pkt.Size(); if (!sseClients[i]->Write(pkt.Buffer(), sz)) { sseClients[i]->Close(); delete sseClients[i]; sseClients[i] = NULL_PTR(BasicTCPSocket *); } } } sseMutex.FastUnLock(); } void WebDebugService::RemoveDeadSseClients() { sseMutex.FastLock(); for (uint32 i = 0u; i < MAX_SSE_CLIENTS; i++) { if (sseClients[i] != NULL_PTR(BasicTCPSocket *) && !sseClients[i]->IsConnected()) { sseClients[i]->Close(); delete sseClients[i]; sseClients[i] = NULL_PTR(BasicTCPSocket *); } } sseMutex.FastUnLock(); } 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" "Cache-Control: no-cache\r\n" "Connection: keep-alive\r\n" "Access-Control-Allow-Origin: *\r\n" "\r\n"; uint32 sz = StringHelper::Length(hdr); (void)client->Write(hdr, sz); // Send an initial status event so the browser knows the connection is alive 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); StreamString initPkt; initPkt += "event: message\ndata: "; initPkt += initEv; initPkt += "\n\n"; uint32 isz = initPkt.Size(); (void)client->Write(initPkt.Buffer(), isz); // Replay log history so the browser sees messages emitted before it connected logHistoryMutex.FastLock(); { uint32 startIdx = (logHistoryFill >= LOG_HISTORY_SIZE) ? logHistoryWriteIdx : 0u; uint32 count = logHistoryFill; for (uint32 i = 0u; i < count; i++) { uint32 idx = (startIdx + i) % LOG_HISTORY_SIZE; if (logHistory[idx][0] != '\0') { StreamString histPkt; histPkt += "event: message\ndata: "; histPkt += logHistory[idx]; histPkt += "\n\n"; uint32 hsz = histPkt.Size(); (void)client->Write(histPkt.Buffer(), hsz); } } } logHistoryMutex.FastUnLock(); // Register in the SSE client slot array sseMutex.FastLock(); bool registered = false; for (uint32 i = 0u; i < MAX_SSE_CLIENTS; i++) { if (sseClients[i] == NULL_PTR(BasicTCPSocket *)) { sseClients[i] = client; registered = true; break; } } sseMutex.FastUnLock(); if (!registered) { // No free slot: close immediately client->Close(); delete client; } // Do NOT close or delete client here — Streamer owns it now } // --------------------------------------------------------------------------- // HTTP request parsing // --------------------------------------------------------------------------- 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; uint32 headerEnd = 0u; bool foundEnd = false; while (total < MAX_HEADER && !foundEnd) { uint32 sz = MAX_HEADER - total; 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') { headerEnd = i + 4u; foundEnd = true; break; } } } if (!foundEnd) return false; // Parse request line const char8 *line = buf; 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); } const char8 *sp2 = StringHelper::SearchChar(sp1 + 1, ' '); if (sp2 == NULL_PTR(const char8 *)) return false; 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 *)) { cl += 15u; while (*cl == ' ') cl++; AnyType cv(UnsignedInteger32Bit, 0u, &contentLength); AnyType cs(CharString, 0u, cl); (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); } static const uint32 MAX_BODY = 16384u; if (contentLength > alreadyRead && contentLength <= MAX_BODY) { char8 bodyBuf[MAX_BODY]; uint32 toRead = contentLength - alreadyRead; uint32 sz = toRead; if (client->Read(bodyBuf, sz) && sz > 0u) { (void)body.Write(bodyBuf, sz); } } } return true; } void WebDebugService::SendHttp(BasicTCPSocket *client, uint32 code, const char8 *ct, StreamString &body) { StreamString resp; resp.Printf("HTTP/1.1 %u %s\r\n" "Content-Type: %s\r\n" "Content-Length: %u\r\n" "Cache-Control: no-cache\r\n" "Access-Control-Allow-Origin: *\r\n" "Connection: close\r\n" "\r\n", code, (code == 200u) ? "OK" : "Error", ct, (uint32)body.Size()); resp += body; uint32 sz = resp.Size(); (void)client->Write(resp.Buffer(), sz); } void WebDebugService::HandleRequest(BasicTCPSocket *client, const StreamString &method, const StreamString &path, const StreamString &body) { if (method == "GET" && (path == "/" || path == "/index.html")) { 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 } 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" "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n" "Access-Control-Allow-Headers: Content-Type\r\n" "Connection: close\r\n" "\r\n"; uint32 sz = StringHelper::Length(hdr); (void)client->Write(hdr, sz); } else { StreamString body404 = "Not Found"; SendHttp(client, 404u, "text/plain", body404); } client->Close(); delete client; } // --------------------------------------------------------------------------- // HTTP server thread // --------------------------------------------------------------------------- ErrorManagement::ErrorType WebDebugService::HttpServer(ExecutionInfo &info) { if (info.GetStage() == ExecutionInfo::TerminationStage) return ErrorManagement::NoError; if (info.GetStage() == ExecutionInfo::StartupStage) { Sleep::MSec(500u); // wait for ORD to finish return ErrorManagement::NoError; } BasicTCPSocket *client = tcpServer.WaitConnection(TimeoutType(100u)); if (client == NULL_PTR(BasicTCPSocket *)) return ErrorManagement::NoError; 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 } else { client->Close(); delete client; } return ErrorManagement::NoError; } // --------------------------------------------------------------------------- // Streamer thread — reads ring buffer, broadcasts SSE events, polls monitors // --------------------------------------------------------------------------- // 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; if (info.GetStage() == ExecutionInfo::StartupStage) return ErrorManagement::NoError; uint64 nowNs = (uint64)((float64)HighResolutionTimer::Counter() * HighResolutionTimer::Period() * 1.0e9); uint64 nowMs = (nowNs >= streamerT0Ns) ? (nowNs - streamerT0Ns) / 1000000u : 0u; // ----------------------------------------------------------------- // Drain MARTe2 log pages → SSE log events // ----------------------------------------------------------------- { static const uint32 MAX_LOGS_PER_CYCLE = 32u; uint32 logCount = 0u; LoggerPage *logPage = Logger::Instance()->GetLogEntry(); while (logPage != NULL_PTR(LoggerPage *) && logCount < MAX_LOGS_PER_CYCLE) { StreamString levelStr; ErrorManagement::ErrorCodeToStream( logPage->errorInfo.header.errorType, levelStr); StreamString msgEsc; WDS_EscapeJson(logPage->errorStrBuffer, msgEsc); StreamString ev; 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); 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++; } } // ----------------------------------------------------------------- // Poll monitored signals // ----------------------------------------------------------------- mutex.FastLock(); for (uint32 i = 0u; i < monitoredSignals.Size(); i++) { if (nowMs >= (monitoredSignals[i].lastPollTime + monitoredSignals[i].periodMs)) { monitoredSignals[i].lastPollTime = nowMs; uint32 tsMs = (uint32)nowMs; void *addr = NULL_PTR(void *); if (monitoredSignals[i].dataSource->GetSignalMemoryBuffer( monitoredSignals[i].signalIdx, 0u, addr) && addr != NULL_PTR(void *)) { uint32 sz = monitoredSignals[i].size; if (sz > 1024u) sz = 1024u; uint8 lb[1024]; memcpy(lb, addr, sz); 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 == monitoredSignals[i].internalID) { sigName = aliases[j].name; break; } } 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 vsrc(Float64Bit, 0u, &val); (void)TypeConvert(vdst, vsrc); } StreamString ev; ev.Printf("{\"type\":\"monitor\",\"name\":\"%s\",\"ts\":%u,\"value\":%s}", sigName.Buffer(), tsMs, valBuf); mutex.FastUnLock(); BroadcastSse("message", ev); mutex.FastLock(); } } } mutex.FastUnLock(); // ----------------------------------------------------------------- // Drain ring buffer → SSE trace events (capped to limit CPU usage) // ----------------------------------------------------------------- static const uint32 SAMPLE_BUF_SIZE = 1024u; static const uint32 MAX_TRACE_CYCLE = 50u; // max samples per Streamer cycle uint32 id, size; uint64 sampleTs; uint8 sampleData[SAMPLE_BUF_SIZE]; bool hasData = false; uint32 traceCount = 0u; while (traceCount < MAX_TRACE_CYCLE && traceBuffer.Pop(id, sampleTs, sampleData, size, SAMPLE_BUF_SIZE)) { hasData = true; traceCount++; if (size > SAMPLE_BUF_SIZE) continue; StreamString sigName; TypeDescriptor sigType = UnsignedInteger32Bit; mutex.FastLock(); for (uint32 i = 0u; i < signals.Size(); i++) { if (signals[i]->internalID == id) { 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); AnyType vsrc(Float64Bit, 0u, &val); (void)TypeConvert(vdst, vsrc); } StreamString ev; ev.Printf("{\"type\":\"trace\",\"name\":\"%s\",\"ts\":%u,\"value\":%s}", sigName.Buffer(), tsMs, valBuf); BroadcastSse("message", ev); } // ----------------------------------------------------------------- // Periodic status heartbeat (every 500 ms) // ----------------------------------------------------------------- static uint64 lastStatusMs = 0u; if (nowMs - lastStatusMs >= 500u) { lastStatusMs = nowMs; mutex.FastLock(); bool paused = isPaused; StreamString gam = pausedAtGam; uint32 rem = stepRemaining; mutex.FastUnLock(); StreamString ev; 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; } } // namespace MARTe