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)]
69pub struct MetadataParts {
70    /// Format identifier; currently [`FORMAT_VERSION`].
71    pub version: u8,
72    /// Object profile.
73    pub config: Config,
74    /// Exact number of nonempty frames.
75    pub frame_count: u64,
76    /// Only for a nonempty fixed-framing object. Full final frames are allowed.
77    pub last_frame_len: Option<u64>,
78    /// Required only for variable logical framing; includes the last frame.
79    pub raw_lengths: Option<Lengths>,
80    /// Required only with compression; excludes tags, includes raw fallbacks.
81    pub payload_lengths: Option<Lengths>,
82}
83
84/// Validated compact frame metadata, with cached scalar totals only.
85/// Append sizes after the corresponding frame has been written successfully.
86#[derive(Clone, Debug, Eq, PartialEq)]
87pub struct Metadata {
88    parts: MetadataParts,
89    logical_len: u64,
90    stored_len: u64,
91}
92
93impl Metadata {
94    /// Start an empty object. This allocates no index entries.
95    pub fn new(config: Config) -> Self {
96        let table = || Lengths::new(config.max_frame_len()).expect("validated maximum");
97        Self {
98            parts: MetadataParts {
99                version: FORMAT_VERSION,
100                config,
101                frame_count: 0,
102                last_frame_len: None,
103                raw_lengths: matches!(config.framing(), Framing::Variable(_)).then(table),
104                payload_lengths: (config.compression() != Compression::None).then(table),
105            },
106            logical_len: 0,
107            stored_len: 0,
108        }
109    }
110
111    /// Restore and validate trusted external metadata without copying its arrays.
112    /// Fixed uncompressed metadata is checked in O(1); indexed modes in O(N).
113    pub fn from_parts(parts: MetadataParts) -> Result<Self> {
114        if parts.version != FORMAT_VERSION {
115            return Err(Error::UnsupportedVersion);
116        }
117        let config = parts.config;
118        let count = parts.frame_count;
119        if count > config.max_frames() {
120            return Err(Error::UsageLimit);
121        }
122        let width = Lengths::new(config.max_frame_len())?.width();
123        let check_table = |table: &Option<Lengths>, required: bool| -> Result<()> {
124            match (table, required) {
125                (None, false) => Ok(()),
126                (Some(table), true) if table.len() as u64 == count && table.width() == width => {
127                    Ok(())
128                }
129                _ => Err(Error::InvalidMetadata),
130            }
131        };
132        check_table(
133            &parts.raw_lengths,
134            matches!(config.framing(), Framing::Variable(_)),
135        )?;
136        check_table(
137            &parts.payload_lengths,
138            config.compression() != Compression::None,
139        )?;
140        match (config.framing(), count, parts.last_frame_len) {
141            (Framing::Fixed(max), 1.., Some(last)) if last > 0 && last <= max => {}
142            (Framing::Fixed(_), 0, None) | (Framing::Variable(_), _, None) => {}
143            _ => return Err(Error::InvalidMetadata),
144        }
145        let mut result = Self {
146            parts,
147            logical_len: 0,
148            stored_len: 0,
149        };
150        if let Framing::Fixed(size) = config.framing() {
151            if config.compression() == Compression::None {
152                let logical = if count == 0 {
153                    0
154                } else {
155                    (count - 1)
156                        .checked_mul(size)
157                        .and_then(|n| n.checked_add(result.parts.last_frame_len.unwrap()))
158                        .ok_or(Error::Overflow)?
159                };
160                result.logical_len = logical;
161                result.stored_len = count
162                    .checked_mul(config.encryption().tag_len() as u64)
163                    .and_then(|tags| logical.checked_add(tags))
164                    .ok_or(Error::Overflow)?;
165                return Ok(result);
166            }
167        }
168        for i in 0..count {
169            let raw = match config.framing() {
170                Framing::Fixed(size) if i + 1 < count => size,
171                Framing::Fixed(_) => result.parts.last_frame_len.unwrap(),
172                Framing::Variable(_) => result
173                    .parts
174                    .raw_lengths
175                    .as_ref()
176                    .unwrap()
177                    .get(i as usize)
178                    .ok_or(Error::InvalidMetadata)?,
179            };
180            let payload = match &result.parts.payload_lengths {
181                Some(table) => table.get(i as usize).ok_or(Error::InvalidMetadata)?,
182                None => raw,
183            };
184            config.validate_frame(i, raw, payload)?;
185            result.logical_len = result.logical_len.checked_add(raw).ok_or(Error::Overflow)?;
186            result.stored_len = result
187                .stored_len
188                .checked_add(payload)
189                .and_then(|n| n.checked_add(config.encryption().tag_len() as u64))
190                .ok_or(Error::Overflow)?;
191        }
192        Ok(result)
193    }
194
195    /// Borrow the validated persistence representation.
196    pub fn parts(&self) -> &MetadataParts {
197        &self.parts
198    }
199    /// Transfer the persistence representation without copying.
200    pub fn into_parts(self) -> MetadataParts {
201        self.parts
202    }
203    /// Object-wide configuration.
204    pub fn config(&self) -> Config {
205        self.parts.config
206    }
207    /// Exact number of frames.
208    pub fn frame_count(&self) -> u64 {
209        self.parts.frame_count
210    }
211    /// Logical object length, in bytes.
212    pub fn logical_len(&self) -> u64 {
213        self.logical_len
214    }
215    /// Stored object length including tags, in bytes.
216    pub fn stored_len(&self) -> u64 {
217        self.stored_len
218    }
219    /// Last raw frame length; absent for an empty object.
220    pub fn last_frame_len(&self) -> Option<u64> {
221        self.frame_count().checked_sub(1).map(|i| self.sizes(i).raw)
222    }
223
224    /// Append one successfully stored frame. Failures leave metadata unchanged.
225    /// Once a short fixed frame is appended, no further frames may follow it.
226    pub fn push(&mut self, sizes: FrameSizes) -> Result<()> {
227        let config = self.config();
228        config.validate_frame(self.frame_count(), sizes.raw, sizes.payload)?;
229        if let Framing::Fixed(size) = config.framing() {
230            if self.parts.last_frame_len.is_some_and(|last| last != size) {
231                return Err(Error::FrameAfterFinal);
232            }
233        }
234        let count = self.frame_count().checked_add(1).ok_or(Error::Overflow)?;
235        let logical = self
236            .logical_len
237            .checked_add(sizes.raw)
238            .ok_or(Error::Overflow)?;
239        let stored = self
240            .stored_len
241            .checked_add(sizes.payload)
242            .and_then(|n| n.checked_add(config.encryption().tag_len() as u64))
243            .ok_or(Error::Overflow)?;
244        // Reserve both arrays before changing either logical contents.
245        if let Some(table) = &mut self.parts.raw_lengths {
246            table.reserve_one()?;
247        }
248        if let Some(table) = &mut self.parts.payload_lengths {
249            table.reserve_one()?;
250        }
251        if let Some(table) = &mut self.parts.raw_lengths {
252            table.push(sizes.raw)?;
253        }
254        if let Some(table) = &mut self.parts.payload_lengths {
255            table.push(sizes.payload)?;
256        }
257        if matches!(config.framing(), Framing::Fixed(_)) {
258            self.parts.last_frame_len = Some(sizes.raw);
259        }
260        self.parts.frame_count = count;
261        self.logical_len = logical;
262        self.stored_len = stored;
263        Ok(())
264    }
265
266    /// Look up a codec descriptor by its original frame index in O(1).
267    pub fn frame(&self, index: u64) -> Option<FrameSpec> {
268        (index < self.frame_count()).then(|| FrameSpec {
269            config: self.config(),
270            index,
271            sizes: self.sizes(index),
272        })
273    }
274
275    fn sizes(&self, index: u64) -> FrameSizes {
276        let raw = match self.config().framing() {
277            Framing::Fixed(size) if index + 1 < self.frame_count() => size,
278            Framing::Fixed(_) => self.parts.last_frame_len.unwrap(),
279            Framing::Variable(_) => self
280                .parts
281                .raw_lengths
282                .as_ref()
283                .unwrap()
284                .get(index as usize)
285                .unwrap(),
286        };
287        let payload = self
288            .parts
289            .payload_lengths
290            .as_ref()
291            .map_or(raw, |v| v.get(index as usize).unwrap());
292        FrameSizes { raw, payload }
293    }
294
295    /// Map logical `[start, end)` bytes to a physical range of complete frames.
296    /// `None` means logical EOF. Empty ranges return `0..0` and an empty iterator.
297    /// No allocation, I/O, or decoding occurs. Variable-size layouts scan their
298    /// compact index to the last selected frame; fixed uncompressed layouts use arithmetic.
299    pub fn range(&self, start: u64, end: Option<u64>) -> Result<(Range<u64>, FrameIter<'_>)> {
300        let end = end.unwrap_or(self.logical_len);
301        if start > end || end > self.logical_len {
302            return Err(Error::InvalidRange);
303        }
304        let iter = |first, stop, logical| FrameIter {
305            metadata: self,
306            next: first,
307            stop,
308            logical,
309            selection: start..end,
310        };
311        if start == end {
312            return Ok((0..0, iter(0, 0, 0)));
313        }
314        if let Framing::Fixed(size) = self.config().framing() {
315            let first = start / size;
316            let stop = (end - 1) / size + 1;
317            if self.config().compression() == Compression::None {
318                let stride = size + self.config().encryption().tag_len() as u64;
319                let physical_start = first * stride;
320                let physical_end = if stop == self.frame_count() {
321                    self.stored_len
322                } else {
323                    stop * stride
324                };
325                return Ok((
326                    physical_start..physical_end,
327                    iter(first, stop, first * size),
328                ));
329            }
330            let mut physical = 0;
331            for i in 0..first {
332                physical += self.sizes(i).payload + self.config().encryption().tag_len() as u64;
333            }
334            let physical_start = physical;
335            if stop == self.frame_count() {
336                physical = self.stored_len;
337            } else {
338                for i in first..stop {
339                    physical += self.sizes(i).payload + self.config().encryption().tag_len() as u64;
340                }
341            }
342            return Ok((physical_start..physical, iter(first, stop, first * size)));
343        }
344        let mut logical = 0;
345        let mut physical = 0;
346        let mut first = 0;
347        while logical + self.sizes(first).raw <= start {
348            let sizes = self.sizes(first);
349            logical += sizes.raw;
350            physical += sizes.payload + self.config().encryption().tag_len() as u64;
351            first += 1;
352        }
353        let logical_start = logical;
354        let physical_start = physical;
355        let mut stop = first;
356        if end == self.logical_len {
357            stop = self.frame_count();
358            physical = self.stored_len;
359        } else {
360            while logical < end {
361                let sizes = self.sizes(stop);
362                logical += sizes.raw;
363                physical += sizes.payload + self.config().encryption().tag_len() as u64;
364                stop += 1;
365            }
366        }
367        Ok((physical_start..physical, iter(first, stop, logical_start)))
368    }
369}
370
371/// Borrowing, allocation-free iterator over a selected logical range.
372#[derive(Clone, Debug)]
373pub struct FrameIter<'a> {
374    metadata: &'a Metadata,
375    next: u64,
376    stop: u64,
377    logical: u64,
378    selection: Range<u64>,
379}
380
381impl FrameIter<'_> {
382    /// Exact remaining frame count, including partial boundary frames.
383    pub fn remaining(&self) -> u64 {
384        self.stop - self.next
385    }
386}
387
388impl Iterator for FrameIter<'_> {
389    type Item = FrameRead;
390
391    fn next(&mut self) -> Option<Self::Item> {
392        if self.next == self.stop {
393            return None;
394        }
395        let spec = self.metadata.frame(self.next).unwrap();
396        let logical_end = self.logical + spec.sizes.raw;
397        let selected = (self.selection.start.saturating_sub(self.logical) as usize)
398            ..((self.selection.end.min(logical_end) - self.logical) as usize);
399        self.logical = logical_end;
400        self.next += 1;
401        Some(FrameRead { spec, selected })
402    }
403
404    fn size_hint(&self) -> (usize, Option<usize>) {
405        match usize::try_from(self.remaining()) {
406            Ok(n) => (n, Some(n)),
407            Err(_) => (usize::MAX, None),
408        }
409    }
410}
411
412impl FusedIterator for FrameIter<'_> {}