zerodds-endpoint-cpp 1.0 — Spec Coverage
Source: docs/specs/zerodds-endpoint-cpp-1.0.md (226 lines) — the
normative C++17 endpoint SDK spec (XRCE framing, sync client, async
Reader/Writer, reliable stream). Implemented in endpoints/cpp/: XRCE
framing + the sync client over the C89 wire core zdw (endpoints/c),
async Reader/Writer as a thin C++17 facade (zerodds_async.hpp), and the
header-only reliable stream (zerodds_reliable.hpp). The generated topic_type_support<T> codec comes from crates/lang-rules,
column Language::Cpp; this document covers the endpoint that transports it.
§1 XRCE framing
Spec: zerodds-endpoint-cpp-1.0.en.md §1 – DDS-XRCE 1.0 message header
(8 bytes: session, stream, seq LE, submsg id, flags, len LE) + body;
best-effort stream 0x01.
Repo: endpoints/c/include/zerodds_endpoint.h — zdw_xrce_write_frame
/ zdw_xrce_read_frame, ZDW_XRCE_SESSION_NOKEY,
ZDW_XRCE_STREAM_BEST_EFFORT. The C++ SDK does not re-implement the
framing — it binds the C89 wire core directly.
Tests: crates/endpoint-e2e/tests/cpp.rs::run_sync / run_async frame
and deframe every ping/pong over real UDP.
Status: done.
§2 Sync client
Spec: zerodds-endpoint-cpp-1.0.en.md §2 – A blocking transport-poll
client: zdw_endpoint_send / zdw_endpoint_recv over an
integrator-supplied zdw_transport (deliver/receive callbacks, ADR-0013
frame hook), marshalling via the C++98 facade zerodds::Writer /
zerodds::Reader.
Repo: endpoints/c/include/zerodds_endpoint.h (zdw_transport,
zdw_endpoint_send/recv); endpoints/cpp/include/zerodds_wire.hpp
(zerodds::Writer/Reader, a C++98 facade over the C89 core); C++ call
site crates/endpoint-e2e/tests/cpp.rs::run_sync (UDP callbacks
udp_deliver / udp_receive, 100 ms poll loop for up to 100 iterations);
endpoints/cpp/example_sync.cpp (sensor-telemetry deep example
Reading{id,value,label}, C++98 poll loop over an in-memory FIFO, full
field decode).
Tests: (Linux, g++/gcc gated) cpp_endpoint_sync — live ping-pong E2E
against the shared Rust XRCE peer over real UDP; plus cpp_raw_udp (bare
XCDR2, no XRCE framing, a direct codec call through the generated types
topic_type_support<Ping>/<Pong> from idl-cpp) as codec build
integration for the same app compilation. 2/2 passed. Loud skip only when
no compiler is on PATH.
Status: done.
§3 Async Reader/Writer
Spec: zerodds-endpoint-cpp-1.0.en.md §3 – An event-driven,
non-blocking reader (a callback per received sample) and a fire-and-forget
writer, both thin C++17 facades over the audited C reactor
(zerodds_async.c) — additive to the conservative C++98 wire facade
(zerodds_wire.hpp).
Repo: endpoints/cpp/include/zerodds_async.hpp — zerodds::AsyncReader
(poll() / run(max), RAII, std::function trampoline over
zdw_async_reader_init) and zerodds::AsyncWriter (write() over
zdw_async_writer_init / zdw_async_write); endpoints/cpp/example_async.cpp
(sensor-telemetry deep example, C++17 reactor).
Tests: (Linux, g++/gcc gated) crates/endpoint-e2e/tests/cpp.rs::run_async
— AsyncReader dispatches the received pong to a lambda, AsyncWriter
sends the ping; live ping-pong E2E against the Rust peer over real UDP;
test name cpp_endpoint_async. 1/1 passed. Loud skip only when no compiler
is on PATH.
Status: done.
§4 Reliable stream — state machine, async writer, loss recovery, byte-golden, latency
Spec: zerodds-endpoint-cpp-1.0.en.md §4 (normative:
reliable-endpoint-1.0.en.md) – DDS-XRCE reliable stream (stream_id ≥ 128, §8.4.10/§8.4.11), mirroring the reference crates/xrce/src/reliable.rs:
sender submit/pending_heartbeat/recv_acknack/get_in_flight; receiver
recv_data/drain_in_order/pending_acknack/reset. The async writer
decouples the producer from I/O: enqueue() is wait-free into a lock-free
SPSC ring; a dedicated drain thread owns the Sender state, batches
WRITE_DATA via sendmmsg, fires HEARTBEATs periodically, and retransmits
on ACKNACK. The producer must never enter the kernel on the
async-decoupled path — a measurement should show the decoupling against the
inline-sendto path (design rationale from reliable-endpoint-1.0.en.md
§2).
Repo: endpoints/cpp/include/zerodds_reliable.hpp (381 lines,
header-only C++17) — Sender/Receiver state machine, wire codec
(write_frame/unframe/acknack_frame/heartbeat_frame/parse_acknack/
parse_heartbeat), AsyncWriter (SPSC ring with std::atomic<size_t> head_/tail_, std::thread drain, enqueue/finish/stop). Pure C++17,
header-only; no linking against the Rust layer, cross-compile-safe.
endpoints/cpp/example_reliable.cpp (in-process demo: sender + receiver,
every 3rd sample dropped in the first round, ACKNACK-driven recovery
rounds). endpoints/cpp/test/test_reliable_cpp.cpp (12 unit checks +
byte-golden + latency bench).
Tests: (Linux, g++12)
cpp_reliable_unit_and_golden— 12 unit checks (sender: monotonic seq, payload-too-large, window-full, heartbeat first/silence/period; receiver: acknack-clear, acknack-full-clear, in-order, reorder, duplicate-drop, buffer-full, pending-acknack bitmap, reset) + byte-golden (acknack_frame(0x80, NONE, 1, 1, 0, 0, 0x80)==golden_acknack_le.bin,heartbeat_frame(0x80, NONE, 1, 1, 3, 0x80)==golden_heartbeat_le.bin, 13 bytes each, byte-identical to the Rust-generated goldens) + a latency bench (median over K=20000 iterations:AsyncWriter::enqueue, no-op drain hooks, no syscall, vswrite_frame+ inlinesendto a bound loopback sink) →ALL OK.cpp_reliable_loss_recovery— the Rust peer drops every 3rd datagram; the app retransmits on ACKNACK; 12/12 samples delivered gap-free in order.cpp_reliable_lossless_baseline— lossless; 12/12.cpp_reliable_example— in-process demo →RELIABLE OK N=16.
4/4 passed. Latency measured: 31–40 ns (enqueue) vs 3427 ns (inline
sendto), ~85–110× depending on the run (run-to-run variance, not a single
value).
Finding during development: a drain deadlock with no responder — finish()
waits until both window and ring are empty, which never happens without an
ACKNACK reply. Fixed by adding stop() (unconditional teardown, for
responder-less contexts such as the latency bench).
Status: done — sender and receiver state machine unit-verified; the
live E2E against the Rust peer covers the sender role (app sends, Rust peer
receives + ACKNACKs); the receiver role is verified in-process
(example_reliable.cpp), not live-network against an external sender;
latency decoupling measured.
§5 Test and evidence obligations
Spec: zerodds-endpoint-cpp-1.0.en.md §5 (mirrors
reliable-endpoint-1.0.en.md §5) – unit, byte-golden, E2E loss recovery, a
latency bench, a runnable example_reliable_*; no false-green, a loud skip
only when the toolchain is missing.
Repo: All five required artifacts present, evidenced in §4:
test_reliable_cpp.cpp (unit + byte-golden + latency bench),
crates/endpoint-e2e/tests/cpp_reliable.rs (loss recovery + lossless
baseline against the Rust peer), example_reliable.cpp (a runnable
example, not a stub). Loud skip only when g++/gcc is missing from
PATH.
Tests: see §4 (cpp_reliable_unit_and_golden, cpp_reliable_loss_recovery,
cpp_reliable_lossless_baseline, cpp_reliable_example) and §2/§3
(cpp_raw_udp, cpp_endpoint_sync, cpp_endpoint_async).
Status: done.
Audit status
5 done / 0 partial / 0 open / 0 blocked / 0 n/a (informative).
Test run (Linux, g++12, verified): cargo test -p zerodds-endpoint-e2e --test cpp → 3/3 (cpp_raw_udp, cpp_endpoint_sync, cpp_endpoint_async);
--test cpp_reliable → 4/4 (cpp_reliable_unit_and_golden incl.
12-checks+byte-golden+bench, cpp_reliable_loss_recovery,
cpp_reliable_lossless_baseline, cpp_reliable_example); latency bench
enqueue 31–40 ns / inline sendto 3427 ns.
Open items: none.
zerodds-endpoint-cpp 1.0 — Spec-Coverage
Quelle: docs/specs/zerodds-endpoint-cpp-1.0.md (226 Zeilen) — die
normative C++17-Endpoint-SDK-Spec (XRCE-Framing, sync Client, async
Reader/Writer, reliable Stream). Implementiert in endpoints/cpp/: das
XRCE-Framing + der sync Client über den C89-Wire-Core zdw
(endpoints/c), async Reader/Writer als dünne C++17-Fassade
(zerodds_async.hpp), und der header-only reliable Stream
(zerodds_reliable.hpp). Der generierte topic_type_support<T>-Codec kommt aus crates/lang-rules,
Spalte Language::Cpp; hier steht der Endpoint, der ihn transportiert.
§1 XRCE-Framing
Spec: zerodds-endpoint-cpp-1.0.md §1 – DDS-XRCE-1.0-Message-Header (8
Byte: session, stream, seq LE, submsg id, flags, len LE) + Body; best-effort
Stream 0x01.
Repo: endpoints/c/include/zerodds_endpoint.h — zdw_xrce_write_frame
/ zdw_xrce_read_frame, ZDW_XRCE_SESSION_NOKEY,
ZDW_XRCE_STREAM_BEST_EFFORT. Der C++-SDK re-implementiert das Framing
nicht, sondern bindet den C89-Wire-Core direkt ein.
Tests: crates/endpoint-e2e/tests/cpp.rs::run_sync / run_async framen
bzw. deframen jedes Ping/Pong über echtes UDP.
Status: done.
§2 sync Client
Spec: zerodds-endpoint-cpp-1.0.md §2 – Ein blockierender
Transport-Poll-Client: zdw_endpoint_send / zdw_endpoint_recv über einen
integrator-gestellten zdw_transport (deliver/receive-Callbacks,
ADR-0013-Frame-Hook), Marshalling per C++98-Fassade zerodds::Writer /
zerodds::Reader.
Repo: endpoints/c/include/zerodds_endpoint.h (zdw_transport,
zdw_endpoint_send/recv); endpoints/cpp/include/zerodds_wire.hpp
(zerodds::Writer/Reader, C++98-Fassade über den C89-Kern); C++-Aufrufstelle
crates/endpoint-e2e/tests/cpp.rs::run_sync (UDP-Callbacks udp_deliver /
udp_receive, 100-ms-Poll-Loop bis zu 100 Iterationen);
endpoints/cpp/example_sync.cpp (Sensor-Telemetrie-Deep-Example
Reading{id,value,label}, C++98-Poll-Loop über eine In-Memory-FIFO, voller
Feld-Decode).
Tests: (Linux, g++/gcc gated) cpp_endpoint_sync — Live-Ping-Pong-E2E
gegen den geteilten Rust-XRCE-Peer über echtes UDP; sowie cpp_raw_udp
(bares XCDR2 ohne XRCE-Framing, direkter Codec-Aufruf über die generierten
Typen topic_type_support<Ping>/<Pong> von idl-cpp) als
Codec-Build-Integration derselben App-Kompilierung. 2/2 passed. Lauter Skip
nur bei fehlendem Compiler auf dem PATH.
Status: done.
§3 async Reader/Writer
Spec: zerodds-endpoint-cpp-1.0.md §3 – Ein event-driven,
nicht-blockierender Reader (Callback pro empfangenem Sample) und ein
Fire-and-forget-Writer, beide dünne C++17-Fassaden über den auditierten
C-Reaktor (zerodds_async.c) — additiv zur konservativen C++98-Wire-Fassade
(zerodds_wire.hpp).
Repo: endpoints/cpp/include/zerodds_async.hpp — zerodds::AsyncReader
(poll() / run(max), RAII, std::function-Trampolin über
zdw_async_reader_init) und zerodds::AsyncWriter (write() über
zdw_async_writer_init / zdw_async_write); endpoints/cpp/example_async.cpp
(Sensor-Telemetrie-Deep-Example, C++17-Reactor).
Tests: (Linux, g++/gcc gated) crates/endpoint-e2e/tests/cpp.rs::run_async
— AsyncReader dispatcht das empfangene Pong an eine Lambda, AsyncWriter
schickt das Ping; Live-Ping-Pong-E2E gegen den Rust-Peer über echtes UDP;
Testname cpp_endpoint_async. 1/1 passed. Lauter Skip nur bei fehlendem
Compiler auf dem PATH.
Status: done.
§4 Reliable Stream — State-Machine, Async-Writer, Loss-Recovery, Byte-Golden, Latenz
Spec: zerodds-endpoint-cpp-1.0.md §4 (normativ: reliable-endpoint-1.0.md)
– DDS-XRCE reliable Stream (stream_id ≥ 128, §8.4.10/§8.4.11), spiegelt die
Referenz crates/xrce/src/reliable.rs: Sender
submit/pending_heartbeat/recv_acknack/get_in_flight; Receiver
recv_data/drain_in_order/pending_acknack/reset. Der Async-Writer
entkoppelt den Producer vom I/O: enqueue() ist wait-free in einen
lock-free SPSC-Ring, ein dedizierter Drain-Thread besitzt den
Sender-State, batched WRITE_DATA via sendmmsg, feuert HEARTBEATs
periodisch und retransmittet auf ACKNACK. Der Producer darf im
async-entkoppelten Pfad nie in den Kernel eintreten — ein Messwert soll die
Entkopplung gegen den inline-sendto-Pfad zeigen (Design-Motiv aus
reliable-endpoint-1.0.md §2).
Repo: endpoints/cpp/include/zerodds_reliable.hpp (381 Zeilen,
header-only C++17) — Sender/Receiver-State-Machine, Wire-Codec
(write_frame/unframe/acknack_frame/heartbeat_frame/parse_acknack/
parse_heartbeat), AsyncWriter (SPSC-Ring mit std::atomic<size_t> head_/tail_, std::thread-Drain, enqueue/finish/stop). Pure C++17,
header-only; kein Linking gegen Rust-Layer, cross-compile-fest.
endpoints/cpp/example_reliable.cpp (In-Process-Demo: Sender + Receiver,
jedes 3. Sample im ersten Round gedroppt, ACKNACK-getriebene Recovery-Runden).
endpoints/cpp/test/test_reliable_cpp.cpp (12 Unit-Checks + Byte-Golden +
Latenz-Bench).
Tests: (Linux, g++12)
cpp_reliable_unit_and_golden— 12 Unit-Checks (Sender: monotone seq, payload-too-large, window-full, heartbeat first/silence/period; Receiver: acknack-clear, acknack-full-clear, in-order, reorder, duplicate-drop, buffer-full, pending-acknack-Bitmap, reset) + Byte-Golden (acknack_frame(0x80, NONE, 1, 1, 0, 0, 0x80)==golden_acknack_le.bin,heartbeat_frame(0x80, NONE, 1, 1, 3, 0x80)==golden_heartbeat_le.bin, je 13 Byte, byte-identisch zu den Rust-generierten Goldens) + Latenz-Bench (Median über K=20000 Iterationen:AsyncWriter::enqueue, No-op-Drain-Hooks, kein Syscall, vs.write_frame+ inlinesendauf einen gebundenen Loopback-Sink) →ALL OK.cpp_reliable_loss_recovery— der Rust-Peer dropt jedes 3. Datagramm; App retransmittet auf ACKNACK; 12/12 Samples lückenlos in-order geliefert.cpp_reliable_lossless_baseline— lossless; 12/12.cpp_reliable_example— In-Process-Demo →RELIABLE OK N=16.
4/4 passed. Latenz gemessen: 31–40 ns (enqueue) vs. 3427 ns (inline
sendto), ~85–110× je nach Lauf (Lauf-zu-Lauf-Streuung, kein Einzelwert).
Fund während der Entwicklung: ein Drain-Deadlock ohne Responder — finish()
wartet, bis Fenster und Ring leer sind, was ohne ACKNACK-Antwort nie
eintritt. Gefixt durch stop() (unconditionaler Teardown, für
responder-lose Kontexte wie den Latenz-Bench).
Status: done — Sender- und Receiver-State-Machine unit-verifiziert;
das live-E2E gegen den Rust-Peer deckt die Sender-Rolle ab (App sendet,
Rust-Peer empfängt + ACKNACKt); die Receiver-Rolle ist in-process
(example_reliable.cpp) verifiziert, nicht live-network gegen einen
externen Sender; Latenz-Entkopplung gemessen.
§5 Test- und Beleg-Pflicht
Spec: zerodds-endpoint-cpp-1.0.md §5 (spiegelt reliable-endpoint-1.0.md
§5) – Unit, Byte-Golden, E2E-Loss-Recovery, Latenz-Bench, ein lauffähiges
example_reliable_*; kein false-green, lauter Skip nur bei fehlender
Toolchain.
Repo: Alle fünf Pflicht-Artefakte vorhanden, belegt in §4:
test_reliable_cpp.cpp (Unit + Byte-Golden + Latenz-Bench),
crates/endpoint-e2e/tests/cpp_reliable.rs (Loss-Recovery +
Lossless-Baseline gegen den Rust-Peer), example_reliable.cpp (lauffähiges
Example, kein Stub). Lauter Skip nur bei fehlendem g++/gcc auf dem
PATH.
Tests: siehe §4 (cpp_reliable_unit_and_golden,
cpp_reliable_loss_recovery, cpp_reliable_lossless_baseline,
cpp_reliable_example) sowie §2/§3 (cpp_raw_udp, cpp_endpoint_sync,
cpp_endpoint_async).
Status: done.
Audit-Status
5 done / 0 partial / 0 open / 0 blocked / 0 n/a (informative).
Test-Lauf: Linux, g++12, verifiziert — cargo test -p zerodds-endpoint-e2e --test cpp → 3/3 (cpp_raw_udp, cpp_endpoint_sync, cpp_endpoint_async);
--test cpp_reliable → 4/4 (cpp_reliable_unit_and_golden inkl.
12-Checks+Byte-Golden+Bench, cpp_reliable_loss_recovery,
cpp_reliable_lossless_baseline, cpp_reliable_example); Latenz-Bench
enqueue 31–40 ns / inline sendto 3427 ns.
Offene Punkte: keine.