Skip to main content

xenia_wire/
lib.rs

1// Copyright (c) 2024-2026 Tristan Stoltz / Luminous Dynamics
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! # xenia-wire
5//!
6//! PQC-sealed binary wire protocol for remote-control streams.
7//!
8//! **Pre-alpha.** The wire format is not yet frozen and breaking changes
9//! will land between `0.1.x` releases. Do not deploy in production.
10//!
11//! ## What this crate provides
12//!
13//! - **[`Session`]** — minimal AEAD session state: a current key, an
14//!   optional previous key with grace period for rekey, per-session
15//!   random `source_id` + `epoch`, monotonic nonce counter, and a
16//!   64-slot sliding replay window.
17//! - **[`ReplayWindow`]** — sliding-window replay protection keyed by
18//!   `(source_id, payload_type)`. IPsec/DTLS semantics.
19//! - **[`Sealable`]** — generic bincode-based serialization contract.
20//!   Bring your own payload type.
21//! - **[`seal`] / [`open`]** — generic functions for any `Sealable` payload.
22//! - **[`seal_frame`] / [`open_frame`] / [`seal_input`] / [`open_input`]** —
23//!   convenience wrappers for the reference [`Frame`] + [`Input`] types.
24//!   Available under the default `reference-frame` feature.
25//! - **[`seal_frame_lz4`] / [`open_frame_lz4`]** — LZ4-before-AEAD
26//!   compression variants. Available under the `lz4` feature.
27//!
28//! ## What this crate deliberately does NOT do
29//!
30//! - **No transport.** Sealed bytes are returned to the caller; the
31//!   caller ships them over TCP / WebSocket / QUIC / whatever.
32//! - **No handshake.** Session keys arrive from somewhere else
33//!   (ML-KEM-768 in real deployments). Call [`Session::install_key`]
34//!   directly in tests or early prototypes.
35//! - **No state machine.** `Session` has no lifecycle — no connecting,
36//!   authenticating, closing. Those are application concerns.
37//! - **No domain semantics.** The reference [`Frame`] / [`Input`] types
38//!   carry opaque byte payloads. Implement [`Sealable`] on your own
39//!   types for anything real.
40//!
41//! ## Quick start
42//!
43//! ```
44//! use xenia_wire::{Session, seal_frame, open_frame, Frame};
45//!
46//! let key = [0xAB; 32];
47//! let mut sender = Session::new();
48//! let mut receiver = Session::new();
49//! sender.install_key(key);
50//! receiver.install_key(key);
51//!
52//! let frame = Frame {
53//!     frame_id: 1,
54//!     timestamp_ms: 1_700_000_000_000,
55//!     payload: b"hello, xenia".to_vec(),
56//! };
57//! let sealed = seal_frame(&frame, &mut sender).unwrap();
58//! let opened = open_frame(&sealed, &mut receiver).unwrap();
59//! assert_eq!(opened.payload, b"hello, xenia");
60//!
61//! // Replaying the same bytes fails — the sliding window catches it.
62//! assert!(open_frame(&sealed, &mut receiver).is_err());
63//! ```
64//!
65//! ## Wire format
66//!
67//! ```text
68//! envelope = nonce || ciphertext || tag
69//!   nonce       : 12 bytes — source_id[0..6] || payload_type || epoch || seq[0..4]
70//!   ciphertext  : len(plaintext) bytes — ChaCha20-Poly1305 encrypt(plaintext)
71//!   tag         : 16 bytes — Poly1305 authentication tag
72//! ```
73//!
74//! The plaintext is typically `bincode::serialize(payload)`. Under the
75//! `lz4` feature the plaintext is `lz4_flex::compress_prepend_size(bincode_bytes)`.
76//!
77//! ## Feature flags
78//!
79//! | Feature           | Default | Description                                          |
80//! |-------------------|---------|------------------------------------------------------|
81//! | `reference-frame` | yes     | Ships [`Frame`] + [`Input`] reference types.         |
82//! | `lz4`             | no      | Adds LZ4-before-AEAD variants for frame sealing.     |
83//!
84//! ## License
85//!
86//! Dual-licensed under Apache-2.0 OR MIT.
87
88#![cfg_attr(docsrs, feature(doc_cfg))]
89#![warn(missing_docs)]
90#![warn(rust_2018_idioms)]
91#![deny(unsafe_code)]
92
93mod error;
94pub mod payload_types;
95mod replay_window;
96mod session;
97mod wire;
98
99mod frame;
100
101#[cfg(feature = "consent")]
102pub mod consent;
103
104pub use error::WireError;
105pub use payload_types::{
106    PAYLOAD_TYPE_APPLICATION_MIN, PAYLOAD_TYPE_ATTESTED_ACTION, PAYLOAD_TYPE_CONSENT_REQUEST,
107    PAYLOAD_TYPE_CONSENT_RESPONSE, PAYLOAD_TYPE_CONSENT_REVOCATION, PAYLOAD_TYPE_FRAME,
108    PAYLOAD_TYPE_FRAME_LZ4, PAYLOAD_TYPE_INPUT,
109};
110pub use replay_window::{ReplayWindow, DEFAULT_WINDOW_BITS, MAX_WINDOW_BITS, WINDOW_BITS};
111pub use session::{Session, SessionBuilder, DEFAULT_REKEY_GRACE};
112
113pub use frame::Sealable;
114pub use wire::{open, seal};
115
116#[cfg(feature = "reference-frame")]
117pub use frame::{Frame, Input};
118
119#[cfg(feature = "reference-frame")]
120pub use wire::{open_frame, open_input, seal_frame, seal_input};
121
122#[cfg(feature = "lz4")]
123pub use wire::{open_frame_lz4, seal_frame_lz4};
124
125#[cfg(feature = "consent")]
126pub use wire::{
127    open_consent_request, open_consent_response, open_consent_revocation, seal_consent_request,
128    seal_consent_response, seal_consent_revocation,
129};