Skip to main content

craft_codec/
config.rs

1use crate::{Error, Result, error::buffer_len};
2
3/// Version of the headerless data format and its external metadata.
4pub const FORMAT_VERSION: u8 = 1;
5/// AES-GCM authentication tag length in bytes.
6pub const TAG_LEN: usize = 16;
7
8/// Upper bound on raw bytes covered by the AES profile's frame slots per key.
9/// This is a library usage policy, not a claim of 128-bit security at this limit.
10pub const AES_MAX_BYTES: u64 = 1 << 36;
11/// Maximum number of AES-GCM frame slots per object/key.
12pub const AES_MAX_FRAMES: u64 = 1 << 32;
13/// Maximum configured raw frame length for the AES-GCM profile, 16 MiB.
14pub const AES_MAX_FRAME_LEN: u64 = 1 << 24;
15
16/// Logical frame boundaries, before compression or encryption.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub enum Framing {
19    /// All frames have this size, except a possibly shorter final frame.
20    Fixed(u64),
21    /// Each nonempty frame may have any size up to this limit.
22    Variable(u64),
23}
24
25impl Framing {
26    /// Maximum number of raw bytes in a frame.
27    pub const fn max_frame_len(self) -> u64 {
28        match self {
29            Self::Fixed(size) | Self::Variable(size) => size,
30        }
31    }
32}
33
34/// Compression identifier in trusted external metadata.
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36#[repr(u8)]
37pub enum Compression {
38    /// Store raw bytes.
39    None = 0,
40    /// Independent LZ4 blocks, retained only when strictly smaller.
41    Lz4 = 1,
42}
43
44/// Encryption identifier in trusted external metadata.
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46#[repr(u8)]
47pub enum Encryption {
48    /// No authentication or encryption.
49    None = 0,
50    /// AES-256-GCM with a 96-bit frame-index nonce and a 16-byte suffix tag.
51    Aes256Gcm = 1,
52}
53
54impl Encryption {
55    /// Stored suffix length in bytes.
56    pub const fn tag_len(self) -> usize {
57        match self {
58            Self::None => 0,
59            Self::Aes256Gcm => TAG_LEN,
60        }
61    }
62}
63
64/// Validated object-wide settings. Contains no key.
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub struct Config {
67    framing: Framing,
68    compression: Compression,
69    encryption: Encryption,
70}
71
72impl Config {
73    /// Validate a profile, including platform, codec and AES usage limits.
74    /// Metadata for disabled codecs may still be inspected with this type.
75    pub fn new(framing: Framing, compression: Compression, encryption: Encryption) -> Result<Self> {
76        let max = framing.max_frame_len();
77        if max == 0 {
78            return Err(Error::InvalidFrameSize);
79        }
80        buffer_len(
81            max.checked_add(encryption.tag_len() as u64)
82                .ok_or(Error::Overflow)?,
83        )?;
84        // LZ4 block offsets/tables use 32-bit positions. Leave room for its bound.
85        if compression == Compression::Lz4 && max > 0x7e00_0000 {
86            return Err(Error::InvalidFrameSize);
87        }
88        if encryption == Encryption::Aes256Gcm && max > AES_MAX_FRAME_LEN {
89            return Err(Error::InvalidFrameSize);
90        }
91        Ok(Self {
92            framing,
93            compression,
94            encryption,
95        })
96    }
97
98    /// Logical framing rule.
99    pub const fn framing(self) -> Framing {
100        self.framing
101    }
102    /// Compression format.
103    pub const fn compression(self) -> Compression {
104        self.compression
105    }
106    /// Encryption format.
107    pub const fn encryption(self) -> Encryption {
108        self.encryption
109    }
110    /// Maximum raw length, in bytes.
111    pub const fn max_frame_len(self) -> u64 {
112        self.framing.max_frame_len()
113    }
114
115    /// Maximum frame slots for this profile. AES reserves the configured
116    /// maximum length for each slot, even if an actual frame is shorter.
117    pub fn max_frames(self) -> u64 {
118        match self.encryption {
119            Encryption::None => u64::MAX,
120            Encryption::Aes256Gcm => AES_MAX_FRAMES.min(AES_MAX_BYTES / self.max_frame_len()),
121        }
122    }
123
124    pub(crate) fn validate_frame(self, index: u64, raw: u64, payload: u64) -> Result<()> {
125        if raw == 0 || raw > self.max_frame_len() {
126            return Err(Error::InvalidFrameSize);
127        }
128        if payload == 0
129            || payload > raw
130            || (self.compression == Compression::None && payload != raw)
131        {
132            return Err(Error::InvalidMetadata);
133        }
134        if index >= self.max_frames() {
135            return Err(Error::UsageLimit);
136        }
137        Ok(())
138    }
139}