Skip to main content

base64_ng/v2/
specifications.rs

1//! Sealed, validated codec specifications for the 2.0 core.
2
3use super::alphabet::{
4    STANDARD_ALPHABET, URL_SAFE_ALPHABET, ValidatedAlphabet, ValidatedAlphabetError,
5};
6
7/// Whether ordinary encoding emits canonical `=` padding.
8#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
9pub enum EncodePadding {
10    /// Emit canonical padding.
11    Padded,
12    /// Omit padding.
13    Unpadded,
14}
15
16/// Which padding forms ordinary decoding accepts.
17#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
18pub enum DecodePadding {
19    /// Require the canonical padding count for the decoded length.
20    RequireCanonical,
21    /// Reject every padding byte.
22    Forbid,
23    /// Accept canonical, absent, or partially present trailing padding.
24    Indifferent,
25}
26
27/// Whether ordinary decoding enforces zero unused trailing bits.
28#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
29pub enum TrailingBits {
30    /// Reject noncanonical unused trailing bits.
31    RequireCanonical,
32    /// Ignore nonzero unused trailing bits for compatibility inputs.
33    AllowNonCanonical,
34}
35
36/// One complete, immutable ordinary codec policy.
37#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
38pub struct CodecSettings {
39    alphabet: ValidatedAlphabet,
40    encode_padding: EncodePadding,
41    decode_padding: DecodePadding,
42    trailing_bits: TrailingBits,
43}
44
45impl CodecSettings {
46    const fn new(
47        alphabet: ValidatedAlphabet,
48        encode_padding: EncodePadding,
49        decode_padding: DecodePadding,
50        trailing_bits: TrailingBits,
51    ) -> Self {
52        Self {
53            alphabet,
54            encode_padding,
55            decode_padding,
56            trailing_bits,
57        }
58    }
59
60    /// Returns the validated alphabet owned by this specification.
61    #[must_use]
62    pub const fn alphabet(&self) -> &ValidatedAlphabet {
63        &self.alphabet
64    }
65
66    /// Returns the encode-padding policy.
67    #[must_use]
68    pub const fn encode_padding(&self) -> EncodePadding {
69        self.encode_padding
70    }
71
72    /// Returns the decode-padding policy.
73    #[must_use]
74    pub const fn decode_padding(&self) -> DecodePadding {
75        self.decode_padding
76    }
77
78    /// Returns the trailing-bit policy.
79    #[must_use]
80    pub const fn trailing_bits(&self) -> TrailingBits {
81        self.trailing_bits
82    }
83
84    /// Returns whether the policy is eligible for a future secret codec.
85    ///
86    /// Runtime alphabets are eligible, but padding-indifferent and
87    /// noncanonical-trailing-bit policies are deliberately excluded.
88    #[must_use]
89    pub const fn permits_secret_processing(&self) -> bool {
90        !matches!(self.decode_padding, DecodePadding::Indifferent)
91            && matches!(self.trailing_bits, TrailingBits::RequireCanonical)
92    }
93}
94
95mod sealed {
96    pub trait Sealed {}
97}
98
99/// The single sealed consumer boundary for a complete codec specification.
100///
101/// The trait is object-safe for integration boundaries, while ordinary hot
102/// paths remain generic over one whole specification type. It intentionally
103/// has no associated constants so runtime specifications and trait objects use
104/// the same contract.
105pub trait Codec: sealed::Sealed + Send + Sync {
106    /// Returns the complete validated settings value.
107    fn settings(&self) -> CodecSettings;
108}
109
110/// A codec value parameterized by one complete sealed specification.
111#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
112pub struct Base64<S> {
113    specification: S,
114}
115
116impl<S: Codec> Base64<S> {
117    /// Constructs a codec from one sealed specification value.
118    pub const fn new(specification: S) -> Self {
119        Self { specification }
120    }
121
122    /// Returns the owned specification value.
123    pub const fn specification(&self) -> &S {
124        &self.specification
125    }
126    /// Returns the codec's complete validated settings.
127    pub fn settings(&self) -> CodecSettings {
128        self.specification.settings()
129    }
130}
131
132macro_rules! strict_specification {
133    ($name:ident, $alphabet:expr, $encode:expr, $decode:expr) => {
134        #[doc = "A sealed strict RFC 4648 built-in specification."]
135        #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
136        pub struct $name;
137
138        impl $name {
139            const SETTINGS: CodecSettings =
140                CodecSettings::new($alphabet, $encode, $decode, TrailingBits::RequireCanonical);
141
142            pub(crate) const fn const_settings() -> CodecSettings {
143                Self::SETTINGS
144            }
145        }
146
147        impl sealed::Sealed for $name {}
148
149        impl Codec for $name {
150            fn settings(&self) -> CodecSettings {
151                Self::SETTINGS
152            }
153        }
154    };
155}
156
157strict_specification!(
158    StrictStandardPadded,
159    STANDARD_ALPHABET,
160    EncodePadding::Padded,
161    DecodePadding::RequireCanonical
162);
163strict_specification!(
164    StrictStandardUnpadded,
165    STANDARD_ALPHABET,
166    EncodePadding::Unpadded,
167    DecodePadding::Forbid
168);
169strict_specification!(
170    StrictUrlSafePadded,
171    URL_SAFE_ALPHABET,
172    EncodePadding::Padded,
173    DecodePadding::RequireCanonical
174);
175strict_specification!(
176    StrictUrlSafeUnpadded,
177    URL_SAFE_ALPHABET,
178    EncodePadding::Unpadded,
179    DecodePadding::Forbid
180);
181
182/// Strict RFC 4648 Standard Base64 with canonical padding.
183pub const STRICT_STANDARD_PADDED: Base64<StrictStandardPadded> = Base64::new(StrictStandardPadded);
184/// Strict RFC 4648 Standard Base64 without padding.
185pub const STRICT_STANDARD_UNPADDED: Base64<StrictStandardUnpadded> =
186    Base64::new(StrictStandardUnpadded);
187/// Strict RFC 4648 URL-safe Base64 with canonical padding.
188pub const STRICT_URL_SAFE_PADDED: Base64<StrictUrlSafePadded> = Base64::new(StrictUrlSafePadded);
189/// Strict RFC 4648 URL-safe Base64 without padding.
190pub const STRICT_URL_SAFE_UNPADDED: Base64<StrictUrlSafeUnpadded> =
191    Base64::new(StrictUrlSafeUnpadded);
192
193/// A complete owned runtime specification.
194#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
195pub struct RuntimeSpec {
196    settings: CodecSettings,
197}
198
199impl RuntimeSpec {
200    pub(crate) const fn const_settings(&self) -> CodecSettings {
201        self.settings
202    }
203}
204
205pub(crate) const fn runtime_codec(
206    alphabet: ValidatedAlphabet,
207    encode_padding: EncodePadding,
208    decode_padding: DecodePadding,
209    trailing_bits: TrailingBits,
210) -> Base64<RuntimeSpec> {
211    Base64::new(RuntimeSpec {
212        settings: CodecSettings::new(alphabet, encode_padding, decode_padding, trailing_bits),
213    })
214}
215
216pub(crate) const fn compatibility_codec(
217    alphabet: ValidatedAlphabet,
218    encode_padding: EncodePadding,
219    decode_padding: DecodePadding,
220    trailing_bits: TrailingBits,
221) -> Base64<RuntimeSpec> {
222    runtime_codec(alphabet, encode_padding, decode_padding, trailing_bits)
223}
224
225impl sealed::Sealed for RuntimeSpec {}
226
227impl Codec for RuntimeSpec {
228    fn settings(&self) -> CodecSettings {
229        self.settings
230    }
231}
232
233/// A policy combination that cannot form a self-consistent codec.
234#[derive(Clone, Copy, Debug, Eq, PartialEq)]
235pub enum CodecBuilderError {
236    /// Encoding emits padding that decoding always rejects.
237    EncodedPaddingRejected,
238    /// Encoding omits padding that decoding requires.
239    EncodedPaddingRequired,
240}
241
242impl core::fmt::Display for CodecBuilderError {
243    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
244        match self {
245            Self::EncodedPaddingRejected => {
246                formatter.write_str("encoding emits padding rejected by decode policy")
247            }
248            Self::EncodedPaddingRequired => {
249                formatter.write_str("encoding omits padding required by decode policy")
250            }
251        }
252    }
253}
254
255/// Fallible no-allocation builder for an advanced ordinary runtime codec.
256#[derive(Clone, Copy, Debug, Eq, PartialEq)]
257pub struct CodecBuilder {
258    settings: CodecSettings,
259}
260
261impl CodecBuilder {
262    /// Starts with strict canonical padded policies and an owned alphabet.
263    #[must_use]
264    pub const fn new(alphabet: ValidatedAlphabet) -> Self {
265        Self {
266            settings: CodecSettings::new(
267                alphabet,
268                EncodePadding::Padded,
269                DecodePadding::RequireCanonical,
270                TrailingBits::RequireCanonical,
271            ),
272        }
273    }
274
275    /// Validates and owns an alphabet table before constructing the builder.
276    pub const fn from_table(table: [u8; 64]) -> Result<Self, ValidatedAlphabetError> {
277        match ValidatedAlphabet::new(table) {
278            Ok(alphabet) => Ok(Self::new(alphabet)),
279            Err(error) => Err(error),
280        }
281    }
282
283    /// Copies and validates a runtime alphabet slice.
284    pub const fn from_slice(bytes: &[u8]) -> Result<Self, ValidatedAlphabetError> {
285        match ValidatedAlphabet::try_from_slice(bytes) {
286            Ok(alphabet) => Ok(Self::new(alphabet)),
287            Err(error) => Err(error),
288        }
289    }
290
291    /// Sets whether encoding emits padding.
292    #[must_use]
293    pub const fn encode_padding(mut self, policy: EncodePadding) -> Self {
294        self.settings.encode_padding = policy;
295        self
296    }
297
298    /// Sets the ordinary decode-padding policy.
299    #[must_use]
300    pub const fn decode_padding(mut self, policy: DecodePadding) -> Self {
301        self.settings.decode_padding = policy;
302        self
303    }
304
305    /// Sets the ordinary trailing-bit canonicality policy.
306    #[must_use]
307    pub const fn trailing_bits(mut self, policy: TrailingBits) -> Self {
308        self.settings.trailing_bits = policy;
309        self
310    }
311
312    /// Validates policy compatibility and constructs an owned runtime codec.
313    pub const fn build(self) -> Result<Base64<RuntimeSpec>, CodecBuilderError> {
314        match (self.settings.encode_padding, self.settings.decode_padding) {
315            (EncodePadding::Padded, DecodePadding::Forbid) => {
316                Err(CodecBuilderError::EncodedPaddingRejected)
317            }
318            (EncodePadding::Unpadded, DecodePadding::RequireCanonical) => {
319                Err(CodecBuilderError::EncodedPaddingRequired)
320            }
321            _ => Ok(Base64::new(RuntimeSpec {
322                settings: self.settings,
323            })),
324        }
325    }
326}