macula_frame (macula v3.13.0)

View Source

CBOR-encoded wire frames for Macula V2 (Part 6 §3 canonical wire).

A wire frame is a length-prefixed deterministic CBOR map:

    <<Length:32/big, Cbor/binary>>
  

where Cbor is the RFC 8949 §4.2.1 deterministic encoding of a single map. The map carries the common header fields (Part 6 §3) plus type-specific fields and an Ed25519 signature.

PLAN_WIRE_CBOR.md migrated this codec from BERT to CBOR so hecate-station and the macula 3.x SDK share a wire format. Frame schemas (atom-keyed maps in process memory) are unchanged; the codec transparently round-trips atom keys/values to text strings on the wire and reconstitutes them via binary_to_existing_atom on decode.

Phase 1 covers CONNECT / HELLO / GOODBYE. Phase 2 adds SWIM. Phase 3 (Session 3.4) adds the DHT operation frames from Part 6 §7: PING / PONG, FIND_NODE / NODES, FIND_VALUE / VALUE, STORE / STORE_ACK, REPLICATE / REPLICATE_ACK. Phase 4 (Session 4.1) adds the CALL / RESULT / ERROR frames from Part 6 §5 plus the BOLT#4 error taxonomy in hecate_bolt4. PUBLISH frames land later.

Signatures are Ed25519 over "macula-v2-frame\0" ++ canonical_cbor(unsigned) where canonical_cbor is macula_record_cbor:encode/1 (RFC 8949 §4.2.1 deterministic).

Summary

Types

advertise_spec/0

-type advertise_spec() ::
          #{realm := id256(),
            procedure := binary(),
            advertiser := macula_identity:pubkey(),
            options => map()}.

block_spec/0

-type block_spec() :: #{mcid := mcid(), payload := binary()}.

call_error_spec/0

-type call_error_spec() ::
          #{call_id := call_id(),
            code := macula_bolt4:code(),
            reported_by := macula_identity:pubkey(),
            detail => binary() | undefined,
            offending_hop => macula_identity:pubkey() | undefined,
            source_route_partial => binary()}.

call_id/0

-type call_id() :: <<_:128>>.

call_spec/0

-type call_spec() ::
          #{call_id := call_id(),
            procedure := binary(),
            realm := id256(),
            payload := term(),
            deadline_ms := integer(),
            caller := macula_identity:pubkey(),
            source_route => binary(),
            retry_budget => non_neg_integer()}.

cancel_spec/0

-type cancel_spec() :: #{blocks := [mcid()]}.

connect_spec/0

-type connect_spec() ::
          #{node_id := macula_identity:pubkey(),
            station_id := macula_identity:pubkey(),
            realms := [macula_identity:pubkey()],
            capabilities := non_neg_integer(),
            puzzle_evidence := <<_:256>>,
            addresses => [map()],
            site => map() | undefined,
            endorsements => [map()]}.

country/0

-type country() :: <<_:16>>.

delivery_channel/0

-type delivery_channel() :: plumtree | dht | direct.

event_spec/0

-type event_spec() ::
          #{topic := binary(),
            realm := id256(),
            publisher := macula_identity:pubkey(),
            seq := non_neg_integer(),
            payload := term(),
            delivered_via := delivery_channel()}.

find_node_spec/0

-type find_node_spec() ::
          #{key := id256(), origin := macula_identity:pubkey(), depth := non_neg_integer()}.

find_value_spec/0

-type find_value_spec() :: #{key := id256(), origin := macula_identity:pubkey()}.

frame/0

-type frame() :: map().

frame_type/0

-type frame_type() ::
          connect | hello | goodbye | swim_ping | swim_ack | swim_suspect | swim_confirm | ping | pong |
          find_node | nodes | find_value | value | store | store_ack | replicate | replicate_ack |
          call | result | error | hyparview_join | hyparview_forward_join | hyparview_neighbor |
          hyparview_disconnect | hyparview_shuffle | hyparview_shuffle_reply | plumtree_gossip |
          plumtree_ihave | plumtree_graft | plumtree_prune | publish | subscribe | unsubscribe | event |
          advertise | unadvertise | want | have | block | manifest_req | manifest_res | cancel.

have_entry/0

-type have_entry() :: #{mcid := mcid(), size := non_neg_integer()}.

have_spec/0

-type have_spec() :: #{blocks := [have_entry()]}.

hello_spec/0

-type hello_spec() ::
          #{node_id := macula_identity:pubkey(),
            station_id := macula_identity:pubkey(),
            realms := [macula_identity:pubkey()],
            capabilities := non_neg_integer(),
            accepted := boolean(),
            negotiated_capabilities := non_neg_integer(),
            addresses => [map()],
            site => map() | undefined,
            refusal_code => non_neg_integer() | undefined}.

hyparview_disconnect_spec/0

-type hyparview_disconnect_spec() :: #{realm := id256()}.

hyparview_forward_join_spec/0

-type hyparview_forward_join_spec() ::
          #{realm := id256(),
            new_member := macula_identity:pubkey(),
            ttl := non_neg_integer(),
            arwl := non_neg_integer(),
            prwl := non_neg_integer()}.

hyparview_join_spec/0

-type hyparview_join_spec() :: #{realm := id256(), new_member := macula_identity:pubkey()}.

hyparview_neighbor_spec/0

-type hyparview_neighbor_spec() :: #{realm := id256(), priority := neighbor_priority()}.

hyparview_shuffle_reply_spec/0

-type hyparview_shuffle_reply_spec() :: #{realm := id256(), peer_sample := [macula_identity:pubkey()]}.

hyparview_shuffle_spec/0

-type hyparview_shuffle_spec() ::
          #{realm := id256(),
            origin := macula_identity:pubkey(),
            ttl := non_neg_integer(),
            peer_sample := [macula_identity:pubkey()]}.

id256/0

-type id256() :: <<_:256>>.

manifest_req_spec/0

-type manifest_req_spec() :: #{mcid := mcid()}.

manifest_res_spec/0

-type manifest_res_spec() :: #{mcid := mcid(), manifest := map() | not_found}.

mcid/0

-type mcid() :: <<_:272>>.

member_state/0

-type member_state() :: alive | suspect | confirmed_failed.

msg_id/0

-type msg_id() :: <<_:128>>.

neighbor_priority/0

-type neighbor_priority() :: high | low.

nodes_spec/0

-type nodes_spec() :: #{key := id256(), nodes := [station_ref()]}.

nonce128/0

-type nonce128() :: <<_:128>>.

ping_spec/0

-type ping_spec() :: #{nonce := nonce128()}.

plumtree_gossip_spec/0

-type plumtree_gossip_spec() ::
          #{realm := id256(), msg_id := msg_id(), round := non_neg_integer(), payload := term()}.

plumtree_graft_spec/0

-type plumtree_graft_spec() :: #{realm := id256(), msg_id := msg_id(), round := non_neg_integer()}.

plumtree_ihave_spec/0

-type plumtree_ihave_spec() :: #{realm := id256(), msg_id := msg_id(), round := non_neg_integer()}.

plumtree_prune_spec/0

-type plumtree_prune_spec() :: #{realm := id256()}.

pong_spec/0

-type pong_spec() :: #{nonce := nonce128()}.

publish_spec/0

-type publish_spec() ::
          #{topic := binary(),
            realm := id256(),
            publisher := macula_identity:pubkey(),
            seq := non_neg_integer(),
            payload := term(),
            published_at_ms := non_neg_integer(),
            ttl_ms => non_neg_integer() | undefined}.

replicate_ack_spec/0

-type replicate_ack_spec() :: #{key := id256(), accepted := boolean()}.

replicate_spec/0

-type replicate_spec() :: #{record := macula_record:record(), new_custodian := boolean()}.

result_spec/0

-type result_spec() ::
          #{call_id := call_id(),
            payload := term(),
            responded_by := macula_identity:pubkey(),
            source_route_reverse => binary()}.

station_ref/0

-type station_ref() ::
          #{node_id := macula_identity:pubkey(),
            station_id := macula_identity:pubkey(),
            addresses := [map()],
            tier := tier(),
            asn := non_neg_integer() | undefined,
            country := country(),
            last_seen_at := pos_integer()}.

station_ref_spec/0

-type station_ref_spec() ::
          #{node_id := macula_identity:pubkey(),
            station_id := macula_identity:pubkey(),
            addresses => [map()],
            tier := tier(),
            asn => non_neg_integer() | undefined,
            country := country(),
            last_seen_at := pos_integer()}.

store_ack_spec/0

-type store_ack_spec() :: #{key := id256(), stored := boolean(), reason => atom() | undefined}.

store_spec/0

-type store_spec() :: #{record := macula_record:record()}.

subscribe_spec/0

-type subscribe_spec() ::
          #{topic := binary(),
            realm := id256(),
            subscriber := macula_identity:pubkey(),
            filter => term() | undefined,
            options => map()}.

swim_ack_spec/0

-type swim_ack_spec() ::
          #{round := non_neg_integer(),
            responder := macula_identity:pubkey(),
            incarnation := non_neg_integer(),
            piggyback => [swim_update()]}.

swim_ping_spec/0

-type swim_ping_spec() ::
          #{round := non_neg_integer(), incarnation := non_neg_integer(), piggyback => [swim_update()]}.

swim_suspect_spec/0

-type swim_suspect_spec() ::
          #{target := macula_identity:pubkey(),
            target_incarnation := non_neg_integer(),
            suspected_by := macula_identity:pubkey(),
            ttl := non_neg_integer()}.

swim_update/0

-type swim_update() ::
          #{target := macula_identity:pubkey(),
            state := member_state(),
            incarnation := non_neg_integer(),
            observed_at := pos_integer(),
            by := macula_identity:pubkey(),
            signature => <<_:512>>}.

swim_update_spec/0

-type swim_update_spec() ::
          #{target := macula_identity:pubkey(),
            state := member_state(),
            incarnation := non_neg_integer(),
            observed_at := pos_integer(),
            by := macula_identity:pubkey()}.

tier/0

-type tier() :: 0..4.

unadvertise_spec/0

-type unadvertise_spec() ::
          #{realm := id256(), procedure := binary(), advertiser := macula_identity:pubkey()}.

unsubscribe_spec/0

-type unsubscribe_spec() ::
          #{topic := binary(), realm := id256(), subscriber := macula_identity:pubkey()}.

value_spec/0

-type value_spec() :: #{key := id256(), records := [macula_record:record()]}.

want_entry/0

-type want_entry() :: #{mcid := mcid(), priority => want_priority()}.

want_priority/0

-type want_priority() :: 0..255.

want_spec/0

-type want_spec() :: #{blocks := [want_entry()]}.

Functions

advertise(Spec)

-spec advertise(advertise_spec()) -> frame().

block(_)

-spec block(block_spec()) -> frame().

call(Spec)

-spec call(call_spec()) -> frame().

call_error(Spec)

-spec call_error(call_error_spec()) -> frame().

cancel(_)

-spec cancel(cancel_spec()) -> frame().

connect(Spec)

-spec connect(connect_spec()) -> frame().

decode(Buf)

-spec decode(binary()) -> {ok, frame(), binary()} | {more, pos_integer()} | {error, term()}.

Decode a single length-prefixed frame from the head of a buffer. Returns {ok, Frame, RestBuffer}, {more, BytesNeeded} if the buffer is short, or {error, Reason} if the framing is malformed.

encode(Frame)

-spec encode(frame()) -> binary().

event(_)

-spec event(event_spec()) -> frame().

find_node(_)

-spec find_node(find_node_spec()) -> frame().

find_value(_)

-spec find_value(find_value_spec()) -> frame().

frame_id(_)

frame_type(_)

goodbye(Reason, Detail)

-spec goodbye(atom(), binary() | undefined) -> frame().

goodbye(Reason, Detail, Caps)

-spec goodbye(atom(), binary() | undefined, non_neg_integer()) -> frame().

have(_)

-spec have(have_spec()) -> frame().

hello(Spec)

-spec hello(hello_spec()) -> frame().

hyparview_disconnect(_)

-spec hyparview_disconnect(hyparview_disconnect_spec()) -> frame().

hyparview_forward_join(_)

-spec hyparview_forward_join(hyparview_forward_join_spec()) -> frame().

hyparview_join(_)

-spec hyparview_join(hyparview_join_spec()) -> frame().

hyparview_neighbor(_)

-spec hyparview_neighbor(hyparview_neighbor_spec()) -> frame().

hyparview_shuffle(_)

-spec hyparview_shuffle(hyparview_shuffle_spec()) -> frame().

hyparview_shuffle_reply(_)

-spec hyparview_shuffle_reply(hyparview_shuffle_reply_spec()) -> frame().

manifest_req(_)

-spec manifest_req(manifest_req_spec()) -> frame().

manifest_res(_)

-spec manifest_res(manifest_res_spec()) -> frame().

nodes(_)

-spec nodes(nodes_spec()) -> frame().

parse_stream(Buf)

-spec parse_stream(binary()) -> {[frame()], binary()}.

Drain all complete frames from a buffer. Returns the list of frames (in order) and the remaining (incomplete) buffer.

ping(_)

-spec ping(ping_spec()) -> frame().

plumtree_gossip(_)

-spec plumtree_gossip(plumtree_gossip_spec()) -> frame().

plumtree_graft(_)

-spec plumtree_graft(plumtree_graft_spec()) -> frame().

plumtree_ihave(_)

-spec plumtree_ihave(plumtree_ihave_spec()) -> frame().

plumtree_prune(_)

-spec plumtree_prune(plumtree_prune_spec()) -> frame().

pong(_)

-spec pong(pong_spec()) -> frame().

publish(Spec)

-spec publish(publish_spec()) -> frame().

replicate(_)

-spec replicate(replicate_spec()) -> frame().

replicate_ack(_)

-spec replicate_ack(replicate_ack_spec()) -> frame().

result(Spec)

-spec result(result_spec()) -> frame().

sent_at_ms(_)

sign(Frame, Identity)

sign_swim_update(Update, Identity)

signature(_)

station_ref(Spec)

-spec station_ref(station_ref_spec()) -> station_ref().

store(_)

-spec store(store_spec()) -> frame().

store_ack(Spec)

-spec store_ack(store_ack_spec()) -> frame().

subscribe(Spec)

-spec subscribe(subscribe_spec()) -> frame().

swim_ack(Spec)

-spec swim_ack(swim_ack_spec()) -> frame().

swim_confirm(Spec)

-spec swim_confirm(swim_suspect_spec()) -> frame().

swim_ping(Spec)

-spec swim_ping(swim_ping_spec()) -> frame().

swim_suspect(Spec)

-spec swim_suspect(swim_suspect_spec()) -> frame().

swim_update(_)

-spec swim_update(swim_update_spec()) -> swim_update().

unadvertise(_)

-spec unadvertise(unadvertise_spec()) -> frame().

unsubscribe(_)

-spec unsubscribe(unsubscribe_spec()) -> frame().

value(_)

-spec value(value_spec()) -> frame().

verify(Frame, Pub)

-spec verify(frame(), macula_identity:pubkey()) -> {ok, frame()} | {error, term()}.

verify_swim_update(Update)

-spec verify_swim_update(swim_update()) -> {ok, swim_update()} | {error, term()}.

version(_)

want(_)

-spec want(want_spec()) -> frame().