Skip to main content

coven_format/
floor.rs

1//! The floor's encrypted header and original announcement frames (D8).
2
3use crate::error::{require, Error, Rule};
4use crate::path::ObjectPath;
5use crate::sequence::MAX_BOUNDED_SLOT;
6use crate::value::Slot;
7use crate::wire::{
8    decode_frame, decode_frame_value, get_blob, put_blob, wire_struct, Decoder, Encoder, Wire,
9};
10use coven_foundation::id_source::{DeviceId, StoreId};
11use std::collections::BTreeSet;
12
13/// Exact original slot evidence, verified at its original slot path.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct OriginalSlot {
16    /// The original slot, even if retention has since deleted its path.
17    pub slot: Slot,
18    /// Complete original sealed bytes and signature.
19    pub object: Vec<u8>,
20}
21impl OriginalSlot {
22    /// Where the slot's object lived in `store`.
23    pub fn path(&self, store: StoreId) -> ObjectPath {
24        ObjectPath::slot(store, self.slot.into())
25    }
26}
27impl Wire for OriginalSlot {
28    fn put(&self, out: &mut Encoder) -> Result<(), Error> {
29        self.slot.put(out)?;
30        put_blob(&self.object, out)
31    }
32    fn get(input: &mut Decoder<'_>) -> Result<Self, Error> {
33        Ok(Self { slot: Wire::get(input)?, object: get_blob(input)? })
34    }
35}
36impl OriginalSlot {
37    /// Check that the clear slot kind is one of `kinds` and the bounded
38    /// envelope. Authentication and the encrypted header's path binding are
39    /// checked when opening it.
40    pub fn validate_kind(&self, kinds: &[u8]) -> Result<(), Error> {
41        crate::error::bound(self.object.len(), MAX_BOUNDED_SLOT, "retained slot")?;
42        let object = crate::sealed_slot::BoundedSlot::decode(&self.object)?;
43        require(kinds.contains(&object.prefix.kind.tag()), "retained slot kind", Rule::Kind)
44    }
45}
46
47/// Header of the signed floor stream. Announcements follow in this order.
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct FloorHeader {
50    /// Device signing the floor in the state at `at`.
51    pub author: DeviceId,
52    /// Signer's applied slot, covering every named announcement.
53    pub at: u64,
54    /// First slot the sequence retains.
55    pub first: Slot,
56    /// One announcement per required audience, in audience order, store first.
57    pub snapshots: Vec<Slot>,
58}
59wire_struct!(FloorHeader, author, at, first, snapshots);
60impl FloorHeader {
61    fn validate(&self) -> Result<(), Error> {
62        require(!self.snapshots.is_empty(), "floor snapshots", Rule::Required)?;
63        require(
64            self.first.get() - 1 <= self.at
65                && self.snapshots.iter().all(|slot| slot.get() <= self.at),
66            "floor coverage",
67            Rule::Coverage,
68        )?;
69        let unique: BTreeSet<_> = self.snapshots.iter().collect();
70        require(unique.len() == self.snapshots.len(), "floor announcements", Rule::Order)
71    }
72    /// Encode the kind-12 header frame.
73    pub fn encode(&self) -> Result<Vec<u8>, Error> {
74        self.validate()?;
75        crate::encode_frame(crate::kinds::FLOOR_HEADER, self)
76    }
77    /// Decode exactly the kind-12 header frame.
78    pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
79        let header: Self = decode_frame_value(bytes, crate::kinds::FLOOR_HEADER, "floor header")?;
80        header.validate()?;
81        Ok(header)
82    }
83}
84
85/// Incremental floor frame codec; no bound is imposed on the complete stream.
86pub struct FloorFrames {
87    header: FloorHeader,
88    next: usize,
89    ended: bool,
90}
91impl FloorFrames {
92    /// Begin encoding with the validated floor header.
93    pub fn start(header: FloorHeader) -> Result<(Self, Vec<u8>), Error> {
94        let frame = header.encode()?;
95        Ok((Self { header, next: 0, ended: false }, frame))
96    }
97    /// Start decoding from its initial header frame, checking the clear prefix.
98    pub fn decode_header(bytes: &[u8], first: Slot) -> Result<Self, Error> {
99        let header = FloorHeader::decode(bytes)?;
100        require(header.first == first, "floor first", Rule::Coverage)?;
101        Ok(Self { header, next: 0, ended: false })
102    }
103    /// Unauthenticated header; verify the floor before granting authority.
104    pub fn header(&self) -> &FloorHeader {
105        &self.header
106    }
107    fn accept(&mut self, slot: &OriginalSlot) -> Result<(), Error> {
108        slot.validate_kind(&[3])?;
109        require(
110            !self.ended && self.header.snapshots.get(self.next) == Some(&slot.slot),
111            "floor announcement order",
112            Rule::Order,
113        )?;
114        self.next += 1;
115        Ok(())
116    }
117    /// Encode the next original announcement, checking header order.
118    pub fn announcement(&mut self, slot: &OriginalSlot) -> Result<Vec<u8>, Error> {
119        let bytes = crate::encode_frame(crate::kinds::SNAPSHOT_RECORD, slot)?;
120        self.accept(slot)?;
121        Ok(bytes)
122    }
123    /// Encode the required end frame after all announcements.
124    pub fn end(&mut self) -> Result<Vec<u8>, Error> {
125        require(
126            !self.ended && self.next == self.header.snapshots.len(),
127            "floor end",
128            Rule::SnapshotSequence,
129        )?;
130        self.ended = true;
131        crate::encode_frame_with(crate::kinds::SNAPSHOT_END, |_| Ok(()))
132    }
133    /// Decode one announcement or the end marker, preserving exact evidence.
134    pub fn frame(&mut self, bytes: &[u8]) -> Result<Option<OriginalSlot>, Error> {
135        let (kind, mut input) = decode_frame(bytes)?;
136        if kind == crate::kinds::SNAPSHOT_END {
137            input.finish()?;
138            require(
139                !self.ended && self.next == self.header.snapshots.len(),
140                "floor end",
141                Rule::SnapshotSequence,
142            )?;
143            self.ended = true;
144            return Ok(None);
145        }
146        require(kind == crate::kinds::SNAPSHOT_RECORD, "floor announcement", Rule::Kind)?;
147        let slot = OriginalSlot::get(&mut input)?;
148        input.finish()?;
149        self.accept(&slot)?;
150        Ok(Some(slot))
151    }
152    /// Require the end marker before accepting the stream.
153    pub fn finish(&self) -> Result<(), Error> {
154        if self.ended {
155            Ok(())
156        } else {
157            Err(Error::Truncated)
158        }
159    }
160}
161
162#[cfg(test)]
163#[path = "floor_tests.rs"]
164mod tests;