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 working = self.clone();
91        let mut written = 0;
92        for &byte in input {
93            if let Some(value) = working.push(byte) {
94                output[written] = ALPHABET[value % 91];
95                output[written + 1] = ALPHABET[value / 91];
96                written += 2;
97            }
98        }
99        *self = working;
100        Ok(written)
101    }
102
103    /// Returns the final encoded tail and its length.
104    #[must_use]
105    pub fn finish(self) -> ([u8; 2], usize) {
106        let mut tail = [0; 2];
107        if self.nbits == 0 {
108            return (tail, 0);
109        }
110
111        tail[0] = ALPHABET[self.queue as usize % 91];
112        if self.nbits > 7 || self.queue > 90 {
113            tail[1] = ALPHABET[self.queue as usize / 91];
114            (tail, 2)
115        } else {
116            (tail, 1)
117        }
118    }
119
120    fn push(&mut self, byte: u8) -> Option<usize> {
121        self.queue |= u32::from(byte) << self.nbits;
122        self.nbits += 8;
123        if self.nbits <= 13 {
124            return None;
125        }
126
127        let mut value = self.queue & 8191;
128        let consumed = if value > 88 {
129            13
130        } else {
131            value = self.queue & 16383;
132            14
133        };
134        self.queue >>= consumed;
135        self.nbits -= consumed;
136        Some(value as usize)
137    }
138
139    fn update_capacity(&self, input_len: usize) -> usize {
140        // This is 2 * floor((nbits + 8 * input_len) / 13), split at
141        // 13-byte boundaries so that 8 * input_len is never formed.
142        let quotient = input_len / 13;
143        let remainder = input_len % 13;
144        let prefix = quotient.saturating_mul(16);
145        let remaining_bits = usize::from(self.nbits) + remainder * 8;
146        prefix.saturating_add((remaining_bits / 13) * 2)
147    }
148
149    pub(crate) fn encode_into(
150        mut self,
151        input: &[u8],
152        output: &mut [u8],
153    ) -> Result<usize, OutputTooSmall> {
154        let tail_capacity = if self.nbits == 0 && input.is_empty() {
155            0
156        } else {
157            2
158        };
159        let required = self
160            .update_capacity(input.len())
161            .saturating_add(tail_capacity);
162        if output.len() < required {
163            return Err(OutputTooSmall::new(required));
164        }
165
166        let written = self.update(input, output)?;
167        let (tail, tail_len) = self.finish();
168        let end = written + tail_len;
169        output[written..end].copy_from_slice(&tail[..tail_len]);
170        Ok(end)
171    }
172}
173
174#[cfg(feature = "alloc")]
175pub fn encode(input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
176    let capacity = max_encoded_len(input.len()).ok_or(EncodeError::AllocationFailed)?;
177    if capacity > isize::MAX as usize {
178        return Err(EncodeError::AllocationFailed);
179    }
180
181    let mut output = alloc::vec::Vec::new();
182    output
183        .try_reserve_exact(capacity)
184        .map_err(|_| EncodeError::AllocationFailed)?;
185    output.resize(capacity, 0);
186    let written = Encoder::new()
187        .encode_into(input, &mut output)
188        .map_err(EncodeError::OutputTooSmall)?;
189    output.truncate(written);
190    Ok(output)
191}
192
193/// A caller-visible output capacity error.
194#[non_exhaustive]
195#[derive(Clone, Copy, Debug, Eq, PartialEq)]
196pub struct OutputTooSmall {
197    required: usize,
198}
199
200impl OutputTooSmall {
201    pub(crate) const fn new(required: usize) -> Self {
202        Self { required }
203    }
204
205    /// Returns the capacity required by the attempted operation.
206    pub const fn required(self) -> usize {
207        self.required
208    }
209}
210
211impl fmt::Display for OutputTooSmall {
212    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
213        write!(
214            formatter,
215            "output buffer is too small; need {} bytes",
216            self.required
217        )
218    }
219}
220
221impl core::error::Error for OutputTooSmall {}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn update_capacity_matches_wide_formula_and_saturates() {
229        for nbits in [0, 7, 13] {
230            let encoder = Encoder { queue: 0, nbits };
231            for input_len in [0, 1, 12, 13, 14, usize::MAX] {
232                let wide = 2 * ((u128::from(nbits) + 8 * input_len as u128) / 13);
233                let expected = usize::try_from(wide).unwrap_or(usize::MAX);
234                assert_eq!(encoder.update_capacity(input_len), expected);
235            }
236        }
237    }
238
239    #[test]
240    fn allocation_limit_is_distinct_from_math_overflow() {
241        let bound = max_encoded_len(isize::MAX as usize).expect("bound fits usize");
242        assert!(bound > isize::MAX as usize);
243        assert_eq!(max_encoded_len(usize::MAX), None);
244    }
245}