Implemented common base for debug services

This commit is contained in:
Martino Ferrari
2026-05-07 12:12:14 +02:00
parent 774808a054
commit e9f25f5867
9 changed files with 1947 additions and 3436 deletions
File diff suppressed because it is too large Load Diff
@@ -3,31 +3,24 @@
#include "BasicTCPSocket.h" #include "BasicTCPSocket.h"
#include "BasicUDPSocket.h" #include "BasicUDPSocket.h"
#include "ConfigurationDatabase.h" #include "DebugServiceBase.h"
#include "DebugServiceI.h"
#include "EmbeddedServiceMethodBinderI.h" #include "EmbeddedServiceMethodBinderI.h"
#include "MessageI.h" #include "MessageI.h"
#include "ReferenceContainer.h"
#include "ReferenceT.h" #include "ReferenceT.h"
#include "SingleThreadService.h" #include "SingleThreadService.h"
namespace MARTe { namespace MARTe {
class DataSourceI;
/** /**
* @brief TCP/UDP implementation of DebugServiceI. * @brief TCP/UDP implementation of DebugServiceI (via DebugServiceBase).
* *
* Provides signal tracing (UDP), forced-value injection, break conditions, * Provides signal tracing (UDP), forced-value injection, break conditions,
* and a text command channel (TCP) with a log-forwarding sidecar (TcpLogger). * and a text command channel (TCP) with a log-forwarding sidecar (TcpLogger).
* Alternative transports (TTY, WebSocket, …) can be added by implementing * All signal management and command dispatch logic lives in DebugServiceBase.
* DebugServiceI without touching this class or any broker wrapper code.
*/ */
class DebugService : public ReferenceContainer, class DebugService : public DebugServiceBase,
public MessageI, public MessageI,
public EmbeddedServiceMethodBinderI, public EmbeddedServiceMethodBinderI {
public DebugServiceI {
public: public:
friend class DebugServiceTest; friend class DebugServiceTest;
CLASS_REGISTER_DECLARATION() CLASS_REGISTER_DECLARATION()
@@ -37,85 +30,27 @@ public:
virtual bool Initialise(StructuredDataI &data); virtual bool Initialise(StructuredDataI &data);
// DebugServiceI RT-path overrides
virtual DebugSignalInfo *RegisterSignal(void *memoryAddress, TypeDescriptor type,
const char8 *name,
uint8 numberOfDimensions = 0,
uint32 numberOfElements = 1);
virtual void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size,
uint64 timestamp);
virtual void RegisterBroker(DebugSignalInfo **signalPointers, uint32 numSignals,
MemoryMapBroker *broker, volatile bool *anyActiveFlag,
Vec<uint32> *activeIndices, Vec<uint32> *activeSizes,
FastPollingMutexSem *activeMutex,
volatile bool *anyBreakFlag, Vec<uint32> *breakIndices,
const char8 *gamName = NULL_PTR(const char8 *),
bool isOutput = false);
virtual bool IsPaused() const { return isPaused; }
virtual void SetPaused(bool paused) { isPaused = paused; }
virtual bool IsStepPending() const { return stepRemaining > 0u; }
virtual void ConsumeStepIfNeeded(const char8 *gamName,
const char8 *threadName = NULL_PTR(const char8 *));
// DebugServiceI control-path overrides
virtual uint32 ForceSignal (const char8 *name, const char8 *valueStr);
virtual uint32 UnforceSignal(const char8 *name);
virtual uint32 TraceSignal (const char8 *name, bool enable, uint32 decimation = 1);
virtual uint32 SetBreak (const char8 *name, uint8 op, float64 threshold);
virtual uint32 ClearBreak (const char8 *name);
virtual bool IsInstrumented(const char8 *fullPath, bool &traceable, bool &forcable);
virtual uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs);
virtual uint32 UnmonitorSignal (const char8 *path);
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info); virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info);
virtual ErrorManagement::ErrorType HandleMessage(ReferenceT<Message> &data); virtual ErrorManagement::ErrorType HandleMessage(ReferenceT<Message> &data);
// TCP-transport-specific methods (not part of DebugServiceI) protected:
void GetStepStatus(BasicTCPSocket *client); // Transport-specific overrides of DebugServiceBase hooks
void Step(uint32 n, const char8 *threadName = NULL_PTR(const char8 *)); virtual void GetServiceInfo(StreamString &out);
void GetSignalValue(const char8 *name, BasicTCPSocket *client); virtual void RebuildTransportConfig();
void Discover(BasicTCPSocket *client);
void InfoNode(const char8 *path, BasicTCPSocket *client);
void ListNodes(const char8 *path, BasicTCPSocket *client);
void ServeConfig(BasicTCPSocket *client);
void SetFullConfig(ConfigurationDatabase &config);
void RebuildConfigFromRegistry();
struct MonitoredSignal {
ReferenceT<DataSourceI> dataSource;
uint32 signalIdx;
uint32 internalID;
uint32 periodMs;
uint64 lastPollTime;
uint32 size;
StreamString path;
};
private: private:
void HandleCommand(StreamString cmd, BasicTCPSocket *client);
void UpdateBrokersActiveStatus();
void UpdateBrokersBreakStatus();
void InjectTcpLoggerIfNeeded(); void InjectTcpLoggerIfNeeded();
uint32 ExportTree(ReferenceContainer *container, StreamString &json, const char8 *pathPrefix); ErrorManagement::ErrorType Server (ExecutionInfo &info);
void PatchRegistry();
void EnrichWithConfig(const char8 *path, StreamString &json);
static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json);
ErrorManagement::ErrorType Server(ExecutionInfo &info);
ErrorManagement::ErrorType Streamer(ExecutionInfo &info); ErrorManagement::ErrorType Streamer(ExecutionInfo &info);
// TCP/UDP-specific configuration
uint16 controlPort; uint16 controlPort;
uint16 streamPort; uint16 streamPort;
uint16 logPort; uint16 logPort;
StreamString streamIP; StreamString streamIP;
bool isServer; bool isServer;
bool suppressTimeoutLogs; bool suppressTimeoutLogs;
volatile bool isPaused;
volatile uint32 stepRemaining;
StreamString pausedAtGam;
StreamString stepThreadFilter; // empty = all threads; non-empty = only this OS thread name
BasicTCPSocket tcpServer; BasicTCPSocket tcpServer;
BasicUDPSocket udpSocket; BasicUDPSocket udpSocket;
@@ -126,12 +61,9 @@ private:
ServiceBinder(DebugService *parent, ServiceType type) ServiceBinder(DebugService *parent, ServiceType type)
: parent(parent), type(type) {} : parent(parent), type(type) {}
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info) { virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info) {
if (type == StreamerType) { if (type == StreamerType) return parent->Streamer(info);
return parent->Streamer(info);
}
return parent->Server(info); return parent->Server(info);
} }
private: private:
DebugService *parent; DebugService *parent;
ServiceType type; ServiceType type;
@@ -139,157 +71,36 @@ private:
ServiceBinder binderServer; ServiceBinder binderServer;
ServiceBinder binderStreamer; ServiceBinder binderStreamer;
SingleThreadService threadService; SingleThreadService threadService;
SingleThreadService streamerService; SingleThreadService streamerService;
ThreadIdentifier serverThreadId; ThreadIdentifier serverThreadId;
ThreadIdentifier streamerThreadId; ThreadIdentifier streamerThreadId;
Vec<DebugSignalInfo *> signals; // activeClient — exclusively owned by Server thread
Vec<SignalAlias> aliases;
Vec<BrokerInfo> brokers;
Vec<MonitoredSignal> monitoredSignals;
FastPollingMutexSem mutex;
TraceRingBuffer traceBuffer;
/**
* @brief Mutex protecting traceBuffer.Push() from concurrent RT broker threads.
*
* TraceRingBuffer is designed for single-producer / single-consumer access.
* In practice, multiple RT threads (one per RealTimeThread) call ProcessSignal()
* concurrently, making it a multi-producer scenario. Without a lock they can
* both pass the free-space check, both pick the same write index, and corrupt
* each other's samples. This mutex serialises the Push path.
*
* The Streamer thread (sole consumer) never holds this lock, so the lock is
* only contended between RT threads — not between RT and Streamer.
*
* FIX #2 — TraceRingBuffer multi-producer race.
*/
FastPollingMutexSem tracePushMutex;
/**
* @brief Mutex protecting the activeClient pointer.
*
* activeClient is currently owned exclusively by the Server thread:
* it is created, read, and destroyed only inside Server(). The mutex
* enforces this invariant and makes future cross-thread access safe.
*
* Rules:
* - Hold clientMutex whenever assigning (including to NULL) activeClient.
* - Do NOT hold clientMutex across blocking I/O calls (Read / Write)
* to avoid starving threads that only need the pointer.
* - Any future code that needs to send data to the active client from a
* non-Server thread must lock clientMutex, copy the pointer, release
* the lock, then do the Write.
*
* FIX #2 — activeClient cross-thread safety.
*/
FastPollingMutexSem clientMutex; FastPollingMutexSem clientMutex;
BasicTCPSocket *activeClient; BasicTCPSocket *activeClient;
/** // Streamer assembly buffer
* Maximum payload bytes in a single UDP datagram (leave room for IP+UDP headers
* on a standard 1500-byte Ethernet MTU). Used in the Streamer to decide when
* to flush the current packet and start a new one.
*
* FIX #1 — named constant to replace the magic number 1400.
*/
static const uint32 STREAMER_MTU = 1400u; static const uint32 STREAMER_MTU = 1400u;
/**
* Size of the streamer assembly buffer. Must be >= STREAMER_MTU so at least
* one full UDP payload can be staged before flushing. A sample that on its own
* would exceed (STREAMER_BUFFER_SIZE - sizeof(TraceHeader) - 16) bytes is
* silently dropped and logged — it can never fit in any packet.
*
* FIX #1 — named constant; prevents silent integer arithmetic relying on
* the array bound being "obviously 4096".
*/
static const uint32 STREAMER_BUFFER_SIZE = 4096u; static const uint32 STREAMER_BUFFER_SIZE = 4096u;
// Streamer state persisted across Execute() calls (framework loops Execute)
uint8 streamerPacketBuffer[STREAMER_BUFFER_SIZE]; uint8 streamerPacketBuffer[STREAMER_BUFFER_SIZE];
uint32 streamerPacketOffset; uint32 streamerPacketOffset;
uint32 streamerSequenceNumber; uint32 streamerSequenceNumber;
/** // Rate-limiting and idle-timeout constants / state
* @brief Maximum commands allowed per 1-second sliding window per client.
*
* A client sending more than this many commands in one second is disconnected.
* This prevents a tight command loop from monopolising the Server thread and
* starving normal usage. The limit is intentionally generous (100/s) so that
* legitimate scripted tooling is unaffected.
*
* FIX #6 — TCP command rate limiting.
*/
static const uint32 CMD_RATE_LIMIT = 100u; static const uint32 CMD_RATE_LIMIT = 100u;
/**
* @brief Close the active TCP client if no data is received for this many ms.
*
* A client that sends a partial command and never completes it would otherwise
* hold the single active-client slot open indefinitely, blocking all other
* connections. After CLIENT_IDLE_TIMEOUT_MS of silence the connection is
* closed and the slot freed.
*
* FIX #8 — active-client idle timeout.
*/
static const uint32 CLIENT_IDLE_TIMEOUT_MS = 30000u; static const uint32 CLIENT_IDLE_TIMEOUT_MS = 30000u;
/**
* @brief Maximum array elements returned by GetSignalValue / VALUE command.
*
* Without a cap, a 1-million-element uint8 array would produce a multi-MB
* response string, exhausting heap and blocking the Server thread. Elements
* beyond this limit are omitted; the response includes a "Truncated" flag.
*
* FIX #9 — GetSignalValue unbounded array output.
*/
static const uint32 GET_VALUE_MAX_ELEMENTS = 256u;
// Rate-limiter state (per active TCP connection, reset on each new connection)
uint32 cmdCountInWindow; // commands received in the current 1-second window
uint64 cmdWindowStartMs; // start of the current window, in milliseconds
// Idle-timeout state
uint64 lastDataTimeMs; // last time the active client sent any data (ms)
/**
* @brief Carry-over buffer for multi-segment TCP commands.
*
* A single TCP Read() call may return only part of a command if the OS
* delivers it in multiple segments. Bytes after the last '\n' in the
* current receive window are saved here and prepended to the next Read().
* This ensures that "FORCE Signal " arriving in one segment and "123\n"
* arriving in the next are assembled into the single command
* "FORCE Signal 123" before dispatch.
*
* The buffer is bounded by INPUT_BUFFER_MAX (8 KiB). If a client sends
* more than that without a newline it is disconnected.
*
* FIX #10 — incomplete input buffer handling for multi-line commands.
*/
StreamString inputBuffer;
/**
* @brief Maximum carry-over bytes allowed before a client is disconnected.
*
* A client that sends a very long line without a newline would otherwise
* grow inputBuffer without bound. At 8 KiB the limit is generous enough
* for any legitimate command while still protecting against memory exhaustion.
*
* FIX #10 — DoS guard for the input carry-over buffer.
*/
static const uint32 INPUT_BUFFER_MAX = 8192u; static const uint32 INPUT_BUFFER_MAX = 8192u;
ConfigurationDatabase fullConfig; uint32 cmdCountInWindow;
bool manualConfigSet; uint64 cmdWindowStartMs;
uint64 lastDataTimeMs;
// Carry-over buffer for multi-segment TCP commands
StreamString inputBuffer;
}; };
} // namespace MARTe } // namespace MARTe
#endif #endif // DEBUGSERVICE_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,190 @@
#ifndef DEBUGSERVICEBASE_H
#define DEBUGSERVICEBASE_H
/**
* @file DebugServiceBase.h
* @brief Shared base class for DebugService and WebDebugService.
*
* Extracts all signal-management, command-handling and config logic that is
* common to both the TCP/UDP transport (DebugService) and the HTTP/SSE
* transport (WebDebugService), eliminating the previous code duplication.
*/
#include "ConfigurationDatabase.h"
#include "DataSourceI.h"
#include "DebugServiceI.h"
#include "FastPollingMutexSem.h"
#include "ReferenceContainer.h"
#include "ReferenceT.h"
#include "StreamString.h"
namespace MARTe {
/**
* @brief Intermediate base class that holds all shared debugging logic.
*
* Inherits from ReferenceContainer (to be a MARTe2 Object) and DebugServiceI
* (to expose the RT-path / control-path interface). Transport subclasses
* (DebugService, WebDebugService) inherit from this class and add only their
* transport-specific threading and socket logic.
*/
class DebugServiceBase : public ReferenceContainer, public DebugServiceI {
public:
friend class DebugServiceTest;
DebugServiceBase();
virtual ~DebugServiceBase();
// =========================================================================
// DebugServiceI RT-path overrides (shared implementation)
// =========================================================================
virtual DebugSignalInfo *RegisterSignal(void *memoryAddress,
TypeDescriptor type,
const char8 *name,
uint8 numberOfDimensions = 0,
uint32 numberOfElements = 1);
virtual void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size,
uint64 timestamp);
virtual void RegisterBroker(DebugSignalInfo **signalPointers,
uint32 numSignals,
MemoryMapBroker *broker,
volatile bool *anyActiveFlag,
Vec<uint32> *activeIndices,
Vec<uint32> *activeSizes,
FastPollingMutexSem *activeMutex,
volatile bool *anyBreakFlag,
Vec<uint32> *breakIndices,
const char8 *gamName = NULL_PTR(const char8 *),
bool isOutput = false);
virtual bool IsPaused() const { return isPaused; }
virtual void SetPaused(bool paused) { isPaused = paused; }
virtual bool IsStepPending() const { return stepRemaining > 0u; }
virtual void ConsumeStepIfNeeded(const char8 *gamName,
const char8 *threadName = NULL_PTR(const char8 *));
// =========================================================================
// DebugServiceI control-path overrides (shared implementation)
// =========================================================================
virtual uint32 ForceSignal (const char8 *name, const char8 *valueStr);
virtual uint32 UnforceSignal(const char8 *name);
virtual uint32 TraceSignal (const char8 *name, bool enable, uint32 decimation = 1);
virtual uint32 SetBreak (const char8 *name, uint8 op, float64 threshold);
virtual uint32 ClearBreak (const char8 *name);
virtual bool IsInstrumented(const char8 *fullPath, bool &traceable, bool &forcable);
virtual uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs);
virtual uint32 UnmonitorSignal (const char8 *path);
// =========================================================================
// Shared control-path methods (used by transport subclasses)
// =========================================================================
/**
* @brief Parse and dispatch a debug command; write text response to @p out.
*
* Handles: TRACE, FORCE, UNFORCE, BREAK, PAUSE, RESUME, STEP, STEP_STATUS,
* VALUE, DISCOVER, TREE, INFO, LS, CONFIG, MONITOR, UNMONITOR,
* SERVICE_INFO, MSG.
*
* SERVICE_INFO delegates to GetServiceInfo() so each transport can fill in
* its own port/state information.
*/
void HandleCommand(const StreamString &cmdIn, StreamString &out);
void GetStepStatus(StreamString &out);
void GetSignalValue(const char8 *name, StreamString &out);
void Discover (StreamString &out);
void InfoNode (const char8 *path, StreamString &out);
void ListNodes (const char8 *path, StreamString &out);
void ServeConfig (StreamString &out);
void SetFullConfig(ConfigurationDatabase &config);
void RebuildConfigFromRegistry();
// =========================================================================
// Struct shared by both transports
// =========================================================================
struct MonitoredSignal {
ReferenceT<DataSourceI> dataSource;
uint32 signalIdx;
uint32 internalID;
uint32 periodMs;
uint64 lastPollTime;
uint32 size;
StreamString path;
};
// =========================================================================
// Constants
// =========================================================================
static const uint32 GET_VALUE_MAX_ELEMENTS = 256u;
protected:
// =========================================================================
// Virtual hooks for transport subclasses
// =========================================================================
/**
* @brief Fill the SERVICE_INFO response text (transport-specific).
*
* Called by HandleCommand when the SERVICE_INFO command is received.
* The base implementation writes nothing; subclasses override to append
* e.g. "OK SERVICE_INFO TCP_CTRL:8080 UDP_STREAM:8081 STATE:RUNNING\n".
*/
virtual void GetServiceInfo(StreamString &out) = 0;
/**
* @brief Write back transport-specific config keys after RebuildConfigFromRegistry().
*
* Called at the end of RebuildConfigFromRegistry() so that port numbers
* and other transport parameters that were read in Initialise() are
* reflected back into fullConfig (ExportData on ReferenceContainers
* doesn't re-emit config-file parameters).
*/
virtual void RebuildTransportConfig() {}
// =========================================================================
// Shared protected helpers
// =========================================================================
void Step(uint32 n, const char8 *threadName = NULL_PTR(const char8 *));
void UpdateBrokersActiveStatus();
void UpdateBrokersBreakStatus();
void PatchRegistry();
uint32 ExportTree(ReferenceContainer *container, StreamString &json,
const char8 *pathPrefix);
void EnrichWithConfig(const char8 *path, StreamString &json);
static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json);
// =========================================================================
// Shared data members
// =========================================================================
Vec<DebugSignalInfo *> signals;
Vec<SignalAlias> aliases;
Vec<BrokerInfo> brokers;
Vec<MonitoredSignal> monitoredSignals;
FastPollingMutexSem mutex;
FastPollingMutexSem tracePushMutex;
TraceRingBuffer traceBuffer;
volatile bool isPaused;
volatile uint32 stepRemaining;
StreamString pausedAtGam;
StreamString stepThreadFilter;
ConfigurationDatabase fullConfig;
bool manualConfigSet;
};
} // namespace MARTe
#endif // DEBUGSERVICEBASE_H
@@ -23,7 +23,7 @@
# $Id: Makefile.inc 3 2012-01-15 16:26:07Z aneto $ # $Id: Makefile.inc 3 2012-01-15 16:26:07Z aneto $
# #
############################################################# #############################################################
OBJSX=DebugService.x OBJSX=DebugService.x DebugServiceBase.x
PACKAGE=Components/Interfaces PACKAGE=Components/Interfaces
@@ -1,4 +1,4 @@
OBJSX=WebDebugService.x OBJSX=WebDebugService.x DebugServiceBase.x
PACKAGE=Components/Interfaces PACKAGE=Components/Interfaces
@@ -26,9 +26,16 @@ INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L5GAMs
INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L1Portability INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L1Portability
INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L3Streams INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L3Streams
DEBUGSERVICE_SRC=$(ROOT_DIR)/Source/Components/Interfaces/DebugService
all: $(OBJS) $(SUBPROJ) \ all: $(OBJS) $(SUBPROJ) \
$(BUILD_DIR)/WebDebugService$(LIBEXT) \ $(BUILD_DIR)/WebDebugService$(LIBEXT) \
$(BUILD_DIR)/WebDebugService$(DLLEXT) $(BUILD_DIR)/WebDebugService$(DLLEXT)
echo $(OBJS) echo $(OBJS)
include $(MAKEDEFAULTDIR)/MakeStdLibRules.$(TARGET) include $(MAKEDEFAULTDIR)/MakeStdLibRules.$(TARGET)
# Explicit rule to compile DebugServiceBase.cpp from the DebugService directory
$(BUILD_DIR)/DebugServiceBase.o: $(DEBUGSERVICE_SRC)/DebugServiceBase.cpp
$(COMPILER) -c $(OPTIM) $(INCLUDES) $(CPPFLAGS) $(CFLAGSPEC) $(DEBUG) \
$(DEBUGSERVICE_SRC)/DebugServiceBase.cpp -o $(BUILD_DIR)/DebugServiceBase.o
File diff suppressed because it is too large Load Diff
@@ -2,18 +2,15 @@
#define WEBDEBUGSERVICE_H #define WEBDEBUGSERVICE_H
#include "BasicTCPSocket.h" #include "BasicTCPSocket.h"
#include "ConfigurationDatabase.h" #include "DebugServiceBase.h"
#include "DataSourceI.h"
#include "DebugServiceI.h"
#include "EmbeddedServiceMethodBinderI.h" #include "EmbeddedServiceMethodBinderI.h"
#include "ReferenceContainer.h"
#include "ReferenceT.h" #include "ReferenceT.h"
#include "SingleThreadService.h" #include "SingleThreadService.h"
namespace MARTe { namespace MARTe {
/** /**
* @brief HTTP/SSE implementation of DebugServiceI. * @brief HTTP/SSE implementation of DebugServiceI (via DebugServiceBase).
* *
* Serves a single-page web UI on httpPort (default 8090). * Serves a single-page web UI on httpPort (default 8090).
* Endpoints: * Endpoints:
@@ -21,20 +18,11 @@ namespace MARTe {
* POST /api/command — execute a debug command; returns text response * POST /api/command — execute a debug command; returns text response
* GET /api/events — Server-Sent Events stream for real-time telemetry * GET /api/events — Server-Sent Events stream for real-time telemetry
* *
* SSE event types (JSON payload on each "data:" line): * All signal management and command dispatch logic lives in DebugServiceBase.
* {"type":"trace", "name":"…","ts":<ns>,"value":<f64>} * This class adds only the HTTP/SSE transport.
* {"type":"monitor", "name":"…","ts":<ns>,"value":<f64>}
* {"type":"log", "level":"…","msg":"…"}
* {"type":"status", "paused":<bool>,"gam":"…","remaining":<u32>}
* {"type":"discover","signals":[…]}
* {"type":"tree", "tree":{…}}
*
* This is an alternative to DebugService (TCP/UDP) and registers itself as the
* global DebugServiceI singleton on Initialise().
*/ */
class WebDebugService : public ReferenceContainer, class WebDebugService : public DebugServiceBase,
public EmbeddedServiceMethodBinderI, public EmbeddedServiceMethodBinderI {
public DebugServiceI {
public: public:
friend class WebDebugServiceTest; friend class WebDebugServiceTest;
CLASS_REGISTER_DECLARATION() CLASS_REGISTER_DECLARATION()
@@ -44,60 +32,13 @@ public:
virtual bool Initialise(StructuredDataI &data); virtual bool Initialise(StructuredDataI &data);
// -------------------------------------------------------------------------
// DebugServiceI RT-path overrides
// -------------------------------------------------------------------------
virtual DebugSignalInfo *RegisterSignal(void *memoryAddress,
TypeDescriptor type,
const char8 *name,
uint8 numberOfDimensions = 0,
uint32 numberOfElements = 1);
virtual void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size,
uint64 timestamp);
virtual void RegisterBroker(DebugSignalInfo **signalPointers,
uint32 numSignals, MemoryMapBroker *broker,
volatile bool *anyActiveFlag,
Vec<uint32> *activeIndices,
Vec<uint32> *activeSizes,
FastPollingMutexSem *activeMutex,
volatile bool *anyBreakFlag,
Vec<uint32> *breakIndices,
const char8 *gamName = NULL_PTR(const char8 *),
bool isOutput = false);
virtual bool IsPaused() const { return isPaused; }
virtual void SetPaused(bool paused) { isPaused = paused; }
virtual bool IsStepPending() const { return stepRemaining > 0u; }
virtual void ConsumeStepIfNeeded(const char8 *gamName,
const char8 *threadName = NULL_PTR(const char8 *));
// -------------------------------------------------------------------------
// DebugServiceI control-path overrides
// -------------------------------------------------------------------------
virtual uint32 ForceSignal (const char8 *name, const char8 *valueStr);
virtual uint32 UnforceSignal (const char8 *name);
virtual uint32 TraceSignal (const char8 *name, bool enable, uint32 decimation = 1);
virtual uint32 SetBreak (const char8 *name, uint8 op, float64 threshold);
virtual uint32 ClearBreak (const char8 *name);
virtual bool IsInstrumented(const char8 *fullPath, bool &traceable, bool &forcable);
virtual uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs);
virtual uint32 UnmonitorSignal (const char8 *path);
// EmbeddedServiceMethodBinderI (framework dispatches to sub-binders) // EmbeddedServiceMethodBinderI (framework dispatches to sub-binders)
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info); virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info);
// Inject the full config CDB (call after ConfigureApplication) protected:
void SetFullConfig(ConfigurationDatabase &config); // Transport-specific overrides of DebugServiceBase hooks
void RebuildConfigFromRegistry(); virtual void GetServiceInfo(StreamString &out);
virtual void RebuildTransportConfig() {} // no transport-specific keys
struct MonitoredSignal {
ReferenceT<DataSourceI> dataSource;
uint32 signalIdx;
uint32 internalID;
uint32 periodMs;
uint64 lastPollTime;
uint32 size;
StreamString path;
};
private: private:
// HTTP server helpers // HTTP server helpers
@@ -109,29 +50,6 @@ private:
const StreamString &path, const StreamString &body); const StreamString &path, const StreamString &body);
void UpgradeToSse(BasicTCPSocket *client); void UpgradeToSse(BasicTCPSocket *client);
// Command handler — writes response to StreamString (not a socket)
void HandleCommand(const StreamString &cmd, StreamString &out);
void Discover (StreamString &out);
void InfoNode (const char8 *path, StreamString &out);
void ListNodes (const char8 *path, StreamString &out);
void GetSignalValue(const char8 *name, StreamString &out);
void ServeConfig (StreamString &out);
void GetStepStatus(StreamString &out);
void Step(uint32 n, const char8 *threadName = NULL_PTR(const char8 *));
// Tree / config helpers
uint32 ExportTree(ReferenceContainer *container, StreamString &json,
const char8 *pathPrefix);
void EnrichWithConfig(const char8 *path, StreamString &json);
static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json);
// Registry patching (same as DebugService)
void PatchRegistry();
// Broker index maintenance
void UpdateBrokersActiveStatus();
void UpdateBrokersBreakStatus();
// SSE broadcast // SSE broadcast
void BroadcastSse(const char8 *eventType, const StreamString &data); void BroadcastSse(const char8 *eventType, const StreamString &data);
void RemoveDeadSseClients(); void RemoveDeadSseClients();
@@ -156,11 +74,6 @@ private:
uint16 httpPort; uint16 httpPort;
volatile bool isPaused;
volatile uint32 stepRemaining;
StreamString pausedAtGam;
StreamString stepThreadFilter;
BasicTCPSocket tcpServer; BasicTCPSocket tcpServer;
WdsBinder binderHttp; WdsBinder binderHttp;
@@ -168,15 +81,6 @@ private:
SingleThreadService httpService; SingleThreadService httpService;
SingleThreadService streamerService; SingleThreadService streamerService;
Vec<DebugSignalInfo *> signals;
Vec<SignalAlias> aliases;
Vec<BrokerInfo> brokers;
Vec<MonitoredSignal> monitoredSignals;
FastPollingMutexSem mutex;
FastPollingMutexSem tracePushMutex;
TraceRingBuffer traceBuffer;
// SSE client list // SSE client list
static const uint32 MAX_SSE_CLIENTS = 8u; static const uint32 MAX_SSE_CLIENTS = 8u;
BasicTCPSocket *sseClients[MAX_SSE_CLIENTS]; BasicTCPSocket *sseClients[MAX_SSE_CLIENTS];
@@ -186,11 +90,6 @@ private:
uint32 streamerSeq; uint32 streamerSeq;
uint64 streamerT0Ns; // nanosecond origin for relative timestamps sent over SSE uint64 streamerT0Ns; // nanosecond origin for relative timestamps sent over SSE
ConfigurationDatabase fullConfig;
bool manualConfigSet;
static const uint32 GET_VALUE_MAX_ELEMENTS = 256u;
// Log history buffer — replayed to newly connecting SSE clients // Log history buffer — replayed to newly connecting SSE clients
static const uint32 LOG_HISTORY_SIZE = 64u; static const uint32 LOG_HISTORY_SIZE = 64u;
static const uint32 LOG_ENTRY_MAX_SIZE = 512u; static const uint32 LOG_ENTRY_MAX_SIZE = 512u;
+17 -18
View File
@@ -48,14 +48,14 @@ public:
service.UnforceSignal("Z"); service.UnforceSignal("Z");
// 2. Commands // 2. Commands
service.HandleCommand("TREE", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("TREE", out); }
service.HandleCommand("DISCOVER", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("DISCOVER", out); }
service.HandleCommand("CONFIG", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("CONFIG", out); }
service.HandleCommand("PAUSE", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("PAUSE", out); }
service.HandleCommand("RESUME", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("RESUME", out); }
service.HandleCommand("LS /", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("LS /", out); }
service.HandleCommand("INFO X.Y.Z", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("INFO X.Y.Z", out); }
service.HandleCommand("MSG DebugService DummyFunc 0 K=V", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("MSG DebugService DummyFunc 0 K=V", out); }
// 3. Broker Active Status // 3. Broker Active Status
volatile bool active = false; volatile bool active = false;
@@ -111,12 +111,12 @@ public:
service.SetPaused(false); service.SetPaused(false);
service.Step(0u, NULL_PTR(const char8 *)); service.Step(0u, NULL_PTR(const char8 *));
// 5. VALUE command (NULL client — smoke test, no crash) // 5. VALUE command — smoke test, no crash
service.HandleCommand("VALUE X.Y.Z", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("VALUE X.Y.Z", out); }
service.HandleCommand("VALUE NoSuchSignal", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("VALUE NoSuchSignal", out); }
service.HandleCommand("STEP 1 ThreadA", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("STEP 1 ThreadA", out); }
assert(service.stepThreadFilter == "ThreadA"); assert(service.stepThreadFilter == "ThreadA");
service.HandleCommand("STEP 3", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("STEP 3", out); }
assert(service.stepThreadFilter.Size() == 0u); assert(service.stepThreadFilter.Size() == 0u);
} }
@@ -282,8 +282,8 @@ public:
for (uint32 i = 0; i < 512; i++) bigBuf[i] = (uint8)(i & 0xFF); for (uint32 i = 0; i < 512; i++) bigBuf[i] = (uint8)(i & 0xFF);
service.RegisterSignal(bigBuf, UnsignedInteger8Bit, "Big.Signal", 1, 512); service.RegisterSignal(bigBuf, UnsignedInteger8Bit, "Big.Signal", 1, 512);
// VALUE command with NULL client is a smoke test must not crash or loop // VALUE command smoke test, must not crash or loop
service.HandleCommand("VALUE Signal", NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand("VALUE Signal", out); }
printf(" -> PASS: VALUE on large array completed without hang\n"); printf(" -> PASS: VALUE on large array completed without hang\n");
// Verify directly: nElem is capped before the byte limit // Verify directly: nElem is capped before the byte limit
@@ -334,12 +334,11 @@ public:
cmd += "=value"; cmd += "=value";
// Must not crash or assert // Must not crash or assert
service.HandleCommand(cmd, NULL_PTR(BasicTCPSocket*)); { StreamString out; service.HandleCommand(cmd, out); }
printf(" -> PASS: oversized key handled without buffer overflow\n"); printf(" -> PASS: oversized key handled without buffer overflow\n");
// A normal-length key should still be accepted (regression check) // A normal-length key should still be accepted (regression check)
service.HandleCommand("MSG DebugService DummyFunc 0 NormalKey=NormalValue", { StreamString out; service.HandleCommand("MSG DebugService DummyFunc 0 NormalKey=NormalValue", out); }
NULL_PTR(BasicTCPSocket*));
printf(" -> PASS: normal key not rejected by bounds check\n"); printf(" -> PASS: normal key not rejected by bounds check\n");
} }