From 1ddb4fe356ec260735395c36ea145a83d98553a8 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Sun, 9 Aug 2026 18:51:51 +0200 Subject: [PATCH] Implemented hearthbit client side + tests --- AGENTS.md | 470 ++++++++---------- Common/Client/go/udpsprotocol/protocol.go | 14 + Common/Client/go/wshub/keepalive_test.go | 69 +++ Common/Client/go/wshub/sources.go | 71 ++- .../UDPStreamerClient/UDPStreamerClient.cpp | 11 + .../UDPStreamerClient/UDPStreamerClient.h | 1 + .../Interfaces/UDPStream/UDPSClient.cpp | 38 ++ .../Interfaces/UDPStream/UDPSClient.h | 10 + Test/GTest/Makefile.inc | 3 +- Test/GTest/UDPSClientGTest.cpp | 296 +++++++++++ Test/GTest/depends.x86-linux | 148 ++++++ Test/GTest/dependsRaw.x86-linux | 148 ++++++ 12 files changed, 994 insertions(+), 285 deletions(-) create mode 100644 Common/Client/go/wshub/keepalive_test.go create mode 100644 Test/GTest/UDPSClientGTest.cpp diff --git a/AGENTS.md b/AGENTS.md index ca41078..55df71e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,313 +1,241 @@ -# AGENTS.md +# Repository Guidelines -Guide for agents working in the MARTe2 Integrated Components repository. Read -`ARCHITECTURE.md` and `CLAUDE.md` for deeper detail; this file focuses on -non-obvious knowledge, commands, and conventions that are not self-evident from -a single file read. +Guide for AI assistants working in the MARTe2 Integrated Components repository. +Focuses on non-obvious facts: commands, conventions, cross-module contracts, and +gotchas that are not self-evident from a single file read. ---- +## Project Overview -## Repository at a glance +MARTe2 component library with **two independent real-time data paths** sharing +one binary wire protocol (`Common/UDP/UDPSProtocol.h`): -MARTe2 component library with two independent real-time data paths: - -1. **Streaming path** — `UDPStreamer` DataSource (serialises signals to UDPS - binary packets on UDP 44500) → `StreamHub` (headless C++ hub: ring buffers, - LTTB decimation, trigger FSM, history writer) → WebSocket 8090 → clients - (browser SPA, native ImGui, native Qt). -2. **Debug path** — `DebugService` Interface patches `ClassRegistryDatabase` at +1. **Streaming path** — `UDPStreamer` DataSource serialises DDB signals into UDPS + binary packets on UDP → `StreamHub` (headless C++ hub: ring buffers, LTTB + decimation, trigger FSM, history writer, binary recorder) → WebSocket 8090 → + clients (browser SPA, native ImGui, native Qt). +2. **Debug path** — `DebugService` patches `ClassRegistryDatabase` at `Initialise()` so `ConfigureApplication()` wraps all `MemoryMap*Broker` types - with `DebugBrokerWrapper`. Zero application code changes. Exposes TCP 8080 - (text commands), UDP 8081 (trace telemetry), TCP 8082 (`TcpLogger`). + with `DebugBrokerWrapper` — **zero application code changes**. Exposes + TCP 8080 (text commands), UDP 8081 (UDPS trace telemetry), TCP 8082 + (`TcpLogger` log forward). -Key shared artefact: `Common/UDP/UDPSProtocol.h` — the UDPS binary wire format -(17-byte packed header, 136-byte signal descriptors, little-endian). It is -deliberately MARTe2-free and is mirrored across **four** codebases that must stay -in sync: C++ producers (`UDPStreamer`, `DebugService`), C++ consumer -(`Source/Components/Interfaces/UDPStream/UDPSClient`), Go decoder -(`Common/Client/go/udpsprotocol`), and the JS client parsers. Any protocol -change must be mirrored in all of them. +## Architecture & Data Flow -StreamHub WebSocket protocol: JSON text frames for commands/events, binary -frames for data pushes (spec in `ARCHITECTURE.md` §6). The Go hub -(`Client/udpstreamer`) and the C++ StreamHub implement the identical protocol; -browser JS, ImGui, and Qt clients must stay compatible with both. +``` +[SineArrayGAM/TimeArrayGAM] → DDB → UDPStreamer (UDPS over UDP) + ├─→ UDPStreamerClient (input DS back into a MARTe2 RT app, round-trip) + └─→ StreamHub: UDPSourceSession (receive thread → SignalRingBuffer) + → push loop @30Hz: LTTB decimate temporal sigs → WS binary frames → clients +DebugService: patches broker builders at Initialise(); TCP 8080 commands, +UDP 8081 telemetry, TcpLogger 8082 (REPORT_ERROR → "LOG " lines) +``` ---- +- **Wire protocol**: `Common/UDP/UDPSProtocol.h` is the canonical spec (17-byte + packed header, magic `0x53504455` 'UDPS', 136-byte signal descriptors, + CONFIG/DATA/ACK/CONNECT/DISCONNECT packet types, quant/time/publish modes). + Deliberately MARTe2-free so Go clients reuse it. **Mirrored across four + codebases that must stay in sync**: C++ producers (UDPStreamer, DebugService), + C++ consumer (`Source/Components/Interfaces/UDPStream/UDPSClient`), Go decoder + (`Common/Client/go/udpsprotocol/protocol.go`), and JS parsers + (`Client/udpstreamer/static/`, `Client/debugger/static/`). Any protocol change + must be mirrored in all of them. +- **WS protocol** has two implementations — Go hub (`Common/Client/go/wshub`) and + C++ StreamHub — that must behave identically; every client (SPA, ImGui, Qt) + must satisfy both. JSON text frames for commands/events (`addSource`, + `removeSource`, `setTrigger`, `arm`, `zoom`, `historyZoom`, `recStart`…), binary + frames for data pushes (live v1 + trigger capture v2). +- **Threading model**: RT threads only spinlock+memcpy (`FastPollingMutexSem`); + all socket I/O, fragmentation, and reassembly lives on background + `SingleThreadService` threads. StreamHub: per-session UDPSClient receive + threads + WS accept/read threads + one push loop. +- **DebugService patching**: `PatchRegistry()` replaces the ObjectBuilder for 11 + `MemoryMap*Broker` classes; runs only when `ControlPort > 0`; static guard + against double-patching; wrappers persist for process lifetime. -## Environment setup (do this first, always) +## Key Directories -`source env.sh` is **required** before building *or* running anything. It sets: +| Path | Purpose | +|---|---| +| `Source/Components/DataSources/UDPStreamer/` | Output DataSource; UDP I/O on bg thread, RT thread only spinlock+memcpy in `Synchronise()` | +| `Source/Components/DataSources/UDPStreamerClient/` | Input DataSource (shared `UDPSClient`), double-buffered ready/scratch | +| `Source/Components/GAMs/` | `SineArrayGAM` (float32 sine, continuous phase), `TimeArrayGAM` (us-timer → per-sample timestamp array) | +| `Source/Components/Interfaces/DebugService/` | Registry patching, `DebugBrokerWrapper.h`, TCP/UDP services | +| `Source/Components/Interfaces/TCPLogger/` | `LoggerConsumerI` forwarding `REPORT_ERROR` to ≤8 TCP clients | +| `Source/Components/Interfaces/UDPStream/` | Plain-C++ helpers (not MARTe2 Objects): `UDPSClient` (auto-reconnect + fragment reassembly), `UDPSServer` (not thread-safe — owner's Execute thread only) | +| `Source/Applications/StreamHub/` | Standalone app (links MARTe2 core): `StreamHub`, `UDPSourceSession`, `WSServer`, `TriggerEngine`, `HistoryWriter`, `BinaryRecorder`, `LTTB`, `SignalRingBuffer` | +| `Common/UDP/` | Canonical wire protocol (header-only, MARTe2-free) | +| `Common/Client/go/` | Go mirror: `udpsprotocol` (decoder), `wshub` (WS hub client) | +| `Client/udpstreamer/` | Go legacy direct-UDP oscilloscope web UI (connects straight to UDPStreamer, no StreamHub) | +| `Client/webui/` | Go thin static server; SPA talks WS directly to C++ StreamHub (discovers via `GET /hub`) | +| `Client/debugger/` | Go debug web UI for DebugService | +| `Client/streamhub/` | Native ImGui+SDL2+OpenGL oscilloscope (C++17, no MARTe2) | +| `Client/streamhub-qt/` | Native Qt Widgets oscilloscope (Qt6 preferred, Qt5 fallback) | +| `Test/` | GTest, legacy Integration tests, Configurations (.cfg), E2E suite | +| `Docs/` | Per-component reference: `Protocol.md`, `UDPStreamer.md`, `StreamHub-{API,UserGuide,Developer}.md`, `DebugService.md`, `WebUI.md`, `Tutorial.md`, `E2E-Suite.md` | -- `MARTe2_DIR` (default `~/workspace/MARTe2`) and `MARTe2_Components_DIR` - (default `~/workspace/MARTe2-components`) — external dependencies, not in - this repo. Edit `env.sh` if your MARTe2 install lives elsewhere. -- `TARGET=x86-linux` -- `LD_LIBRARY_PATH` — prepends MARTe2 Core, MARTe2-components (LinuxTimer, - FileDataSource), and this repo's built `.so`s (UDPStreamer, SineArrayGAM, - TimeArrayGAM, DebugService, TCPLogger). +## Development Commands -Without sourcing `env.sh`, builds fail (can't find `MakeDefaults`) and binaries -fail to load shared libs. The E2E scripts source it themselves, but a bare -`make` or `./MainGTest.ex` from a fresh shell will not. - ---- - -## Build commands - -All C++ builds use the MARTe2 `Makefile.gcc` wrapper system. There is one -top-level `Makefile.gcc` and one per component directory. `TARGET` defaults to -`x86-linux`; pass `TARGET=x86-linux` explicitly when invoking `make -C` from -elsewhere. +`source env.sh` is **mandatory** before any MARTe2 build or run (sets +`MARTe2_DIR`, `MARTe2_Components_DIR`, `TARGET=x86-linux`, `LD_LIBRARY_PATH`). +The E2E scripts source it themselves; a bare `make` from a fresh shell will not +work. `run_streamhub.sh` hard-errors if `MARTe2_DIR` is unset. ```bash source env.sh -make -f Makefile.gcc core # all C++ MARTe2 components (UDPStreamer, GAMs, DebugService, TCPLogger, UDPStream) -make -f Makefile.gcc apps # StreamHub standalone app -make -f Makefile.gcc test # build GTest + Integration test binaries +make -f Makefile.gcc core # 7 components (UDPStream interface FIRST, then UDPStreamer, UDPStreamerClient, GAMs, TCPLogger, DebugService) +make -f Makefile.gcc apps # StreamHub standalone app → Build/x86-linux/StreamHub/StreamHub.ex +make -f Makefile.gcc test # GTest + Integration test binaries + component test libs +make -f Makefile.gcc all # core + apps + test make -f Makefile.gcc clean -make -f Makefile.gcc # = all: core apps test -# Build a single component (each component dir has its own Makefile.gcc): -make -C Source/Components/DataSources/UDPStreamer -f Makefile.gcc -make -C Source/Applications/StreamHub -f Makefile.gcc +# Single component: +make -C Source/Components/GAMs/SineArrayGAM -f Makefile.gcc ``` -Build output goes to `Build/x86-linux/` — shared libs under -`Build/x86-linux/Components/<...>/`, the StreamHub executable at -`Build/x86-linux/StreamHub/StreamHub.ex`, test binaries under -`Build/x86-linux/GTest/` and `Build/x86-linux/Test/Integration/`. +Build output → `Build/x86-linux/` mirroring `PACKAGE` paths (both `libX.so` and +`X.so` are produced). `compile_commands.json` (repo root, gitignored) feeds +LSP/clangd; CMake clients export their own into `Client/*/build/`. -`compile_commands.json` is generated at the repo root (gitignored) for LSP / -clangd. The CMake-based clients (ImGui, Qt) also export it into their `build/`. - -### Non-MARTe2 clients (separate build systems, no env.sh needed) +### Non-MARTe2 clients (no env.sh needed) ```bash -# Go clients (UDPS web client, debug client, E2E chain-client) cd Common/Client/go && go build ./... -cd Client/debugger && go build ./... -cd Client/udpstreamer && go build ./... # or: cd Client/webui && go build -cd Test/E2E/suite/client && go build ./... # chain-client (E2E driver) + its tests - -# ImGui desktop client (needs SDL2; fetches Dear ImGui + ImPlot via FetchContent) +cd Client/debugger && go build ./... cd Client/streamhub && cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build - -# Qt desktop client (Qt5 or Qt6 Widgets + WebSockets; autodetects, prefers Qt6) cd Client/streamhub-qt && cmake -B build && cmake --build build ``` ---- +### Key scripts -## Run / test commands - -### Unit & integration tests (C++) - -```bash -./Build/x86-linux/GTest/MainGTest.ex # UDPStreamer unit tests -./Build/x86-linux/GTest/MainGTest.ex --gtest_filter='Name*' # single test -./Build/x86-linux/Test/Integration/Integration/IntegrationTests.ex # DebugService integration tests -``` - -### Go tests - -```bash -cd Test/E2E/suite/client && go test ./... -``` - -### Python framework tests (E2E framework logic, no live stack needed) - -```bash -cd Test/E2E/suite && python3 -m unittest tests_py -``` - -### E2E / demo scripts (build + launch the full stack) - -| Script | What it does | +| Script | Purpose | |---|---| -| `./run_streamhub.sh` | Launch MARTe2 app (UDPStreamer) + StreamHub, optionally web UI (`-w`) and ImGui client (`-g`). Ports documented in its header. | -| `./Test/E2E/suite/run_e2e.sh` | **Unified E2E suite**: per-scenario generates data + cfgs, runs MARTe2+StreamHub, drives the Go `chain-client` (live/zoom/window/trigger) for `chain` scenarios plus `direct`/`recorder`/`debug`/`tcplogger` scenario kinds, runs a stress matrix, unit suites + coverage, builds a Typst PDF report. Flags: `--skip-build`, `--only `, `--pdf-only`, `--cpp-coverage`, `--skip-coverage`, `--skip-stress`, `--skip-datasources`, `--skip-recorder`, `--skip-debug`, `--skip-tcplogger`. | -| `./Test/E2E/suite/run_stress.sh` | **Capacity/stress harness**: sweeps one load axis at a time (signal size/count, subscriber fan-out, source count, WS-client count, zoom rate), gates on survival+liveness (hard) and RSS+zoom-p95 latency (soft). Flags: `--skip-build`, `--only `, `--axis `. | +| `./run_streamhub.sh` | Demo stack: build + launch MARTe2 app + StreamHub, optional web (`-w`) / ImGui (`-g`) clients. Flags `-m/-c` MARTe2 dirs, `-b TARGET`, `-p WS_PORT`, `-n MAX_POINTS` (actual default 1000000, header says 10000), `-s` skip build. Generates temp hub cfg with `+History`/`+Recorder` blocks in `/tmp`. Ctrl-C kills all. | +| `./Test/E2E/suite/run_e2e.sh` | Full E2E: 57-scenario matrix + stress + unit suites + gcov coverage + Typst PDF report. Flags: `--skip-build`, `--only `, `--pdf-only`, `--skip-coverage`, `--skip-stress`, `--skip-datasources`, `--skip-recorder`, `--skip-debug`, `--skip-tcplogger` | +| `./Test/E2E/suite/run_stress.sh` | Capacity harness: sweeps one load axis at a time (`--axis`), hard gates survival+liveness, soft gates RSS+zoom-p95 | -The E2E suite produces `Build/x86-linux/E2E/chain/` artifacts: `report_data.json`, -`history.jsonl` (per-run headline metrics for trend/regression tracking), -`trend_*.png` plots, and `E2E_Report.pdf` (compiled from `E2E_Report.typ` via -`typst`). `--cpp-coverage` triggers an instrumented `--coverage` rebuild, captures -with `lcov` (restricted to `Source/*` + `Test/*`), then restores a clean build. +## Code Conventions & Common Patterns ---- +- **No STL in `Source/Components/**` (and StreamHub)**: use `StreamString` (not + `std::string`), `FastPollingMutexSem`/`EventSem` (not `std::mutex`/threads), + fixed arrays / MARTe2 `Vector` (not `std::vector`), `REPORT_ERROR` / + `REPORT_ERROR_STATIC` macros (no exceptions). C stdlib is fine. Heap + `new`/`delete[]` is normal. STL/C++17 is fine in `Client/streamhub/` and + `Client/streamhub-qt/`. +- **RT hot-path rule**: `FastPollingMutexSem` on real-time hot paths, never OS + mutexes; RT cycle must not block on the scheduler. +- **Class registration**: `CLASS_REGISTER_DECLARATION()` in the class `public:` + section of the header; `CLASS_REGISTER(Name, "1.0")` at the end of the `.cpp` + inside `namespace MARTe`. Every component `.cpp` ends with it. +- **EUPL v1.1 license headers** on all C++ sources and `Makefile.inc` — preserve + on new files. +- **Per-component build**: each dir has one-line `Makefile.gcc` wrapper + (`include Makefile.inc`) + `Makefile.inc` declaring `OBJSX`, `PACKAGE`, + `ROOT_DIR`, `INCLUDES` (re-declared per file, ~12 MARTe2 layer dirs), + `LIBRARIES`, including `MakeStdLibDefs.$(TARGET)` then + `MakeStdLibRules.$(TARGET)`. Generated `depends.x86-linux` (gcc -MM) is + committed but **never hand-edited** — delete to regenerate. +- **Qt client**: `QT_NO_KEYWORDS` is required (reused `Protocol.h` structs have + members named `signals`); Qt classes use `Q_SIGNALS`/`Q_SLOTS`/`Q_EMIT`. Run + with long options: `--host HOST --port 8090` (single-dash misparsed). Single + GUI thread, 60 Hz QTimer repaint. +- **StreamHub config** is *not* a MARTe2 `RealTimeApplication`: `Hub = { WSPort + MaxPoints PushRate MaxPushPoints RingTemporal RingScalar +Recorder{...} + Sources={id={Label Addr Port}} }`. `+History` keys: `Directory` (required), + `DurationHours` (1), `Decimation` (1), `FlushIntervalSec` (5), + `MinDiskFreeMB` (500). `.shist` files: 64-byte header ('SHR1') + circular + (t,v) float64 pairs. +- **UDPStreamer config**: `Port` (44500; multicast data = `DataPort`, default + `Port+1`), `MaxPayloadSize` (1400), `PublishingMode` `Strict`/`Accumulate`, + per-signal `Signals={Name={Type,Unit,NumberOfDimensions,NumberOfElements, + TimeMode}}` with `TimeMode` `PacketTime`/`FirstSample`/`LastSample`/`FullArray`; + multicast needs `MulticastGroup` + `Interface`. -## Code organization +## Important Files -``` -Common/ - UDP/UDPSProtocol.h # shared UDPS binary protocol (header-only, MARTe2-free) - Client/go/ # Go packages: udpsprotocol (decoder), wshub (WS hub client) -Source/ - Components/ # MARTe2 components — NO STL allowed here - DataSources/UDPStreamer/ # real-time UDP signal streaming DataSource - DataSources/UDPStreamerClient/ # (MARTe2-side UDPS client DataSource) - GAMs/SineArrayGAM/ # sine-wave array generator GAM - GAMs/TimeArrayGAM/ # time-reference array GAM - Interfaces/DebugService/ # tracing/forcing/breakpoint Interface (registry-patched brokers) - Interfaces/TCPLogger/ # REPORT_ERROR → TCP forwarder (LoggerConsumerI) - Interfaces/UDPStream/ # UDPSClient/UDPSServer (C++ consumer/producer of UDPS) - Applications/StreamHub/ # headless C++ hub app (links MARTe2 core but follows MARTe2 style) -Client/ - debugger/ # Go web client for DebugService (browser UI in static/) - udpstreamer/ # Go web server + static SPA for UDPStreamer (legacy direct-UDP path) - webui/ # Go StreamHub WebSocket web UI (newer, hub-based) - streamhub/ # native ImGui desktop client (SDL2 + OpenGL + ImPlot, C++17, no MARTe2) - streamhub-qt/ # native Qt Widgets desktop client (C++17, Qt5/Qt6, no MARTe2) -Test/ - GTest/ # GTest harness (MainGTest.cpp) for UDPStreamer unit tests - Integration/ # DebugService integration tests (link against DebugService .so) - Components/DataSources/UDPStreamer/ # UDPStreamer unit test sources - Configurations/ # MARTe2 .cfg files for tests and demos - E2E/ - chain/ # streaming-chain E2E + stress suite (Python orchestrator + Go client) - datasources/ recorder/ streamhub/ # older per-component E2E scripts -Docs/ # per-component reference docs (Protocol, UDPStreamer, StreamHub-*, DebugService, ...) -docs/superpowers/{specs,plans}/ # design specs + implementation plans (dated) -ARCHITECTURE.md # full architecture (data flow diagrams, protocol tables, WS protocol) +- `env.sh` — environment; source first, always. +- `Makefile.gcc` / `Makefile.inc` (root) — build orchestration. +- `Common/UDP/UDPSProtocol.h` — canonical wire format; changing it triggers the + 4-way mirror checklist above. +- `Source/Applications/StreamHub/main.cpp` — hub entry (`[-cfg file.cfg] + [-port N] [-maxPoints N]`); hub **must be heap-allocated** (~128 MB, exceeds + the 8 MB stack). +- `Test/Configurations/*.cfg` — MARTe2 app configs (`$App = { Class = + RealTimeApplication }` with `+Functions`, `+DataSources`, `+States`, `+Timings` + blocks); `streamhub_demo.cfg` and `TestApp.cfg` are good templates. +- `Test/E2E/suite/{scenarios,gen_data,gen_cfg,validate_waveform,stress}.py` — + declarative scenario matrix and generators consumed identically by the Go + chain-client and validators. +- `Client/debugger/main.go` — `-addr :7777` default, `-enable-dangerous-commands` + safety gate (CR-4) for FORCE/PAUSE/RESUME/STEP/BREAK/MSG. + +## Runtime/Tooling Preferences + +- **OS**: Linux x86_64 (`TARGET=x86-linux`). External deps live outside this + repo: `MARTe2_DIR` (default `~/workspace/MARTe2`) and + `MARTe2_Components_DIR` (default `~/workspace/MARTe2-components`) — edit + `env.sh` if they differ. `env.sh`'s `LD_LIBRARY_PATH` does **not** cover + UDPStreamerClient/UDPStream lib dirs. +- **C++**: MARTe2 `Makefile.gcc` wrapper system, gtest-1.7.0 for tests. +- **Go**: `go 1.21`; modules use `replace marte2/common => ../../Common/Client/go` + (`gorilla/websocket` v1.5.1). Go binaries are gitignored. +- **ImGui client**: needs SDL2; CMake FetchContent pins Dear ImGui **v1.91.8** + + ImPlot **v0.17** (`implot_items.cpp` is a slow -O3 TU, ~2 min rebuild). +- **Qt client**: Qt6 preferred, Qt5 fallback, Widgets + WebSockets, custom + QPainter plotting (no QtCharts). +- **E2E report**: `typst compile E2E_Report.typ`; Python 3 + numpy for the suite. +- Remove `vgore.*` core dumps when you see them; they are not gitignored. + +## Testing & QA + +Four test layers; `env.sh` + built stack required for all but the standalone +ones. Only `tests_py.py`, Go tests, and the built C++ test binaries run +standalone. + +```bash +./Build/x86-linux/GTest/MainGTest.ex --gtest_filter='Name*' # C++ GTest +./Build/x86-linux/Test/Integration/Integration/IntegrationTests.ex # legacy DebugService runtime tests +cd Test/E2E/suite/client && go test ./... # Go chain-client unit tests +cd Test/E2E/suite && python3 -m unittest tests_py # framework logic, standalone ``` -### Component layout convention +- **GTest**: `MainGTest.ex` currently holds only `DebugServiceGTest` + (TraceRingBuffer SPSC, DebugSignalInfo, BreakOp). Component GTests + (`UDPStreamerGTest.cpp` ~46 cases, `StreamHubTest.a`, `UDPStreamerClientTest.a`) + compile **as libraries only — no standalone executable**. +- **Legacy IntegrationTests.ex**: 9 printf-narrated DebugService runtime tests, + always returns 0; `collect.py` parses stdout blocks. +- **E2E suite** (`run_e2e.sh`): 57 curated scenarios (s01–s57) across kinds + `chain`/`direct`/`recorder`/`debug`/`debug_pause_resume`/`tcplogger`, driven + against live MARTeApp.ex + StreamHub.ex + Go chain-client. `scenarios.py` is a + curated covering set: **every configurable UDPStreamer option value appears in + ≥1 scenario** — add a scenario when adding an option. +- **Oracle gates** (`validate_waveform.py`): **fidelity** (every received value + within `tol` of ground truth; 0 for un-quantised ints, float epsilon for + un-quantised floats, `quant_step/2 + 1e-6·range` for quantised) is the + **correctness gate**. **Shape** is a *gross* sanity gate + tracked metric + (`corr >= 0.5`, `nRMSE <= 0.30` relaxed by quant step, frequency searched + ±5% band); a correct sinusoid yields corr ~0.82–0.98, wrong frequency + collapses to ~0.00. Do **not** tighten shape into a correctness gate — + timestamp calibration (Phase-A) is pending. +- **Stress** (`run_stress.sh`): 7 axes (signal size/count/fan-out/sources/WS + clients/zoom rate), hard gates survival+liveness, soft gates RSS+zoom-p95. +- **Coverage**: `--cpp-coverage` rebuilds with gcov, captures via `lcov` + restricted to `Source/*` + `Test/*`, then restores a clean build. +- Artifacts → `Build/x86-linux/E2E/chain/`: `results.json` (XFAIL/XPASS for + `known_issue` markers), `report_data.json`, `history.jsonl`, `trend_*.png`, + `E2E_Report.pdf`; stress → `stress/stress_results.json`. -Every MARTe2 component directory contains: - -- `Makefile.gcc` — thin wrapper (`TARGET = x86-linux` then `include Makefile.inc`). -- `Makefile.inc` — real build definition: `OBJSX`, `PACKAGE`, `INCLUDES`, - `LIBRARIES`, includes `$(MARTe2_DIR)/MakeDefaults/MakeStdLibDefs.$(TARGET)` - and `MakeStdLibRules.$(TARGET)`, and `depends.$(TARGET)`. -- `depends.x86-linux` / `dependsRaw.x86-linux` — generated header dependency - files (committed; regenerated by the build). Do not hand-edit. -- `.{h,cpp}` — the implementation, with `CLASS_REGISTER(ClassName, - "1.0")` at the bottom of the `.cpp` (MARTe2 class-registration macro). - -To add a new component, copy an existing one and adapt `Makefile.inc`'s -`OBJSX` / `PACKAGE` / `INCLUDES` / `LIBRARIES`. - ---- - -## Conventions and gotchas - -### No STL in MARTe2 components - -`Source/Components/**` must not use the C++ standard library. Use MARTe2 types: -`StreamString` (not `std::string`), `FastPollingMutexSem` (not `std::mutex`), -fixed arrays / `StaticList` (not `std::vector`), MARTe2 error macros -(`REPORT_ERROR`, `REPORT_ERROR_STATIC`) instead of exceptions. Includes come -from `$(MARTe2_DIR)/Source/Core/...` (BareMetal, Scheduler, FileSystem). - -`Source/Applications/StreamHub/` links MARTe2 core and follows the same -no-STL-on-RT-paths style, but is a standalone app (not a loadable component). - -STL / C++17 is **fine** in `Client/streamhub/` and `Client/streamhub-qt/` -(framework-free C++17, no MARTe2 dependency). - -### RT hot-path mutex rule - -Use `FastPollingMutexSem` on real-time hot paths — never OS mutexes -(`pthread_mutex`, `std::mutex`). The RT cycle must not block on the scheduler. - -### Protocol change checklist - -Changing `Common/UDP/UDPSProtocol.h` (or the Go mirror) requires mirroring -across all four consumers listed in §1, plus the JS client parsers in -`Client/udpstreamer/static/`. The Go decoder lives in -`Common/Client/go/udpsprotocol/protocol.go` and re-declares the constants -(`HeaderSize`, `SigDescSize`, packet types, quant types, time modes) — it does -**not** include the C header. - -### Qt client: `QT_NO_KEYWORDS` - -`Client/streamhub-qt/` reuses `../streamhub/Protocol.{h,cpp}` and -`SignalBuffer.h` verbatim. Those structs have members named `signals`, which -collide with Qt's `signals`/`slots`/`emit` macros. The Qt build sets -`QT_NO_KEYWORDS` and all Qt classes use `Q_SIGNALS:`/`Q_SLOTS:`/`Q_EMIT`. Do -not remove that define, and do not use the lowercase Qt keywords in Qt-client -code. Single GUI thread — `QWebSocket` signals arrive on the GUI thread, no -locks; a 60 Hz `QTimer` drives repaint. Run with long options: -`./build/StreamHubQtClient --host HOST --port 8090` (single-dash `-host` is -misparsed as clustered short flags). - -### StreamHub history - -`HistoryWriter` (`Source/Applications/StreamHub/HistoryWriter.{h,cpp}`) does -disk-backed circular storage: per-signal `.shist` files with a 64-byte header -and a pre-allocated circular region of `(float64 time, float64 value)` pairs. -Configured via a `+History` block (`Directory` required; `DurationHours` -default 1, `Decimation` default 1, `FlushIntervalSec` default 5, -`MinDiskFreeMB` default 500). WS commands: `historyInfo`, `historyZoom`. - -### E2E scenario / stress matrix - -`Test/E2E/suite/scenarios.py` is a *curated covering set*: every configurable -UDPStreamer option value appears in at least one scenario, plus high-risk -interactions. `stress.py` is the capacity sibling — it sweeps one load axis at -a time and records survival/liveness (hard gates) and RSS/zoom-p95 latency -(soft gates). Both feed the same `gen_data.py` / `gen_cfg.py` generators. When -adding a new UDPStreamer option, add a scenario that exercises it and update -`validate_scenario` / the stress matrix accordingly. - -`validate_waveform.py` has two gates: **fidelity** (every received value within -`tol` of some ground-truth value; `tol` is 0 for un-quantised ints, a float -epsilon for un-quantised floats, `quant_step/2 + 1e-6·range` for quantised -floats) — this is the **correctness** gate. **Shape** (least-squares sine fit, -correlation ≥ 0.99) is a gross-sanity gate + tracked metric, pending Phase-A -timestamp calibration. Do not tighten the shape gate to a correctness gate. - -### Gitignored build artefacts - -`Build/`, `compile_commands.json`, `*.gcov`/`*.gcda`/`*.gcno` (coverage), Go -binaries (`Client/*/marte2debugger`, `udpstreamer-webui`, `streamhub-e2e`, -`Client/*/bin/`), and the `udp_standalone_webui/` scratch dir are all -gitignored. `vgore.*` core dumps (e.g. `vgcore.26968`) are not — remove them. - ---- - -## Documentation map - -- `ARCHITECTURE.md` — full architecture: data-flow diagrams, UDPS packet/header - tables, CONFIG payload, StreamHub WebSocket protocol (commands, events, - binary frames), threading model. -- `Docs/` — per-component reference docs (`Protocol.md`, `UDPStreamer.md`, - `SineArrayGAM.md`, `DebugService.md`, `StreamHub-{UserGuide,API,Developer}.md`, - `Tutorial.md`, `WebUI.md`). -- `docs/superpowers/specs/` — design specs (dated, e.g. - `2026-06-26-stress-suite-report-integration-design.md`). -- `docs/superpowers/plans/` — implementation plans paired with specs. -- `.superpowers/sdd/` — task briefs, reports, and a `progress.md` ledger for - in-flight feature work (not always present; per-branch). -- `README.md` — high-level overview + quick-start config snippets. -- `CLAUDE.md` — condensed agent guidance (overlaps with this file). - ---- - -## Ports reference (defaults) +## Ports Reference (defaults) | Port | Protocol | Component | Purpose | -|------|----------|-----------|---------| -| 44500 | UDP | UDPStreamer | scalar signals (unicast control) | -| 44501 | UDP | UDPStreamer | packed arrays (FirstSample/LastSample) | -| 44502 | UDP | UDPStreamer | packed arrays (FullArray) | +|---|---|---|---| +| 44500 | UDP | UDPStreamer | scalar signals (unicast control + data) | +| 44501/44502 | UDP | UDPStreamer | packed arrays (FirstSample/LastSample, FullArray) | | 44503 | UDP | UDPStreamer | multicast data (group 239.0.0.1) | -| 8080 | TCP | DebugService | text command channel | -| 8081 | UDP | DebugService | trace telemetry stream | +| 8080 | TCP | DebugService | text command channel (one client at a time, newline-terminated) | +| 8081 | UDP | DebugService | trace telemetry (UDPS format) | | 8082 | TCP | TcpLogger | REPORT_ERROR log forward | | 8090 | TCP/WS | StreamHub | WebSocket (commands + binary data) | -| 8080 | TCP | udpstreamer-webui | web UI listen (legacy direct-UDP path) | -| 9090 | TCP | debugger | debug web UI listen | - -Note the 8080 collision: DebugService control vs. the legacy web UI listen port. -In combined demos that run both, the scripts adjust one of them — check the -script header before assuming. - ---- - -## License - -EUPL v1.1 — license headers are present on all C++ sources. Preserve them on -new files; do not relicense. +| 7777 | TCP | Client/debugger | debug web UI (older docs say 9090; current flag is `-addr`) | +| 8080 | TCP | Client/udpstreamer, Client/webui | web UI listen (collides with DebugService in combined demos — scripts adjust) | diff --git a/Common/Client/go/udpsprotocol/protocol.go b/Common/Client/go/udpsprotocol/protocol.go index 77beb27..f213b3a 100644 --- a/Common/Client/go/udpsprotocol/protocol.go +++ b/Common/Client/go/udpsprotocol/protocol.go @@ -99,6 +99,20 @@ func BuildDisconnectPacket() []byte { }) } +// BuildAckPacket returns a 17-byte ACK datagram. Unicast clients send it +// periodically as a keepalive: UDPSServer refreshes the client's last-seen +// without re-sending CONFIG (which a repeated CONNECT would trigger). +func BuildAckPacket() []byte { + return buildHeader(PacketHeader{ + Magic: MagicUDPS, + Type: PktACK, + Counter: 0, + FragmentIdx: 0, + TotalFragments: 1, + PayloadBytes: 0, + }) +} + // ─── Signal descriptor (136 bytes) ─────────────────────────────────────────── // SignalInfo holds the parsed metadata for one signal. diff --git a/Common/Client/go/wshub/keepalive_test.go b/Common/Client/go/wshub/keepalive_test.go new file mode 100644 index 0000000..364755e --- /dev/null +++ b/Common/Client/go/wshub/keepalive_test.go @@ -0,0 +1,69 @@ +package wshub + +import ( + "net" + "testing" + "time" + + "marte2/common/udpsprotocol" +) + +// TestUDPClientSendsPeriodicKeepAliveAcks verifies that a unicast UDPClient +// re-sends ACK datagrams from the SAME socket at keepAliveInterval. The +// UDPSServer refreshes a unicast client's last-seen only on client->server +// traffic; without this keepalive it evicts the client after ClientTimeout +// (default 30 s) and the stream dies. +func TestUDPClientSendsPeriodicKeepAliveAcks(t *testing.T) { + srv, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")}) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + c := NewUDPClient(srv.LocalAddr().String(), "ka1", NewHub(), "", 0) + c.keepAliveInterval = 150 * time.Millisecond + go c.Run() + defer c.Stop() + + buf := make([]byte, 512) + + // 1) CONNECT from the client's ephemeral socket. + srv.SetReadDeadline(time.Now().Add(3 * time.Second)) + n, clientAddr, err := srv.ReadFromUDP(buf) + if err != nil { + t.Fatalf("expected CONNECT: %v", err) + } + hdr, err := udpsprotocol.ParseHeader(buf[:n]) + if err != nil { + t.Fatalf("parse CONNECT: %v", err) + } + if hdr.Type != udpsprotocol.PktConnect { + t.Fatalf("first packet type = %d, want CONNECT (%d)", hdr.Type, udpsprotocol.PktConnect) + } + + // 2) Collect ACKs for ~1 s: must be periodic and from the SAME socket + // (a new ephemeral socket would be registered as a new client). + deadline := time.Now().Add(time.Second) + acks := 0 + for time.Now().Before(deadline) { + srv.SetReadDeadline(deadline) + n, addr, err := srv.ReadFromUDP(buf) + if err != nil { + break + } + hdr, err := udpsprotocol.ParseHeader(buf[:n]) + if err != nil { + continue + } + if hdr.Type != udpsprotocol.PktACK { + t.Fatalf("unexpected packet type %d from %s", hdr.Type, addr) + } + if addr.String() != clientAddr.String() { + t.Fatalf("ACK from %s, want same socket as CONNECT (%s)", addr, clientAddr) + } + acks++ + } + if acks < 3 { + t.Fatalf("expected >= 3 keepalive ACKs in 1 s, got %d", acks) + } +} diff --git a/Common/Client/go/wshub/sources.go b/Common/Client/go/wshub/sources.go index c0dc155..c5f04b8 100644 --- a/Common/Client/go/wshub/sources.go +++ b/Common/Client/go/wshub/sources.go @@ -172,27 +172,34 @@ const ( reconnectDelay = 2 * time.Second readBufSize = 65536 udpRcvBufSize = 8 * 1024 * 1024 + // keepAliveInterval is the unicast keepalive period. The UDPStreamer + // server evicts silent unicast clients after its ClientTimeout (default + // 30 s); an ACK from the same socket refreshes its last-seen without + // triggering a CONFIG resend (a CONNECT would). + keepAliveInterval = 15 * time.Second ) // UDPClient manages the connection to one MARTe2 streamer source. type UDPClient struct { - serverAddr string - sourceID string - hub *Hub - multicastGroup string - dataPort int - stopCh chan struct{} + serverAddr string + sourceID string + hub *Hub + multicastGroup string + dataPort int + keepAliveInterval time.Duration + stopCh chan struct{} } // NewUDPClient creates a UDPClient bound to a specific source ID. func NewUDPClient(serverAddr, sourceID string, hub *Hub, multicastGroup string, dataPort int) *UDPClient { return &UDPClient{ - serverAddr: serverAddr, - sourceID: sourceID, - hub: hub, - multicastGroup: multicastGroup, - dataPort: dataPort, - stopCh: make(chan struct{}), + serverAddr: serverAddr, + sourceID: sourceID, + hub: hub, + multicastGroup: multicastGroup, + dataPort: dataPort, + keepAliveInterval: keepAliveInterval, + stopCh: make(chan struct{}), } } @@ -253,6 +260,20 @@ func (u *UDPClient) runSession() error { return err } log.Printf("[%s] udp: sent CONNECT", u.sourceID) + lastData := time.Now() + lastKeepAlive := time.Now() + // sendKeepAliveIfDue sends an ACK if the keepalive interval has elapsed. + // ACK refreshes the server's last-seen without re-sending CONFIG (which a + // repeated CONNECT would trigger). + sendKeepAliveIfDue := func() error { + if u.keepAliveInterval > 0 && time.Since(lastKeepAlive) >= u.keepAliveInterval { + if _, err := conn.WriteToUDP(udpsprotocol.BuildAckPacket(), serverAddr); err != nil { + return err + } + lastKeepAlive = time.Now() + } + return nil + } reassembler := udpsprotocol.NewReassembler(2 * time.Second) buf := make([]byte, readBufSize) @@ -260,14 +281,34 @@ func (u *UDPClient) runSession() error { var currentPublishMode uint8 for { - conn.SetReadDeadline(time.Now().Add(silenceTimeout)) + // Wake up at least every keepalive interval so ACKs are sent even + // when the server is idle; the read deadline also doubles as the + // silence detector (no data for silenceTimeout = server gone). + wakeup := silenceTimeout + if u.keepAliveInterval > 0 && u.keepAliveInterval < wakeup { + wakeup = u.keepAliveInterval + } + conn.SetReadDeadline(time.Now().Add(wakeup)) n, _, err := conn.ReadFromUDP(buf) arrivalTime := time.Now() if err != nil { + if ne, ok := err.(net.Error); ok && ne.Timeout() { + if time.Since(lastData) >= silenceTimeout { + // True silence: stream is dead — Run() reconnects. + conn.WriteToUDP(udpsprotocol.BuildDisconnectPacket(), serverAddr) + return err + } + // Short wakeup: keepalive if due, then keep waiting. + if kaErr := sendKeepAliveIfDue(); kaErr != nil { + return kaErr + } + continue + } conn.WriteToUDP(udpsprotocol.BuildDisconnectPacket(), serverAddr) return err } + lastData = arrivalTime if n < udpsprotocol.HeaderSize { log.Printf("[%s] udp: short datagram (%d bytes), skipping", u.sourceID, n) @@ -334,6 +375,10 @@ func (u *UDPClient) runSession() error { return nil default: } + + if kaErr := sendKeepAliveIfDue(); kaErr != nil { + return kaErr + } } } diff --git a/Source/Components/DataSources/UDPStreamerClient/UDPStreamerClient.cpp b/Source/Components/DataSources/UDPStreamerClient/UDPStreamerClient.cpp index 11b1910..c15ce88 100644 --- a/Source/Components/DataSources/UDPStreamerClient/UDPStreamerClient.cpp +++ b/Source/Components/DataSources/UDPStreamerClient/UDPStreamerClient.cpp @@ -55,6 +55,9 @@ static const uint16 UDPS_CLIENT_DEFAULT_DP_OFFSET = 1u; /** Default max payload per UDP datagram (bytes). */ static const uint32 UDPS_CLIENT_DEFAULT_MAX_PAYLOAD = 1400u; +/** Default unicast keepalive interval (seconds); 0 disables. */ +static const uint32 UDPS_CLIENT_DEFAULT_KEEPALIVE_INTERVAL_S = 15u; + /** Bytes prepended to each DATA payload for the HRT packet timestamp. */ static const uint32 UDPS_CLIENT_TIMESTAMP_BYTES = 8u; @@ -129,6 +132,7 @@ UDPStreamerClient::UDPStreamerClient() : serverAddress = UDPS_CLIENT_DEFAULT_ADDR; port = UDPS_CLIENT_DEFAULT_PORT; maxPayloadSize = UDPS_CLIENT_DEFAULT_MAX_PAYLOAD; + keepAliveInterval = UDPS_CLIENT_DEFAULT_KEEPALIVE_INTERVAL_S; cpuMask = 0xFFFFFFFFu; stackSize = THREADS_DEFAULT_STACKSIZE; dataPort = UDPS_CLIENT_DEFAULT_PORT + UDPS_CLIENT_DEFAULT_DP_OFFSET; @@ -201,6 +205,12 @@ bool UDPStreamerClient::Initialise(StructuredDataI &data) { } } + if (ok) { + if (!data.Read("KeepAliveInterval", keepAliveInterval)) { + keepAliveInterval = UDPS_CLIENT_DEFAULT_KEEPALIVE_INTERVAL_S; + } + } + if (ok) { if (!data.Read("CPUMask", cpuMask)) { cpuMask = 0xFFFFFFFFu; @@ -245,6 +255,7 @@ bool UDPStreamerClient::Initialise(StructuredDataI &data) { if (ok) { ok = cdb.Write("DataPort", static_cast(dataPort)); } } if (ok) { ok = cdb.Write("MaxPayloadSize", maxPayloadSize); } + if (ok) { ok = cdb.Write("KeepAliveInterval", keepAliveInterval); } if (ok) { ok = cdb.Write("CPUMask", cpuMask); } if (ok) { ok = cdb.Write("StackSize", stackSize); } if (ok) { ok = cdb.MoveToRoot(); } diff --git a/Source/Components/DataSources/UDPStreamerClient/UDPStreamerClient.h b/Source/Components/DataSources/UDPStreamerClient/UDPStreamerClient.h index 8ab8962..13c6177 100644 --- a/Source/Components/DataSources/UDPStreamerClient/UDPStreamerClient.h +++ b/Source/Components/DataSources/UDPStreamerClient/UDPStreamerClient.h @@ -174,6 +174,7 @@ private: StreamString serverAddress; /**< Server IP address. */ uint16 port; /**< Server port. */ uint32 maxPayloadSize; /**< Max payload bytes per datagram. */ + uint32 keepAliveInterval; /**< Seconds between unicast keepalive ACKs (0 disables). */ uint32 cpuMask; /**< Background thread CPU affinity. */ uint32 stackSize; /**< Background thread stack size. */ StreamString multicastGroup; /**< Multicast group IP; empty = unicast. */ diff --git a/Source/Components/Interfaces/UDPStream/UDPSClient.cpp b/Source/Components/Interfaces/UDPStream/UDPSClient.cpp index 42ac3cd..4a0887d 100644 --- a/Source/Components/Interfaces/UDPStream/UDPSClient.cpp +++ b/Source/Components/Interfaces/UDPStream/UDPSClient.cpp @@ -24,6 +24,7 @@ UDPSClient::UDPSClient() useMulticast(false), silenceTimeoutTicks(0u), reconnectDelayTicks(0u), + keepAliveIntervalTicks(0u), maxPayloadSize(UDPS_CLIENT_DEFAULT_MAX_PAYLOAD), cpuMask(0xFFFFFFFFu), stackSize(65536u), @@ -33,6 +34,7 @@ UDPSClient::UDPSClient() connected(false), lastDataTicks(0u), disconnectTick(0u), + lastKeepAliveTicks(0u), localPort(0u), lastGcTicks(0u) { @@ -93,6 +95,10 @@ bool UDPSClient::Initialise(StructuredDataI &data) { (void) data.Read("ReconnectDelay", reconnectS); reconnectDelayTicks = static_cast(reconnectS) * HighResolutionTimer::Frequency(); + uint32 keepAliveS = UDPS_CLIENT_DEFAULT_KEEPALIVE_INTERVAL_S; + (void) data.Read("KeepAliveInterval", keepAliveS); + keepAliveIntervalTicks = static_cast(keepAliveS) * HighResolutionTimer::Frequency(); + uint32 mps = UDPS_CLIENT_DEFAULT_MAX_PAYLOAD; (void) data.Read("MaxPayloadSize", mps); maxPayloadSize = mps; @@ -185,6 +191,18 @@ ErrorManagement::ErrorType UDPSClient::Execute(ExecutionInfo &info) { } } + // Unicast keepalive: UDPSServer evicts silent unicast clients after its + // ClientTimeout (default 30 s). Re-sending CONNECT would also re-trigger + // a CONFIG resend; an ACK refreshes the server's last-seen with no side + // effects, so it is the keepalive packet of choice. Multicast clients + // hold a persistent TCP control connection and need no keepalive. + if (!useMulticast && (keepAliveIntervalTicks > 0u)) { + if ((now - lastKeepAliveTicks) >= keepAliveIntervalTicks) { + SendKeepAlive(); + lastKeepAliveTicks = now; + } + } + // Periodic GC of stale reassembly slots (~every 1 s) uint64 gcFreq = HighResolutionTimer::Frequency(); if ((now - lastGcTicks) >= gcFreq) { @@ -204,6 +222,7 @@ bool UDPSClient::Connect() { if (ok) { connected = true; lastDataTicks = HighResolutionTimer::Counter(); + lastKeepAliveTicks = lastDataTicks; if (listener != NULL_PTR(UDPSClientListener *)) { listener->OnUDPSConnected(); } @@ -378,6 +397,25 @@ void UDPSClient::Disconnect() { } } +// --------------------------------------------------------------------------- +// Private: SendKeepAlive +// --------------------------------------------------------------------------- + +void UDPSClient::SendKeepAlive() { + if (useMulticast || !recvSocket.IsValid()) { + return; + } + uint8 ackPkt[UDPS_HEADER_SIZE]; + UDPSBuildHeader(ackPkt, UDPS_TYPE_ACK, 0u, 0u, 1u, 0u); + InternetHost serverDest(serverPort, serverAddr.Buffer()); + (void) recvSocket.SetDestination(serverDest); + uint32 sendSize = UDPS_HEADER_SIZE; + if (!recvSocket.Write(reinterpret_cast(ackPkt), sendSize)) { + /* Non-fatal: if the server is truly gone, the silence timeout + * triggers the usual disconnect + reconnect. */ + } +} + // --------------------------------------------------------------------------- // Private: ReceiveAndProcess // --------------------------------------------------------------------------- diff --git a/Source/Components/Interfaces/UDPStream/UDPSClient.h b/Source/Components/Interfaces/UDPStream/UDPSClient.h index 25078b4..6439b96 100644 --- a/Source/Components/Interfaces/UDPStream/UDPSClient.h +++ b/Source/Components/Interfaces/UDPStream/UDPSClient.h @@ -92,6 +92,11 @@ public: /** Default delay between reconnect attempts (seconds). */ static const uint32 UDPS_CLIENT_DEFAULT_RECONNECT_DELAY_S = 2u; + /** Default unicast keepalive interval (seconds). UDPSServer evicts silent + * unicast clients after its ClientTimeout (default 30 s); the client + * re-sends an ACK on this interval to stay registered. 0 disables. */ + static const uint32 UDPS_CLIENT_DEFAULT_KEEPALIVE_INTERVAL_S = 15u; + /** Default maximum payload size (bytes, excluding 17-byte header). */ static const uint32 UDPS_CLIENT_DEFAULT_MAX_PAYLOAD = 1400u; @@ -115,6 +120,7 @@ public: * - DataPort (uint16) UDP multicast data port (defaults to Port+1). * - SilenceTimeout (uint32) Seconds of no data before reconnect. Default 5. * - ReconnectDelay (uint32) Seconds to wait between reconnect attempts. Default 2. + * - KeepAliveInterval (uint32) Seconds between unicast keepalive ACKs. Default 15. 0 disables. * - MaxPayloadSize (uint32) Max payload bytes per datagram, excluding header. Default 1400. * - CPUMask (uint32) CPU affinity mask for the receive thread. Default 0xFFFFFFFF. * - StackSize (uint32) Stack size for the receive thread. Default 65536. @@ -168,6 +174,8 @@ private: // ------------------------------------------------------------------------- bool Connect(); void Disconnect(); + /** Send a keepalive ACK to the server (unicast only, same socket). */ + void SendKeepAlive(); bool ReceiveAndProcess(); bool ConnectUnicast(); @@ -196,6 +204,7 @@ private: bool useMulticast; uint64 silenceTimeoutTicks; uint64 reconnectDelayTicks; + uint64 keepAliveIntervalTicks; ///< 0 = keepalive disabled uint32 maxPayloadSize; uint32 cpuMask; uint32 stackSize; @@ -209,6 +218,7 @@ private: bool connected; uint64 lastDataTicks; ///< Ticks at last received DATA/CONFIG uint64 disconnectTick; ///< Ticks when we disconnected (for delay) + uint64 lastKeepAliveTicks; ///< Ticks at last keepalive ACK sent // Unicast BasicUDPSocket recvSocket; ///< Bound to ephemeral port; receives DATA diff --git a/Test/GTest/Makefile.inc b/Test/GTest/Makefile.inc index fe53853..f8d6871 100644 --- a/Test/GTest/Makefile.inc +++ b/Test/GTest/Makefile.inc @@ -46,9 +46,10 @@ INCLUDES += -I$(ROOT_DIR)/Common/UDP INCLUDES += -I$(ROOT_DIR)/Source/Components/DataSources/UDPStreamer INCLUDES += -I$(ROOT_DIR)/Source/Components/Interfaces/DebugService INCLUDES += -I$(ROOT_DIR)/Source/Components/Interfaces/TCPLogger +INCLUDES += -I$(ROOT_DIR)/Source/Components/Interfaces/UDPStream INCLUDES += -I$(ROOT_DIR)/Test/Components/DataSources/UDPStreamer -OBJSX = DebugServiceGTest.x +OBJSX = DebugServiceGTest.x UDPSClientGTest.x all: $(BUILD_DIR)/MainGTest$(EXEEXT) diff --git a/Test/GTest/UDPSClientGTest.cpp b/Test/GTest/UDPSClientGTest.cpp new file mode 100644 index 0000000..8cc3ab7 --- /dev/null +++ b/Test/GTest/UDPSClientGTest.cpp @@ -0,0 +1,296 @@ +/** + * @file UDPSClientGTest.cpp + * @brief GTest coverage for UDPSClient unicast keepalive. + * + * UDPSServer evicts silent unicast clients after its ClientTimeout (default + * 30 s). UDPSClient must therefore re-send a keepalive ACK from the same + * socket on KeepAliveInterval so the server refreshes its last-seen without + * re-sending CONFIG. These tests drive a real UDPSClient against a local + * UDP socket acting as the server and assert the wire behaviour; a final + * pair runs the REAL UDPSServer + UDPSClient past the eviction deadline to + * lock the fix in against regression. + * + * @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and + * the Development of Fusion Energy ('Fusion for Energy'). + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved + * by the European Commission - subsequent versions of the EUPL (the "Licence") + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl + * + * @warning Unless required by applicable law or agreed to in writing, + * software distributed under the Licence is distributed on an "AS IS" + * basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence permissions and limitations under the Licence. + */ + +#define DLL_API + +/*---------------------------------------------------------------------------*/ +/* Standard header includes */ +/*---------------------------------------------------------------------------*/ +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include + +/*---------------------------------------------------------------------------*/ +/* Project header includes */ +/*---------------------------------------------------------------------------*/ +#include "BasicUDPSocket.h" +#include "ConfigurationDatabase.h" +#include "InternetHost.h" +#include "Sleep.h" +#include "UDPSClient.h" +#include "UDPSProtocol.h" +#include "UDPSServer.h" + +using namespace MARTe; + +namespace { + +/** @return the bound local port of @p sock, or 0 on failure. */ +uint16 GetBoundPort(BasicUDPSocket &sock) { + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + if (getsockname(sock.GetReadHandle(), + reinterpret_cast(&addr), &len) != 0) { + return 0u; + } + return ntohs(addr.sin_port); +} + +/** + * @brief Read one datagram from @p sock within @p timeoutMs. + * @return true and fills @p type/@p srcPort on a valid UDPS datagram; false + * on timeout or malformed packet. + */ +bool WaitDatagram(BasicUDPSocket &sock, int timeoutMs, uint8 &type, + uint16 &srcPort) { + int fd = sock.GetReadHandle(); + if (fd < 0) { + return false; + } + fd_set rset; + FD_ZERO(&rset); + FD_SET(fd, &rset); + struct timeval tv; + tv.tv_sec = timeoutMs / 1000; + tv.tv_usec = (timeoutMs % 1000) * 1000; + int nready = select(fd + 1, &rset, NULL, NULL, &tv); + if (nready <= 0) { + return false; + } + uint8 buf[UDPS_HEADER_SIZE]; + uint32 size = UDPS_HEADER_SIZE; + if (!sock.Read(reinterpret_cast(buf), size)) { + return false; + } + if (size < UDPS_HEADER_SIZE) { + return false; + } + const UDPSPacketHeader *hdr = reinterpret_cast(buf); + if (hdr->magic != UDPS_MAGIC) { + return false; + } + type = hdr->type; + InternetHost src = sock.GetSource(); + srcPort = src.GetPort(); + return true; +} + +/** + * @brief Pump the UDPSServer service loop for @p durationMs, like UDPStreamer + * does from its background thread. + */ +void PumpServer(UDPSServer &server, uint32 durationMs) { + uint32 elapsed = 0u; + while (elapsed < durationMs) { + server.ServiceClients(); + Sleep::MSec(20u); + elapsed += 20u; + } +} + +/** + * @brief Pump the server loop until a client registers (or timeout). + * @return true if at least one client connected. + */ +bool WaitForClient(UDPSServer &server, uint32 timeoutMs) { + uint32 elapsed = 0u; + while (elapsed < timeoutMs) { + server.ServiceClients(); + if (server.GetClientCount() > 0u) { + return true; + } + Sleep::MSec(20u); + elapsed += 20u; + } + return false; +} + +} // namespace + +/*---------------------------------------------------------------------------*/ +/* Method definitions */ +/*---------------------------------------------------------------------------*/ + +TEST(UDPSClientGTest, TestUnicastKeepAliveSendsPeriodicAck) { + /* Fake server socket (ephemeral port) */ + BasicUDPSocket server; + ASSERT_TRUE(server.Open()); + ASSERT_TRUE(server.Listen(0u)); + uint16 serverPort = GetBoundPort(server); + ASSERT_NE(serverPort, 0u); + + ConfigurationDatabase cfg; + ASSERT_TRUE(cfg.Write("ServerAddr", "127.0.0.1")); + ASSERT_TRUE(cfg.Write("Port", static_cast(serverPort))); + ASSERT_TRUE(cfg.Write("KeepAliveInterval", 1u)); + /* SilenceTimeout=0 keeps the session stable for the whole test */ + ASSERT_TRUE(cfg.Write("SilenceTimeout", 0u)); + + UDPSClient client; + ASSERT_TRUE(client.Initialise(cfg)); + ASSERT_TRUE(client.Start()); + + /* 1) CONNECT from the client's ephemeral socket */ + uint8 type = 0xFFu; + uint16 clientPort = 0u; + ASSERT_TRUE(WaitDatagram(server, 3000, type, clientPort)); + EXPECT_EQ(type, UDPS_TYPE_CONNECT); + ASSERT_NE(clientPort, 0u); + + /* 2) Keepalive ACKs arrive periodically from the SAME socket */ + uint32 acks = 0u; + uint32 elapsedMs = 0u; + while ((acks < 2u) && (elapsedMs < 3500u)) { + uint8 t = 0xFFu; + uint16 port = 0u; + bool got = WaitDatagram(server, 1000, t, port); + elapsedMs += 1000u; + if (!got) { + continue; + } + if ((t == UDPS_TYPE_ACK) && (port == clientPort)) { + acks++; + } + } + EXPECT_GE(acks, 2u); + + client.Stop(); + server.Close(); +} + +TEST(UDPSClientGTest, TestKeepAliveDisabledWhenIntervalZero) { + BasicUDPSocket server; + ASSERT_TRUE(server.Open()); + ASSERT_TRUE(server.Listen(0u)); + uint16 serverPort = GetBoundPort(server); + ASSERT_NE(serverPort, 0u); + + ConfigurationDatabase cfg; + ASSERT_TRUE(cfg.Write("ServerAddr", "127.0.0.1")); + ASSERT_TRUE(cfg.Write("Port", static_cast(serverPort))); + ASSERT_TRUE(cfg.Write("KeepAliveInterval", 0u)); + ASSERT_TRUE(cfg.Write("SilenceTimeout", 0u)); + + UDPSClient client; + ASSERT_TRUE(client.Initialise(cfg)); + ASSERT_TRUE(client.Start()); + + uint8 type = 0xFFu; + uint16 clientPort = 0u; + ASSERT_TRUE(WaitDatagram(server, 3000, type, clientPort)); + EXPECT_EQ(type, UDPS_TYPE_CONNECT); + + /* No keepalive configured: nothing else must arrive */ + EXPECT_FALSE(WaitDatagram(server, 2000, type, clientPort)); + + client.Stop(); + server.Close(); +} + +TEST(UDPSClientGTest, TestKeepAlivePreventsServerEviction) { + /* Regression test for the 30 s unicast disconnect: UDPSServer evicts a + * silent client after ClientTimeout; the client's periodic ACKs must + * keep it registered. Drive the REAL server + client pair, like + * UDPStreamer + StreamHub do, past the eviction deadline. */ + + /* Free-port probe (UDP has no TIME_WAIT) */ + BasicUDPSocket probe; + ASSERT_TRUE(probe.Open()); + ASSERT_TRUE(probe.Listen(0u)); + uint16 serverPort = GetBoundPort(probe); + probe.Close(); + ASSERT_NE(serverPort, 0u); + + /* Server with a short eviction timeout so the test is fast */ + ConfigurationDatabase serverCfg; + ASSERT_TRUE(serverCfg.Write("Port", static_cast(serverPort))); + ASSERT_TRUE(serverCfg.Write("ClientTimeout", 3u)); + UDPSServer server; + ASSERT_TRUE(server.Initialise(serverCfg)); + ASSERT_TRUE(server.Start()); + + /* Client: keepalive every 1 s (< server timeout), silence disabled */ + ConfigurationDatabase clientCfg; + ASSERT_TRUE(clientCfg.Write("ServerAddr", "127.0.0.1")); + ASSERT_TRUE(clientCfg.Write("Port", static_cast(serverPort))); + ASSERT_TRUE(clientCfg.Write("KeepAliveInterval", 1u)); + ASSERT_TRUE(clientCfg.Write("SilenceTimeout", 0u)); + UDPSClient client; + ASSERT_TRUE(client.Initialise(clientCfg)); + ASSERT_TRUE(client.Start()); + + ASSERT_TRUE(WaitForClient(server, 3000)); /* CONNECT registered */ + EXPECT_EQ(server.GetClientCount(), 1u); + + /* Pump well past ClientTimeout: keepalive ACKs must prevent eviction */ + PumpServer(server, 5000); + EXPECT_EQ(server.GetClientCount(), 1u); + + client.Stop(); + server.Stop(); +} + +TEST(UDPSClientGTest, TestServerEvictsWithoutKeepAlive) { + /* Negative control: without keepalive the same harness MUST evict, which + * proves TestKeepAlivePreventsServerEviction passes because of the ACKs + * and not because eviction is broken. */ + + BasicUDPSocket probe; + ASSERT_TRUE(probe.Open()); + ASSERT_TRUE(probe.Listen(0u)); + uint16 serverPort = GetBoundPort(probe); + probe.Close(); + ASSERT_NE(serverPort, 0u); + + ConfigurationDatabase serverCfg; + ASSERT_TRUE(serverCfg.Write("Port", static_cast(serverPort))); + ASSERT_TRUE(serverCfg.Write("ClientTimeout", 3u)); + UDPSServer server; + ASSERT_TRUE(server.Initialise(serverCfg)); + ASSERT_TRUE(server.Start()); + + ConfigurationDatabase clientCfg; + ASSERT_TRUE(clientCfg.Write("ServerAddr", "127.0.0.1")); + ASSERT_TRUE(clientCfg.Write("Port", static_cast(serverPort))); + ASSERT_TRUE(clientCfg.Write("KeepAliveInterval", 0u)); + ASSERT_TRUE(clientCfg.Write("SilenceTimeout", 0u)); + UDPSClient client; + ASSERT_TRUE(client.Initialise(clientCfg)); + ASSERT_TRUE(client.Start()); + + ASSERT_TRUE(WaitForClient(server, 3000)); /* CONNECT registered */ + EXPECT_EQ(server.GetClientCount(), 1u); + + /* No ACKs: the server must evict after ClientTimeout */ + PumpServer(server, 5000); + EXPECT_EQ(server.GetClientCount(), 0u); + + client.Stop(); + server.Stop(); +} diff --git a/Test/GTest/depends.x86-linux b/Test/GTest/depends.x86-linux index b866640..6c96b06 100644 --- a/Test/GTest/depends.x86-linux +++ b/Test/GTest/depends.x86-linux @@ -195,3 +195,151 @@ /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-test-part.h \ /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-typed-test.h \ /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest_pred_impl.h +../../Build/x86-linux//GTest/UDPSClientGTest.o: UDPSClientGTest.cpp \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-internal.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-port.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-message.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-string.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-filepath.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-type-util.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-death-test.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-death-test-internal.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-param-test.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-param-util.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-linked_ptr.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-printers.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-param-util-generated.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest_prod.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-test-part.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-typed-test.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest_pred_impl.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/BasicUDPSocket.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/BasicSocket.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/InternetHost.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L3Streams/StreamString.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/GeneralDefinitions.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/TypeCharacteristics.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/CompilerTypes.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Architecture/x86_gcc/CompilerTypes.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/GeneralDefinitions.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HeapManager.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HeapI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/ErrorManagement.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/ErrorInformation.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/ErrorType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BitBoolean.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/TemplateParametersVerificator.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BitRange.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/TypeCharacteristics.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/GeneralDefinitions.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StreamI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/TimeoutType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HighResolutionTimer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/TimeStamp.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Architecture/x86_gcc/HighResolutionTimerA.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/CompilerTypes.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/HighResolutionTimer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/../../HighResolutionTimer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/HighResolutionTimerCalibrator.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/../../GeneralDefinitions.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/TimeStamp.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L3Streams/CharBuffer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BitBoolean.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/ErrorManagement.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L3Streams/BufferedStreamI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/TimeoutType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/AnyType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ClassProperties.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/FractionalInteger.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ClassRegistryDatabase.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/GlobalObjectsDatabase.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/GlobalObjectI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/StandardHeap.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/../../HeapI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/../Generic/StandardHeap_Generic.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/FastPollingMutexSem.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Atomic.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Architecture/x86_gcc/AtomicA.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Sleep.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ClassRegistryItem.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/CString.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/ZeroTerminatedArray.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/Introspection.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/IntrospectionEntry.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/TypeDescriptor.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BasicType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/FractionalInteger.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BitRange.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/ZeroTerminatedArray.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/LinkedListable.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/LinkedListHolderT.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/IteratorT.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Iterator.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/LinkedListable.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/LinkedListHolder.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/SortFilter.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/SearchFilter.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/LoadableLibrary.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ObjectBuilder.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HeapI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StaticList.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StaticListHolder.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Matrix.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Vector.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/MemoryOperationsHelper.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/FormatDescriptor.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L3Streams/IOBuffer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StreamI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StringHelper.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L3Streams/StreamStringIOBuffer.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/Environment/Linux/InternetHostCore.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/Environment/Linux/InternetMulticastCore.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HandleI.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/Environment/Linux/SocketCore.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Sleep.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L4Configuration/ConfigurationDatabase.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L4Configuration/AnyObject.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/Object.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/StructuredDataI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/AnyType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/CLASSREGISTER.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ClassRegistryItemT.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ObjectBuilderT.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L4Configuration/ConfigurationDatabaseNode.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Fnv1aHashFunction.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/HashFunction.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceContainer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/LinkedListHolder.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/Object.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/Reference.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceContainerFilter.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceContainerNode.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceT.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/UnorderedMap.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HeapManager.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StringHelper.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceContainerFilterObjectName.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceContainer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/StructuredDataI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L4Configuration/TypeConversion.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Vector.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/InternetHost.h \ + ../../Source/Components/Interfaces/UDPStream/UDPSClient.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/BasicTCPSocket.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceMethodBinderI.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/ExecutionInfo.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L1Portability/Threads.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/ThreadsB.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L1Portability/ExceptionHandler.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/ProcessorType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BitSet.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/ExecutionInfo.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HighResolutionTimer.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/SingleThreadService.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceI.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceMethodBinderI.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceMethodBinderT.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedThreadI.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedThread.h \ + ../../Common/UDP/UDPSProtocol.h diff --git a/Test/GTest/dependsRaw.x86-linux b/Test/GTest/dependsRaw.x86-linux index 58a6c46..8c9afa3 100644 --- a/Test/GTest/dependsRaw.x86-linux +++ b/Test/GTest/dependsRaw.x86-linux @@ -195,3 +195,151 @@ MainGTest.o: MainGTest.cpp \ /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-test-part.h \ /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-typed-test.h \ /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest_pred_impl.h +UDPSClientGTest.o: UDPSClientGTest.cpp \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-internal.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-port.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-message.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-string.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-filepath.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-type-util.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-death-test.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-death-test-internal.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-param-test.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-param-util.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-linked_ptr.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-printers.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/internal/gtest-param-util-generated.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest_prod.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-test-part.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest-typed-test.h \ + /home/martino/workspace/MARTe2/Lib/gtest-1.7.0/include/gtest/gtest_pred_impl.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/BasicUDPSocket.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/BasicSocket.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/InternetHost.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L3Streams/StreamString.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/GeneralDefinitions.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/TypeCharacteristics.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/CompilerTypes.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Architecture/x86_gcc/CompilerTypes.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/GeneralDefinitions.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HeapManager.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HeapI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/ErrorManagement.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/ErrorInformation.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/ErrorType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BitBoolean.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/TemplateParametersVerificator.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BitRange.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/TypeCharacteristics.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/GeneralDefinitions.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StreamI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/TimeoutType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HighResolutionTimer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/TimeStamp.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Architecture/x86_gcc/HighResolutionTimerA.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/CompilerTypes.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/HighResolutionTimer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/../../HighResolutionTimer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/HighResolutionTimerCalibrator.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/../../GeneralDefinitions.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/TimeStamp.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L3Streams/CharBuffer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BitBoolean.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/ErrorManagement.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L3Streams/BufferedStreamI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/TimeoutType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/AnyType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ClassProperties.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/FractionalInteger.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ClassRegistryDatabase.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/GlobalObjectsDatabase.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/GlobalObjectI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/StandardHeap.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/../../HeapI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Environment/Linux/../Generic/StandardHeap_Generic.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/FastPollingMutexSem.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Atomic.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Architecture/x86_gcc/AtomicA.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Sleep.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ClassRegistryItem.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/CString.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/ZeroTerminatedArray.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/Introspection.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/IntrospectionEntry.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/TypeDescriptor.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BasicType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/FractionalInteger.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BitRange.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/ZeroTerminatedArray.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/LinkedListable.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/LinkedListHolderT.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/IteratorT.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Iterator.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/LinkedListable.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/LinkedListHolder.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/SortFilter.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/SearchFilter.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/LoadableLibrary.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ObjectBuilder.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HeapI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StaticList.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StaticListHolder.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Matrix.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Vector.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/MemoryOperationsHelper.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/FormatDescriptor.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L3Streams/IOBuffer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StreamI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StringHelper.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L3Streams/StreamStringIOBuffer.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/Environment/Linux/InternetHostCore.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/Environment/Linux/InternetMulticastCore.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HandleI.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/Environment/Linux/SocketCore.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Sleep.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L4Configuration/ConfigurationDatabase.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L4Configuration/AnyObject.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/Object.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/StructuredDataI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/AnyType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/CLASSREGISTER.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ClassRegistryItemT.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ObjectBuilderT.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L4Configuration/ConfigurationDatabaseNode.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Fnv1aHashFunction.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/HashFunction.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceContainer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/LinkedListHolder.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/Object.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/Reference.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceContainerFilter.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceContainerNode.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceT.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/UnorderedMap.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HeapManager.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/StringHelper.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceContainerFilterObjectName.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/ReferenceContainer.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L2Objects/StructuredDataI.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L4Configuration/TypeConversion.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/Vector.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/InternetHost.h \ + ../../Source/Components/Interfaces/UDPStream/UDPSClient.h \ + /home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/BasicTCPSocket.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceMethodBinderI.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/ExecutionInfo.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L1Portability/Threads.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/ThreadsB.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L1Portability/ExceptionHandler.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/ProcessorType.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L0Types/BitSet.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/ExecutionInfo.h \ + /home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/HighResolutionTimer.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/SingleThreadService.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceI.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceMethodBinderI.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceMethodBinderT.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedThreadI.h \ + /home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedThread.h \ + ../../Common/UDP/UDPSProtocol.h