Expand description
The macula application-frame envelope: construction, Ed25519
signing/verification, and the length-prefixed wire codec. Ported from
src/peering/macula_frame.erl (macula-io/macula).
A wire frame is <<Length:32/big, Cbor/binary>> where Cbor is the
deterministic encoding of a single map (see crate::cbor). Every
frame carries a common envelope — version, frame_type, frame_id
(UUIDv7), sent_at_ms, capabilities, plus realm/call_id/
source_route set to null unless the specific frame type populates
them — and every frame is Ed25519-signed over its own canonical bytes
with signature/publisher_sig stripped first.
This module’s correctness is checked against a real reference frame:
tests::connect_frame_matches_the_reference_byte_for_byte builds
the exact same CONNECT frame macula_frame:connect/1 +
macula_frame:sign/2 produced in a live rebar3 shell — same
identity, same fixed frame_id/sent_at_ms (injected explicitly,
since the reference randomizes both per call and non-determinism
would make an exact byte comparison meaningless) — and asserts the
encoded bytes, including the Ed25519 signature itself, match
exactly. That’s the strongest test available short of dialing a real
station: it proves the canonical-CBOR encoding, the field set, and
the signing domain are all bit-for-bit compatible at once.
Structs§
- Advertise
Spec - Fields for an ADVERTISE frame.
- Call
Error Spec - Fields for an ERROR frame.
nameis derived fromcodeautomatically (matchingmacula_frame:call_error/1’s ownmacula_bolt4:name/1lookup), not a caller-supplied field. - Call
Info - The fields a provider needs from an inbound CALL — the
counterpart to
CallResponsefor the receiving side. Still doesn’t carrysource_route/retry_budget: nothing in the provider role built so far acts on either.ucan_tokenIS carried — added forcrate::connection::Session::serve_one_call_gated’s policy check, which runs before a handler ever sees the call. - Call
Spec - Fields for a CALL frame — see
plans/PLAN_WIRE_PROTOCOL.md§6.4. - Connect
Spec - Fields for a CONNECT frame — see
plans/PLAN_WIRE_PROTOCOL.md§5. - Event
Info - What a subscriber actually receives — parsed fields of an EVENT frame.
- Hello
Info - The fields of a HELLO frame actually needed to drive the handshake
state machine (
plans/PLAN_WIRE_PROTOCOL.md§3). - Publish
Spec - Fields for a PUBLISH frame.
- Result
Spec - Fields for a RESULT frame.
- Stream
Data Spec - Fields for a STREAM_DATA frame — one chunk.
body’s shape followsencoding:Value::BytesforStreamEncoding::Raw, any structuredValueforStreamEncoding::Msgpack(see this section’s module-level note on why that’s still a plain CBOR value, not a second codec). - Stream
EndSpec - Fields for a STREAM_END frame — a half-close (
role: Send) or full close (role: Both) of one direction. SeeStreamDataSpec::signer’s doc — same field, same reasoning. - Stream
Error Spec - Fields for a STREAM_ERROR frame — the explicit abort a well-behaved
peer sends instead of just dropping the stream on any non-normal
termination (
plans/PLAN_WIRE_PROTOCOL.md§13.1, point 4).codehere is a free-form label (is_binary(Code)in the reference), NOT a BOLT#4 numeric code like an ERROR (§6.4) frame’scode— streaming aborts and unary-call errors use unrelated error vocabularies.signer: seeStreamDataSpec::signer’s doc — same field, same reasoning. - Stream
Open Info - The fields a provider needs from an inbound STREAM_OPEN — the
first frame on a freshly-accepted dedicated stream (§13.2). Doesn’t
carry
source_route/retry_budget: nothing in the provider role built so far acts on either. - Stream
Open Spec - Fields for a STREAM_OPEN frame. Mirrors CALL’s auth/routing shape —
deadline_ms/caller/source_route/retry_budget— plus the stream-specificstream_id/mode/args. - Stream
Reply Spec - Fields for a STREAM_REPLY frame — the terminal result of a
client_stream/bidiexchange, sent once by the provider after it has fully consumed and verified whatever the caller streamed. - Subscribe
Spec - Fields for a SUBSCRIBE frame.
- Unadvertise
Spec - Fields for an UNADVERTISE frame.
- Unsubscribe
Spec - Fields for an UNSUBSCRIBE frame.
Enums§
- Call
Response - Parsed fields of a RESULT or ERROR response to a CALL, correlated by
call_id. Returned bycrate::connection::Session::call. - Decode
Frame Error - Decoded
- Result of attempting to decode one frame from the head of a buffer —
mirrors the reference decoder’s three-way
{ok,_,_}/{more,_}/{error,_}contract, adapted to return a consumed-byte count instead of a remainder slice (equally usable, more idiomatic here). - Encode
Frame Error - Parse
Call Error - Parse
Call Response Error - Parse
Event Error - Parse
Hello Error - Parse
Stream Event Error - Parse
Stream Open Error - Stream
Encoding encodingon a STREAM_DATA — a hint for how to interpretbody, not a second wire codec. See this section’s module-level note.- Stream
Event - What a stream consumer actually receives — one parsed STREAM_DATA/STREAM_END/STREAM_ERROR/STREAM_REPLY frame.
- Stream
Mode modeon a STREAM_OPEN — who’s expected to push data. Matchesmacula_stream:mode().- Stream
Role roleon a STREAM_END — which direction(s) are closing.- Verify
Error - Verify
Publisher Error
Constants§
- EVENT_
PUBLISHER_ DOMAIN - MAX_
FRAME_ BYTES - 16 MiB minus one byte — matches
?MAX_FRAME_BYTES(16#FFFFFF) exactly. - PROTOCOL_
VERSION - SIG_
DOMAIN - Domain separator for the per-frame Ed25519 signature (every frame’s
own
signaturefield). Distinct from the SWIM-update and publisher-end-to-end domains documented inplans/PLAN_WIRE_PROTOCOL.md§4 — neither of those is implemented here yet.
Functions§
- advertise
- Build an ADVERTISE frame with a fresh
frame_id/sent_at_ms. - call
- Build a CALL frame with a fresh
frame_id/sent_at_ms. Unsigned — pass the result tosignbefore sending. - call_
error - Build an ERROR frame with a fresh
frame_id/sent_at_ms. - connect
- Build a CONNECT frame with a fresh
frame_id/sent_at_ms. Unsigned — pass the result tosignbefore sending. - decode
- Decode one length-prefixed frame from the head of
buf. - encode
- Encode
frameas<<Length:32/big, Cbor/binary>>. - frame_
call_ id - Extract this frame’s
call_id, regardless of frame type — used to correlate a RESULT/ERROR back to the CALL that requested it. 16 bytes, matchingcall_id() :: <<_:128>>— NOT 32; caught only by re-checking against the spec, since the original test for this function made the identical size mistake and so didn’t catch it. - frame_
stream_ id - Extract this frame’s
stream_id, regardless of frame type — used to correlate STREAM_DATA/STREAM_END/STREAM_ERROR/STREAM_REPLY frames back to the STREAM_OPEN that started the exchange. 16 bytes, matchingstream_id() :: <<_:128>>. - goodbye
- Build a GOODBYE frame.
reasonis a short machine-readable code (e.g."normal");detailis an optional human-readable string. - parse_
call - Parse a decoded frame as a CALL — the provider-side counterpart to
parse_call_response. - parse_
call_ response - Parse a decoded frame as a RESULT or ERROR response to a CALL.
- parse_
event - Parse a decoded frame as an EVENT.
- parse_
hello - Parse a decoded frame as a HELLO, checking
frame_typefirst. - parse_
stream_ event - Parse a decoded frame as one of STREAM_DATA/STREAM_END/STREAM_ERROR/ STREAM_REPLY.
- parse_
stream_ open - Parse a decoded frame as a STREAM_OPEN.
- publish
- Build a PUBLISH frame with a fresh
frame_id/sent_at_ms. Does not setpublisher_sig(the separate end-to-end publisher signature, §4/§6.8 of the spec) — not implemented by this crate yet. - result
- Build a RESULT frame with a fresh
frame_id/sent_at_ms. - sign
- Sign
framewithidentity, overSIG_DOMAIN || canonical_cbor(frame minus signature/publisher_sig), and return the frame with itssignaturefield set (64 bytes). - sign_
publisher - Add
publisher_sigto a PUBLISH or EVENT frame:identity’s Ed25519 signature over(topic, realm, publisher, seq, payload).identitymust be the key pair for the pubkey already in the frame’spublisherfield – this is not checked here (callers build frames with their own identity’s pubkey aspublisherby construction). - stream_
data - Build a STREAM_DATA frame with a fresh
frame_id/sent_at_ms. - stream_
end - Build a STREAM_END frame with a fresh
frame_id/sent_at_ms. - stream_
error - Build a STREAM_ERROR frame with a fresh
frame_id/sent_at_ms. - stream_
open - Build a STREAM_OPEN frame with a fresh
frame_id/sent_at_ms. Unsigned — pass the result tosignbefore sending. - stream_
reply - Build a STREAM_REPLY frame with a fresh
frame_id/sent_at_ms. - subscribe
- Build a SUBSCRIBE frame with a fresh
frame_id/sent_at_ms. No filter, no options — the plainest possible subscription. - unadvertise
- Build an UNADVERTISE frame with a fresh
frame_id/sent_at_ms. - unsubscribe
- Build an UNSUBSCRIBE frame with a fresh
frame_id/sent_at_ms. - verify
- Verify
frame’ssignaturefield againstpubkey, over the same domain-separated bytessignproduces. - verify_
publisher - Verify
frame’spublisher_sigagainst its OWNpublisherfield – unlikeverify(the per-hop signature), there is no separate pubkey parameter:publisher_sig’s whole point is proving “the pubkey named in this frame produced it”, independent of which connection it arrived on.