Skip to main content

base64_ng/v2/
secret_decoder.rs

1//! Fixed-work incremental core for bounded secret decoding frames.
2
3use super::{
4    contracts::Progress,
5    specifications::{CodecSettings, DecodePadding},
6};
7
8/// Maximum decoded capacity accepted by a stack-backed secret frame.
9pub const MAX_SECRET_STACK_DECODED: usize = 1_024;
10
11/// Opaque error returned by a bounded secret decode frame.
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13#[non_exhaustive]
14pub enum SecretDecodeError {
15    /// The public cumulative encoded length exceeds the frame limit.
16    InputTooLarge {
17        /// Public encoded length presented so far.
18        input_len: usize,
19        /// Maximum encoded length derived from decoded capacity and padding.
20        maximum_encoded_len: usize,
21    },
22    /// Caller-provided storage cannot hold the declared decoded capacity.
23    OutputFull {
24        /// Public declared decoded capacity.
25        required: usize,
26        /// Public available bytes.
27        available: usize,
28    },
29    /// Private staging overlaps encoded input or final output.
30    OverlappingBuffers,
31    /// A byte-range end address cannot be represented by `usize`.
32    AddressRangeOverflow,
33    /// The codec permits a compatibility form excluded from secret decoding.
34    UnsupportedPolicy,
35    /// Fixed-work validation rejected the input without localized details.
36    InvalidInput,
37    /// Capacity or source-position arithmetic overflowed.
38    LengthOverflow,
39    /// A preallocated heap frame could not reserve its complete storage.
40    #[cfg(feature = "alloc")]
41    AllocationFailed,
42    /// The state previously failed and is absorbing.
43    Failed,
44    /// The state has already completed successfully.
45    Complete,
46}
47
48impl core::fmt::Display for SecretDecodeError {
49    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        match self {
51            Self::InputTooLarge {
52                input_len,
53                maximum_encoded_len,
54            } => write!(
55                formatter,
56                "secret input length {input_len} exceeds public frame limit {maximum_encoded_len}"
57            ),
58            Self::OutputFull {
59                required,
60                available,
61            } => write!(
62                formatter,
63                "secret frame requires {required} decoded bytes; storage has {available}"
64            ),
65            Self::OverlappingBuffers => {
66                formatter.write_str("secret input, staging, and final output must be disjoint")
67            }
68            Self::AddressRangeOverflow => {
69                formatter.write_str("secret frame byte-range address overflows usize")
70            }
71            Self::UnsupportedPolicy => {
72                formatter.write_str("codec policy is not eligible for secret decoding")
73            }
74            Self::InvalidInput => formatter.write_str("invalid secret base64 input"),
75            Self::LengthOverflow => formatter.write_str("secret frame length overflows usize"),
76            #[cfg(feature = "alloc")]
77            Self::AllocationFailed => {
78                formatter.write_str("failed to reserve bounded secret frame storage")
79            }
80            Self::Failed => formatter.write_str("secret decoder is in an absorbing failed state"),
81            Self::Complete => formatter.write_str("secret decoder is already complete"),
82        }
83    }
84}
85
86#[cfg(feature = "std")]
87impl std::error::Error for SecretDecodeError {}
88
89#[derive(Clone, Copy, Eq, PartialEq)]
90enum Phase {
91    Active,
92    Failed,
93    Complete,
94}
95
96/// Public metadata and fixed-work core shared by secret decode frames.
97///
98/// The state is deliberately not directly constructible. Use
99/// [`super::secret::SecretArrayFrame`], [`super::secret::SecretFrame`], or
100/// `SecretVecFrame`; those owners bind the state to disjoint private staging
101/// and final output for the complete operation.
102pub struct SecretDecoderState {
103    settings: CodecSettings,
104    maximum_decoded_len: usize,
105    maximum_encoded_len: usize,
106    input_len: usize,
107    staged_len: usize,
108    pending_bytes: [u8; 4],
109    pending_values: [u8; 4],
110    pending_valid: [u8; 4],
111    pending_len: usize,
112    invalid: u8,
113    phase: Phase,
114    #[cfg(test)]
115    symbol_scans: usize,
116}
117
118impl SecretDecoderState {
119    pub(super) fn new(
120        settings: CodecSettings,
121        maximum_decoded_len: usize,
122    ) -> Result<Self, SecretDecodeError> {
123        if !settings.permits_secret_processing() {
124            return Err(SecretDecodeError::UnsupportedPolicy);
125        }
126        let padded = settings.decode_padding() == DecodePadding::RequireCanonical;
127        let maximum_encoded_len = crate::checked_encoded_len(maximum_decoded_len, padded)
128            .ok_or(SecretDecodeError::LengthOverflow)?;
129        Ok(Self {
130            settings,
131            maximum_decoded_len,
132            maximum_encoded_len,
133            input_len: 0,
134            staged_len: 0,
135            pending_bytes: [0; 4],
136            pending_values: [0; 4],
137            pending_valid: [0; 4],
138            pending_len: 0,
139            invalid: 0,
140            phase: Phase::Active,
141            #[cfg(test)]
142            symbol_scans: 0,
143        })
144    }
145
146    /// Returns the public maximum decoded bytes.
147    #[must_use]
148    pub const fn maximum_decoded_len(&self) -> usize {
149        self.maximum_decoded_len
150    }
151
152    /// Returns the public maximum encoded bytes for this padding policy.
153    #[must_use]
154    pub const fn maximum_encoded_len(&self) -> usize {
155        self.maximum_encoded_len
156    }
157
158    /// Returns the public encoded bytes accepted so far.
159    #[must_use]
160    pub const fn input_len(&self) -> usize {
161        self.input_len
162    }
163
164    /// Returns whether this state has entered an absorbing failure.
165    #[must_use]
166    pub const fn is_failed(&self) -> bool {
167        matches!(self.phase, Phase::Failed)
168    }
169
170    pub(super) fn update(
171        &mut self,
172        input: &[u8],
173        staging: &mut [u8],
174    ) -> Result<Progress, SecretDecodeError> {
175        self.require_active()?;
176        let attempted = self
177            .input_len
178            .checked_add(input.len())
179            .ok_or_else(|| self.fail(SecretDecodeError::LengthOverflow))?;
180        if attempted > self.maximum_encoded_len {
181            return Err(self.fail(SecretDecodeError::InputTooLarge {
182                input_len: attempted,
183                maximum_encoded_len: self.maximum_encoded_len,
184            }));
185        }
186
187        for &byte in input {
188            if self.pending_len == 4 {
189                self.commit_nonfinal(staging)?;
190            }
191            let slot = self.pending_len;
192            let (value, valid) = decode_symbol(self.settings, byte);
193            #[cfg(test)]
194            {
195                self.symbol_scans += 64;
196            }
197            self.pending_bytes[slot] = byte;
198            self.pending_values[slot] = value;
199            self.pending_valid[slot] = valid;
200            self.pending_len += 1;
201        }
202        self.input_len = attempted;
203        Ok(Progress::new(input.len(), 0))
204    }
205
206    pub(super) fn finish(&mut self) -> Result<FinalCandidate, SecretDecodeError> {
207        self.require_active()?;
208        let candidate = match self.settings.decode_padding() {
209            DecodePadding::RequireCanonical => self.finish_padded(),
210            DecodePadding::Forbid => self.finish_unpadded(),
211            DecodePadding::Indifferent => {
212                return Err(self.fail(SecretDecodeError::UnsupportedPolicy));
213            }
214        };
215        let remaining = self.maximum_decoded_len - self.staged_len;
216        let public_remaining = u8::try_from(remaining.min(3)).unwrap_or(3);
217        let candidate_len = u8::try_from(candidate.len).unwrap_or(3);
218        self.invalid = accumulate(
219            self.invalid,
220            crate::ct_mask_lt_u8(public_remaining, candidate_len),
221        );
222        crate::ct_error_gate_barrier(self.invalid, 0);
223        if core::hint::black_box(self.invalid) != 0 {
224            return Err(self.fail(SecretDecodeError::InvalidInput));
225        }
226        self.phase = Phase::Complete;
227        Ok(candidate)
228    }
229
230    fn commit_nonfinal(&mut self, staging: &mut [u8]) -> Result<(), SecretDecodeError> {
231        let Some(end) = self.staged_len.checked_add(3) else {
232            return Err(self.fail(SecretDecodeError::LengthOverflow));
233        };
234        if end > staging.len() || end > self.maximum_decoded_len {
235            return Err(self.fail(SecretDecodeError::OutputFull {
236                required: end,
237                available: staging.len().min(self.maximum_decoded_len),
238            }));
239        }
240        let candidate = candidate_bytes(self.pending_values);
241        staging[self.staged_len..end].copy_from_slice(&candidate);
242        self.staged_len = end;
243        for valid in self.pending_valid {
244            self.invalid = accumulate(self.invalid, !valid);
245        }
246        self.clear_pending();
247        Ok(())
248    }
249
250    fn finish_padded(&mut self) -> FinalCandidate {
251        if self.input_len == 0 {
252            return FinalCandidate::empty(self.staged_len);
253        }
254        self.invalid = accumulate(
255            self.invalid,
256            crate::ct_mask_nonzero_u8(u8::from(self.pending_len != 4)),
257        );
258        let equals_third = crate::ct_mask_eq_u8(self.pending_bytes[2], b'=');
259        let equals_fourth = crate::ct_mask_eq_u8(self.pending_bytes[3], b'=');
260        let no_padding = !equals_third & !equals_fourth;
261        let one_padding = !equals_third & equals_fourth;
262        let two_padding = equals_third & equals_fourth;
263        let malformed_padding = equals_third & !equals_fourth;
264        let require_third = no_padding | one_padding;
265        self.invalid = accumulate(self.invalid, !self.pending_valid[0]);
266        self.invalid = accumulate(self.invalid, !self.pending_valid[1]);
267        self.invalid = accumulate(self.invalid, !self.pending_valid[2] & require_third);
268        self.invalid = accumulate(self.invalid, !self.pending_valid[3] & no_padding);
269        self.invalid = accumulate(self.invalid, malformed_padding);
270        self.invalid = accumulate(
271            self.invalid,
272            crate::ct_mask_nonzero_u8(self.pending_values[1] & 0x0f) & two_padding,
273        );
274        self.invalid = accumulate(
275            self.invalid,
276            crate::ct_mask_nonzero_u8(self.pending_values[2] & 0x03) & one_padding,
277        );
278        let padding = usize::from((equals_third & 1) + (equals_fourth & 1));
279        FinalCandidate::new(
280            self.staged_len,
281            candidate_bytes(self.pending_values),
282            3 - padding,
283        )
284    }
285
286    fn finish_unpadded(&mut self) -> FinalCandidate {
287        let candidate = candidate_bytes(self.pending_values);
288        let final_len = match self.pending_len {
289            0 => 0,
290            2 => {
291                self.invalid = accumulate(self.invalid, !self.pending_valid[0]);
292                self.invalid = accumulate(self.invalid, !self.pending_valid[1]);
293                self.invalid = accumulate(
294                    self.invalid,
295                    crate::ct_mask_nonzero_u8(self.pending_values[1] & 0x0f),
296                );
297                1
298            }
299            3 => {
300                self.invalid = accumulate(self.invalid, !self.pending_valid[0]);
301                self.invalid = accumulate(self.invalid, !self.pending_valid[1]);
302                self.invalid = accumulate(self.invalid, !self.pending_valid[2]);
303                self.invalid = accumulate(
304                    self.invalid,
305                    crate::ct_mask_nonzero_u8(self.pending_values[2] & 0x03),
306                );
307                2
308            }
309            4 => {
310                for valid in self.pending_valid {
311                    self.invalid = accumulate(self.invalid, !valid);
312                }
313                3
314            }
315            _ => {
316                self.invalid = accumulate(self.invalid, 0xff);
317                0
318            }
319        };
320        FinalCandidate::new(self.staged_len, candidate, final_len)
321    }
322
323    fn clear_pending(&mut self) {
324        crate::wipe_bytes(&mut self.pending_bytes);
325        crate::wipe_bytes(&mut self.pending_values);
326        crate::wipe_bytes(&mut self.pending_valid);
327        self.pending_len = 0;
328    }
329
330    fn require_active(&self) -> Result<(), SecretDecodeError> {
331        match self.phase {
332            Phase::Active => Ok(()),
333            Phase::Failed => Err(SecretDecodeError::Failed),
334            Phase::Complete => Err(SecretDecodeError::Complete),
335        }
336    }
337
338    fn fail(&mut self, error: SecretDecodeError) -> SecretDecodeError {
339        self.phase = Phase::Failed;
340        self.clear_pending();
341        self.invalid = 0;
342        error
343    }
344
345    pub(super) fn latch_external_failure(&mut self) {
346        self.phase = Phase::Failed;
347        self.clear_pending();
348        self.invalid = 0;
349    }
350}
351
352impl Drop for SecretDecoderState {
353    fn drop(&mut self) {
354        self.clear_pending();
355        self.invalid = 0;
356        self.input_len = 0;
357        self.staged_len = 0;
358    }
359}
360
361impl core::fmt::Debug for SecretDecoderState {
362    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
363        formatter
364            .debug_struct("SecretDecoderState")
365            .field("pending", &"<redacted>")
366            .field("input_len", &self.input_len)
367            .field("maximum_encoded_len", &self.maximum_encoded_len)
368            .field("maximum_decoded_len", &self.maximum_decoded_len)
369            .field("failed", &self.is_failed())
370            .finish_non_exhaustive()
371    }
372}
373
374pub(super) struct FinalCandidate {
375    pub(super) staged_len: usize,
376    pub(super) bytes: [u8; 3],
377    pub(super) len: usize,
378}
379
380impl FinalCandidate {
381    const fn new(staged_len: usize, bytes: [u8; 3], len: usize) -> Self {
382        Self {
383            staged_len,
384            bytes,
385            len,
386        }
387    }
388
389    const fn empty(staged_len: usize) -> Self {
390        Self::new(staged_len, [0; 3], 0)
391    }
392
393    pub(super) const fn written(&self) -> usize {
394        self.staged_len + self.len
395    }
396}
397
398impl Drop for FinalCandidate {
399    fn drop(&mut self) {
400        crate::wipe_bytes(&mut self.bytes);
401        self.len = 0;
402        self.staged_len = 0;
403    }
404}
405
406#[inline(never)]
407fn decode_symbol(settings: CodecSettings, byte: u8) -> (u8, u8) {
408    let mut decoded = 0u8;
409    let mut valid = 0u8;
410    let mut candidate = 0u8;
411    while candidate < 64 {
412        let matches = core::hint::black_box(crate::ct_mask_eq_u8(
413            core::hint::black_box(byte),
414            core::hint::black_box(settings.alphabet().as_array()[usize::from(candidate)]),
415        ));
416        decoded = accumulate(decoded, candidate & matches);
417        valid = accumulate(valid, matches);
418        candidate += 1;
419    }
420    (decoded, valid)
421}
422
423fn candidate_bytes(values: [u8; 4]) -> [u8; 3] {
424    [
425        (values[0] << 2) | (values[1] >> 4),
426        (values[1] << 4) | (values[2] >> 2),
427        (values[2] << 6) | values[3],
428    ]
429}
430
431fn accumulate(accumulator: u8, value: u8) -> u8 {
432    crate::ct_accumulate_u8(accumulator, value)
433}
434
435pub(super) fn require_disjoint(left: &[u8], right: &[u8]) -> Result<(), SecretDecodeError> {
436    require_disjoint_ranges(
437        left.as_ptr() as usize,
438        left.len(),
439        right.as_ptr() as usize,
440        right.len(),
441    )
442}
443
444fn require_disjoint_ranges(
445    left_start: usize,
446    left_len: usize,
447    right_start: usize,
448    right_len: usize,
449) -> Result<(), SecretDecodeError> {
450    let left_end = left_start
451        .checked_add(left_len)
452        .ok_or(SecretDecodeError::AddressRangeOverflow)?;
453    let right_end = right_start
454        .checked_add(right_len)
455        .ok_or(SecretDecodeError::AddressRangeOverflow)?;
456    if left_len != 0 && right_len != 0 && left_start < right_end && right_start < left_end {
457        Err(SecretDecodeError::OverlappingBuffers)
458    } else {
459        Ok(())
460    }
461}
462
463#[cfg(test)]
464pub(super) fn require_disjoint_ranges_for_test(
465    left_start: usize,
466    left_len: usize,
467    right_start: usize,
468    right_len: usize,
469) -> Result<(), SecretDecodeError> {
470    require_disjoint_ranges(left_start, left_len, right_start, right_len)
471}
472
473#[cfg(test)]
474impl SecretDecoderState {
475    pub(super) const fn symbol_scans_for_test(&self) -> usize {
476        self.symbol_scans
477    }
478
479    pub(super) fn pending_is_clear_for_test(&self) -> bool {
480        self.pending_len == 0
481            && self.pending_bytes.iter().all(|byte| *byte == 0)
482            && self.pending_values.iter().all(|byte| *byte == 0)
483            && self.pending_valid.iter().all(|byte| *byte == 0)
484            && self.invalid == 0
485    }
486}