envelope
Source: crates/lvcs-net/src/wire/envelope.rs.
/// The envelope's version (design §5).
///
/// `major` changes only with an incompatible layout: a peer rejects an
/// envelope whose `major` is not `lvcs_vocab::ids::WIRE_MAJOR` with
/// [`WireError::UnknownMajor`]. A new message kind is a `minor` change:
/// peers accept any `minor` and ignore kinds they don't know (design §10).
/// Encoded as the map `{ major, minor }`. Owner: `M3.envelope`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WireVersion {
/// Changes only with an incompatible layout.
pub major: u16,
/// Changes when a message kind is added; peers accept any.
pub minor: u16,
}
/// Who signed an envelope (design §5, §9, §15).
///
/// Chosen at the M3 freeze: gossip messages are signed by the participant
/// who sent them, so capability checks and per-signer token buckets key on
/// the participant, while gossipsub's own message author (the forwarding
/// device) is what a repeat offense down-scores; sync-stream frames are
/// signed by the sending device, whose identity Noise already
/// authenticated. Participant and device keys are separate keys (design
/// §2): a participant key is never a device key. Encoded as a one-entry map
/// `{ participant: <bytes> }` or `{ device: <bytes> }`. Owner:
/// `M3.envelope`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Sender {
/// A participant's Ed25519 public key: the bytes of an
/// `lvcs_doc::ParticipantKey`.
Participant([u8; 32]),
/// A device's Ed25519 public key, from which its libp2p `PeerId`
/// derives.
Device([u8; 32]),
}
/// One message between peers (design §5, decisions 20 and 67).
///
/// `payload` is the canonical CBOR encoding of the payload type `kind` names
/// (the `wire_kind` set in `docs/generated/vocabulary.md`, an open set: a
/// kind outside it is ignored, never rejected). `frontier` is the sender's
/// visible frontier id when the message is about project state (absent
/// otherwise; encoded as CBOR null, so the field set never varies). `sig`
/// is `sender`'s `SIGNATURE` over the canonical CBOR array
/// `[v, kind, sender, frontier, payload]` (an array of the signed fields,
/// like `ChangeSig`'s, chosen at the M3 freeze). Encoded as the map
/// `{ v, kind, sender, frontier, payload, sig }` in that order, digests and
/// `payload` as byte strings. Automerge sync messages travel inside a
/// `sync` payload unchanged (decision 30). Owner: `M3.envelope`.
#[derive(Debug, Clone, PartialEq, Eq)]
#[expect(
clippy::min_ident_chars,
reason = "the field `v` is named for the encoded envelope's `v` map key (design section 5), \
which this contract type mirrors field for field; Clippy checks a field's name \
against the struct's lint level, so the struct is the narrowest item"
)]
pub struct Envelope {
/// The envelope's version.
pub v: WireVersion,
/// The message kind: a `wire_kind` value, or one this peer doesn't know.
pub kind: String,
/// Who signed the envelope.
pub sender: Sender,
/// The sender's visible frontier id, when the message is about project
/// state.
pub frontier: Option<[u8; 32]>,
/// The canonical CBOR encoding of the payload type `kind` names.
pub payload: Vec<u8>,
/// `sender`'s signature over the signed fields.
pub sig: [u8; 64],
}
/// Why bytes are not an acceptable envelope (design §5).
///
/// Each maps to one `rejection` value: `TooLarge` and `Malformed` to
/// `malformed`, `UnknownMajor` to `unknown_version`, `BadSignature` to
/// `bad_signature`. Owner: `M3.envelope`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WireError {
/// Longer than `lvcs_vocab::limits::WIRE_MAX_BYTES`; not decoded.
TooLarge {
/// The length received, in bytes.
len: usize,
},
/// Not a canonical CBOR envelope or payload.
Malformed(String),
/// The envelope's major version is not `WIRE_MAJOR`.
UnknownMajor {
/// The major version the envelope carried.
major: u16,
},
/// `sig` is not `sender`'s signature over the signed fields.
BadSignature,
}