Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 94 additions & 22 deletions .fusa-reqs.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,86 @@
"level": "ASIL-A",
"asil": "ASIL-A"
},
{
"id": "REQ-MOCK-001",
"title": "Server starts HW_UNCONFIGURED with a representative two-endpoint register map",
"text": "rcp::mock::Server shall construct with rcp::lifecycle::ServerLifecycle in state HwUnconfigured and an rcp::regmap::RegisterMap whose endpoint_count, generic_configs, functional_configs, and ep_id_mapping describe exactly two endpoints: GPIO at endpoint id/byte_bus_id 1 and SPI at endpoint id/byte_bus_id 2.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-002",
"title": "advance_to_rcp_configured drives the lifecycle straight to RCP_CONFIGURED",
"text": "rcp::mock::Server::advance_to_rcp_configured shall advance the server's ServerLifecycle from HwUnconfigured through HwConfigured to RcpConfigured and return a falsy std::error_code, since Server supplies no PlausibilityCheck of its own.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-003",
"title": "EP0 read answers the register map's magic number for any client",
"text": "rcp::mock::Server::dispatch, for a read request addressed to regmap::kEp0, shall return a ReadResponse whose payload is the RegisterMap::magic field encoded big-endian, for any client identity, per Ep0::check_read_access's unrestricted-read rule.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-004",
"title": "EP0 write via dispatch is always rejected",
"text": "rcp::mock::Server::dispatch, for a write request addressed to regmap::kEp0, shall return regmap::RegMapErrc::request_rejected and an ErrorResponse, since whole-map writes are only reachable through Ep0::write_whole_map directly, not through dispatch.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-005",
"title": "write_whole_map requires the root client",
"text": "rcp::mock::Server::ep0()'s write_whole_map shall return regmap::RegMapErrc::unauthorized_access for any client that is not the current root client, and shall apply the replacement RegisterMap when called by the root client.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-006",
"title": "Operational GPIO/SPI requests are rejected before RCP_CONFIGURED",
"text": "rcp::mock::Server::dispatch, for a request addressed to the GPIO or SPI endpoint while the server's lifecycle state is not RcpConfigured, shall return regmap::RegMapErrc::request_rejected and an ErrorResponse without touching either endpoint's live state.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-007",
"title": "GPIO write applies evt[2:0] semantics and read reflects the new state",
"text": "rcp::mock::Server::dispatch, for a write request addressed to the GPIO endpoint while RcpConfigured, shall decode the request's evt[2:0] field as an endpoint::WriteSemantics, apply it to the endpoint's live pin-value bitmask via gpio::GpioEndpoint::handle_write, and a subsequent read request shall observe the resulting value.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-008",
"title": "GPIO write with evt_ack set produces an Acknowledge response",
"text": "rcp::mock::Server::dispatch, for a GPIO write request with AcfMessageInfo::evt_ack set, shall build its response via acf::ResponseKind::Acknowledge instead of WriteResponse.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-009",
"title": "SPI transfer answers with the scripted POCI-in bytes for the addressed channel",
"text": "rcp::mock::Server::dispatch, for a request addressed to the SPI endpoint while RcpConfigured, shall decode the request's evt[2:0] field as a channel selector, drive spi::SpiEndpoint::transfer with the request payload as PICO-out data and whatever bytes set_spi_poci last scripted for that channel as POCI-in data, and answer with those POCI-in bytes as a ReadResponse.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-010",
"title": "dispatch to an unmapped byte_bus_id reports invalid_parameter",
"text": "rcp::mock::Server::dispatch, for a request whose byte_bus_id addresses neither EP0, GPIO, nor SPI, shall return regmap::RegMapErrc::invalid_parameter and an ErrorResponse.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-CTRL-001",
"title": "Controller::send returns StatusOK with no handler",
Expand Down Expand Up @@ -1250,64 +1330,56 @@
},
{
"id": "REQ-SIM-001",
"title": "send() returns OK by default",
"text": "sim::Controller::send() shall return ResponseStatus::OK unless a fault has been injected.",
"title": "dispatch forwards to the wrapped mock::Server by default",
"text": "sim::Simulator::dispatch shall, absent an injected fault, forward its request unchanged to the wrapped mock::Server::dispatch and return its result.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-SIM-002",
"title": "zone() returns configured zone",
"text": "sim::Controller::zone() shall return the Zone value from the Config struct.",
"title": "fault() causes dispatch to fail without touching the server",
"text": "sim::Simulator::fault(ec) shall cause every subsequent dispatch() call to return ec and an ErrorResponse without calling mock::Server::dispatch, until recover() is called.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-SIM-003",
"title": "Constant latency model",
"text": "When LatencyModel::Constant is configured, send() shall sleep for exactly base_latency before returning.",
"title": "recover() restores normal dispatch",
"text": "sim::Simulator::recover() shall clear any injected fault and restore dispatch() to forwarding requests to the wrapped mock::Server.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-SIM-004",
"title": "Jitter latency model",
"text": "When LatencyModel::Jitter is configured, send() shall sleep for a duration uniformly distributed in [base_latency, base_latency + jitter].",
"title": "Constant latency model always reports base_latency",
"text": "sim::Simulator::simulated_latency_ms, when Config::latency_model is LatencyModel::Constant, shall always return Config::base_latency.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-SIM-005",
"title": "fault() causes send to fail",
"text": "fault(ec) shall cause all subsequent send() calls to return ec instead of delegating to the handler.",
"title": "Jitter latency model reports a delay within [base_latency, base_latency+jitter]",
"text": "sim::Simulator::simulated_latency_ms, when Config::latency_model is LatencyModel::Jitter, shall return a value uniformly distributed in [base_latency, base_latency + jitter].",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-SIM-006",
"title": "recover() restores normal operation",
"text": "recover() shall clear any injected fault and restore send() to OK responses.",
"title": "dispatch kicks a registered stream's watchdog on every accepted request",
"text": "sim::Simulator::dispatch shall call watchdog::Manager::on_request_received for stream_key when that stream is registered (via register_stream), resetting its watchdog per rcp/watchdog.hpp's reset-by-any-inbound-request rule.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-SIM-007",
"title": "Status published on interval",
"text": "If Config::status_interval > 0, the simulator shall publish a Status on that cadence to all subscribers.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-SIM-008",
"title": "close() stops background threads",
"text": "close() shall terminate the status and watchdog background threads without hanging.",
"title": "dispatch on an unregistered stream does not touch the watchdog manager",
"text": "sim::Simulator::dispatch, for a stream_key never passed to register_stream, shall not call watchdog::Manager::on_request_received and shall leave that stream unregistered.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.21)

project(cpp-rcp
VERSION 2.11.0
VERSION 2.12.0
DESCRIPTION "Remote Control Protocol for automotive zonal architecture (C++)"
HOMEPAGE_URL "https://github.com/SoundMatt/cpp-RCP"
LANGUAGES CXX
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ reference.
| `<rcp/rcp.hpp>` | Core interfaces: `Controller`, `Registry`, `Command`, `Response`, `Status`, `Zone`, `Context`, `StatusChannel` |
| `<rcp/adapt.hpp>` | `Adapt()` — the RELAY §10.3 entry point, wraps a `Controller` as a `relay::Caller`; `ToMessage`/`FromMessage` conversions |
| `<relay/relay.hpp>` | `relay::` namespace types (§18.2): `Protocol`, `Message`, `Errc` sentinels, `Channel<T>`, `Context`, `Node`, `Caller` |
| `<rcp/mock.hpp>` | In-process mock controller and registry — zero I/O, default for unit tests |
| `<rcp/mock.hpp>` | In-process TC18 RC Server simulator (lifecycle + register map + GPIO/SPI) — zero I/O, default for unit tests (ROADMAP.md v2.12.0) |
| `<rcp/legacy_mock.hpp>` | Pre-replacement in-process `Controller`/`Registry`, kept only for the old-model dependents not yet rebound to the new request model (`capi_impl.hpp`/`cli.hpp`/`config.hpp`, v2.16.0) |
| `<rcp/cli.hpp>` | RELAY-conformant CLI (§11/§12): `version`/`capabilities`/`status`/`send`; `cli/main.cpp` is a thin wrapper around it |
| `<rcp/version.hpp>` | Binary version string — single source of truth for the CLI |
| `<rcp/capi.h>` / `<rcp/capi_impl.hpp>` | C ABI / FFI surface for RTOS/bare-metal targets (Zephyr/FreeRTOS) |
Expand Down Expand Up @@ -79,7 +80,7 @@ reference.
| `<rcp/avtp.hpp>` | TC18 wire codec, framing half — IEEE 1722 AVTPDU (NTSCF/TSCF) header framing (ROADMAP.md v2.0.0) |
| `<rcp/acf.hpp>` | TC18 wire codec, message half — ACF_ABB/ACF_GBB message format (ROADMAP.md v2.0.0) |
| `<rcp/legacy_wire.hpp>` | Pre-replacement 16-byte frame codec, kept only for `<rcp/udp.hpp>`'s internal use until it is rebuilt at v2.13.0 |
| `<rcp/sim.hpp>` | Timing-realistic zone controller simulator for SiL/HIL testing |
| `<rcp/sim.hpp>` | Timing-realistic RC Server simulator for SiL/HIL testing — wraps `<rcp/mock.hpp>` with latency/jitter and Fault/Recover controls, watchdog wired via `<rcp/watchdog.hpp>` (ROADMAP.md v2.12.0) |

## Build

Expand All @@ -93,13 +94,19 @@ ctest --test-dir build --output-on-failure

## Quick start

The example below uses `<rcp/legacy_mock.hpp>`'s pre-replacement
`Controller`/`Registry` pair, since `rcp/rcp.hpp`'s `Zone`/`Command`/
`Response` model is itself still pre-replacement (see ROADMAP.md's
Satellite Package Disposition table). For a TC18-shaped in-process server,
see `<rcp/mock.hpp>`'s `mock::Server` instead.

```cpp
#include <rcp/rcp.hpp>
#include <rcp/mock.hpp>
#include <rcp/legacy_mock.hpp>
#include <cassert>

int main() {
auto reg = rcp::mock::new_registry();
auto reg = rcp::legacy_mock::new_registry();

std::shared_ptr<rcp::Controller> ctrl;
reg->lookup(rcp::Zone::FrontLeft, ctrl);
Expand Down
69 changes: 69 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,75 @@ pattern in `rcp/regmap.hpp`/`rcp/request.hpp`/`rcp/e2e.hpp`/
- Depends on: everything in Phase 13 through v2.6.0 at minimum, to have a
representative register map and safe-state model to simulate

**Done (v2.12.0):** `rcp/mock.hpp` is replaced in full, per the Satellite
Package Disposition table's entry for this file — the prior in-process
`Controller`/`Registry` pair built on rcp.hpp's Zone/Command model is
discarded, not adapted, since zone-addressed request/response has no
analog once addressing moves to server+endpoint identifiers. `mock::Server`
holds a real `lifecycle::ServerLifecycle` (starting `HW_UNCONFIGURED`, per
v2.1.0), a real `regmap::RegisterMap` plus `regmap::Ep0` (including EP0's
unrestricted whole-map read and root-client-gated whole-map write, also
v2.1.0), and one instance each of `gpio::GpioEndpoint` and
`spi::SpiEndpoint` (v2.3.0) as its representative endpoint set — GPIO and
SPI were the natural choice, being the two endpoint types the roadmap
itself sequenced first specifically for having the simplest fully-built
request/response shapes (see milestone 47's own note). `Server::dispatch`
is the single request/response entry point: it decodes the mandatory
standard request kind's `evt[2:0]`/`op` fields (`rcp/acf.hpp`, v2.0.0),
answers EP0 reads with the register map's magic number (the one field a
`rcp/discovery.hpp`-shaped read needs by default — whole-map wire
serialization itself remains out of scope, per `regmap.hpp`'s own header
comment), rejects EP0 writes (reachable only through `ep0().write_whole_map`
directly), and gates GPIO/SPI operational traffic on the lifecycle being
`RCP_CONFIGURED` — this mock's own choice, not a rule reused from
`regmap::Ep0::check_write_access`'s config-block locking, which models a
different thing (whether an endpoint's *configuration* may still change,
not whether the endpoint may be *operated*).

The old `Controller`/`Registry` content is preserved unchanged, under
`rcp/legacy_mock.hpp`, purely so the still-untouched old-model dependents
that build against it keep working until each is rebound at its own later
milestone — `rcp/capi_impl.hpp`, `rcp/cli.hpp`, and `rcp/config.hpp` (all
v2.16.0 per the disposition table), plus every test file that used the old
mock as a generic in-process test double for its own not-yet-rebuilt
package (`firmware`/`prioqueue`/`proxy`/`record`/`redundancy`/`tsn`/
`zonegroup`/`federation`/`observe`/`faultinject`/`loan`/`admin`, most
themselves DEPRECATE/ADAPT candidates for later milestones). This is the
same file-split precedent `rcp/wire.hpp`'s `rcp/legacy_wire.hpp` established
at v2.0.0 — REQ-CTRL-*/REQ-REG-*/REQ-RESP-*/REQ-STAT-*/REQ-ERR-011 and
their `tests/test_mock.cpp` coverage move to `tests/test_legacy_mock.cpp`
unchanged, matching `REQ-UDP-*`'s equivalent move at that milestone.

`rcp/sim.hpp` is likewise replaced in full — the prior `sim::Controller`,
which implemented the full old `rcp::Controller` interface plus a
client-driven `CommandType::Watchdog` kick model, is discarded outright
(nothing else in this tree depended on it beyond its own test, so no
legacy shim was needed here, unlike `mock.hpp`'s split). `sim::Simulator`
wraps one `mock::Server` with the latency/jitter modeling concept carried
forward unchanged from the pre-replacement design (`LatencyModel::
Constant`/`Jitter`, `simulated_latency_ms()`) and the same Fault/Recover
scenario-testing concept, re-targeted at this module's
`std::error_code`/`AcfMessageInfo` response shape. Watchdog-miss detection
is wired through `rcp::watchdog::StreamWatchdog`/`Manager` (v2.10.0)
exactly as the roadmap called for: `Simulator::dispatch` kicks a
registered stream's watchdog via `Manager::on_request_received` on every
accepted request, and `poll_watchdog` forwards to `Manager::poll` for the
caller to drive on its own schedule. Per the same "primitives, not a
scheduler" convention every Phase 14 header has followed since v2.9.0/
v2.10.0, `Simulator` spawns no background thread of its own — the old
`sim::Controller`'s status/watchdog polling threads are not carried
forward; `simulated_latency_ms()` reports a delay for the caller to apply
however it sees fit rather than sleeping on the caller's behalf.

New/rewritten coverage lives in `tests/test_mock.cpp` (`REQ-MOCK-001..010`,
new) and `tests/test_sim.cpp` (`REQ-SIM-001..007`, entirely rewritten under
the same file/prefix identity as the discarded pre-replacement coverage),
traced in `.fusa-reqs.json`; the pre-replacement mock coverage lives on,
unchanged, as `tests/test_legacy_mock.cpp`. Full bit-for-bit conformance
against other TC18 implementations is not claimed, same as the equivalent
disclaimers in `rcp/regmap.hpp`, `rcp/lifecycle.hpp`, `rcp/gpio.hpp`, and
`rcp/spi.hpp`.

---
### Phase 15 — Transport & Ecosystem Bridge Migration
---
Expand Down
9 changes: 8 additions & 1 deletion include/rcp/adapt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@
// underlying protocol with a single constructor change.
//
// Usage:
// auto ctrl = std::make_shared<rcp::mock::Controller>(rcp::Zone::FrontLeft);
// auto ctrl = std::make_shared<rcp::legacy_mock::Controller>(rcp::Zone::FrontLeft);
// auto caller = rcp::Adapt(ctrl); // relay::Caller*
// auto [resp, ec] = caller->call(ctx, msg);
//
// The example above uses rcp/legacy_mock.hpp's Controller (renamed from
// rcp/mock.hpp at ROADMAP.md milestone 56, v2.12.0 — see its own header
// comment) since this header itself is still built on rcp.hpp's
// pre-replacement Zone/Command/Controller model; per the Satellite Package
// Disposition table's entry for `adapt.hpp`, rebinding Adapt()/ToMessage/
// FromMessage to the new request/response shapes is v2.16.0 scope.
#pragma once

#include <rcp/rcp.hpp>
Expand Down
16 changes: 12 additions & 4 deletions include/rcp/capi_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
// declared in capi.h. Bridges flat C handles to the C++ mock controller
// and in-process registry.
//
// Still built on rcp.hpp's pre-replacement Zone/Command/Controller/Registry
// model and rcp/legacy_mock.hpp's Controller/Registry backing it (renamed
// from rcp/mock.hpp at ROADMAP.md milestone 56, v2.12.0, when that name
// became the new RC Server simulator — see rcp/legacy_mock.hpp's header
// comment). Per the Satellite Package Disposition table's entry for
// `capi.h`/`capi_impl.hpp`, this file's own REPLACE against the new
// request model is v2.16.0 scope, not this milestone's.
//
// fusa:req REQ-CAPI-001
// fusa:req REQ-CAPI-002
// fusa:req REQ-CAPI-003
Expand All @@ -15,18 +23,18 @@
#pragma once

#include "capi.h"
#include "mock.hpp"
#include "legacy_mock.hpp"
#include "rcp.hpp"

// ── Backing storage layout ────────────────────────────────────────────────────
// We use placement-new into caller-supplied buffers to avoid heap allocation.

struct rcp_registry_s {
rcp::mock::Registry impl;
rcp::legacy_mock::Registry impl;
};

struct rcp_ctrl_s {
std::shared_ptr<rcp::mock::Controller> impl;
std::shared_ptr<rcp::legacy_mock::Controller> impl;
};

static_assert(sizeof(rcp_registry_s) <= 512, "rcp_registry_s too large");
Expand All @@ -50,7 +58,7 @@ inline rcp_err_t rcp_registry_close(rcp_registry_h reg) {
inline rcp_err_t rcp_ctrl_init(rcp_zone_t zone, void* buf, size_t buf_len, rcp_ctrl_h* out) {
if (buf_len < sizeof(rcp_ctrl_s)) return RCP_ERR_NOMEM;
auto* h = new (buf) rcp_ctrl_s{};
h->impl = std::make_shared<rcp::mock::Controller>(static_cast<rcp::Zone>(zone));
h->impl = std::make_shared<rcp::legacy_mock::Controller>(static_cast<rcp::Zone>(zone));
*out = h;
return RCP_OK;
}
Expand Down
Loading
Loading