wire-payloads
Source: crates/lvcs-net/src/wire/mod.rs.
/// A participant that signs gossip messages, by its Ed25519 public key (the
/// bytes of an `lvcs_doc::ParticipantKey`; `Sender::Participant`).
///
/// The net actors' per-signer state (capabilities, token buckets) keys on
/// it. Journaled as a byte string. Owner: `M3.envelope`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Signer(#[serde(with = "cbor::fixed")] pub [u8; 32]);
/// Payload of kind `sync` (design §2, decisions 30 and 68; lane `M3.sync`).
///
/// It is one document's sync message on the one sync stream of a peer pair.
/// `doc` is the document address the message is tagged with; `payload` is
/// what that document's actor emitted (`DocOutput::SyncOut`), handed
/// unchanged to the receiving peer's actor for `doc` (`DocEvent::SyncIn`).
/// The envelope's sender is the sending device.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SyncFrame {
/// The document address the message is tagged with.
pub doc: String,
/// What that document's actor emitted (`DocOutput::SyncOut`), opaque
/// here.
#[serde(with = "cbor::bytes")]
pub payload: Vec<u8>,
}
/// Payload of kind `inventory` (wire minor 1; refines decision 215).
///
/// Sent on the sync stream each time the sender's caps round with the
/// receiver settles: how many documents the sender syncs with the receiver
/// then, the caps document among them, which are every document it joined
/// with the receiver. A count, not a list, so it stays far under
/// `WIRE_MAX_BYTES` at any project size. A join waits until that many
/// distinct documents arrived from one connected peer. The envelope's
/// sender is the sending device.
///
/// It also names the participant keys the sender's daemon holds for the
/// project (decision 314), at most `INVENTORY_HOLDERS_MAX`, each with its
/// signature over [`holder_input`], and the sender's machine name, at most
/// `HOST_NAME_MAX_BYTES`, so `peer.list` names who and what each peer is.
/// The receiver checks each signature; the name is the sender's own say.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SyncInventory {
/// How many documents the sender syncs with the receiver.
pub docs: u64,
/// The participant keys the sender's daemon holds, each signed.
pub holders: Vec<Holder>,
/// The sender's machine name, if it has one.
pub host: Option<String>,
}
/// A participant key a daemon holds, with the participant's signature
/// over [`holder_input`] of the project and the daemon's device key
/// (decision 314).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Holder {
/// The participant's public key.
#[serde(with = "cbor::fixed")]
pub key: [u8; 32],
/// Its Ed25519 signature over [`holder_input`].
#[serde(with = "cbor::fixed")]
pub sig: [u8; 64],
}
/// What a [`Holder`]'s signature covers: a domain tag, the project id, and
/// the holding daemon's device key, so a holder line is bound to one
/// project and one device.
#[must_use]
pub fn holder_input(project: &[u8; 32], device: &[u8; 32]) -> Vec<u8> {
let mut input = b"lvcs holder v1\0".to_vec();
input.extend_from_slice(project);
input.extend_from_slice(device);
input
}
/// What a notification says the sender will do to its range (design §5).
/// Encoded as `adding`, `changing`, or `removing`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NotifyOp {
/// The sender will add to the range.
Adding,
/// The sender will change the range.
Changing,
/// The sender will remove the range.
Removing,
}
/// Payload of kind `notification` (design §5, decisions 20 and 51; lane
/// `M3.coord`).
///
/// It is `{ doc, range, op, what, ref?, by, ts, workspace?, auto, issue? }`,
/// published on the document's topic and every ancestor directory's topic.
/// `range` is the canonical CBOR encoding of the `lvcs_doc::RangeAnchor`
/// cursor pair the notifier's document anchored (`DocEvent::Anchor`),
/// opaque here: the receiving daemon resolves it against its own copy of
/// `doc` (the coord actor sees only resolved spans). An empty `range` is a whole-document
/// notification (`AnchorOutcome::Whole`: an empty document has no character
/// to anchor on), whose span is `coord::Span::WHOLE` (chosen at the M3
/// freeze). `ref` optionally names more detail (`kad://<key>` or
/// `lvcs://<doc>#<pointer>`). `by` is the notifying participant's key bytes
/// and must equal the envelope's `Sender::Participant`; `ts` is its Unix
/// time in milliseconds, which only orders overlapping notifications: by
/// `ts`, then `by`, then the notification id (decision 51 as the M3 freeze
/// revises it; see `coord`). `workspace` is the id of the workspace whose
/// view the range is in, absent for `main` (chosen at the M3 freeze).
/// `auto` is true for a notification the daemon made from its author's
/// save through a mirror (decisions 155 and 165), false for one a client
/// sent (`announce`, `track_edit`). `issue` names the issue the notification
/// reports, absent for one that reports none (decision 187). A
/// notification's id is the `HASH` of this payload's canonical CBOR
/// encoding (`coord::NotificationId::of`), so the id and the signature
/// cover `auto` and `issue`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Notification {
/// The document notified about.
pub doc: String,
/// The canonical CBOR encoding of the anchored `lvcs_doc::RangeAnchor`
/// cursor pair; empty for the whole document.
#[serde(with = "cbor::bytes")]
pub range: Vec<u8>,
/// What the sender will do to the range.
pub op: NotifyOp,
/// What the change is, in the sender's words.
pub what: String,
/// Where more detail is (`kad://<key>` or `lvcs://<doc>#<pointer>`),
/// under the key `ref`.
#[serde(rename = "ref")]
pub reference: Option<String>,
/// The notifying participant's key bytes.
#[serde(with = "cbor::fixed")]
pub by: [u8; 32],
/// The notifier's Unix time in milliseconds.
pub ts: i64,
/// The workspace whose view the range is in; `None` for `main`.
#[serde(with = "cbor::opt_fixed")]
pub workspace: Option<[u8; 32]>,
/// Whether the daemon made it from its author's save through a mirror,
/// not a client.
pub auto: bool,
/// The issue it reports; `None` for one that reports none.
#[serde(with = "cbor::opt_fixed")]
pub issue: Option<[u8; 32]>,
}
/// Payload of kinds `note` and `hold` (design §5; lane `M3.coord`).
///
/// It is a response to the notification whose id is `notification`,
/// published on the notifier's inbox topic and the document's topic. A
/// `note` is information only; a `hold` is an objection, and `thread` is
/// the address of the thread the holder opened on the range (design §9), so
/// the notifier's edit pauses and both sides talk there. `by` must equal
/// the envelope's `Sender::Participant`; `ts` is its Unix time in
/// milliseconds.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Response {
/// The id of the notification answered.
#[serde(with = "cbor::fixed")]
pub notification: [u8; 32],
/// The responding participant's key bytes.
#[serde(with = "cbor::fixed")]
pub by: [u8; 32],
/// The response's text.
pub text: String,
/// For a `hold`, the address of the thread the holder opened on the
/// range.
pub thread: Option<String>,
/// The responder's Unix time in milliseconds.
pub ts: i64,
}
/// Payload of kinds `say`, `ask`, and `answer` (design §9; lane
/// `M3.gossip`).
///
/// It is a thread message published live on the thread anchor's topics (the
/// address's topic and every ancestor's, `ws/<id>/talk`, or each
/// addressee's inbox), and also appended to the thread document at
/// `thread`, so offline participants catch up through sync. `message` is
/// the canonical CBOR encoding of the `lvcs_doc::ThreadMessage`, opaque
/// here; its `author` must equal the envelope's `Sender::Participant`. `ts`
/// is the post's Unix time in milliseconds, signed outside the opaque
/// message so decision 112's stale and duplicate rules cover thread posts
/// like the other gossip kinds (decision 128); it must equal the message's
/// `time_ms`, which the receiving daemon checks where it decodes the
/// message, and drops a post whose two times differ. Direct messages on an
/// inbox topic are signed but not encrypted in M3: the M3 freeze defers
/// design §9's end-to-end encryption to M4, which settles it together with
/// the confidentiality of the participant-anchored thread document they are
/// appended to (revising decision 16).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ThreadPost {
/// The address of the thread document the message is appended to.
pub thread: String,
/// The canonical CBOR encoding of the `lvcs_doc::ThreadMessage`, opaque
/// here.
#[serde(with = "cbor::bytes")]
pub message: Vec<u8>,
/// The post's Unix time in milliseconds, equal to the message's
/// `time_ms`.
pub ts: i64,
}
/// Payload of kind `presence` (design §5, §9; lane `M3.coord`).
///
/// It says `by` is on the document `doc`, or in the workspace `workspace`,
/// as of `ts` (Unix milliseconds), and counts as present there until
/// `PRESENCE_TTL_SECS` after it. Never stored.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Presence {
/// The document `by` is on, if any.
pub doc: Option<String>,
/// The workspace `by` is in, if any.
#[serde(with = "cbor::opt_fixed")]
pub workspace: Option<[u8; 32]>,
/// The present participant's key bytes.
#[serde(with = "cbor::fixed")]
pub by: [u8; 32],
/// The Unix time in milliseconds `by` is present as of.
pub ts: i64,
}
/// Payload of kind `typing` (design §9; lane `M3.gossip`): `by` is typing
/// in `doc` as of `ts` (Unix milliseconds). Never stored.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Typing {
/// The document `by` is typing in.
pub doc: String,
/// The typing participant's key bytes.
#[serde(with = "cbor::fixed")]
pub by: [u8; 32],
/// The Unix time in milliseconds `by` is typing as of.
pub ts: i64,
}
/// Payload of kind `run` (decision 303): runner `by`'s run of action
/// `action` of the channel whose document is `doc`, on the frontier
/// `frontier`, is in state `state` (a `run_state` value) as of `ts`. It
/// was taken at `started` and ended at `ended` (Unix milliseconds by the
/// runner's clock). Published on the channel document's topics and
/// accepted only under `Attest` of that channel, as a runner's presence
/// is (decision 206). Never stored but in the receiver's runs actor.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RunNotice {
/// The channel document's address.
pub doc: String,
/// The action's name.
pub action: String,
/// The action's id, which a receiver checks is one of the channel's
/// actions under its approved `lvcs.toml`.
#[serde(with = "cbor::fixed")]
pub id: [u8; 32],
/// The frontier's id.
#[serde(with = "cbor::fixed")]
pub frontier: [u8; 32],
/// A `run_state` value.
pub state: String,
/// When the runner took the run.
pub started: i64,
/// When it ended, for an ended state.
pub ended: Option<i64>,
/// The runner's participant key bytes.
#[serde(with = "cbor::fixed")]
pub by: [u8; 32],
/// The Unix time in milliseconds the notice is as of.
pub ts: i64,
}
/// Payload of kinds `action_failed` and `action_succeeded` (decision 193).
///
/// A runner `by` tells `to`, an author of the failing window, that the
/// action `check` failed at the frontier `failed_at` after it last passed at
/// `passed_at` (absent when it never passed on the channel, so the window
/// starts at its genesis), or, as `action_succeeded`, that it passes again,
/// which ends that failing streak. It is published on `to`'s inbox topic.
/// `paths` are the documents the action's `paths` or `uses` match that `to`
/// changed in the window, each with the line runs of its changes at
/// `failed_at`; `others` are the window's other authors. `tail` is the
/// failing output's last `CHECK_OUTPUT_TAIL_BYTES` and `tests` the failing
/// test names read from it, both empty for `action_succeeded`. `by` must
/// equal the envelope's `Sender::Participant`, which must hold `attest`
/// on a channel; `ts` is its Unix time in milliseconds. Owner:
/// decision 193.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CheckNotice {
/// The check's id.
#[serde(with = "cbor::fixed")]
pub check: [u8; 32],
/// The id of the frontier where it last passed, if it ever did.
#[serde(with = "cbor::opt_fixed")]
pub passed_at: Option<[u8; 32]>,
/// The id of the frontier where it failed.
#[serde(with = "cbor::fixed")]
pub failed_at: [u8; 32],
/// The author told.
#[serde(with = "cbor::fixed")]
pub to: [u8; 32],
/// What `to` changed in the window that the check reads.
pub paths: Vec<CheckPath>,
/// The window's other authors.
pub others: Vec<Signer>,
/// The failing output's tail.
pub tail: String,
/// The failing test names read from the tail.
pub tests: Vec<String>,
/// The runner's participant key bytes.
#[serde(with = "cbor::fixed")]
pub by: [u8; 32],
/// The runner's Unix time in milliseconds.
pub ts: i64,
}
/// One document a [`CheckNotice`]'s recipient changed in the window: its
/// path and the line runs of its changes, 1-based and inclusive, at the
/// failing frontier (none for a binary or deleted file).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CheckPath {
/// The document's path.
pub path: String,
/// The line runs.
pub lines: Vec<CheckLines>,
}
/// One run of lines of a [`CheckPath`]: `first` to `last`, 1-based and
/// inclusive.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct CheckLines {
/// The run's first line.
pub first: u64,
/// Its last line.
pub last: u64,
}