Skip to main content

craft_codec/
metadata.rs

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