Files
marte-debug/Source/Components/Interfaces/WebDebugService/WebDebugService.cpp
T
2026-05-07 10:49:24 +02:00

1563 lines
62 KiB
C++

#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<ReferenceContainer *>(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<ReferenceContainer *>(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<uint32> *activeIndices,
Vec<uint32> *activeSizes,
FastPollingMutexSem *activeMutex,
volatile bool *anyBreakFlag,
Vec<uint32> *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<StreamString> 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<DataSourceI> 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<uint32> 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<uint32> 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<ReferenceContainer *>(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<ReferenceContainer *>(child.operator->());
DataSourceI *ds = dynamic_cast<DataSourceI *>(child.operator->());
GAM *gam = dynamic_cast<GAM *>(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