fastbase91_core/
encode.rs1use core::fmt;
2
3use crate::tables::{ALPHABET, ENCODE_PAIRS};
4
5pub 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#[non_exhaustive]
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum EncodeError {
31 OutputTooSmall(OutputTooSmall),
33 #[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#[derive(Clone, Debug, Default, Eq, PartialEq)]
68pub struct Encoder {
69 queue: u32,
70 nbits: u8,
71}
72
73impl Encoder {
74 pub const fn new() -> Self {
76 Self { queue: 0, nbits: 0 }
77 }
78
79 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 = u64::from(self.queue);
93 let mut nbits = u32::from(self.nbits);
94 let mut written = 0;
95
96 macro_rules! emit_pair {
97 ($out:expr) => {{
98 let low13 = (queue & 8191) as usize;
99 if low13 > 88 {
100 queue >>= 13;
101 nbits -= 13;
102 $out.copy_from_slice(&ENCODE_PAIRS[low13]);
103 } else {
104 let value = (queue & 16383) as usize;
105 queue >>= 14;
106 nbits -= 14;
107 $out.copy_from_slice(&ENCODE_PAIRS[value]);
108 }
109 }};
110 }
111
112 let mut pos = 0;
119 while pos + 8 <= input.len() {
120 let word =
121 u64::from_le_bytes(input[pos..pos + 8].try_into().expect("slice of length 8"));
122 let whole_bytes = (64 - nbits) >> 3;
123 let whole_bits = whole_bytes << 3;
124 queue |= (word & (u64::MAX >> (64 - whole_bits))) << nbits;
125 nbits += whole_bits;
126 pos += whole_bytes as usize;
127 let out = &mut output[written..written + 8];
128 emit_pair!(&mut out[0..2]);
129 emit_pair!(&mut out[2..4]);
130 emit_pair!(&mut out[4..6]);
131 emit_pair!(&mut out[6..8]);
132 written += 8;
133 }
134
135 for &byte in &input[pos..] {
136 queue |= u64::from(byte) << nbits;
137 nbits += 8;
138 if nbits <= 13 {
139 continue;
140 }
141 emit_pair!(&mut output[written..written + 2]);
142 written += 2;
143 }
144 self.queue = queue as u32;
146 self.nbits = nbits as u8;
147 Ok(written)
148 }
149
150 #[must_use]
152 pub fn finish(self) -> ([u8; 2], usize) {
153 let mut tail = [0; 2];
154 if self.nbits == 0 {
155 return (tail, 0);
156 }
157
158 tail[0] = ALPHABET[self.queue as usize % 91];
159 if self.nbits > 7 || self.queue > 90 {
160 tail[1] = ALPHABET[self.queue as usize / 91];
161 (tail, 2)
162 } else {
163 (tail, 1)
164 }
165 }
166
167 fn update_capacity(&self, input_len: usize) -> usize {
168 let quotient = input_len / 13;
171 let remainder = input_len % 13;
172 let prefix = quotient.saturating_mul(16);
173 let remaining_bits = usize::from(self.nbits) + remainder * 8;
174 prefix.saturating_add((remaining_bits / 13) * 2)
175 }
176
177 pub(crate) fn encode_into(
178 mut self,
179 input: &[u8],
180 output: &mut [u8],
181 ) -> Result<usize, OutputTooSmall> {
182 let tail_capacity = if self.nbits == 0 && input.is_empty() {
183 0
184 } else {
185 2
186 };
187 let required = self
188 .update_capacity(input.len())
189 .saturating_add(tail_capacity);
190 if output.len() < required {
191 return Err(OutputTooSmall::new(required));
192 }
193
194 let written = self.update(input, output)?;
195 let (tail, tail_len) = self.finish();
196 let end = written + tail_len;
197 output[written..end].copy_from_slice(&tail[..tail_len]);
198 Ok(end)
199 }
200}
201
202#[cfg(feature = "alloc")]
203pub fn encode(input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
212 let capacity = max_encoded_len(input.len()).ok_or(EncodeError::AllocationFailed)?;
213 if capacity > isize::MAX as usize {
214 return Err(EncodeError::AllocationFailed);
215 }
216
217 let mut output = alloc::vec::Vec::new();
218 output
219 .try_reserve_exact(capacity)
220 .map_err(|_| EncodeError::AllocationFailed)?;
221 output.resize(capacity, 0);
222 let written = Encoder::new()
223 .encode_into(input, &mut output)
224 .map_err(EncodeError::OutputTooSmall)?;
225 output.truncate(written);
226 Ok(output)
227}
228
229#[non_exhaustive]
231#[derive(Clone, Copy, Debug, Eq, PartialEq)]
232pub struct OutputTooSmall {
233 required: usize,
234}
235
236impl OutputTooSmall {
237 pub(crate) const fn new(required: usize) -> Self {
238 Self { required }
239 }
240
241 pub const fn required(self) -> usize {
243 self.required
244 }
245}
246
247impl fmt::Display for OutputTooSmall {
248 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
249 write!(
250 formatter,
251 "output buffer is too small; need {} bytes",
252 self.required
253 )
254 }
255}
256
257impl core::error::Error for OutputTooSmall {}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn update_capacity_matches_wide_formula_and_saturates() {
265 for nbits in [0, 7, 13] {
266 let encoder = Encoder { queue: 0, nbits };
267 for input_len in [0, 1, 12, 13, 14, usize::MAX] {
268 let wide = 2 * ((u128::from(nbits) + 8 * input_len as u128) / 13);
269 let expected = usize::try_from(wide).unwrap_or(usize::MAX);
270 assert_eq!(encoder.update_capacity(input_len), expected);
271 }
272 }
273 }
274
275 #[test]
276 fn allocation_limit_is_distinct_from_math_overflow() {
277 let bound = max_encoded_len(isize::MAX as usize).expect("bound fits usize");
278 assert!(bound > isize::MAX as usize);
279 assert_eq!(max_encoded_len(usize::MAX), None);
280 }
281}