Skip to main content

craft_codec/
metadata.rs

1use std::{iter::FusedIterator, ops::Range};
2
3use crate::{Compression, Config, Error, FORMAT_VERSION, Framing, Lengths, Result};
4
5/// Lengths returned by an encoder, before the authentication tag.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub struct FrameSizes {
8    pub(crate) raw: u64,
9    pub(crate) payload: u64,
10}
11
12impl FrameSizes {
13    /// Validate two positive lengths. Object-specific checks happen on insertion.
14    pub fn new(raw: u64, payload: u64) -> Result<Self> {
15        if raw == 0 || payload == 0 || payload > raw {
16            return Err(Error::InvalidMetadata);
17        }
18        Ok(Self { raw, payload })
19    }
20    /// Original length, in bytes.
21    pub const fn raw_len(self) -> u64 {
22        self.raw
23    }
24    /// Stored payload length excluding the tag.
25    pub const fn payload_len(self) -> u64 {
26        self.payload
27    }
28}
29
30/// Codec input derived from validated metadata, independent of logical slicing.
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub struct FrameSpec {
33    pub(crate) config: Config,
34    pub(crate) index: u64,
35    pub(crate) sizes: FrameSizes,
36}
37
38impl FrameSpec {
39    /// Frame index in the original object, also used to derive its nonce.
40    pub const fn index(self) -> u64 {
41        self.index
42    }
43    /// Complete raw buffer length.
44    pub fn raw_len(self) -> usize {
45        self.sizes.raw as usize
46    }
47    /// Complete stored length, including the tag.
48    pub fn stored_len(self) -> usize {
49        self.sizes.payload as usize + self.config.encryption().tag_len()
50    }
51    /// Payload size excluding the tag.
52    pub fn payload_len(self) -> usize {
53        self.sizes.payload as usize
54    }
55}
56
57/// One complete frame to decode, followed by a slice selected by the caller.
58#[derive(Clone, Debug, Eq, PartialEq)]
59pub struct FrameRead {
60    /// Descriptor to pass to the decoder.
61    pub spec: FrameSpec,
62    /// Half-open range inside the fully decoded frame.
63    pub selected: Range<usize>,
64}
65
66/// External metadata representation. It contains no key and is not self-authenticating.
67/// Persist it in trusted storage and restore with [`Metadata::from_parts`].
68#[derive(Clone, Debug, Eq, PartialEq)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70pub struct MetadataParts {
71    /// Format identifier; currently [`FORMAT_VERSION`].
72    pub version: u8,
73    /// Object profile.
74    pub config: Config,
75    /// Exact number of nonempty frames.
76    pub frame_count: u64,
77    /// Only for a nonempty fixed-framing object. Full final frames are allowed.
78    pub last_frame_len: Option<u64>,
79    /// Required only for variable logical framing; includes the last frame.
80    pub raw_lengths: Option<Lengths>,
81    /// Required only with compression; excludes tags, includes raw fallbacks.
82    pub payload_lengths: Option<Lengths>,
83}
84
85/// Validated compact frame metadata, with cached scalar totals only.
86/// Append sizes after the corresponding frame has been written successfully.
87#[derive(Clone, Debug, Eq, PartialEq)]
88pub struct Metadata {
89    parts: MetadataParts,
90    logical_len: u64,
91    stored_len: u64,
92}
93
94#[cfg(feature = "serde")]
95impl serde::Serialize for Metadata {
96    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
97    where
98        S: serde::Serializer,
99    {
100        serde::Serialize::serialize(&self.parts, serializer)
101    }
102}
103
104#[cfg(feature = "serde")]
105impl<'de> serde::Deserialize<'de> for Metadata {
106    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
107    where
108        D: serde::Deserializer<'de>,
109    {
110        let parts = <MetadataParts as serde::Deserialize>::deserialize(deserializer)?;
111        Self::from_parts(parts).map_err(serde::de::Error::custom)
112    }
113}
114
115impl Metadata {
116    /// Start an empty object. This allocates no index entries.
117    pub fn new(config: Config) -> Self {
118        let table = || Lengths::new(config.max_frame_len()).expect("validated maximum");
119        Self {
120            parts: MetadataParts {
121                version: FORMAT_VERSION,
122                config,
123                frame_count: 0,
124                last_frame_len: None,
125                raw_lengths: matches!(config.framing(), Framing::Variable(_)).then(table),
126                payload_lengths: (config.compression() != Compression::None).then(table),
127            },
128            logical_len: 0,
129            stored_len: 0,
130        }
131    }
132
133    /// Restore and validate trusted external metadata without copying its arrays.
134    /// Fixed uncompressed metadata is checked in O(1); indexed modes in O(N).
135    pub fn from_parts(parts: MetadataParts) -> Result<Self> {
136        if parts.version != FORMAT_VERSION {
137            return Err(Error::UnsupportedVersion);
138        }
139        let config = parts.config;
140        let count = parts.frame_count;
141        if count > config.max_frames() {
142            return Err(Error::UsageLimit);
143        }
144        let width = Lengths::new(config.max_frame_len())?.width();
145        let check_table = |table: &Option<Lengths>, required: bool| -> Result<()> {
146            match (table, required) {
147                (None, false) => Ok(()),
148                (Some(table), true) if table.len() as u64 == count && table.width() == width => {
149                    Ok(())
150                }
151                _ => Err(Error::InvalidMetadata),
152            }
153        };
154        check_table(
155            &parts.raw_lengths,
156            matches!(config.framing(), Framing::Variable(_)),
157        )?;
158        check_table(
159            &parts.payload_lengths,
160            config.compression() != Compression::None,
161        )?;
162        match (config.framing(), count, parts.last_frame_len) {
163            (Framing::Fixed(max), 1.., Some(last)) if last > 0 && last <= max => {}
164            (Framing::Fixed(_), 0, None) | (Framing::Variable(_), _, None) => {}
165            _ => return Err(Error::InvalidMetadata),
166        }
167        let mut result = Self {
168            parts,
169            logical_len: 0,
170            stored_len: 0,
171        };
172        if let Framing::Fixed(size) = config.framing() {
173            if config.compression() == Compression::None {
174                let logical = if count == 0 {
175                    0
176                } else {
177                    (count - 1)
178                        .checked_mul(size)
179                        .and_then(|n| n.checked_add(result.parts.last_frame_len.unwrap()))
180                        .ok_or(Error::Overflow)?
181                };
182                result.logical_len = logical;
183                result.stored_len = count
184                    .checked_mul(config.encryption().tag_len() as u64)
185                    .and_then(|tags| logical.checked_add(tags))
186                    .ok_or(Error::Overflow)?;
187                return Ok(result);
188            }
189        }
190        for i in 0..count {
191            let raw = match config.framing() {
192                Framing::Fixed(size) if i + 1 < count => size,
193                Framing::Fixed(_) => result.parts.last_frame_len.unwrap(),
194                Framing::Variable(_) => result
195                    .parts
196                    .raw_lengths
197                    .as_ref()
198                    .unwrap()
199                    .get(i as usize)
200                    .ok_or(Error::InvalidMetadata)?,
201            };
202            let payload = match &result.parts.payload_lengths {
203                Some(table) => table.get(i as usize).ok_or(Error::InvalidMetadata)?,
204                None => raw,
205            };
206            config.validate_frame(i, raw, payload)?;
207            result.logical_len = result.logical_len.checked_add(raw).ok_or(Error::Overflow)?;
208            result.stored_len = result
209                .stored_len
210                .checked_add(payload)
211                .and_then(|n| n.checked_add(config.encryption().tag_len() as u64))
212                .ok_or(Error::Overflow)?;
213        }
214        Ok(result)
215    }
216
217    /// Borrow the validated persistence representation.
218    pub fn parts(&self) -> &MetadataParts {
219        &self.parts
220    }
221    /// Transfer the persistence representation without copying.
222    pub fn into_parts(self) -> MetadataParts {
223        self.parts
224    }
225    /// Object-wide configuration.
226    pub fn config(&self) -> Config {
227        self.parts.config
228    }
229    /// Exact number of frames.
230    pub fn frame_count(&self) -> u64 {
231        self.parts.frame_count
232    }
233    /// Logical object length, in bytes.
234    pub fn logical_len(&self) -> u64 {
235        self.logical_len
236    }
237    /// Stored object length including tags, in bytes.
238    pub fn stored_len(&self) -> u64 {
239        self.stored_len
240    }
241    /// Last raw frame length; absent for an empty object.
242    pub fn last_frame_len(&self) -> Option<u64> {
243        self.frame_count().checked_sub(1).map(|i| self.sizes(i).raw)
244    }
245
246    /// Append one successfully stored frame. Failures leave metadata unchanged.
247    /// Once a short fixed frame is appended, no further frames may follow it.
248    pub fn push(&mut self, sizes: FrameSizes) -> Result<()> {
249        let config = self.config();
250        config.validate_frame(self.frame_count(), sizes.raw, sizes.payload)?;
251        if let Framing::Fixed(size) = config.framing() {
252            if self.parts.last_frame_len.is_some_and(|last| last != size) {
253                return Err(Error::FrameAfterFinal);
254            }
255        }
256        let count = self.frame_count().checked_add(1).ok_or(Error::Overflow)?;
257        let logical = self
258            .logical_len
259            .checked_add(sizes.raw)
260            .ok_or(Error::Overflow)?;
261        let stored = self
262            .stored_len
263            .checked_add(sizes.payload)
264            .and_then(|n| n.checked_add(config.encryption().tag_len() as u64))
265            .ok_or(Error::Overflow)?;
266        // Reserve both arrays before changing either logical contents.
267        if let Some(table) = &mut self.parts.raw_lengths {
268            table.reserve_one()?;
269        }
270        if let Some(table) = &mut self.parts.payload_lengths {
271            table.reserve_one()?;
272        }
273        if let Some(table) = &mut self.parts.raw_lengths {
274            table.push(sizes.raw)?;
275        }
276        if let Some(table) = &mut self.parts.payload_lengths {
277            table.push(sizes.payload)?;
278        }
279        if matches!(config.framing(), Framing::Fixed(_)) {
280            self.parts.last_frame_len = Some(sizes.raw);
281        }
282        self.parts.frame_count = count;
283        self.logical_len = logical;
284        self.stored_len = stored;
285        Ok(())
286    }
287
288    /// Look up a codec descriptor by its original frame index in O(1).
289    pub fn frame(&self, index: u64) -> Option<FrameSpec> {
290        (index < self.frame_count()).then(|| FrameSpec {
291            config: self.config(),
292            index,
293            sizes: self.sizes(index),
294        })
295    }
296
297    fn sizes(&self, index: u64) -> FrameSizes {
298        let raw = match self.config().framing() {
299            Framing::Fixed(size) if index + 1 < self.frame_count() => size,
300            Framing::Fixed(_) => self.parts.last_frame_len.unwrap(),
301            Framing::Variable(_) => self
302                .parts
303                .raw_lengths
304                .as_ref()
305                .unwrap()
306                .get(index as usize)
307                .unwrap(),
308        };
309        let payload = self
310            .parts
311            .payload_lengths
312            .as_ref()
313            .map_or(raw, |v| v.get(index as usize).unwrap());
314        FrameSizes { raw, payload }
315    }
316
317    /// Map logical `[start, end)` bytes to a physical range of complete frames.
318    /// `None` means logical EOF. Empty ranges return `0..0` and an empty iterator.
319    /// No allocation, I/O, or decoding occurs. Variable-size layouts scan their
320    /// compact index to the last selected frame; fixed uncompressed layouts use arithmetic.
321    pub fn range(&self, start: u64, end: Option<u64>) -> Result<(Range<u64>, FrameIter<'_>)> {
322        let end = end.unwrap_or(self.logical_len);
323        if start > end || end > self.logical_len {
324            return Err(Error::InvalidRange);
325        }
326        let iter = |first, stop, logical| FrameIter {
327            metadata: self,
328            next: first,
329            stop,
330            logical,
331            selection: start..end,
332        };
333        if start == end {
334            return Ok((0..0, iter(0, 0, 0)));
335        }
336        if let Framing::Fixed(size) = self.config().framing() {
337            let first = start / size;
338            let stop = (end - 1) / size + 1;
339            if self.config().compression() == Compression::None {
340                let stride = size + self.config().encryption().tag_len() as u64;
341                let physical_start = first * stride;
342                let physical_end = if stop == self.frame_count() {
343                    self.stored_len
344                } else {
345                    stop * stride
346                };
347                return Ok((
348                    physical_start..physical_end,
349                    iter(first, stop, first * size),
350                ));
351            }
352            let mut physical = 0;
353            for i in 0..first {
354                physical += self.sizes(i).payload + self.config().encryption().tag_len() as u64;
355            }
356            let physical_start = physical;
357            if stop == self.frame_count() {
358                physical = self.stored_len;
359            } else {
360                for i in first..stop {
361                    physical += self.sizes(i).payload + self.config().encryption().tag_len() as u64;
362                }
363            }
364            return Ok((physical_start..physical, iter(first, stop, first * size)));
365        }
366        let mut logical = 0;
367        let mut physical = 0;
368        let mut first = 0;
369        while logical + self.sizes(first).raw <= start {
370            let sizes = self.sizes(first);
371            logical += sizes.raw;
372            physical += sizes.payload + self.config().encryption().tag_len() as u64;
373            first += 1;
374        }
375        let logical_start = logical;
376        let physical_start = physical;
377        let mut stop = first;
378        if end == self.logical_len {
379            stop = self.frame_count();
380            physical = self.stored_len;
381        } else {
382            while logical < end {
383                let sizes = self.sizes(stop);
384                logical += sizes.raw;
385                physical += sizes.payload + self.config().encryption().tag_len() as u64;
386                stop += 1;
387            }
388        }
389        Ok((physical_start..physical, iter(first, stop, logical_start)))
390    }
391}
392
393/// Borrowing, allocation-free iterator over a selected logical range.
394#[derive(Clone, Debug)]
395pub struct FrameIter<'a> {
396    metadata: &'a Metadata,
397    next: u64,
398    stop: u64,
399    logical: u64,
400    selection: Range<u64>,
401}
402
403impl FrameIter<'_> {
404    /// Exact remaining frame count, including partial boundary frames.
405    pub fn remaining(&self) -> u64 {
406        self.stop - self.next
407    }
408}
409
410impl Iterator for FrameIter<'_> {
411    type Item = FrameRead;
412
413    fn next(&mut self) -> Option<Self::Item> {
414        if self.next == self.stop {
415            return None;
416        }
417        let spec = self.metadata.frame(self.next).unwrap();
418        let logical_end = self.logical + spec.sizes.raw;
419        let selected = (self.selection.start.saturating_sub(self.logical) as usize)
420            ..((self.selection.end.min(logical_end) - self.logical) as usize);
421        self.logical = logical_end;
422        self.next += 1;
423        Some(FrameRead { spec, selected })
424    }
425
426    fn size_hint(&self) -> (usize, Option<usize>) {
427        match usize::try_from(self.remaining()) {
428            Ok(n) => (n, Some(n)),
429            Err(_) => (usize::MAX, None),
430        }
431    }
432}
433
434impl FusedIterator for FrameIter<'_> {}