Implemented common base for debug services
This commit is contained in:
@@ -3,293 +3,104 @@
|
||||
|
||||
#include "BasicTCPSocket.h"
|
||||
#include "BasicUDPSocket.h"
|
||||
#include "ConfigurationDatabase.h"
|
||||
#include "DebugServiceI.h"
|
||||
#include "DebugServiceBase.h"
|
||||
#include "EmbeddedServiceMethodBinderI.h"
|
||||
#include "MessageI.h"
|
||||
#include "ReferenceContainer.h"
|
||||
#include "ReferenceT.h"
|
||||
#include "SingleThreadService.h"
|
||||
|
||||
namespace MARTe {
|
||||
|
||||
class DataSourceI;
|
||||
|
||||
|
||||
/**
|
||||
* @brief TCP/UDP implementation of DebugServiceI.
|
||||
* @brief TCP/UDP implementation of DebugServiceI (via DebugServiceBase).
|
||||
*
|
||||
* Provides signal tracing (UDP), forced-value injection, break conditions,
|
||||
* and a text command channel (TCP) with a log-forwarding sidecar (TcpLogger).
|
||||
* Alternative transports (TTY, WebSocket, …) can be added by implementing
|
||||
* DebugServiceI without touching this class or any broker wrapper code.
|
||||
* All signal management and command dispatch logic lives in DebugServiceBase.
|
||||
*/
|
||||
class DebugService : public ReferenceContainer,
|
||||
class DebugService : public DebugServiceBase,
|
||||
public MessageI,
|
||||
public EmbeddedServiceMethodBinderI,
|
||||
public DebugServiceI {
|
||||
public EmbeddedServiceMethodBinderI {
|
||||
public:
|
||||
friend class DebugServiceTest;
|
||||
CLASS_REGISTER_DECLARATION()
|
||||
friend class DebugServiceTest;
|
||||
CLASS_REGISTER_DECLARATION()
|
||||
|
||||
DebugService();
|
||||
virtual ~DebugService();
|
||||
DebugService();
|
||||
virtual ~DebugService();
|
||||
|
||||
virtual bool Initialise(StructuredDataI &data);
|
||||
virtual bool Initialise(StructuredDataI &data);
|
||||
|
||||
// DebugServiceI RT-path overrides
|
||||
virtual DebugSignalInfo *RegisterSignal(void *memoryAddress, TypeDescriptor type,
|
||||
const char8 *name,
|
||||
uint8 numberOfDimensions = 0,
|
||||
uint32 numberOfElements = 1);
|
||||
virtual void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size,
|
||||
uint64 timestamp);
|
||||
virtual void RegisterBroker(DebugSignalInfo **signalPointers, uint32 numSignals,
|
||||
MemoryMapBroker *broker, volatile bool *anyActiveFlag,
|
||||
Vec<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 *));
|
||||
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info);
|
||||
virtual ErrorManagement::ErrorType HandleMessage(ReferenceT<Message> &data);
|
||||
|
||||
// DebugServiceI control-path overrides
|
||||
virtual uint32 ForceSignal (const char8 *name, const char8 *valueStr);
|
||||
virtual uint32 UnforceSignal(const char8 *name);
|
||||
virtual uint32 TraceSignal (const char8 *name, bool enable, uint32 decimation = 1);
|
||||
virtual uint32 SetBreak (const char8 *name, uint8 op, float64 threshold);
|
||||
virtual uint32 ClearBreak (const char8 *name);
|
||||
virtual bool IsInstrumented(const char8 *fullPath, bool &traceable, bool &forcable);
|
||||
virtual uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs);
|
||||
virtual uint32 UnmonitorSignal (const char8 *path);
|
||||
|
||||
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info);
|
||||
virtual ErrorManagement::ErrorType HandleMessage(ReferenceT<Message> &data);
|
||||
|
||||
// TCP-transport-specific methods (not part of DebugServiceI)
|
||||
void GetStepStatus(BasicTCPSocket *client);
|
||||
void Step(uint32 n, const char8 *threadName = NULL_PTR(const char8 *));
|
||||
void GetSignalValue(const char8 *name, BasicTCPSocket *client);
|
||||
void Discover(BasicTCPSocket *client);
|
||||
void InfoNode(const char8 *path, BasicTCPSocket *client);
|
||||
void ListNodes(const char8 *path, BasicTCPSocket *client);
|
||||
void ServeConfig(BasicTCPSocket *client);
|
||||
void SetFullConfig(ConfigurationDatabase &config);
|
||||
void RebuildConfigFromRegistry();
|
||||
|
||||
struct MonitoredSignal {
|
||||
ReferenceT<DataSourceI> dataSource;
|
||||
uint32 signalIdx;
|
||||
uint32 internalID;
|
||||
uint32 periodMs;
|
||||
uint64 lastPollTime;
|
||||
uint32 size;
|
||||
StreamString path;
|
||||
};
|
||||
protected:
|
||||
// Transport-specific overrides of DebugServiceBase hooks
|
||||
virtual void GetServiceInfo(StreamString &out);
|
||||
virtual void RebuildTransportConfig();
|
||||
|
||||
private:
|
||||
void HandleCommand(StreamString cmd, BasicTCPSocket *client);
|
||||
void UpdateBrokersActiveStatus();
|
||||
void UpdateBrokersBreakStatus();
|
||||
void InjectTcpLoggerIfNeeded();
|
||||
void InjectTcpLoggerIfNeeded();
|
||||
|
||||
uint32 ExportTree(ReferenceContainer *container, StreamString &json, const char8 *pathPrefix);
|
||||
void PatchRegistry();
|
||||
ErrorManagement::ErrorType Server (ExecutionInfo &info);
|
||||
ErrorManagement::ErrorType Streamer(ExecutionInfo &info);
|
||||
|
||||
void EnrichWithConfig(const char8 *path, StreamString &json);
|
||||
static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json);
|
||||
// TCP/UDP-specific configuration
|
||||
uint16 controlPort;
|
||||
uint16 streamPort;
|
||||
uint16 logPort;
|
||||
StreamString streamIP;
|
||||
bool isServer;
|
||||
bool suppressTimeoutLogs;
|
||||
|
||||
ErrorManagement::ErrorType Server(ExecutionInfo &info);
|
||||
ErrorManagement::ErrorType Streamer(ExecutionInfo &info);
|
||||
BasicTCPSocket tcpServer;
|
||||
BasicUDPSocket udpSocket;
|
||||
|
||||
uint16 controlPort;
|
||||
uint16 streamPort;
|
||||
uint16 logPort;
|
||||
StreamString streamIP;
|
||||
bool isServer;
|
||||
bool suppressTimeoutLogs;
|
||||
volatile bool isPaused;
|
||||
volatile uint32 stepRemaining;
|
||||
StreamString pausedAtGam;
|
||||
StreamString stepThreadFilter; // empty = all threads; non-empty = only this OS thread name
|
||||
class ServiceBinder : public EmbeddedServiceMethodBinderI {
|
||||
public:
|
||||
enum ServiceType { ServerType, StreamerType };
|
||||
ServiceBinder(DebugService *parent, ServiceType type)
|
||||
: parent(parent), type(type) {}
|
||||
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info) {
|
||||
if (type == StreamerType) return parent->Streamer(info);
|
||||
return parent->Server(info);
|
||||
}
|
||||
private:
|
||||
DebugService *parent;
|
||||
ServiceType type;
|
||||
};
|
||||
|
||||
BasicTCPSocket tcpServer;
|
||||
BasicUDPSocket udpSocket;
|
||||
ServiceBinder binderServer;
|
||||
ServiceBinder binderStreamer;
|
||||
SingleThreadService threadService;
|
||||
SingleThreadService streamerService;
|
||||
|
||||
class ServiceBinder : public EmbeddedServiceMethodBinderI {
|
||||
public:
|
||||
enum ServiceType { ServerType, StreamerType };
|
||||
ServiceBinder(DebugService *parent, ServiceType type)
|
||||
: parent(parent), type(type) {}
|
||||
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info) {
|
||||
if (type == StreamerType) {
|
||||
return parent->Streamer(info);
|
||||
}
|
||||
return parent->Server(info);
|
||||
}
|
||||
ThreadIdentifier serverThreadId;
|
||||
ThreadIdentifier streamerThreadId;
|
||||
|
||||
private:
|
||||
DebugService *parent;
|
||||
ServiceType type;
|
||||
};
|
||||
// activeClient — exclusively owned by Server thread
|
||||
FastPollingMutexSem clientMutex;
|
||||
BasicTCPSocket *activeClient;
|
||||
|
||||
ServiceBinder binderServer;
|
||||
ServiceBinder binderStreamer;
|
||||
// Streamer assembly buffer
|
||||
static const uint32 STREAMER_MTU = 1400u;
|
||||
static const uint32 STREAMER_BUFFER_SIZE = 4096u;
|
||||
uint8 streamerPacketBuffer[STREAMER_BUFFER_SIZE];
|
||||
uint32 streamerPacketOffset;
|
||||
uint32 streamerSequenceNumber;
|
||||
|
||||
SingleThreadService threadService;
|
||||
SingleThreadService streamerService;
|
||||
// Rate-limiting and idle-timeout constants / state
|
||||
static const uint32 CMD_RATE_LIMIT = 100u;
|
||||
static const uint32 CLIENT_IDLE_TIMEOUT_MS = 30000u;
|
||||
static const uint32 INPUT_BUFFER_MAX = 8192u;
|
||||
|
||||
ThreadIdentifier serverThreadId;
|
||||
ThreadIdentifier streamerThreadId;
|
||||
uint32 cmdCountInWindow;
|
||||
uint64 cmdWindowStartMs;
|
||||
uint64 lastDataTimeMs;
|
||||
|
||||
Vec<DebugSignalInfo *> signals;
|
||||
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;
|
||||
|
||||
BasicTCPSocket *activeClient;
|
||||
|
||||
/**
|
||||
* Maximum payload bytes in a single UDP datagram (leave room for IP+UDP headers
|
||||
* on a standard 1500-byte Ethernet MTU). Used in the Streamer to decide when
|
||||
* to flush the current packet and start a new one.
|
||||
*
|
||||
* FIX #1 — named constant to replace the magic number 1400.
|
||||
*/
|
||||
static const uint32 STREAMER_MTU = 1400u;
|
||||
|
||||
/**
|
||||
* Size of the streamer assembly buffer. Must be >= STREAMER_MTU so at least
|
||||
* one full UDP payload can be staged before flushing. A sample that on its own
|
||||
* would exceed (STREAMER_BUFFER_SIZE - sizeof(TraceHeader) - 16) bytes is
|
||||
* silently dropped and logged — it can never fit in any packet.
|
||||
*
|
||||
* FIX #1 — named constant; prevents silent integer arithmetic relying on
|
||||
* the array bound being "obviously 4096".
|
||||
*/
|
||||
static const uint32 STREAMER_BUFFER_SIZE = 4096u;
|
||||
|
||||
// Streamer state persisted across Execute() calls (framework loops Execute)
|
||||
uint8 streamerPacketBuffer[STREAMER_BUFFER_SIZE];
|
||||
uint32 streamerPacketOffset;
|
||||
uint32 streamerSequenceNumber;
|
||||
|
||||
/**
|
||||
* @brief Maximum commands allowed per 1-second sliding window per client.
|
||||
*
|
||||
* A client sending more than this many commands in one second is disconnected.
|
||||
* This prevents a tight command loop from monopolising the Server thread and
|
||||
* starving normal usage. The limit is intentionally generous (100/s) so that
|
||||
* legitimate scripted tooling is unaffected.
|
||||
*
|
||||
* FIX #6 — TCP command rate limiting.
|
||||
*/
|
||||
static const uint32 CMD_RATE_LIMIT = 100u;
|
||||
|
||||
/**
|
||||
* @brief Close the active TCP client if no data is received for this many ms.
|
||||
*
|
||||
* A client that sends a partial command and never completes it would otherwise
|
||||
* hold the single active-client slot open indefinitely, blocking all other
|
||||
* connections. After CLIENT_IDLE_TIMEOUT_MS of silence the connection is
|
||||
* closed and the slot freed.
|
||||
*
|
||||
* FIX #8 — active-client idle timeout.
|
||||
*/
|
||||
static const uint32 CLIENT_IDLE_TIMEOUT_MS = 30000u;
|
||||
|
||||
/**
|
||||
* @brief Maximum array elements returned by GetSignalValue / VALUE command.
|
||||
*
|
||||
* Without a cap, a 1-million-element uint8 array would produce a multi-MB
|
||||
* response string, exhausting heap and blocking the Server thread. Elements
|
||||
* beyond this limit are omitted; the response includes a "Truncated" flag.
|
||||
*
|
||||
* FIX #9 — GetSignalValue unbounded array output.
|
||||
*/
|
||||
static const uint32 GET_VALUE_MAX_ELEMENTS = 256u;
|
||||
|
||||
// Rate-limiter state (per active TCP connection, reset on each new connection)
|
||||
uint32 cmdCountInWindow; // commands received in the current 1-second window
|
||||
uint64 cmdWindowStartMs; // start of the current window, in milliseconds
|
||||
|
||||
// Idle-timeout state
|
||||
uint64 lastDataTimeMs; // last time the active client sent any data (ms)
|
||||
|
||||
/**
|
||||
* @brief Carry-over buffer for multi-segment TCP commands.
|
||||
*
|
||||
* A single TCP Read() call may return only part of a command if the OS
|
||||
* delivers it in multiple segments. Bytes after the last '\n' in the
|
||||
* current receive window are saved here and prepended to the next Read().
|
||||
* This ensures that "FORCE Signal " arriving in one segment and "123\n"
|
||||
* arriving in the next are assembled into the single command
|
||||
* "FORCE Signal 123" before dispatch.
|
||||
*
|
||||
* The buffer is bounded by INPUT_BUFFER_MAX (8 KiB). If a client sends
|
||||
* more than that without a newline it is disconnected.
|
||||
*
|
||||
* FIX #10 — incomplete input buffer handling for multi-line commands.
|
||||
*/
|
||||
StreamString inputBuffer;
|
||||
|
||||
/**
|
||||
* @brief Maximum carry-over bytes allowed before a client is disconnected.
|
||||
*
|
||||
* A client that sends a very long line without a newline would otherwise
|
||||
* grow inputBuffer without bound. At 8 KiB the limit is generous enough
|
||||
* for any legitimate command while still protecting against memory exhaustion.
|
||||
*
|
||||
* FIX #10 — DoS guard for the input carry-over buffer.
|
||||
*/
|
||||
static const uint32 INPUT_BUFFER_MAX = 8192u;
|
||||
|
||||
ConfigurationDatabase fullConfig;
|
||||
bool manualConfigSet;
|
||||
// Carry-over buffer for multi-segment TCP commands
|
||||
StreamString inputBuffer;
|
||||
};
|
||||
|
||||
} // namespace MARTe
|
||||
|
||||
#endif
|
||||
#endif // DEBUGSERVICE_H
|
||||
|
||||
Reference in New Issue
Block a user