transport — the wire seam (L4)¶
In one paragraph
transport_t is the seam between the FWD router and one wire technology: send
framed bytes (single buffer or a scatter-gather iovec), register a
receiver_t for inbound frames. It never sees TLV semantics — only bytes.
Implementations: loopback_channel_t (in-process dev/test),
udp_transport_t (localhost/LAN UDP), tcp_transport_t (reliable TCP
stream, 4-byte u32-LE length-prefix framing — the prefix is transport framing,
not part of the TLV), transport_ws_client / transport_ws_server (the
browser↔robot WebSocket keystone, RFC 6455), transport_can (SocketCAN,
classic + CAN-FD), quic_transport_t (the separate libtracer_quic
module, msquic — TLS 1.3, connection migration; one bidirectional stream
carrying the same length-prefix framing as TCP; a host that talks QUIC links
the module and registers quic_transport_factory(); ADR-0043 Phase A — the
hosted, secure link; the MCU class keeps UDP/CAN), and
webtransport_transport_t (same module, ADR-0043 Phase B — WebTransport
over HTTP/3, the browser-reachable form of QUIC: a minimal module-private
H3/QPACK handshake layer in front of one WebTransport bidirectional stream
carrying the same length-prefix framing; kind webtransport via
webtransport_transport_factory(); the TS twin is
@avatarsd-llc/libtracer-webtransport — the #92 / ADR-0031 browser↔robot
path).
What it does¶
A transport accepts a complete frame’s bytes (a FWD frame, or a route-handle
control frame — ADVERTISE/COMPACT/HANDLE_NACK) and
emits them; inbound frames arrive on the registered receiver (which may fire on an
internal transport thread). The reference catalog defines a poll-based
transport_vtable; the C++ seam is callback + recv-thread — an implementation
choice that matches how a real socket’s receive loop feeds the FWD router.
A transport that can hand up owning frames additionally implements the
rope-receiver seam (ADR-0042,
generalized to ropes by ADR-0053): it overrides
delivers_ropes() and delivers each inbound frame as a rope_t of refcounted links — a
contiguous frame is the single-link case (e.g. tcp_transport_t reads a frame straight into
one segment); a scattered one (a CAN reassembly group, a fragmented WS message) crosses the
seam as the rope it already is, never a flatten copy. fwd_router_t::add_child installs
whichever receiver matches the link’s capability; every other transport keeps the
borrowed-span receiver.
loopback_channel_t wires two endpoints: a frame sent on one is delivered to the
other’s receiver on that endpoint’s thread (modeling async cross-“wire” delivery).
shutdown() joins the receive threads before the receivers are destroyed.
Interface¶
using peer_id_t = std::array<std::byte, 16>; // the node identity
class transport_t {
virtual void send(std::span<const std::byte> frame) = 0;
// Scatter-gather: ship a rope's to_iovec() as one frame, no flatten copy.
// Default gathers+send(); native transports override (sendmsg/writev/RDMA-SGE).
virtual void send(std::span<const std::span<const std::byte>> iov);
using receiver_t = std::function<void(std::span<const std::byte>)>;
virtual void set_receiver(receiver_t) = 0;
};
class loopback_channel_t { // dev/test transport
loopback_endpoint_t& a(); loopback_endpoint_t& b(); // each a transport_t
void shutdown(); // join recv threads
};
class udp_transport_t : public transport_t { // real UDP (M5)
udp_transport_t(uint16_t bind_port, const std::string& peer_host, uint16_t peer_port);
// send(span) = one sendto; send(iov) = one sendmsg(iovec) — the structural
// batch (a composite rope in one syscall; see Performance).
};
The forward hop that feeds a transport (zero-heap)¶
What the FWD router hands send(iov) on a forward hop is not a re-encoded frame:
the hop reads a few headers of the inbound frame by offset, builds the shortened
headers in small stack buffers, and scatter-gathers those heads with untouched
views of the inbound frame — zero heap allocations, CI-gated
(bench_forward_heap, ZEROHEAP_MAX=0).
flowchart LR
IN["inbound frame bytes"] --> PEEK["offset peek:<br/>first dst NAME"]
PEEK --> DEMUX["child_registry_t<br/>NAME → transport"]
DEMUX --> SG["stack-built heads<br/>+ untouched frame views"]
SG -->|"send(iov) — one syscall"| T(["transport_t"])
Two nodes over a wire¶
flowchart LR
FA["fwd_router A"] -->|send| EA["endpoint a"]
EA -->|enqueue| QB[("inbox B")]
QB -->|recv thread| EB["endpoint b"]
EB -->|receiver| FB["fwd_router B"]
FB -->|FWD REPLY send| EB2["endpoint b"]
EB2 -->|enqueue| QA[("inbox A")]
QA -->|recv thread| EA2["endpoint a"]
EA2 -->|receiver| FA
classDef m fill:#fef9c3,stroke:#92400e
class EA,EB,EA2,EB2 m
Benefits¶
One seam, many wires — the FWD router and the whole stack above are transport- agnostic; a new socket transport plugs in with no changes upstream.
Deterministic testing — the loopback exercises the full encode→FWD-route→decode path with no sockets, so forward/reply behavior is unit-testable (and the benchmark measures it).
Bytes only — a transport can’t accidentally depend on graph semantics.
API reference¶
Generated from core/include/libtracer/transport.hpp by Doxygen.
-
class transport_t¶
A point-to-point (or bus-facet-exposing) transport link: the byte seam between the routing plane and one wire (ws/tcp/udp/quic/CAN).
The router sends complete TLV frames via send and receives them through an installed sink (set_receiver for borrowed spans, or set_rope_receiver for owning refcounted rope frames when delivers_ropes is true). A multi-peer bus link additionally exposes a bus_link_t facet via bus.
Subclassed by tr::net::loopback_endpoint_t, tr::net::quic_transport_t, tr::net::tcp_transport_t, tr::net::transport_can, tr::net::transport_ws_client, tr::net::transport_ws_server, tr::net::udp_transport_t, tr::net::webtransport_transport_t
Public Types
-
using receiver_t = std::function<void(std::span<const std::byte>)>¶
The borrowed-span inbound sink: a frame valid only for the callback.
-
using rope_receiver_t = std::function<void(view::rope_t)>¶
The OWNING inbound sink (ADR-0042, generalized to ropes per ADR-0053): each frame is a
rope_tof refcounted links the receiver may keep, subrope, or forward — a contiguous frame is the trivial single-link case (“delivers views” and “delivers ropes” are ONE capability, not two tiers — CONTEXT.md §ingress rope delivery).
Public Functions
-
virtual void send(std::span<const std::byte> frame) = 0¶
Emit one frame (a complete TLV’s bytes) onto the wire.
-
inline virtual void send(std::span<const std::span<const std::byte>> iov)¶
Scatter-gather send: emit the gathered spans as ONE frame, no flatten copy.
Hand a rope’s
to_iovec()straight to the wire. The default gathers into a temporary and calls send(std::span<const std::byte>); transports with native scatter-gather (sendmsg/writev/RDMA SGE) override this to avoid the copy.- Parameters:
iov – The spans to emit, in order, as a single frame.
-
virtual void set_receiver(receiver_t receiver) = 0¶
Register the borrowed-span sink for inbound frames (the bridge’s ingest).
Must be set before frames flow; delivery may occur on an internal transport thread. The delivered span is valid only for the callback — a receiver that needs to keep the frame uses set_rope_receiver instead.
- Parameters:
receiver – The inbound frame sink.
-
inline virtual void set_rope_receiver(rope_receiver_t receiver)¶
Register the optional OWNING inbound sink (the ADR-0042 receiver seam).
A transport that can hand up owning frames (its delivers_ropes returns true) delivers each inbound frame to
receiveras aview::rope_twhose links are refcounted views over segments drawn from a host-injectedmem_backend_t— the receiver may pin, subrope, or forward the frame beyond the callback (unlike the borrowed span of set_receiver, which dies when the callback returns). A contiguous frame arrives as a single-link rope; a scattered one (a CAN reassembly group, fragmented WS message) crosses this seam AS THE ROPE IT ALREADY IS — reassembly is chaining views, never a memcpy (ADR-0053 §5). Must be set before frames flow; delivery may occur on an internal transport thread.The base implementation is a documented no-op: a span-only transport ignores an installed rope receiver, honestly — there is NO adapter that wraps a borrowed span into a rope whose refcounts would lie about lifetime (ADR-0042 §1). A transport that honors this seam MUST also override delivers_ropes to return true, so
fwd_router_t::add_childinstalls the receiver matching the link’s capability.
-
inline virtual bool delivers_ropes() const¶
The owning-delivery capability (ADR-0042 §1): true iff this transport honors set_rope_receiver by delivering refcounted rope frames.
-
inline virtual bus_link_t *bus()¶
The multi-peer (bus) capability (ADR-0044): non-null iff this link reaches many peers and exposes them via bus_link_t.
A point-to-point transport keeps the default nullptr; a bus transport (the CAN binding) returns its own bus_link_t facet, which the router and the connection vertex consult for peer resolution and peer enumeration.
-
using receiver_t = std::function<void(std::span<const std::byte>)>¶
-
class bus_link_t¶
The optional multi-peer (bus) capability of a transport link (ADR-0044).
A point-to-point link (ws/tcp/udp/quic) carries exactly one peer, so its child NAME fully addresses the far side. A BUS link (CAN) reaches many peers over one wire; this interface is how such a link exposes them to the routing plane with ZERO stored graph state (ADR-0044 §1 — no vertex is ever created for a peer):
enumerate_peers synthesizes, on the fly, the names of the peers currently audible on the bus (from the transport kind’s own live announce/heartbeat traffic) — the
:children[]listing of the link’s connection vertex;peer_link resolves one such NAME to a directed sending endpoint, the seam
child_registry_tfalls back to when a FWD’s nextdstsegment names no static child — so a peer name IS a routable hop segment;set_peer_receiver replaces the flat inbound sink with a peer-named one: each inbound frame arrives tagged with the SENDING peer’s name, which the router uses as the hop’s inbound NAME — so the return route grown into
srcnames the bus peer to route the reply back to, symmetrically, with no per-request state.
Peer names are transport-defined but MUST be deterministic and collision-safe within the bus (the CAN binding derives them from the structured ID’s
nodefield). All three calls may race the transport’s receive thread; impls synchronize internally.Subclassed by tr::net::transport_can
Public Types
-
using peer_visitor_t = std::function<void(std::string_view)>¶
Visitor invoked once per currently-audible peer name.
-
using peer_receiver_t = std::function<void(std::string_view, std::span<const std::byte>)>¶
The peer-named inbound sink: (sending peer’s name, frame bytes).
Public Functions
-
virtual void enumerate_peers(const peer_visitor_t &visit) const = 0¶
Visit the peers currently audible on the bus (a live-traffic snapshot).
Note
Synthesized on the fly — no call allocates peer state or graph structure.
-
virtual transport_t *peer_link(std::string_view peer) = 0¶
Resolve a peer NAME to a directed sending endpoint on this bus.
The returned transport sends to THAT peer only (the bus binding’s directed framing); it is owned by this link and stays valid for the link’s lifetime.
- Return values:
nullptr –
peernames no currently-known bus peer.
-
virtual void set_peer_receiver(peer_receiver_t receiver) = 0¶
Register the peer-named inbound sink (used INSTEAD of
set_receiver).Must be set before frames flow; delivery may occur on an internal transport thread. When set, it takes precedence over a flat transport_t receiver.
-
inline virtual void set_peer_rope_receiver(peer_rope_receiver_t receiver)¶
Register the OWNING peer-named sink (ADR-0053 §5) — used INSTEAD of set_peer_receiver when the bus delivers_ropes.
A reassembling bus (CAN groups, fragmented WS) hands the frame up as the rope its reassembly already built — chained refcounted slice views, never a flatten memcpy; transport padding is trimmed by shortening the tail link. The base implementation is a documented no-op, the same honesty rule as
transport_t::set_rope_receiver: a span-only bus must not fake ownership.
-
inline virtual bool delivers_ropes() const¶
True iff this bus honors set_peer_rope_receiver (ADR-0053 §5).