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
158 changes: 79 additions & 79 deletions .fusa-reqs.json

Large diffs are not rendered by default.

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.13.0
VERSION 2.14.0
DESCRIPTION "Remote Control Protocol for automotive zonal architecture (C++)"
HOMEPAGE_URL "https://github.com/SoundMatt/cpp-RCP"
LANGUAGES CXX
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,19 @@ reference.
| `<rcp/proxy.hpp>` | Transparent zone proxy for cascaded zonal topologies |
| `<rcp/zonegroup.hpp>` | Atomic multi-zone command broadcast with typed zone group sets |
| `<rcp/dyndata.hpp>` | Runtime schema registry and dynamic payload encoding |
| `<rcp/loan.hpp>` | `LoaningController` — zero-copy payload loaning via a pre-allocated pool |
| `<rcp/record.hpp>` | Binary record and replay of RCP traffic |
| `<rcp/observe.hpp>` | OpenTelemetry-style observability: spans, gauges, counters |
| `<rcp/mdns.hpp>` | mDNS/DNS-SD zone controller discovery (RFC 6762/6763) |
| `<rcp/loan.hpp>` | `loan::BufferPool` — zero-copy payload loaning via a pre-allocated pool, for AVTPDU/ACF request-building (ROADMAP.md v2.14.0) |
| `<rcp/record.hpp>` | Binary record and replay of RC-Client-level request/response traffic (ROADMAP.md v2.14.0) |
| `<rcp/observe.hpp>` | OpenTelemetry-style observability: spans and counters around a client-side send-equivalent call (ROADMAP.md v2.14.0) |
| `<rcp/mdns.hpp>` | mDNS/DNS-SD host:port discovery (RFC 6762/6763), scoped to the UDP/IP transport variant (ROADMAP.md v2.14.0) |

### Transports

| Header | Description |
|---|---|
| `<rcp/udp.hpp>` | Native IEEE 1722-over-UDP/IP transport (Annex J): `Server`/`Client` carry AVTPDU (NTSCF/TSCF) + ACF_ABB/ACF_GBB frames over UDP sockets (ROADMAP.md v2.13.0) |
| `<rcp/tls.hpp>` | Mutual TLS transport for zone controller communication |
| `<rcp/shmem.hpp>` | Zero-copy intra-host command delivery via shared in-process memory |
| `<rcp/tsn.hpp>` | IEEE 802.1Qbv-aware UDP transport adapter for hard real-time Ethernet |
| `<rcp/tls.hpp>` | Secure-channel option for the UDP/IP transport variant (DTLS/application-layer, `SecureClient`/`SecureServer`); the specification's own preferred link security is MACsec (802.1AE) at layer 2, which this package does not address (ROADMAP.md v2.14.0) |
| `<rcp/shmem.hpp>` | Zero-copy in-process request `Channel`/`Registry`, keyed by opaque stream_key (ROADMAP.md v2.14.0) |
| `<rcp/tsn.hpp>` | IEEE 802.1p PCP-priority hint (`apply_priority`) keyed off `rcp::request::RequestCategory`'s execution-priority ordering; prefer genuine IEEE 1722 stream reservation where available (ROADMAP.md v2.14.0) |
| `<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/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) |
Expand Down
76 changes: 76 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,82 @@ not claimed, same as the equivalent disclaimers in `rcp/avtp.hpp`,
- Depends on: Wire format core (v2.0.0); UDP/IP transport (v2.13.0) for the
`mdns`/`tls` items specifically

**Done (v2.14.0):** All seven files are ADAPTed in place per the Satellite
Package Disposition table's entries — each concept survives, rebound onto
the new stream/`byte_bus_id` addressing model instead of the removed
`Zone`/`Command`/`Controller`/`Registry` types. `rcp/rcp.hpp`'s own
"nothing new should build on this" notice held throughout: none of the
seven include it for anything beyond `rcp::Context`/the generic
`ErrClosed`/`ErrNotFound`/`ErrAlreadyExists`/`ErrTimeout` error constants
(also not part of that pre-replacement model) and, for `loan.hpp`, the
already-generic `rcp::Loan` RAII buffer holder.

`mdns.hpp`'s `Discoverer`/`StaticDiscoverer`/`Announcer` interfaces are
unchanged in shape; `ZoneInfo` becomes `ServerInfo`, keyed by an opaque
`stream_key` (typically `avtp::StreamId::to_u64()`) instead of `Zone`.
`tls.hpp`'s `Config` (cert/key/ca/verify_peer) survives unchanged;
`Controller`/`ZoneServer`/`Registry` are replaced by `SecureClient`/
`SecureServer` wrapping `rcp/udp.hpp`'s `Server`/`Client` (v2.13.0), same
OpenSSL-gated-real-backend/`function_not_supported`-stub split as before.
The header now also states plainly, per this milestone's own instruction,
that the specification's preferred link-security mechanism is MACsec
(802.1AE) at layer 2 — this package addresses the UDP/IP path only and
does not implement MACsec itself.

`tsn.hpp`'s `PCPMap` now maps all seven `rcp::request::RequestCategory`
values (v2.5.0) instead of the removed `Priority` enum's three, preserving
the same cancellation > triggered > timed > compound > compound-wait >
chained > standard execution-priority ordering `rcp/request.hpp`'s
`priority_rank` already defines (extraction §3.14). The `Controller`
wrapper is dropped for a standalone `apply_priority(fd, cfg, category)`
free function — there is no unified client-side `send()` chokepoint left
to wrap (that unification, if any, does not land until the CLI/capi/adapt
rebuilds at v2.16.0, per `rcp/authz.hpp`'s equivalent v2.11.0 note) — and
its header comment now notes that genuine IEEE 1722 stream reservation
(802.1Qat SRP), where available, is preferable to this socket-level hint.

`shmem.hpp`'s `ZoneServer`/`Controller` pair becomes `Channel`/`Registry`,
keyed by `stream_key` the same way `rcp/watchdog.hpp`'s `Manager` already
is; `Channel::request()` still dispatches to a caller-supplied handler
in-process with no wire encode/decode step, the same zero-copy value
proposition as before, now shaped to match `udp::Server::Handler`/
`rcp::mock::Server::dispatch`'s signature.

`loan.hpp`'s `loan::Controller` (which wrapped the now-gone
`rcp::LoaningController`) becomes a standalone `BufferPool`, the same
"primitive, not a wrapped chokepoint" choice `tsn.hpp` makes at this
milestone — a caller draws a `rcp::Loan` from it immediately before
building an AVTPDU/ACF-framed request. Buffers are re-zeroed on reuse
(closing a small aliasing gap the pre-replacement pool didn't guarantee).

`record.hpp` and `observe.hpp` both now wrap a `RequestFn` — a
`std::function` shaped like `udp::Client::request`'s core signature (the
"new client-side send-equivalent call" this milestone calls for) — rather
than the removed `rcp::Controller`. `record::Entry` carries
`acf::AcfMessageInfo`/payload pairs for both the request and response
instead of `Command`/`Response`/`Status`; `Record::write_binary` now has a
matching `read_binary`, encoding each message via `rcp::acf::encode_acf_abb`/
`encode_acf_gbb` (the same codec the wire path itself uses) so the on-disk
format round-trips losslessly rather than only being asserted non-empty.
`observe::Span`/`Metric` swap `Zone`/`CommandType` for `byte_bus_id`/
`acf_msg_type` and an opaque `stream_key`; the OTel-style span/counter
approach itself (`MetricsSink`/`NoopSink`/`InMemorySink`) is unchanged.

Grepping the tree for consumers of each file's pre-v2.14.0 API beyond its
own (now-rewritten) test found none, so no legacy-shim split file was
needed for any of the seven, same as `rcp/udp.hpp`'s v2.13.0 rebuild.
New/rewritten coverage lives in `tests/test_mdns.cpp` (`REQ-MDNS-001..008`),
`tests/test_tls.cpp` (`REQ-TLS-001..010`), `tests/test_tsn.cpp`
(`REQ-TSN-001..006`), `tests/test_shmem.cpp` (`REQ-SHMEM-001..008`),
`tests/test_loan.cpp` (`REQ-LOAN-001..006`), `tests/test_record.cpp`
(`REQ-REC-001..008`), and `tests/test_observe.cpp` (`REQ-OBS-001..008`) —
all entirely rewritten under the same file/prefix identity as the
discarded pre-replacement coverage, the same convention `tests/test_udp.cpp`
followed at v2.13.0 — traced in `.fusa-reqs.json`. Full bit-for-bit
conformance against other TC18/Annex-J implementations is not claimed,
same as the equivalent disclaimers in `rcp/avtp.hpp`, `rcp/acf.hpp`,
`rcp/discovery.hpp`, and `rcp/udp.hpp`.

### 59. Application-Layer Protocol Bridge Rebind (v2.15.0)

- Rebind `mqttbr.hpp`, `ddsbr.hpp`, `someipbr.hpp`, `restbridge.hpp`,
Expand Down
100 changes: 57 additions & 43 deletions include/rcp/loan.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,31 @@
// fusa:req REQ-LOAN-005
// fusa:req REQ-LOAN-006

// LoaningController wrapper — extends any rcp::Controller with zero-copy
// payload loaning via a pool of pre-allocated byte vectors.
// BufferPool — zero-copy payload loaning for AVTPDU-framed request/response
// construction, via a pool of pre-allocated byte vectors.
//
// loan() obtains a zeroed buffer from the pool; send_loaned() delivers it to
// the inner controller without copying. The Loan's destructor returns the
// buffer to the pool automatically if not sent.
// ROADMAP.md milestone 58, "Auxiliary Transport & Cross-Cutting Rebind
// (v2.14.0)": this header is ADAPTed, per the Satellite Package
// Disposition table's entry for `loan.hpp` — the pre-replacement
// loan::Controller wrapped rcp::LoaningController, a chokepoint that no
// longer exists (there is no unified client-side send() interface yet;
// that unification, if any, does not land until the CLI/capi/adapt
// rebuilds at v2.16.0 — see rcp/authz.hpp's equivalent v2.11.0 note). This
// rebind drops the wrapper and keeps the pool as a standalone primitive a
// caller draws a buffer from immediately before building an
// AVTPDU/ACF-framed request (rcp/avtp.hpp, rcp/acf.hpp) — e.g. the
// req_payload/out_resp_payload arguments to rcp/udp.hpp's Client::request
// — same "primitives driven by the embedding application" convention every
// Phase 14/15 header has used since v2.9.0. This likely matters more now
// than it did against the old 16-byte header: AVTPDU + ACF_ABB/ACF_GBB
// framing carries more bytes worth avoiding a fresh heap allocation for on
// a hot request path.
//
// loan() obtains a zeroed buffer from the pool; the returned rcp::Loan (a
// generic RAII buffer holder — not itself part of the pre-replacement
// Zone/Command/Controller/Registry model rcp/rcp.hpp's own header comment
// warns against building on) returns its buffer to the pool automatically
// once it goes out of scope, or immediately via its own ret() method.
#pragma once

#include "rcp.hpp"
Expand All @@ -23,65 +42,60 @@
namespace rcp {
namespace loan {

class Controller final : public rcp::LoaningController {
class BufferPool {
public:
explicit Controller(std::shared_ptr<rcp::Controller> inner)
: inner_(std::move(inner)) {}

~Controller() override { auto ec = close(); (void)ec; }

Zone zone() const noexcept override { return inner_->zone(); }

std::error_code send(const rcp::Context& ctx,
const Command& cmd,
Response& out) override {
return inner_->send(ctx, cmd, out);
}

std::error_code subscribe(const rcp::Context& ctx,
std::shared_ptr<StatusChannel>& out) override {
return inner_->subscribe(ctx, out);
}

std::error_code close() override {
closed_.store(true, std::memory_order_release);
return inner_->close();
}
~BufferPool() { close(); }

// loan returns a zeroed buffer of exactly size bytes from the pool.
std::error_code loan(int size, std::unique_ptr<rcp::Loan>& out) override {
// loan returns a zeroed buffer of exactly size bytes, drawn from the
// pool if a same-or-larger buffer is available for reuse, or freshly
// allocated otherwise.
std::error_code loan(int size, std::unique_ptr<rcp::Loan>& out) {
if (closed_.load(std::memory_order_acquire)) return ErrClosed;
if (size < 0) return make_error_code(Errc::busy);
if (size < 0) return std::make_error_code(std::errc::invalid_argument);

std::vector<uint8_t> buf;
{
std::lock_guard<std::mutex> lk(pool_mu_);
if (!pool_.empty()) {
buf = std::move(*pool_.back());
pool_.pop_back();
}
}
buf.assign(static_cast<size_t>(size), 0); // re-zero: no stale data leaks across reuse

std::vector<uint8_t> buf(static_cast<size_t>(size), 0);
// Release function returns the buffer to the pool.
auto* raw = new std::vector<uint8_t>(std::move(buf));
out = std::make_unique<rcp::Loan>(
*raw,
[this, raw]() mutable {
std::lock_guard<std::mutex> lk(pool_mu_);
raw->clear();
pool_.push_back(std::unique_ptr<std::vector<uint8_t>>(raw));
});
return {};
}

std::error_code send_loaned(const rcp::Context& ctx,
Command& cmd,
Response& out) override {
if (closed_.load(std::memory_order_acquire)) return ErrClosed;
return inner_->send(ctx, cmd, out);
// close is idempotent — safe to call more than once, including while
// Loans obtained before the call are still alive (their eventual
// release simply grows a pool nobody will draw from again).
void close() { closed_.store(true, std::memory_order_release); }

bool ok() const noexcept { return !closed_.load(std::memory_order_acquire); }

// pooled_count reports how many released buffers are currently held
// for reuse — introspection for tests, not part of the loan/release
// contract itself.
size_t pooled_count() const {
std::lock_guard<std::mutex> lk(pool_mu_);
return pool_.size();
}

private:
std::shared_ptr<rcp::Controller> inner_;
std::atomic<bool> closed_{false};
std::mutex pool_mu_;
mutable std::mutex pool_mu_;
std::vector<std::unique_ptr<std::vector<uint8_t>>> pool_;
};

inline std::shared_ptr<Controller> new_controller(std::shared_ptr<rcp::Controller> inner) {
return std::make_shared<Controller>(std::move(inner));
inline std::unique_ptr<BufferPool> new_buffer_pool() {
return std::make_unique<BufferPool>();
}

} // namespace loan
Expand Down
Loading
Loading