Skip to main content

fastbase91_core/
decode.rs

1use core::fmt;
2
3use crate::tables::{DECODE_FF, DECODE_TABLE};
4use crate::OutputTooSmall;
5
6/// Decoder policy options.
7#[non_exhaustive]
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct DecodeOptions {
10    /// Whether bytes outside the basE91 alphabet should be rejected.
11    pub reject_non_alphabet: bool,
12}
13
14impl DecodeOptions {
15    /// Creates lenient options that ignore bytes outside the basE91 alphabet.
16    pub const fn new() -> Self {
17        Self {
18            reject_non_alphabet: false,
19        }
20    }
21}
22
23impl Default for DecodeOptions {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29/// Error returned by the decoding APIs.
30#[non_exhaustive]
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub enum DecodeError {
33    /// The caller-provided output storage cannot hold the result.
34    OutputTooSmall(OutputTooSmall),
35    /// A strict decode encountered a byte outside the alphabet.
36    InvalidByte { byte: u8, offset: usize },
37    /// The allocator could not reserve the requested storage.
38    #[cfg(feature = "alloc")]
39    AllocationFailed,
40}
41
42impl fmt::Display for DecodeError {
43    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Self::OutputTooSmall(error) => error.fmt(formatter),
46            Self::InvalidByte { byte, offset } => {
47                write!(formatter, "invalid byte 0x{byte:02x} at offset {offset}")
48            }
49            #[cfg(feature = "alloc")]
50            Self::AllocationFailed => formatter.write_str("decoding allocation failed"),
51        }
52    }
53}
54
55impl core::error::Error for DecodeError {}
56
57/// Returns a decoded-length upper bound for `input_len` bytes.
58///
59/// The bound holds for every reachable decoder state and every input, and
60/// includes the possible one-byte tail returned by [`Decoder::finish`]. In the
61/// worst case, an existing pending symbol makes the number of symbols at most
62/// `input_len + 1`. The resulting bound is monotone in that symbol count, so it
63/// completes `ceil(input_len / 2)` pairs of at most 14 bits and has a tail
64/// exactly when `input_len` is even. The quotient and remainder calculation
65/// avoids forming `14 * input_len`.
66///
67/// `None` means that the mathematical bound does not fit in `usize`. A `Some`
68/// result can still exceed `isize::MAX`, so allocating APIs perform that
69/// separate platform-capacity check.
70pub const fn max_decoded_len(input_len: usize) -> Option<usize> {
71    let pairs = input_len / 2 + input_len % 2;
72    let prefix = match (pairs / 4).checked_mul(7) {
73        Some(value) => value,
74        None => return None,
75    };
76    let remaining_bytes = ((pairs % 4) * 14).div_ceil(8);
77    let tail = if input_len % 2 == 0 { 1 } else { 0 };
78    match prefix.checked_add(remaining_bytes) {
79        Some(value) => value.checked_add(tail),
80        None => None,
81    }
82}
83
84/// Stateful standard basE91 decoder.
85///
86/// A call to [`finish`](Self::finish) ends the current message and consumes the
87/// decoder, so the same stream cannot be continued afterwards. The consuming
88/// contract is enforced by the type system: the decoder is not `Copy`, so
89/// reusing it after `finish` fails to compile.
90///
91/// ```compile_fail
92/// use fastbase91_core::{DecodeOptions, Decoder};
93/// let mut decoder = Decoder::new(DecodeOptions::new());
94/// let mut output = [0_u8; 4];
95/// let _tail = decoder.finish();
96/// // `finish` consumed the decoder; decoding more must not compile.
97/// let _ = decoder.update(b"AA", &mut output);
98/// ```
99#[derive(Clone, Debug, Eq, PartialEq)]
100pub struct Decoder {
101    options: DecodeOptions,
102    pending: Option<u8>,
103    queue: u32,
104    nbits: u8,
105}
106
107impl Decoder {
108    /// Creates a decoder with the requested policy.
109    pub const fn new(options: DecodeOptions) -> Self {
110        Self {
111            options,
112            pending: None,
113            queue: 0,
114            nbits: 0,
115        }
116    }
117
118    /// Decodes a chunk into caller-provided storage.
119    ///
120    /// Capacity is checked first using a content-independent upper bound; on
121    /// [`OutputTooSmall`](DecodeError::OutputTooSmall) neither the decoder state
122    /// nor the output is changed. In strict mode the first byte outside the
123    /// alphabet stops decoding with [`InvalidByte`](DecodeError::InvalidByte),
124    /// whose `offset` is the 0-based index of that byte within this `input`; the
125    /// decoder state is left unchanged, but bytes already written to `output`
126    /// are unspecified and must be discarded.
127    pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
128        let required = self.update_capacity(input.len());
129        if output.len() < required {
130            return Err(DecodeError::OutputTooSmall(OutputTooSmall::new(required)));
131        }
132
133        if self.options.reject_non_alphabet {
134            self.update_strict(input, output)
135        } else {
136            Ok(self.update_lenient(input, output))
137        }
138    }
139
140    // Fast path: lenient never needs the byte offset, so drop the enumerate/early-exit
141    // that constrains the loop. u32 sentinel (u32::MAX = no pending) + u32 nbits in
142    // locals; the Decoder struct stays Option<u8>/u8 (O(1) convert in/out per call).
143    fn update_lenient(&mut self, input: &[u8], output: &mut [u8]) -> usize {
144        let mut queue = self.queue;
145        let mut nbits = u32::from(self.nbits);
146        let mut val = match self.pending {
147            Some(v) => u32::from(v),
148            None => u32::MAX,
149        };
150        let mut written = 0;
151
152        let emit_pair = |low: u32, high: u32, queue: &mut u32, nbits: &mut u32, out: &mut [u8]| {
153            let combined = low + high * 91;
154            *queue |= combined << *nbits;
155            *nbits += if combined & 8191 > 88 { 13 } else { 14 };
156            out[0] = *queue as u8;
157            *queue >>= 8;
158            *nbits -= 8;
159            if *nbits >= 8 {
160                out[1] = *queue as u8;
161                *queue >>= 8;
162                *nbits -= 8;
163                2
164            } else {
165                1
166            }
167        };
168
169        let len = input.len();
170        let mut i = 0;
171        while i < len {
172            // Block fast path: taken whenever no symbol is pending and there is a
173            // full 8-byte input group with 8 bytes of output headroom. Because this
174            // is re-checked every iteration, the block re-engages on the very next
175            // iteration once a pending symbol has been consumed by the scalar step.
176            // DECODE_FF's 0xFF sentinel sets bit 7, so one OR-reduction plus a
177            // `& 0x80` test rejects any non-alphabet byte in the block.
178            if val == u32::MAX && i + 8 <= len && written + 8 <= output.len() {
179                let block = &input[i..i + 8];
180                let d0 = DECODE_FF[usize::from(block[0])];
181                let d1 = DECODE_FF[usize::from(block[1])];
182                let d2 = DECODE_FF[usize::from(block[2])];
183                let d3 = DECODE_FF[usize::from(block[3])];
184                let d4 = DECODE_FF[usize::from(block[4])];
185                let d5 = DECODE_FF[usize::from(block[5])];
186                let d6 = DECODE_FF[usize::from(block[6])];
187                let d7 = DECODE_FF[usize::from(block[7])];
188                if (d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7) & 0x80 == 0 {
189                    let out = &mut output[written..written + 8];
190                    let mut w = 0;
191                    w += emit_pair(
192                        u32::from(d0),
193                        u32::from(d1),
194                        &mut queue,
195                        &mut nbits,
196                        &mut out[w..],
197                    );
198                    w += emit_pair(
199                        u32::from(d2),
200                        u32::from(d3),
201                        &mut queue,
202                        &mut nbits,
203                        &mut out[w..],
204                    );
205                    w += emit_pair(
206                        u32::from(d4),
207                        u32::from(d5),
208                        &mut queue,
209                        &mut nbits,
210                        &mut out[w..],
211                    );
212                    w += emit_pair(
213                        u32::from(d6),
214                        u32::from(d7),
215                        &mut queue,
216                        &mut nbits,
217                        &mut out[w..],
218                    );
219                    written += w;
220                    i += 8;
221                    continue;
222                }
223                // A non-alphabet byte is in this group; fall through to one scalar step.
224            }
225
226            // Scalar step: exactly one input byte, then re-evaluate the block above.
227            let d = DECODE_FF[usize::from(input[i])];
228            i += 1;
229            if d == 0xFF {
230                continue;
231            }
232            let d = u32::from(d);
233            if val == u32::MAX {
234                val = d;
235                continue;
236            }
237            written += emit_pair(val, d, &mut queue, &mut nbits, &mut output[written..]);
238            val = u32::MAX;
239        }
240        self.queue = queue;
241        self.nbits = nbits as u8;
242        self.pending = if val == u32::MAX {
243            None
244        } else {
245            Some(val as u8)
246        };
247        written
248    }
249
250    // Strict path: identical behaviour to the old strict branch (offset + InvalidByte
251    // + transactional state). Since reject_non_alphabet is known true here, any
252    // non-alphabet byte errors (the old `continue` was unreachable in strict mode).
253    fn update_strict(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
254        let mut working = self.clone();
255        let mut written = 0;
256        for (offset, &byte) in input.iter().enumerate() {
257            let value = DECODE_TABLE[usize::from(byte)];
258            if value == 91 {
259                return Err(DecodeError::InvalidByte { byte, offset });
260            }
261            if let Some(first) = working.pending.take() {
262                let combined = u32::from(first) + u32::from(value) * 91;
263                working.queue |= combined << working.nbits;
264                working.nbits += if combined & 8191 > 88 { 13 } else { 14 };
265                while working.nbits > 7 {
266                    output[written] = working.queue as u8;
267                    written += 1;
268                    working.queue >>= 8;
269                    working.nbits -= 8;
270                }
271            } else {
272                working.pending = Some(value);
273            }
274        }
275        *self = working;
276        Ok(written)
277    }
278
279    /// Returns the final decoded byte, if an unpaired symbol remains.
280    pub fn finish(self) -> Result<Option<u8>, DecodeError> {
281        Ok(self
282            .pending
283            .map(|first| (self.queue | (u32::from(first) << self.nbits)) as u8))
284    }
285
286    pub(crate) fn decode_into(
287        mut self,
288        input: &[u8],
289        output: &mut [u8],
290    ) -> Result<usize, DecodeError> {
291        let required = one_shot_capacity(input.len());
292        if output.len() < required {
293            return Err(DecodeError::OutputTooSmall(OutputTooSmall::new(required)));
294        }
295
296        let written = self.update(input, output)?;
297        if let Some(byte) = self.finish()? {
298            output[written] = byte;
299            Ok(written + 1)
300        } else {
301            Ok(written)
302        }
303    }
304
305    fn update_capacity(&self, input_len: usize) -> usize {
306        let pairs = input_len / 2
307            + if self.pending.is_some() {
308                input_len % 2
309            } else {
310                0
311            };
312        let prefix = (pairs / 4).saturating_mul(7);
313        let remaining_bits = usize::from(self.nbits) + (pairs % 4) * 14;
314        prefix.saturating_add(remaining_bits / 8)
315    }
316}
317
318const fn one_shot_capacity(input_len: usize) -> usize {
319    let pairs = input_len / 2;
320    let prefix = (pairs / 4).saturating_mul(7);
321    let remaining_bytes = ((pairs % 4) * 14) / 8;
322    prefix
323        .saturating_add(remaining_bytes)
324        .saturating_add(input_len % 2)
325}
326
327#[cfg(feature = "alloc")]
328/// Decodes standard basE91 from `input` into a newly allocated byte vector.
329///
330/// This function is available when the `alloc` feature is enabled.
331///
332/// # Errors
333///
334/// Returns [`DecodeError::InvalidByte`] when strict decoding is enabled and
335/// `input` contains a byte outside the basE91 alphabet. Returns
336/// [`DecodeError::AllocationFailed`] if the output-length upper bound does not
337/// fit in `usize`, exceeds `isize::MAX`, or allocation fails.
338pub fn decode(input: &[u8], options: DecodeOptions) -> Result<alloc::vec::Vec<u8>, DecodeError> {
339    let capacity = max_decoded_len(input.len()).ok_or(DecodeError::AllocationFailed)?;
340    if capacity > isize::MAX as usize {
341        return Err(DecodeError::AllocationFailed);
342    }
343
344    let mut output = alloc::vec::Vec::new();
345    output
346        .try_reserve_exact(capacity)
347        .map_err(|_| DecodeError::AllocationFailed)?;
348    output.resize(capacity, 0);
349    let written = Decoder::new(options).decode_into(input, &mut output)?;
350    output.truncate(written);
351    Ok(output)
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357
358    #[test]
359    fn all_abstract_state_classes_are_reachable_and_shifts_fit_u32() {
360        let mut reachable = [[false; 8]; 2];
361        reachable[0][0] = true;
362
363        for _ in 0..32 {
364            let mut next = reachable;
365            for nbits in 0..=7 {
366                if reachable[0][nbits] {
367                    next[1][nbits] = true;
368                }
369                if reachable[1][nbits] {
370                    next[0][(nbits + 13) % 8] = true;
371                    next[0][(nbits + 14) % 8] = true;
372                }
373            }
374            reachable = next;
375        }
376
377        assert!(reachable.into_iter().flatten().all(|state| state));
378        for nbits in 0..=7 {
379            let shifted = 8280_u32 << nbits;
380            assert!(shifted < (1_u32 << 21));
381        }
382    }
383
384    #[test]
385    fn update_capacity_matches_wide_formula() {
386        for pending in [None, Some(0)] {
387            for nbits in 0..=7 {
388                let decoder = Decoder {
389                    options: DecodeOptions::new(),
390                    pending,
391                    queue: 0,
392                    nbits,
393                };
394                for input_len in [0, 1, 2, 7, 8, 9, usize::MAX] {
395                    let pairs = input_len as u128 / 2
396                        + u128::from(pending.is_some()) * (input_len as u128 % 2);
397                    let wide = (u128::from(nbits) + 14 * pairs) / 8;
398                    let expected = usize::try_from(wide).unwrap_or(usize::MAX);
399                    assert_eq!(decoder.update_capacity(input_len), expected);
400                }
401            }
402        }
403    }
404}