Skip to main content

fastbase91_core/
encode.rs

1use core::fmt;
2
3use crate::tables::ALPHABET;
4
5/// Returns an encoded-length upper bound for `input_len` bytes.
6///
7/// The bound holds for every reachable encoder state and includes the up-to-two
8/// byte tail returned by [`Encoder::finish`]. Mathematically it is
9/// `2 * ceil((8 * input_len + 13) / 13)`. The quotient/remainder form below
10/// avoids overflowing while forming `8 * input_len`.
11///
12/// `None` means that the mathematical bound does not fit in `usize`. A `Some`
13/// result can still exceed `isize::MAX`, so allocating APIs perform that
14/// separate platform-capacity check.
15pub const fn max_encoded_len(input_len: usize) -> Option<usize> {
16    let quotient = input_len / 13;
17    let remainder = input_len % 13;
18    let prefix = match quotient.checked_mul(16) {
19        Some(value) => value,
20        None => return None,
21    };
22    let remaining_bits = remainder * 8 + 13;
23    let remaining_groups = remaining_bits.div_ceil(13);
24    prefix.checked_add(remaining_groups * 2)
25}
26
27/// Error returned by the fallible encoding APIs.
28#[non_exhaustive]
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum EncodeError {
31    /// The caller-provided output storage cannot hold the result.
32    OutputTooSmall(OutputTooSmall),
33    /// The requested output is not allocatable or allocation failed.
34    #[cfg(feature = "alloc")]
35    AllocationFailed,
36}
37
38impl fmt::Display for EncodeError {
39    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            Self::OutputTooSmall(error) => error.fmt(formatter),
42            #[cfg(feature = "alloc")]
43            Self::AllocationFailed => formatter.write_str("encoding allocation failed"),
44        }
45    }
46}
47
48impl core::error::Error for EncodeError {}
49
50/// Stateful standard basE91 encoder.
51///
52/// A call to [`finish`](Self::finish) ends the current message and consumes the
53/// encoder, so the same stream cannot be continued afterwards. Separately
54/// finished messages must not be concatenated and treated as one basE91 stream.
55///
56/// The consuming contract is enforced by the type system: the encoder is not
57/// `Copy`, so reusing it after `finish` fails to compile.
58///
59/// ```compile_fail
60/// use fastbase91_core::Encoder;
61/// let mut encoder = Encoder::new();
62/// let mut output = [0_u8; 4];
63/// let _tail = encoder.finish();
64/// // `finish` consumed the encoder; encoding more must not compile.
65/// let _ = encoder.update(b"x", &mut output);
66/// ```
67#[derive(Clone, Debug, Default, Eq, PartialEq)]
68pub struct Encoder {
69    queue: u32,
70    nbits: u8,
71}
72
73impl Encoder {
74    /// Creates an empty encoder.
75    pub const fn new() -> Self {
76        Self { queue: 0, nbits: 0 }
77    }
78
79    /// Encodes a chunk into caller-provided storage.
80    ///
81    /// If the output is too small, neither the encoder state nor the output
82    /// slice is changed. Capacity is checked using a content-independent upper
83    /// bound derived from the current bit count and the input length.
84    pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, OutputTooSmall> {
85        let required = self.update_capacity(input.len());
86        if output.len() < required {
87            return Err(OutputTooSmall::new(required));
88        }
89
90        let mut queue = self.queue;
91        let mut nbits = self.nbits;
92        let mut written = 0;
93        for &byte in input {
94            queue |= u32::from(byte) << nbits;
95            nbits += 8;
96            if nbits <= 13 {
97                continue;
98            }
99            let low13 = queue & 8191;
100            if low13 > 88 {
101                let value = low13 as usize;
102                queue >>= 13;
103                nbits -= 13;
104                output[written] = ALPHABET[value % 91];
105                output[written + 1] = ALPHABET[value / 91];
106                written += 2;
107            } else {
108                let value = (queue & 16383) as usize;
109                queue >>= 14;
110                nbits -= 14;
111                output[written] = ALPHABET[value % 91];
112                output[written + 1] = ALPHABET[value / 91];
113                written += 2;
114            }
115        }
116        self.queue = queue;
117        self.nbits = nbits;
118        Ok(written)
119    }
120
121    /// Returns the final encoded tail and its length.
122    #[must_use]
123    pub fn finish(self) -> ([u8; 2], usize) {
124        let mut tail = [0; 2];
125        if self.nbits == 0 {
126            return (tail, 0);
127        }
128
129        tail[0] = ALPHABET[self.queue as usize % 91];
130        if self.nbits > 7 || self.queue > 90 {
131            tail[1] = ALPHABET[self.queue as usize / 91];
132            (tail, 2)
133        } else {
134            (tail, 1)
135        }
136    }
137
138    fn update_capacity(&self, input_len: usize) -> usize {
139        // This is 2 * floor((nbits + 8 * input_len) / 13), split at
140        // 13-byte boundaries so that 8 * input_len is never formed.
141        let quotient = input_len / 13;
142        let remainder = input_len % 13;
143        let prefix = quotient.saturating_mul(16);
144        let remaining_bits = usize::from(self.nbits) + remainder * 8;
145        prefix.saturating_add((remaining_bits / 13) * 2)
146    }
147
148    pub(crate) fn encode_into(
149        mut self,
150        input: &[u8],
151        output: &mut [u8],
152    ) -> Result<usize, OutputTooSmall> {
153        let tail_capacity = if self.nbits == 0 && input.is_empty() {
154            0
155        } else {
156            2
157        };
158        let required = self
159            .update_capacity(input.len())
160            .saturating_add(tail_capacity);
161        if output.len() < required {
162            return Err(OutputTooSmall::new(required));
163        }
164
165        let written = self.update(input, output)?;
166        let (tail, tail_len) = self.finish();
167        let end = written + tail_len;
168        output[written..end].copy_from_slice(&tail[..tail_len]);
169        Ok(end)
170    }
171}
172
173#[cfg(feature = "alloc")]
174pub fn encode(input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
175    let capacity = max_encoded_len(input.len()).ok_or(EncodeError::AllocationFailed)?;
176    if capacity > isize::MAX as usize {
177        return Err(EncodeError::AllocationFailed);
178    }
179
180    let mut output = alloc::vec::Vec::new();
181    output
182        .try_reserve_exact(capacity)
183        .map_err(|_| EncodeError::AllocationFailed)?;
184    output.resize(capacity, 0);
185    let written = Encoder::new()
186        .encode_into(input, &mut output)
187        .map_err(EncodeError::OutputTooSmall)?;
188    output.truncate(written);
189    Ok(output)
190}
191
192/// A caller-visible output capacity error.
193#[non_exhaustive]
194#[derive(Clone, Copy, Debug, Eq, PartialEq)]
195pub struct OutputTooSmall {
196    required: usize,
197}
198
199impl OutputTooSmall {
200    pub(crate) const fn new(required: usize) -> Self {
201        Self { required }
202    }
203
204    /// Returns the capacity required by the attempted operation.
205    pub const fn required(self) -> usize {
206        self.required
207    }
208}
209
210impl fmt::Display for OutputTooSmall {
211    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
212        write!(
213            formatter,
214            "output buffer is too small; need {} bytes",
215            self.required
216        )
217    }
218}
219
220impl core::error::Error for OutputTooSmall {}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn update_capacity_matches_wide_formula_and_saturates() {
228        for nbits in [0, 7, 13] {
229            let encoder = Encoder { queue: 0, nbits };
230            for input_len in [0, 1, 12, 13, 14, usize::MAX] {
231                let wide = 2 * ((u128::from(nbits) + 8 * input_len as u128) / 13);
232                let expected = usize::try_from(wide).unwrap_or(usize::MAX);
233                assert_eq!(encoder.update_capacity(input_len), expected);
234            }
235        }
236    }
237
238    #[test]
239    fn allocation_limit_is_distinct_from_math_overflow() {
240        let bound = max_encoded_len(isize::MAX as usize).expect("bound fits usize");
241        assert!(bound > isize::MAX as usize);
242        assert_eq!(max_encoded_len(usize::MAX), None);
243    }
244}