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> {
204 let capacity = max_encoded_len(input.len()).ok_or(EncodeError::AllocationFailed)?;
205 if capacity > isize::MAX as usize {
206 return Err(EncodeError::AllocationFailed);
207 }
208
209 let mut output = alloc::vec::Vec::new();
210 output
211 .try_reserve_exact(capacity)
212 .map_err(|_| EncodeError::AllocationFailed)?;
213 output.resize(capacity, 0);
214 let written = Encoder::new()
215 .encode_into(input, &mut output)
216 .map_err(EncodeError::OutputTooSmall)?;
217 output.truncate(written);
218 Ok(output)
219}
220
221#[non_exhaustive]
223#[derive(Clone, Copy, Debug, Eq, PartialEq)]
224pub struct OutputTooSmall {
225 required: usize,
226}
227
228impl OutputTooSmall {
229 pub(crate) const fn new(required: usize) -> Self {
230 Self { required }
231 }
232
233 pub const fn required(self) -> usize {
235 self.required
236 }
237}
238
239impl fmt::Display for OutputTooSmall {
240 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
241 write!(
242 formatter,
243 "output buffer is too small; need {} bytes",
244 self.required
245 )
246 }
247}
248
249impl core::error::Error for OutputTooSmall {}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn update_capacity_matches_wide_formula_and_saturates() {
257 for nbits in [0, 7, 13] {
258 let encoder = Encoder { queue: 0, nbits };
259 for input_len in [0, 1, 12, 13, 14, usize::MAX] {
260 let wide = 2 * ((u128::from(nbits) + 8 * input_len as u128) / 13);
261 let expected = usize::try_from(wide).unwrap_or(usize::MAX);
262 assert_eq!(encoder.update_capacity(input_len), expected);
263 }
264 }
265 }
266
267 #[test]
268 fn allocation_limit_is_distinct_from_math_overflow() {
269 let bound = max_encoded_len(isize::MAX as usize).expect("bound fits usize");
270 assert!(bound > isize::MAX as usize);
271 assert_eq!(max_encoded_len(usize::MAX), None);
272 }
273}