From 0a380461e2144822b87034b85ff82823fe57d204 Mon Sep 17 00:00:00 2001 From: Matt Jones <47545907+SoundMatt@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:12:08 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20adopt=20RELAY=20spec=20v0.3=20=E2=80=94?= =?UTF-8?q?=20version=20bump,=20topic=5Fname=20option,=20golden=20vector?= =?UTF-8?q?=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopts RELAY spec v0.3 for cpp-RCP (closes issue #11). The v0.3 breaking change (§15.7.6 — SOME/IP numeric msg_type) does not affect the RCP protocol, so RCP's ToMessage()/FromMessage() mappings are unchanged. - relay.hpp: kRelaySpecVersion "0.2" → "0.3" - relay.hpp: SubscriberOptions gains topic_name (§14.1 additive, v0.3); DDS adapters route on it, RCP and all other adapters ignore it - test_relay.cpp: assert version "0.3"; add golden-vector test pinning status_to_message() to RELAY spec/vectors/rcp-status.json (lossless mapping of protocol/id/seq/payload/meta); add SubscriberOptions.topic_name test Note: the v0.3 CLI conformance path (`-DRELAY_BUILD_CLI=ON` + `relay conform` schema validation, §12) is a separate larger component, not included here. Co-Authored-By: Claude Opus 4.8 Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com> --- include/relay/relay.hpp | 9 +++++++- tests/test_relay.cpp | 48 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/include/relay/relay.hpp b/include/relay/relay.hpp index 8a0daff..487b7e8 100644 --- a/include/relay/relay.hpp +++ b/include/relay/relay.hpp @@ -24,7 +24,11 @@ namespace relay { // ── Spec version (§19.4) ───────────────────────────────────────────────────── -constexpr std::string_view kRelaySpecVersion = "0.2"; +constexpr std::string_view kRelaySpecVersion = "0.3"; + +// NOTE: keep in sync with RELAY spec/version.json. The v0.3 breaking change +// (§15.7.6 — SOME/IP numeric msg_type) does NOT affect RCP; RCP's +// ToMessage()/FromMessage() mappings are unchanged from v0.2. // ── Protocol identifiers (§3) ──────────────────────────────────────────────── @@ -137,6 +141,9 @@ enum class BackPressurePolicy { drop_newest = 0, drop_oldest = 1, block = 2 }; struct SubscriberOptions { std::size_t channel_depth = 64; BackPressurePolicy back_pressure = BackPressurePolicy::drop_newest; + // topic_name (§14.1, v0.3): DDS adapters route subscriptions to this topic. + // All other adapters — including RCP — ignore it. Empty = unset. + std::string topic_name; }; // ── Channel — typed bounded channel (§18.2) ─────────────────────────────── diff --git a/tests/test_relay.cpp b/tests/test_relay.cpp index a8cb36c..10384dd 100644 --- a/tests/test_relay.cpp +++ b/tests/test_relay.cpp @@ -21,8 +21,8 @@ using namespace std::chrono_literals; // ── §19.4: SpecVersion constant ─────────────────────────────────────────────── -TEST_CASE("relay: kRelaySpecVersion is 0.2", "[relay][conformance]") { - REQUIRE(relay::kRelaySpecVersion == "0.2"); +TEST_CASE("relay: kRelaySpecVersion is 0.3", "[relay][conformance]") { + REQUIRE(relay::kRelaySpecVersion == "0.3"); } // ── §3: Protocol enum ───────────────────────────────────────────────────────── @@ -228,3 +228,47 @@ TEST_CASE("relay: zone round-trips through relay ID", "[relay][conform]") { REQUIRE(rcp::zone_from_relay_id(rcp::zone_to_relay_id(z)) == z); } } + +// ── v0.3 golden vector: status_to_message matches RELAY spec/vectors ───────── +// +// Pins rcp::status_to_message() to the canonical reference in +// RELAY spec/vectors/rcp-status.json (v0.3). The vector's `value` is the +// rcp.Status input; its `message` is the expected relay.Message output. +// Mandatory fields (protocol, id, payload, seq, meta["rcp.healthy"]) MUST match +// losslessly; timestamp is "ignored on receive" per §15.7 and is not pinned. + +TEST_CASE("relay: status_to_message matches rcp-status golden vector (v0.3)", + "[relay][conformance][vectors]") { + // From spec/vectors/rcp-status.json `value`: + // zone=1 (FrontLeft), seq=3, healthy=true, payload="AQ==" (== byte 0x01) + rcp::Status s; + s.zone = rcp::Zone::FrontLeft; + s.seq = 3; + s.healthy = true; + s.payload = {0x01}; + + auto msg = rcp::status_to_message(s); + + // Expected `message` fields from the golden vector: + REQUIRE(static_cast(msg.protocol) == 5); // relay.RCP + REQUIRE(msg.id == "FrontLeft"); + REQUIRE(msg.seq == 3); + REQUIRE(msg.payload == std::vector{0x01}); // base64 "AQ==" + REQUIRE(msg.meta.at("rcp.healthy") == "true"); +} + +// ── §14.1 (v0.3): SubscriberOptions carries topic_name; RCP ignores it ─────── + +TEST_CASE("relay: SubscriberOptions has topic_name field, default empty", + "[relay][conformance]") { + relay::SubscriberOptions opts; + REQUIRE(opts.topic_name.empty()); + + // RCP adapter must ignore topic_name (only DDS routes on it, §14.1). + auto ctrl = std::make_shared(rcp::Zone::FrontLeft); + auto caller = rcp::Adapt(ctrl); + opts.topic_name = "ignored-by-rcp"; + auto [ch, ec] = caller->subscribe(opts); + REQUIRE_FALSE(ec); + REQUIRE(ch != nullptr); +}