1use crate::error::{Error, ErrorCode};
21
22const BASE38_CHARS: [char; 38] = [
23 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
24 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.',
25];
26
27const UNUSED: u8 = 255;
28
29const DECODE_BASE38: [u8; 46] = [
32 36, 37, UNUSED, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, ];
79
80const RADIX: u32 = BASE38_CHARS.len() as u32;
81
82pub fn encode_string<const N: usize>(bytes: &[u8]) -> Result<heapless::String<N>, Error> {
87 let mut string = heapless::String::new();
88 for c in encode(bytes) {
89 string.push(c).map_err(|_| ErrorCode::BufferTooSmall)?;
90 }
91
92 Ok(string)
93}
94
95pub fn encode(bytes: &[u8]) -> impl Iterator<Item = char> + '_ {
96 (0..bytes.len() / 3)
97 .flat_map(move |index| {
98 let offset = index * 3;
99
100 encode_base38(
101 ((bytes[offset + 2] as u32) << 16)
102 | ((bytes[offset + 1] as u32) << 8)
103 | (bytes[offset] as u32),
104 5,
105 )
106 })
107 .chain(
108 core::iter::once(bytes.len() % 3).flat_map(move |remainder| {
109 let offset = bytes.len() / 3 * 3;
110
111 match remainder {
112 2 => encode_base38(
113 ((bytes[offset + 1] as u32) << 8) | (bytes[offset] as u32),
114 4,
115 ),
116 1 => encode_base38(bytes[offset] as u32, 2),
117 _ => encode_base38(0, 0),
118 }
119 }),
120 )
121}
122
123pub fn encode_bits(bits: u32, bits_count: u8) -> impl Iterator<Item = char> {
124 assert!(bits_count <= 24);
125
126 let repeat = match bits_count / 8 {
127 3 => 5,
128 2 => 4,
129 1 => 2,
130 _ => unreachable!(),
131 };
132
133 encode_base38(bits, repeat)
134}
135
136fn encode_base38(mut value: u32, repeat: usize) -> impl Iterator<Item = char> {
137 (0..repeat).map(move |_| {
138 let remainder = value % RADIX;
139 let c = BASE38_CHARS[remainder as usize];
140
141 value = (value - remainder) / RADIX;
142
143 c
144 })
145}
146
147pub fn decode_vec<const N: usize>(base38_str: &str) -> Result<heapless::Vec<u8, N>, Error> {
148 let mut vec = heapless::Vec::new();
149
150 for byte in decode(base38_str) {
151 vec.push(byte?).map_err(|_| ErrorCode::BufferTooSmall)?;
152 }
153
154 Ok(vec)
155}
156
157pub fn decode(base38_str: &str) -> impl Iterator<Item = Result<u8, Error>> + '_ {
164 let stru = base38_str.as_bytes();
165
166 (0..stru.len() / 5)
167 .flat_map(move |index| {
168 let offset = index * 5;
169 decode_base38(&stru[offset..offset + 5])
170 })
171 .chain({
172 let offset = stru.len() / 5 * 5;
173 decode_base38(&stru[offset..])
174 })
175 .take_while(Result::is_ok)
176}
177
178fn decode_base38(chars: &[u8]) -> impl Iterator<Item = Result<u8, Error>> {
179 let mut value = 0u32;
180 let mut cerr = None;
181
182 let repeat = match chars.len() {
183 5 => 3,
184 4 => 2,
185 2 => 1,
186 0 => 0,
187 _ => -1,
188 };
189
190 if repeat >= 0 {
191 for c in chars.iter().rev() {
192 match decode_char(*c) {
193 Ok(v) => value = value * RADIX + v as u32,
194 Err(err) => {
195 cerr = Some(err.code());
196 break;
197 }
198 }
199 }
200 } else {
201 cerr = Some(ErrorCode::InvalidData)
202 }
203
204 (0..repeat)
205 .map(move |_| {
206 if let Some(err) = cerr {
207 Err(err.into())
208 } else {
209 let byte = (value & 0xff) as u8;
210
211 value >>= 8;
212
213 Ok(byte)
214 }
215 })
216 .take_while(Result::is_ok)
217}
218
219fn decode_char(c: u8) -> Result<u8, Error> {
220 if !(45..=90).contains(&c) {
221 Err(ErrorCode::InvalidData)?;
222 }
223
224 let c = DECODE_BASE38[c as usize - 45];
225 if c == UNUSED {
226 Err(ErrorCode::InvalidData)?;
227 }
228
229 Ok(c)
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 const ENCODED: &str = "-MOA57ZU02IT2L2BJ00";
236 const DECODED: [u8; 11] = [
237 0x88, 0xff, 0xa7, 0x91, 0x50, 0x40, 0x00, 0x47, 0x51, 0xdd, 0x02,
238 ];
239
240 #[test]
241 fn can_base38_encode() {
242 assert_eq!(
243 unwrap!(encode_string::<{ ENCODED.len() }>(&DECODED)),
244 ENCODED
245 );
246 }
247
248 #[test]
249 fn can_base38_decode() {
250 assert_eq!(
251 unwrap!(
252 decode_vec::<{ DECODED.len() }>(ENCODED),
253 "Cannot decode base38"
254 ),
255 DECODED
256 );
257 }
258}