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).saturating_add(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::for_config(config);
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                    let last = result.parts.last_frame_len.ok_or(Error::InvalidMetadata)?;
178                    count
179                        .checked_sub(1)
180                        .ok_or(Error::InvalidMetadata)?
181                        .checked_mul(size)
182                        .and_then(|n| n.checked_add(last))
183                        .ok_or(Error::Overflow)?
184                };
185                result.logical_len = logical;
186                result.stored_len = count
187                    .checked_mul(config.encryption().tag_len() as u64)
188                    .and_then(|tags| logical.checked_add(tags))
189                    .ok_or(Error::Overflow)?;
190                return Ok(result);
191            }
192        }
193        for i in 0..count {
194            let table_index = usize::try_from(i).map_err(|_| Error::InvalidMetadata)?;
195            let raw = match config.framing() {
196                Framing::Fixed(size) if i.checked_add(1).is_some_and(|next| next < count) => size,
197                Framing::Fixed(_) => result.parts.last_frame_len.ok_or(Error::InvalidMetadata)?,
198                Framing::Variable(_) => result
199                    .parts
200                    .raw_lengths
201                    .as_ref()
202                    .ok_or(Error::InvalidMetadata)?
203                    .get(table_index)
204                    .ok_or(Error::InvalidMetadata)?,
205            };
206            let payload = match &result.parts.payload_lengths {
207                Some(table) => table.get(table_index).ok_or(Error::InvalidMetadata)?,
208                None => raw,
209            };
210            config.validate_frame(i, raw, payload)?;
211            result.logical_len = result.logical_len.checked_add(raw).ok_or(Error::Overflow)?;
212            result.stored_len = result
213                .stored_len
214                .checked_add(payload)
215                .and_then(|n| n.checked_add(config.encryption().tag_len() as u64))
216                .ok_or(Error::Overflow)?;
217        }
218        Ok(result)
219    }
220
221    /// Borrow the validated persistence representation.
222    pub fn parts(&self) -> &MetadataParts {
223        &self.parts
224    }
225    /// Transfer the persistence representation without copying.
226    pub fn into_parts(self) -> MetadataParts {
227        self.parts
228    }
229    /// Object-wide configuration.
230    pub fn config(&self) -> Config {
231        self.parts.config
232    }
233    /// Exact number of frames.
234    pub fn frame_count(&self) -> u64 {
235        self.parts.frame_count
236    }
237    /// Logical object length, in bytes.
238    pub fn logical_len(&self) -> u64 {
239        self.logical_len
240    }
241    /// Stored object length including tags, in bytes.
242    pub fn stored_len(&self) -> u64 {
243        self.stored_len
244    }
245    /// Last raw frame length; absent for an empty object.
246    pub fn last_frame_len(&self) -> Option<u64> {
247        self.frame_count()
248            .checked_sub(1)
249            .and_then(|index| self.sizes(index))
250            .map(|sizes| sizes.raw)
251    }
252
253    /// Append one successfully stored frame. Failures leave metadata unchanged.
254    /// Once a short fixed frame is appended, no further frames may follow it.
255    pub fn push(&mut self, sizes: FrameSizes) -> Result<()> {
256        let config = self.config();
257        config.validate_frame(self.frame_count(), sizes.raw, sizes.payload)?;
258        if let Framing::Fixed(size) = config.framing() {
259            if self.parts.last_frame_len.is_some_and(|last| last != size) {
260                return Err(Error::FrameAfterFinal);
261            }
262        }
263        let count = self.frame_count().checked_add(1).ok_or(Error::Overflow)?;
264        let logical = self
265            .logical_len
266            .checked_add(sizes.raw)
267            .ok_or(Error::Overflow)?;
268        let stored = self
269            .stored_len
270            .checked_add(sizes.payload)
271            .and_then(|n| n.checked_add(config.encryption().tag_len() as u64))
272            .ok_or(Error::Overflow)?;
273        // Reserve both arrays before changing either logical contents.
274        if let Some(table) = &mut self.parts.raw_lengths {
275            table.reserve_one()?;
276        }
277        if let Some(table) = &mut self.parts.payload_lengths {
278            table.reserve_one()?;
279        }
280        if let Some(table) = &mut self.parts.raw_lengths {
281            table.push(sizes.raw)?;
282        }
283        if let Some(table) = &mut self.parts.payload_lengths {
284            table.push(sizes.payload)?;
285        }
286        if matches!(config.framing(), Framing::Fixed(_)) {
287            self.parts.last_frame_len = Some(sizes.raw);
288        }
289        self.parts.frame_count = count;
290        self.logical_len = logical;
291        self.stored_len = stored;
292        Ok(())
293    }
294
295    /// Look up a codec descriptor by its original frame index in O(1).
296    pub fn frame(&self, index: u64) -> Option<FrameSpec> {
297        if index >= self.frame_count() {
298            return None;
299        }
300        Some(FrameSpec {
301            config: self.config(),
302            index,
303            sizes: self.sizes(index)?,
304        })
305    }
306
307    fn sizes(&self, index: u64) -> Option<FrameSizes> {
308        let table_index = usize::try_from(index).ok()?;
309        let raw = match self.config().framing() {
310            Framing::Fixed(size)
311                if index
312                    .checked_add(1)
313                    .is_some_and(|next| next < self.frame_count()) =>
314            {
315                size
316            }
317            Framing::Fixed(_) => self.parts.last_frame_len?,
318            Framing::Variable(_) => self
319                .parts
320                .raw_lengths
321                .as_ref()
322                .and_then(|table| table.get(table_index))?,
323        };
324        let payload = match &self.parts.payload_lengths {
325            Some(table) => table.get(table_index)?,
326            None => raw,
327        };
328        Some(FrameSizes { raw, payload })
329    }
330
331    /// Map logical `[start, end)` bytes to a physical range of complete frames.
332    /// `None` means logical EOF. Empty ranges return `0..0` and an empty iterator.
333    /// No allocation, I/O, or decoding occurs. Variable-size layouts scan their
334    /// compact index to the last selected frame; fixed uncompressed layouts use arithmetic.
335    pub fn range(&self, start: u64, end: Option<u64>) -> Result<(Range<u64>, FrameIter<'_>)> {
336        let end = end.unwrap_or(self.logical_len);
337        if start > end || end > self.logical_len {
338            return Err(Error::InvalidRange);
339        }
340        let iter = |first, stop, logical| FrameIter {
341            metadata: self,
342            next: first,
343            stop,
344            logical,
345            selection: start..end,
346        };
347        if start == end {
348            return Ok((0..0, iter(0, 0, 0)));
349        }
350        if let Framing::Fixed(size) = self.config().framing() {
351            let first = start.checked_div(size).ok_or(Error::InvalidMetadata)?;
352            let last = end.checked_sub(1).ok_or(Error::InvalidRange)?;
353            let last_frame = last.checked_div(size).ok_or(Error::InvalidMetadata)?;
354            let stop = last_frame.checked_add(1).ok_or(Error::Overflow)?;
355            if self.config().compression() == Compression::None {
356                let stride = size
357                    .checked_add(self.config().encryption().tag_len() as u64)
358                    .ok_or(Error::Overflow)?;
359                let physical_start = first.checked_mul(stride).ok_or(Error::Overflow)?;
360                let physical_end = if stop == self.frame_count() {
361                    self.stored_len
362                } else {
363                    stop.checked_mul(stride).ok_or(Error::Overflow)?
364                };
365                let logical_start = first.checked_mul(size).ok_or(Error::Overflow)?;
366                return Ok((
367                    physical_start..physical_end,
368                    iter(first, stop, logical_start),
369                ));
370            }
371            let mut physical: u64 = 0;
372            for i in 0..first {
373                let sizes = self.sizes(i).ok_or(Error::InvalidMetadata)?;
374                physical = physical
375                    .checked_add(sizes.payload)
376                    .and_then(|value| {
377                        value.checked_add(self.config().encryption().tag_len() as u64)
378                    })
379                    .ok_or(Error::Overflow)?;
380            }
381            let physical_start = physical;
382            if stop == self.frame_count() {
383                physical = self.stored_len;
384            } else {
385                for i in first..stop {
386                    let sizes = self.sizes(i).ok_or(Error::InvalidMetadata)?;
387                    physical = physical
388                        .checked_add(sizes.payload)
389                        .and_then(|value| {
390                            value.checked_add(self.config().encryption().tag_len() as u64)
391                        })
392                        .ok_or(Error::Overflow)?;
393                }
394            }
395            let logical_start = first.checked_mul(size).ok_or(Error::Overflow)?;
396            return Ok((physical_start..physical, iter(first, stop, logical_start)));
397        }
398        let mut logical: u64 = 0;
399        let mut physical: u64 = 0;
400        let mut first = 0;
401        loop {
402            let sizes = self.sizes(first).ok_or(Error::InvalidMetadata)?;
403            let next_logical = logical.checked_add(sizes.raw).ok_or(Error::Overflow)?;
404            if next_logical > start {
405                break;
406            }
407            logical = next_logical;
408            physical = physical
409                .checked_add(sizes.payload)
410                .and_then(|value| value.checked_add(self.config().encryption().tag_len() as u64))
411                .ok_or(Error::Overflow)?;
412            first = first.checked_add(1).ok_or(Error::Overflow)?;
413        }
414        let logical_start = logical;
415        let physical_start = physical;
416        let mut stop = first;
417        if end == self.logical_len {
418            stop = self.frame_count();
419            physical = self.stored_len;
420        } else {
421            while logical < end {
422                let sizes = self.sizes(stop).ok_or(Error::InvalidMetadata)?;
423                logical = logical.checked_add(sizes.raw).ok_or(Error::Overflow)?;
424                physical = physical
425                    .checked_add(sizes.payload)
426                    .and_then(|value| {
427                        value.checked_add(self.config().encryption().tag_len() as u64)
428                    })
429                    .ok_or(Error::Overflow)?;
430                stop = stop.checked_add(1).ok_or(Error::Overflow)?;
431            }
432        }
433        Ok((physical_start..physical, iter(first, stop, logical_start)))
434    }
435}
436
437/// Borrowing, allocation-free iterator over a selected logical range.
438#[derive(Clone, Debug)]
439pub struct FrameIter<'a> {
440    metadata: &'a Metadata,
441    next: u64,
442    stop: u64,
443    logical: u64,
444    selection: Range<u64>,
445}
446
447impl FrameIter<'_> {
448    /// Exact remaining frame count, including partial boundary frames.
449    pub fn remaining(&self) -> u64 {
450        self.stop.saturating_sub(self.next)
451    }
452}
453
454impl Iterator for FrameIter<'_> {
455    type Item = FrameRead;
456
457    fn next(&mut self) -> Option<Self::Item> {
458        if self.next == self.stop {
459            return None;
460        }
461        let spec = self.metadata.frame(self.next)?;
462        let logical_end = self.logical.checked_add(spec.sizes.raw)?;
463        let selected_end = self
464            .selection
465            .end
466            .min(logical_end)
467            .checked_sub(self.logical)?;
468        let selected =
469            (self.selection.start.saturating_sub(self.logical) as usize)..(selected_end as usize);
470        self.logical = logical_end;
471        self.next = self.next.checked_add(1)?;
472        Some(FrameRead { spec, selected })
473    }
474
475    fn size_hint(&self) -> (usize, Option<usize>) {
476        match usize::try_from(self.remaining()) {
477            Ok(n) => (n, Some(n)),
478            Err(_) => (usize::MAX, None),
479        }
480    }
481}
482
483impl FusedIterator for FrameIter<'_> {}