Skip to main content

coven_format/
lib.rs

1//! Versioned plaintext frames and streaming sealed-object layouts (Appendix F.1).
2//!
3//! No I/O, clock, randomness or signing occurs here. File chunks compose
4//! crypto with validated headers and ranges. Plaintext frames
5//! are bounded; writes and snapshots stream without an object-size bound.
6//! `spec/format.md` specifies stored bytes.
7
8pub mod admin;
9pub mod authorship;
10pub mod chunks;
11pub mod code_fields;
12pub mod codes;
13pub mod coordinates;
14pub mod device_gone;
15pub mod dismissal;
16pub mod error;
17pub mod file;
18pub mod file_reference;
19pub mod floor;
20pub mod invite_end;
21pub mod key;
22mod kinds;
23pub mod loss;
24pub mod merge_fields;
25mod merge_wire;
26pub mod objects;
27pub mod path;
28mod sealed;
29pub mod sealed_floor;
30pub mod sealed_key;
31pub mod sealed_single;
32pub mod sealed_slot;
33pub mod sealed_snapshot;
34pub mod sealed_stream;
35pub mod sealed_write;
36pub mod sequence;
37pub mod snapshot;
38pub mod snapshot_rows;
39pub mod snapshot_stream;
40pub mod value;
41mod version;
42mod wire;
43pub mod write;
44pub mod write_sources;
45pub mod write_stream;
46pub mod writes_slot;
47
48#[cfg(any(test, feature = "test-utils"))]
49pub mod test_utils;
50
51pub use error::Error;
52pub use sealed::STORED_CHUNK_OVERHEAD;
53pub use version::{FormatVersion, RequiredUpdate};
54
55use admin::StoreChange;
56use error::bound;
57use objects::JoinRequest;
58use wire::{decode_frame, Encoder, Wire, MAX_OBJECT};
59
60/// The object format written and read by coven (format.md).
61pub const FORMAT_VERSION: u16 = FormatVersion::CURRENT.number();
62/// The fixed prefix length: kind, format version, payload length.
63pub const FRAME_PREFIX_LEN: usize = 7;
64
65/// Independently decoded plaintext objects. Writes use [`write_stream::WriteEncoder`];
66/// snapshots use [`snapshot::SnapshotDecoder`]
67/// with merge metadata; restore and invite codes use their zeroizing byte APIs.
68#[derive(Clone, Debug, PartialEq, Eq)]
69pub enum Object {
70    /// One membership, device, circle, version or reset entry (§10).
71    Admin(StoreChange),
72    /// A new person's public keys and device name (§13.2).
73    JoinRequest(JoinRequest),
74}
75
76impl Object {
77    /// Checks structure and returns the one canonical byte string for this value.
78    pub fn encode(&self) -> Result<Vec<u8>, Error> {
79        self.encode_in(FormatVersion::CURRENT)
80    }
81
82    /// Encode in the format recorded for an attempted object, preserving its bytes.
83    pub fn encode_in(&self, format: FormatVersion) -> Result<Vec<u8>, Error> {
84        self.validate()?;
85        let FormatVersion::V1 = format;
86        match self {
87            Self::Admin(v) => encode_frame_in(kinds::ADMIN, format, |out| v.put(out)),
88            Self::JoinRequest(v) => {
89                encode_frame_in(kinds::JOIN_REQUEST_FRAME, format, |out| v.put(out))
90            }
91        }
92    }
93    /// Decodes exactly one bounded frame, refusing unknown tags/versions, trailing
94    /// bytes, noncanonical sets and structurally invalid records. Authentication
95    /// and checks requiring other writes or a database belong to their owners.
96    pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
97        let (kind, mut input) = decode_frame(bytes)?;
98        let object = match kind {
99            kinds::ADMIN => Self::Admin(Wire::get(&mut input)?),
100            kinds::JOIN_REQUEST_FRAME => Self::JoinRequest(Wire::get(&mut input)?),
101            tag => return Err(Error::UnknownTag { field: "object kind", tag }),
102        };
103        input.finish()?;
104        object.validate()?;
105        Ok(object)
106    }
107    fn validate(&self) -> Result<(), Error> {
108        match self {
109            Self::Admin(v) => v.validate(),
110            Self::JoinRequest(v) => v.validate(),
111        }
112    }
113}
114
115/// Checks the seven-byte prefix and returns the full frame length, capped at
116/// 16 MiB, before a caller allocates or fetches its payload. Extra supplied bytes
117/// are ignored here; [`Object::decode`] requires exactly one complete frame.
118pub fn frame_length(prefix: &[u8]) -> Result<usize, Error> {
119    let bytes = prefix.get(..3).ok_or(Error::Truncated)?;
120    if !kinds::is_frame(bytes[0]) {
121        return Err(Error::UnknownTag { field: "object kind", tag: bytes[0] });
122    }
123    let FormatVersion::V1 = FormatVersion::decode(bytes)?;
124    let bytes = prefix.get(..FRAME_PREFIX_LEN).ok_or(Error::Truncated)?;
125    let size = u32::from_be_bytes([bytes[3], bytes[4], bytes[5], bytes[6]]) as usize;
126    bound(size, MAX_OBJECT - FRAME_PREFIX_LEN, "frame payload")?;
127    Ok(size + FRAME_PREFIX_LEN)
128}
129
130pub(crate) fn encode_frame<T: Wire>(kind: u8, value: &T) -> Result<Vec<u8>, Error> {
131    encode_frame_with(kind, |out| value.put(out))
132}
133
134pub(crate) fn encode_frame_with(
135    kind: u8,
136    put: impl FnOnce(&mut Encoder) -> Result<(), Error>,
137) -> Result<Vec<u8>, Error> {
138    encode_frame_in(kind, FormatVersion::CURRENT, put)
139}
140
141/// The seven clear bytes that start a frame: `kind | version | payload length`.
142pub(crate) fn frame_prefix(
143    kind: u8,
144    format: FormatVersion,
145    payload: u32,
146) -> [u8; FRAME_PREFIX_LEN] {
147    let [a, b, c] = format.prefix(kind);
148    let [d, e, f, g] = payload.to_be_bytes();
149    [a, b, c, d, e, f, g]
150}
151
152fn encode_frame_in(
153    kind: u8,
154    format: FormatVersion,
155    put: impl FnOnce(&mut Encoder) -> Result<(), Error>,
156) -> Result<Vec<u8>, Error> {
157    let mut out = Encoder::new();
158    out.bytes(&frame_prefix(kind, format, 0))?;
159    put(&mut out)?;
160    let size = (out.bytes.len() - FRAME_PREFIX_LEN) as u32;
161    out.bytes[3..7].copy_from_slice(&size.to_be_bytes());
162    Ok(out.bytes)
163}
164
165#[cfg(test)]
166#[path = "lib_tests.rs"]
167mod tests;
168
169mod member_access;
170pub use member_access::MemberAccess;