Skip to main content

coven_format/
sealed_stream.rs

1//! Canonical 64-KiB sealed streams used by slots, snapshots and floors.
2
3use crate::chunks::CHUNK_SIZE;
4use crate::error::{require, Error, Rule};
5use crate::sealed;
6use coven_crypto::{Signature, SEALED_OBJECT_CHUNK_OVERHEAD};
7
8/// Delimits sealed stream chunks without buffering the object. The caller
9/// reserves the final 64 bytes for the whole-object signature. Acceptance also
10/// requires that signature and the plaintext decoder's end-marker check.
11pub struct StreamObjectLayout {
12    index: u64,
13    partial: bool,
14}
15impl Default for StreamObjectLayout {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20impl StreamObjectLayout {
21    /// Start the stream's section 0 at chunk index 0.
22    pub fn new() -> Self {
23        Self { index: 0, partial: false }
24    }
25
26    /// The index to authenticate when sealing or opening the next chunk.
27    pub fn index(&self) -> u64 {
28        self.index
29    }
30
31    /// Bound a length prefix before fetching or allocating its chunk.
32    pub fn chunk_length(&self, prefix: &[u8]) -> Result<usize, Error> {
33        require(!self.partial, "chunk after stream's final partial chunk", Rule::Chunk)?;
34        sealed::chunk_length(prefix, CHUNK_SIZE)
35    }
36
37    /// Encode one sealed chunk, checking the canonical chunk partition.
38    pub fn encode_chunk(&mut self, bytes: &[u8]) -> Result<Vec<u8>, Error> {
39        require(
40            !self.partial
41                && (SEALED_OBJECT_CHUNK_OVERHEAD + 1..=CHUNK_SIZE + SEALED_OBJECT_CHUNK_OVERHEAD)
42                    .contains(&bytes.len()),
43            "stream chunk length",
44            Rule::Chunk,
45        )?;
46        let piece = sealed::encode_chunk(bytes)?;
47        self.advance(bytes.len())?;
48        Ok(piece)
49    }
50
51    /// Read a complete length-prefixed chunk without copying its sealed bytes.
52    pub fn decode_chunk<'a>(&mut self, piece: &'a [u8]) -> Result<&'a [u8], Error> {
53        self.chunk_length(piece)?;
54        let bytes = sealed::chunk(piece, CHUNK_SIZE)?;
55        self.advance(bytes.len())?;
56        Ok(bytes)
57    }
58
59    /// Consume the layout at its final signature, requiring at least one chunk.
60    /// The caller verifies it against the digest of every preceding object byte,
61    /// including the prefix signature, before applying any plaintext.
62    pub fn read_signature(self, bytes: &[u8]) -> Result<Signature, Error> {
63        require(self.index > 0, "stream chunks", Rule::Required)?;
64        sealed::signature(bytes)
65    }
66
67    fn advance(&mut self, length: usize) -> Result<(), Error> {
68        self.index = self
69            .index
70            .checked_add(1)
71            .ok_or(Error::Invalid { field: "stream chunk index", rule: Rule::Chunk })?;
72        self.partial = length < CHUNK_SIZE + SEALED_OBJECT_CHUNK_OVERHEAD;
73        Ok(())
74    }
75}
76
77/// Recover plaintext size from a complete stream's encrypted size.
78pub fn plaintext_length(sealed: u64) -> Result<u64, Error> {
79    let overhead = crate::STORED_CHUNK_OVERHEAD as u64;
80    let full = CHUNK_SIZE as u64 + overhead;
81    require(sealed > 0, "stream chunks", Rule::Required)?;
82    let chunks = sealed.div_ceil(full);
83    require(sealed - (chunks - 1) * full > overhead, "stream final chunk", Rule::Chunk)?;
84    Ok(sealed - chunks * overhead)
85}