1#![cfg_attr(not(any(feature = "std", test)), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4#[cfg(any(feature = "alloc", test))]
5extern crate alloc;
6
7#[cfg(all(feature = "serde", any(feature = "alloc", test)))]
8mod serde;
9
10#[cfg(target_arch = "aarch64")]
11mod base32_neon;
12
13#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
14mod base32_avx2;
15
16const PAD: u8 = b'=';
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Alphabet {
20 Crockford,
21 Rfc4648,
22 Rfc4648NoPadding,
23 Rfc4648Lower,
24 Rfc4648LowerNoPadding,
25 Rfc4648Hex,
26 Rfc4648HexNoPadding,
27 Rfc4648HexLower,
28 Rfc4648HexLowerNoPadding,
29}
30
31impl Alphabet {
32 #[inline]
33 const fn is_padded(&self) -> bool {
34 match self {
35 Alphabet::Crockford => false,
36 Alphabet::Rfc4648 => true,
37 Alphabet::Rfc4648NoPadding => false,
38 Alphabet::Rfc4648Lower => true,
39 Alphabet::Rfc4648LowerNoPadding => false,
40 Alphabet::Rfc4648Hex => true,
41 Alphabet::Rfc4648HexNoPadding => false,
42 Alphabet::Rfc4648HexLower => true,
43 Alphabet::Rfc4648HexLowerNoPadding => false,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum EncodeError {
50 InvalidOutputLength,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum DecodeError {
55 InvalidInput,
56 InvalidLength,
57 InvalidPadding,
58}
59
60impl core::fmt::Display for EncodeError {
61 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
62 match self {
63 Self::InvalidOutputLength => f.write_str("output buffer size is not valid"),
64 }
65 }
66}
67
68impl core::fmt::Display for DecodeError {
69 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70 match self {
71 Self::InvalidInput => f.write_str("invalid base32 character"),
72 Self::InvalidLength => f.write_str("invalid base32 length"),
73 Self::InvalidPadding => f.write_str("invalid base32 padding"),
74 }
75 }
76}
77
78#[cfg(feature = "std")]
79impl std::error::Error for EncodeError {}
80
81#[cfg(feature = "std")]
82impl std::error::Error for DecodeError {}
83
84pub const fn encoded_length(bytes_len: usize, padding: bool) -> Option<usize> {
85 if bytes_len == 0 {
86 return Some(0);
87 }
88 let complete_chunks = bytes_len / 5;
89 let base = match complete_chunks.checked_mul(8) {
90 Some(v) => v,
91 None => return None,
92 };
93 let rem = bytes_len % 5;
94 if rem == 0 {
95 Some(base)
96 } else if padding {
97 base.checked_add(8)
98 } else {
99 let bits = match bytes_len.checked_mul(8) {
100 Some(v) => v,
101 None => return None,
102 };
103 match bits.checked_add(4) {
104 Some(v) => Some(v / 5),
105 None => None,
106 }
107 }
108}
109
110#[cfg(feature = "alloc")]
115pub fn encode(data: impl AsRef<[u8]>, alphabet: Alphabet) -> alloc::string::String {
116 let data = data.as_ref();
117 let padding = alphabet.is_padded();
118 let len = encoded_length(data.len(), padding).expect("encoded length overflow");
119 let mut output = alloc::vec![0u8; len];
120 encode_into(&mut output, data, alphabet).expect("output buffer sized correctly");
121 unsafe { alloc::string::String::from_utf8_unchecked(output) }
122}
123
124pub const fn encode_array<const OUT: usize>(data: &[u8], alphabet: Alphabet) -> [u8; OUT] {
125 match encoded_length(data.len(), alphabet.is_padded()) {
126 Some(len) if len == OUT => {}
127 _ => panic!("encode_array: output array length is invalid"),
128 }
129 let mut result = [0u8; OUT];
130 match encode_into_constant_time(&mut result, data, alphabet) {
131 Ok(()) => result,
132 Err(_) => panic!("encode_array: output array length is invalid"),
133 }
134}
135
136pub fn encode_into(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
137 let padding = alphabet.is_padded();
138 let expected = encoded_length(data.len(), padding).expect("encoded length overflow");
139 if output.len() < expected {
140 return Err(EncodeError::InvalidOutputLength);
141 }
142
143 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
144 if data.len() >= 40 {
145 return unsafe { base32_neon::encode_into(output, data, alphabet) };
146 }
147
148 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
149 if data.len() >= 40 {
150 return unsafe { base32_avx2::encode_into(output, data, alphabet) };
151 }
152
153 encode_into_constant_time(output, data, alphabet)
154}
155
156pub const fn encode_into_constant_time(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
157 let padding = alphabet.is_padded();
158 let expected = encoded_length(data.len(), padding).expect("encoded length overflow");
159 if output.len() < expected {
160 return Err(EncodeError::InvalidOutputLength);
161 }
162
163 let len = data.len();
164 let mut i = 0;
165
166 while i + 40 <= len {
167 encode_8blocks(output, alphabet, data, i);
168 i += 40;
169 }
170
171 while i + 5 <= len {
172 let b0 = data[i];
173 let b1 = data[i + 1];
174 let b2 = data[i + 2];
175 let b3 = data[i + 3];
176 let b4 = data[i + 4];
177
178 let q0 = b0 >> 3;
179 let q1 = ((b0 & 0x07) << 2) | (b1 >> 6);
180 let q2 = (b1 >> 1) & 0x1F;
181 let q3 = ((b1 & 0x01) << 4) | (b2 >> 4);
182 let q4 = ((b2 & 0x0F) << 1) | (b3 >> 7);
183 let q5 = (b3 >> 2) & 0x1F;
184 let q6 = ((b3 & 0x03) << 3) | (b4 >> 5);
185 let q7 = b4 & 0x1F;
186
187 let o = (i / 5) * 8;
188 output[o] = quintet_to_char(q0, alphabet);
189 output[o + 1] = quintet_to_char(q1, alphabet);
190 output[o + 2] = quintet_to_char(q2, alphabet);
191 output[o + 3] = quintet_to_char(q3, alphabet);
192 output[o + 4] = quintet_to_char(q4, alphabet);
193 output[o + 5] = quintet_to_char(q5, alphabet);
194 output[o + 6] = quintet_to_char(q6, alphabet);
195 output[o + 7] = quintet_to_char(q7, alphabet);
196
197 i += 5;
198 }
199
200 let rem = len - i;
201 if rem > 0 {
202 let o = (i / 5) * 8;
203 let b0 = data[i];
204 let q0 = b0 >> 3;
205 let q1 = (b0 & 0x07) << 2;
206 output[o] = quintet_to_char(q0, alphabet);
207 output[o + 1] = quintet_to_char(q1, alphabet);
208
209 if rem >= 2 {
210 let b1 = data[i + 1];
211 let q1 = ((b0 & 0x07) << 2) | (b1 >> 6);
212 let q2 = (b1 >> 1) & 0x1F;
213 let q3 = (b1 & 0x01) << 4;
214 output[o + 1] = quintet_to_char(q1, alphabet);
215 output[o + 2] = quintet_to_char(q2, alphabet);
216 output[o + 3] = quintet_to_char(q3, alphabet);
217
218 if rem >= 3 {
219 let b2 = data[i + 2];
220 let q3 = ((b1 & 0x01) << 4) | (b2 >> 4);
221 let q4 = (b2 & 0x0F) << 1;
222 output[o + 3] = quintet_to_char(q3, alphabet);
223 output[o + 4] = quintet_to_char(q4, alphabet);
224
225 if rem == 4 {
226 let b3 = data[i + 3];
227 let q4 = ((b2 & 0x0F) << 1) | (b3 >> 7);
228 let q5 = (b3 >> 2) & 0x1F;
229 let q6 = (b3 & 0x03) << 3;
230 output[o + 4] = quintet_to_char(q4, alphabet);
231 output[o + 5] = quintet_to_char(q5, alphabet);
232 output[o + 6] = quintet_to_char(q6, alphabet);
233 }
234 }
235 }
236
237 if padding {
238 let pad_start = match rem {
239 1 => o + 2,
240 2 => o + 4,
241 3 => o + 5,
242 4 => o + 7,
243 _ => unreachable!(),
244 };
245 let pad_end = o + 8;
246 let mut p = pad_start;
247 while p < pad_end {
248 output[p] = PAD;
249 p += 1;
250 }
251 }
252 }
253 Ok(())
254}
255
256#[cfg(feature = "alloc")]
258pub fn encode_into_string(output: &mut alloc::string::String, data: &[u8], alphabet: Alphabet) {
259 let encoded_length = encoded_length(data.len(), alphabet.is_padded()).expect("output length overflow");
260 if encoded_length <= 256 {
261 let mut buf = [0u8; 256];
263 let mut buf = &mut buf[..encoded_length];
264 encode_into(&mut buf, data, alphabet).unwrap();
265 output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
267 } else {
268 let mut buf = alloc::vec![0u8; encoded_length];
269 encode_into(&mut buf, data, alphabet).unwrap();
270 output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
272 }
273}
274
275#[inline]
278const fn not_in_range(v: u8, lo: u8, hi: u8) -> u8 {
279 (((v.wrapping_sub(lo) as i8) | (hi.wrapping_sub(v) as i8)) >> 7) as u8
280}
281
282#[inline]
285const fn check_trailing_bits(value: u8, max_pad: u8) -> u8 {
286 let mask = (1u8 << max_pad).wrapping_sub(1);
287 let pad_bits = value & mask;
288 (!not_in_range(pad_bits, 1, mask)) & 0x20
289}
290
291#[inline]
292const fn encode_8blocks(output: &mut [u8], alphabet: Alphabet, data: &[u8], start: usize) {
293 let mut n = 0;
294 while n < 8 {
295 let i = start + n * 5;
296 let b0 = data[i];
297 let b1 = data[i + 1];
298 let b2 = data[i + 2];
299 let b3 = data[i + 3];
300 let b4 = data[i + 4];
301
302 let q0 = b0 >> 3;
303 let q1 = ((b0 & 0x07) << 2) | (b1 >> 6);
304 let q2 = (b1 >> 1) & 0x1F;
305 let q3 = ((b1 & 0x01) << 4) | (b2 >> 4);
306 let q4 = ((b2 & 0x0F) << 1) | (b3 >> 7);
307 let q5 = (b3 >> 2) & 0x1F;
308 let q6 = ((b3 & 0x03) << 3) | (b4 >> 5);
309 let q7 = b4 & 0x1F;
310
311 let o = (start / 5) * 8 + n * 8;
312 output[o] = quintet_to_char(q0, alphabet);
313 output[o + 1] = quintet_to_char(q1, alphabet);
314 output[o + 2] = quintet_to_char(q2, alphabet);
315 output[o + 3] = quintet_to_char(q3, alphabet);
316 output[o + 4] = quintet_to_char(q4, alphabet);
317 output[o + 5] = quintet_to_char(q5, alphabet);
318 output[o + 6] = quintet_to_char(q6, alphabet);
319 output[o + 7] = quintet_to_char(q7, alphabet);
320
321 n += 1;
322 }
323}
324
325#[inline]
328const fn quintet_to_char(v: u8, alphabet: Alphabet) -> u8 {
329 match alphabet {
330 Alphabet::Crockford => quintet_to_crockford(v),
331 Alphabet::Rfc4648 | Alphabet::Rfc4648NoPadding => {
332 let not_upper = not_in_range(v, 0, 25);
333 let not_digit = not_in_range(v, 26, 31);
334 (v + b'A') & !not_upper | (v.wrapping_sub(26).wrapping_add(b'2')) & !not_digit
335 }
336 Alphabet::Rfc4648Lower | Alphabet::Rfc4648LowerNoPadding => {
337 let not_lower = not_in_range(v, 0, 25);
338 let not_digit = not_in_range(v, 26, 31);
339 (v + b'a') & !not_lower | (v.wrapping_sub(26).wrapping_add(b'2')) & !not_digit
340 }
341 Alphabet::Rfc4648Hex | Alphabet::Rfc4648HexNoPadding => {
342 let not_digit = not_in_range(v, 0, 9);
343 let not_upper = not_in_range(v, 10, 31);
344 (v + b'0') & !not_digit | (v.wrapping_sub(10).wrapping_add(b'A')) & !not_upper
345 }
346 Alphabet::Rfc4648HexLower | Alphabet::Rfc4648HexLowerNoPadding => {
347 let not_digit = not_in_range(v, 0, 9);
348 let not_lower = not_in_range(v, 10, 31);
349 (v + b'0') & !not_digit | (v.wrapping_sub(10).wrapping_add(b'a')) & !not_lower
350 }
351 }
352}
353
354#[inline]
356const fn quintet_to_crockford(v: u8) -> u8 {
357 let not_0_9 = not_in_range(v, 0, 9);
358 let not_10_17 = not_in_range(v, 10, 17);
359 let not_18_19 = not_in_range(v, 18, 19);
360 let not_20_21 = not_in_range(v, 20, 21);
361 let not_22_26 = not_in_range(v, 22, 26);
362 let not_27_31 = not_in_range(v, 27, 31);
363 (v + b'0') & !not_0_9
364 | (v + 55) & !not_10_17
365 | (v + 56) & !not_18_19
366 | (v + 57) & !not_20_21
367 | (v + 58) & !not_22_26
368 | (v + 59) & !not_27_31
369}
370
371#[inline]
376const fn decoded_length(encoded_content_len: usize) -> Result<usize, DecodeError> {
377 let full_blocks = encoded_content_len / 8;
378 let rem = encoded_content_len % 8;
379
380 let base = full_blocks * 5;
381
382 match rem {
383 0 => Ok(base),
384 2 => Ok(base + 1),
385 4 => Ok(base + 2),
386 5 => Ok(base + 3),
387 7 => Ok(base + 4),
388 _ => Err(DecodeError::InvalidLength),
389 }
390}
391
392#[cfg(feature = "alloc")]
393pub fn decode(data: impl AsRef<[u8]>, alphabet: Alphabet) -> Result<alloc::vec::Vec<u8>, DecodeError> {
394 let data = data.as_ref();
395 let padding = alphabet.is_padded();
396 let (content_len, _) = strip_padding_info(data, padding)?;
397 let output_len = decoded_length(content_len)?;
398 let mut output = alloc::vec![0u8; output_len];
399 decode_into(&mut output, data, alphabet)?;
400 Ok(output)
401}
402
403pub const fn decode_array<const OUT: usize>(encoded_data: &[u8], alphabet: Alphabet) -> Result<[u8; OUT], DecodeError> {
404 let mut result = [0u8; OUT];
405 match decode_into_constant_time(&mut result, encoded_data, alphabet) {
406 Ok(()) => Ok(result),
407 Err(err) => Err(err),
408 }
409}
410
411pub fn decode_into(output: &mut [u8], encoded_data: &[u8], alphabet: Alphabet) -> Result<(), DecodeError> {
412 let padding = alphabet.is_padded();
413 let (content_len, _) = strip_padding_info(encoded_data, padding)?;
414 let computed_output = decoded_length(content_len)?;
415 if output.len() < computed_output {
416 return Err(DecodeError::InvalidLength);
417 }
418
419 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
420 if content_len >= 64 {
421 let content = &encoded_data[..content_len];
422 return unsafe { base32_neon::decode_into(output, content, alphabet) };
423 }
424
425 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
426 if content_len >= 32 {
427 let content = &encoded_data[..content_len];
428 return unsafe { base32_avx2::decode_into(output, content, alphabet) };
429 }
430
431 decode_into_constant_time(output, encoded_data, alphabet)
432}
433
434pub const fn decode_into_constant_time(
435 output: &mut [u8],
436 encoded_data: &[u8],
437 alphabet: Alphabet,
438) -> Result<(), DecodeError> {
439 let in_len = encoded_data.len();
440 let padding = alphabet.is_padded();
441
442 if in_len == 0 {
443 return Ok(());
444 }
445
446 let (content_len, _padding_len) = match strip_padding_info(encoded_data, padding) {
447 Ok(info) => info,
448 Err(e) => return Err(e),
449 };
450
451 if content_len == 0 {
452 return Ok(());
453 }
454
455 let computed_output = match decoded_length(content_len) {
456 Ok(len) => len,
457 Err(e) => return Err(e),
458 };
459
460 if output.len() < computed_output {
461 return Err(DecodeError::InvalidLength);
462 }
463
464 let mut err: u8 = 0;
465 let mut i = 0;
466 let mut o = 0;
467
468 while i + 64 <= content_len {
469 decode_8quads(output, alphabet, encoded_data, &mut i, &mut o, &mut err);
470 }
471
472 while i + 8 <= content_len {
473 decode_1quad(output, alphabet, encoded_data, &mut i, &mut o, &mut err);
474 }
475
476 if i < content_len {
477 let remaining = content_len - i;
478 match remaining {
479 2 => {
480 let v0 = char_to_quintet(encoded_data[i], alphabet);
481 let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
482 err |= v0 | v1;
483 err |= check_trailing_bits(v1, 2);
484 output[o] = (v0 << 3) | (v1 >> 2);
485 }
486 4 => {
487 let v0 = char_to_quintet(encoded_data[i], alphabet);
488 let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
489 let v2 = char_to_quintet(encoded_data[i + 2], alphabet);
490 let v3 = char_to_quintet(encoded_data[i + 3], alphabet);
491 err |= v0 | v1 | v2 | v3;
492 err |= check_trailing_bits(v3, 4);
493 output[o] = (v0 << 3) | (v1 >> 2);
494 output[o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
495 }
496 5 => {
497 let v0 = char_to_quintet(encoded_data[i], alphabet);
498 let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
499 let v2 = char_to_quintet(encoded_data[i + 2], alphabet);
500 let v3 = char_to_quintet(encoded_data[i + 3], alphabet);
501 let v4 = char_to_quintet(encoded_data[i + 4], alphabet);
502 err |= v0 | v1 | v2 | v3 | v4;
503 err |= check_trailing_bits(v4, 1);
504 output[o] = (v0 << 3) | (v1 >> 2);
505 output[o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
506 output[o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
507 }
508 7 => {
509 let v0 = char_to_quintet(encoded_data[i], alphabet);
510 let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
511 let v2 = char_to_quintet(encoded_data[i + 2], alphabet);
512 let v3 = char_to_quintet(encoded_data[i + 3], alphabet);
513 let v4 = char_to_quintet(encoded_data[i + 4], alphabet);
514 let v5 = char_to_quintet(encoded_data[i + 5], alphabet);
515 let v6 = char_to_quintet(encoded_data[i + 6], alphabet);
516 err |= v0 | v1 | v2 | v3 | v4 | v5 | v6;
517 err |= check_trailing_bits(v6, 3);
518 output[o] = (v0 << 3) | (v1 >> 2);
519 output[o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
520 output[o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
521 output[o + 3] = (v4.wrapping_shl(7)) | (v5 << 2) | (v6 >> 3);
522 }
523 _ => return Err(DecodeError::InvalidLength),
524 }
525 }
526
527 if err >= 32 {
528 return Err(DecodeError::InvalidInput);
529 }
530
531 Ok(())
532}
533
534#[inline]
535const fn decode_1quad(output: &mut [u8], alphabet: Alphabet, data: &[u8], i: &mut usize, o: &mut usize, err: &mut u8) {
536 let v0 = char_to_quintet(data[*i], alphabet);
537 let v1 = char_to_quintet(data[*i + 1], alphabet);
538 let v2 = char_to_quintet(data[*i + 2], alphabet);
539 let v3 = char_to_quintet(data[*i + 3], alphabet);
540 let v4 = char_to_quintet(data[*i + 4], alphabet);
541 let v5 = char_to_quintet(data[*i + 5], alphabet);
542 let v6 = char_to_quintet(data[*i + 6], alphabet);
543 let v7 = char_to_quintet(data[*i + 7], alphabet);
544 *err |= v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7;
545 output[*o] = (v0 << 3) | (v1 >> 2);
546 output[*o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
547 output[*o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
548 output[*o + 3] = (v4.wrapping_shl(7)) | (v5 << 2) | (v6 >> 3);
549 output[*o + 4] = (v6.wrapping_shl(5)) | v7;
550 *i += 8;
551 *o += 5;
552}
553
554#[inline]
555const fn decode_8quads(output: &mut [u8], alphabet: Alphabet, data: &[u8], i: &mut usize, o: &mut usize, err: &mut u8) {
556 let mut n = 0;
557 while n < 8 {
558 let v0 = char_to_quintet(data[*i], alphabet);
559 let v1 = char_to_quintet(data[*i + 1], alphabet);
560 let v2 = char_to_quintet(data[*i + 2], alphabet);
561 let v3 = char_to_quintet(data[*i + 3], alphabet);
562 let v4 = char_to_quintet(data[*i + 4], alphabet);
563 let v5 = char_to_quintet(data[*i + 5], alphabet);
564 let v6 = char_to_quintet(data[*i + 6], alphabet);
565 let v7 = char_to_quintet(data[*i + 7], alphabet);
566 *err |= v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7;
567 output[*o] = (v0 << 3) | (v1 >> 2);
568 output[*o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
569 output[*o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
570 output[*o + 3] = (v4.wrapping_shl(7)) | (v5 << 2) | (v6 >> 3);
571 output[*o + 4] = (v6.wrapping_shl(5)) | v7;
572 *i += 8;
573 *o += 5;
574 n += 1;
575 }
576}
577
578#[inline]
579const fn strip_padding_info(data: &[u8], expect_padding: bool) -> Result<(usize, usize), DecodeError> {
580 let in_len = data.len();
581
582 if expect_padding {
583 if in_len == 0 {
584 return Ok((0, 0));
585 }
586
587 let count = count_trailing_padding(data);
588 let content_len = in_len - count;
589
590 let err = (count > 0 && in_len % 8 != 0)
591 || count > 6
592 || (count > 0
593 && match count {
594 6 => content_len % 8 != 2,
595 4 => content_len % 8 != 4,
596 3 => content_len % 8 != 5,
597 1 => content_len % 8 != 7,
598 _ => true,
599 });
600
601 if err {
602 return Err(DecodeError::InvalidPadding);
603 }
604
605 Ok((content_len, count))
606 } else {
607 if in_len > 0 && data[in_len - 1] == PAD {
608 return Err(DecodeError::InvalidPadding);
609 }
610 Ok((in_len, 0))
611 }
612}
613
614const fn count_trailing_padding(data: &[u8]) -> usize {
618 let len = data.len();
619 if len == 0 {
620 return 0;
621 }
622 let max_check = if len < 7 { len } else { 7 };
623 let mut count: usize = 0;
624 let mut all_pad: u8 = 0xFF;
625
626 let mut k = 0;
627 while k < max_check {
628 let idx = len - 1 - k;
629 let is_pad = if data[idx] == PAD { 0xFFu8 } else { 0x00u8 };
630 all_pad = all_pad & is_pad;
631 let all_pad_ext = (all_pad as i8 >> 7) as usize;
632 count = ((k + 1) as usize) & all_pad_ext | count & !all_pad_ext;
633 k += 1;
634 }
635
636 count
637}
638
639#[inline]
642const fn char_to_quintet(c: u8, alphabet: Alphabet) -> u8 {
643 match alphabet {
644 Alphabet::Crockford => crockford_to_quintet(c),
645 Alphabet::Rfc4648 | Alphabet::Rfc4648NoPadding => {
646 let not_upper = not_in_range(c, b'A', b'Z');
647 let not_digit = not_in_range(c, b'2', b'7');
648 let value = (c.wrapping_sub(b'A')) & !not_upper | (c.wrapping_sub(b'2').wrapping_add(26)) & !not_digit;
649 let invalid = not_upper & not_digit;
650 value | (invalid & 0x20)
651 }
652 Alphabet::Rfc4648Lower | Alphabet::Rfc4648LowerNoPadding => {
653 let not_lower = not_in_range(c, b'a', b'z');
654 let not_digit = not_in_range(c, b'2', b'7');
655 let value = (c.wrapping_sub(b'a')) & !not_lower | (c.wrapping_sub(b'2').wrapping_add(26)) & !not_digit;
656 let invalid = not_lower & not_digit;
657 value | (invalid & 0x20)
658 }
659 Alphabet::Rfc4648Hex | Alphabet::Rfc4648HexNoPadding => {
660 let not_digit = not_in_range(c, b'0', b'9');
661 let not_upper = not_in_range(c, b'A', b'V');
662 let value = (c.wrapping_sub(b'0')) & !not_digit | (c.wrapping_sub(b'A').wrapping_add(10)) & !not_upper;
663 let invalid = not_digit & not_upper;
664 value | (invalid & 0x20)
665 }
666 Alphabet::Rfc4648HexLower | Alphabet::Rfc4648HexLowerNoPadding => {
667 let not_digit = not_in_range(c, b'0', b'9');
668 let not_lower = not_in_range(c, b'a', b'v');
669 let value = (c.wrapping_sub(b'0')) & !not_digit | (c.wrapping_sub(b'a').wrapping_add(10)) & !not_lower;
670 let invalid = not_digit & not_lower;
671 value | (invalid & 0x20)
672 }
673 }
674}
675
676#[inline]
678const fn crockford_to_quintet(c: u8) -> u8 {
679 let not_0_9 = not_in_range(c, b'0', b'9');
680 let not_a_h = not_in_range(c, b'A', b'H');
681 let not_j_k = not_in_range(c, b'J', b'K');
682 let not_m_n = not_in_range(c, b'M', b'N');
683 let not_p_t = not_in_range(c, b'P', b'T');
684 let not_v_z = not_in_range(c, b'V', b'Z');
685 let value = (c.wrapping_sub(b'0')) & !not_0_9
686 | (c.wrapping_sub(b'A').wrapping_add(10)) & !not_a_h
687 | (c.wrapping_sub(b'J').wrapping_add(18)) & !not_j_k
688 | (c.wrapping_sub(b'M').wrapping_add(20)) & !not_m_n
689 | (c.wrapping_sub(b'P').wrapping_add(22)) & !not_p_t
690 | (c.wrapping_sub(b'V').wrapping_add(27)) & !not_v_z;
691 let invalid = not_0_9 & not_a_h & not_j_k & not_m_n & not_p_t & not_v_z;
692 value | (invalid & 0x20)
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698
699 const ENCODE_VECTORS: &[(&[u8], Alphabet, &str, &str)] = &[
701 (b"", Alphabet::Rfc4648, "", "RFC4648 padded: empty"),
702 (b"", Alphabet::Rfc4648NoPadding, "", "RFC4648 unpadded: empty"),
703 (b"\x00", Alphabet::Rfc4648, "AA======", "RFC4648 padded: 0x00"),
704 (b"\xFF", Alphabet::Rfc4648, "74======", "RFC4648 padded: 0xFF"),
705 (b"\xAB", Alphabet::Rfc4648, "VM======", "RFC4648 padded: 0xAB"),
706 (b"fo", Alphabet::Rfc4648, "MZXQ====", "RFC4648 padded: 'fo'"),
707 (b"foo", Alphabet::Rfc4648, "MZXW6===", "RFC4648 padded: 'foo'"),
708 (b"foob", Alphabet::Rfc4648, "MZXW6YQ=", "RFC4648 padded: 'foob'"),
709 (b"fooba", Alphabet::Rfc4648, "MZXW6YTB", "RFC4648 padded: 'fooba'"),
710 (b"foobar", Alphabet::Rfc4648, "MZXW6YTBOI======", "RFC4648 padded: 'foobar'"),
711 (b"hello", Alphabet::Rfc4648, "NBSWY3DP", "RFC4648 padded: 'hello'"),
712 (b"hello", Alphabet::Rfc4648NoPadding, "NBSWY3DP", "RFC4648 unpadded: 'hello'"),
713 (b"h", Alphabet::Rfc4648NoPadding, "NA", "RFC4648 unpadded: 'h'"),
714 (b"he", Alphabet::Rfc4648NoPadding, "NBSQ", "RFC4648 unpadded: 'he'"),
715 (b"hel", Alphabet::Rfc4648NoPadding, "NBSWY", "RFC4648 unpadded: 'hel'"),
716 (b"hell", Alphabet::Rfc4648NoPadding, "NBSWY3A", "RFC4648 unpadded: 'hell'"),
717 (b"hello", Alphabet::Rfc4648Lower, "nbswy3dp", "RFC4648 lower: 'hello'"),
718 (b"hello", Alphabet::Rfc4648Hex, "D1IMOR3F", "RFC4648 hex: 'hello'"),
719 (b"hello", Alphabet::Rfc4648HexLower, "d1imor3f", "RFC4648 hex lower: 'hello'"),
720 (b"hello", Alphabet::Crockford, "D1JPRV3F", "Crockford: 'hello'"),
721 (b"f", Alphabet::Rfc4648Hex, "CO======", "RFC4648 hex: 'f'"),
723 (b"fo", Alphabet::Rfc4648Hex, "CPNG====", "RFC4648 hex: 'fo'"),
724 (b"foo", Alphabet::Rfc4648Hex, "CPNMU===", "RFC4648 hex: 'foo'"),
725 (b"foob", Alphabet::Rfc4648Hex, "CPNMUOG=", "RFC4648 hex: 'foob'"),
726 (b"fooba", Alphabet::Rfc4648Hex, "CPNMUOJ1", "RFC4648 hex: 'fooba'"),
727 (b"foobar", Alphabet::Rfc4648Hex, "CPNMUOJ1E8======", "RFC4648 hex: 'foobar'"),
728 ];
729
730 const DECODE_VECTORS: &[(&[u8], Alphabet, &[u8], &str)] = &[
732 (b"", Alphabet::Rfc4648, b"", "RFC4648 padded: empty"),
733 (b"AA======", Alphabet::Rfc4648, b"\x00", "RFC4648 padded: 0x00"),
734 (b"AE======", Alphabet::Rfc4648, b"\x01", "RFC4648 padded: 0x01"),
735 (b"MZXQ====", Alphabet::Rfc4648, b"fo", "RFC4648 padded: 'fo'"),
736 (b"MZXW6===", Alphabet::Rfc4648, b"foo", "RFC4648 padded: 'foo'"),
737 (b"MZXW6YQ=", Alphabet::Rfc4648, b"foob", "RFC4648 padded: 'foob'"),
738 (b"MZXW6YTB", Alphabet::Rfc4648, b"fooba", "RFC4648 padded: 'fooba'"),
739 (b"NA", Alphabet::Rfc4648NoPadding, b"h", "RFC4648 unpadded: 'h'"),
740 (b"NBSQ", Alphabet::Rfc4648NoPadding, b"he", "RFC4648 unpadded: 'he'"),
741 (b"NBSWY", Alphabet::Rfc4648NoPadding, b"hel", "RFC4648 unpadded: 'hel'"),
742 (b"NBSWY3A", Alphabet::Rfc4648NoPadding, b"hell", "RFC4648 unpadded: 'hell'"),
743 (b"nbswy3dp", Alphabet::Rfc4648Lower, b"hello", "RFC4648 lower: 'hello'"),
744 (b"D1IMOR3F", Alphabet::Rfc4648Hex, b"hello", "RFC4648 hex: 'hello'"),
745 (b"D1JPRV3F", Alphabet::Crockford, b"hello", "Crockford: 'hello'"),
746 ];
747
748 const DECODE_ERROR_VECTORS: &[(&[u8], Alphabet, DecodeError, &str)] = &[
750 (b"!!!!====", Alphabet::Rfc4648, DecodeError::InvalidInput, "4 invalid chars"),
751 (
752 b"AAA=====",
753 Alphabet::Rfc4648,
754 DecodeError::InvalidPadding,
755 "wrong padding position",
756 ),
757 (
758 b"AA==========",
759 Alphabet::Rfc4648,
760 DecodeError::InvalidPadding,
761 "too many padding chars",
762 ),
763 (
764 b"AA======",
765 Alphabet::Rfc4648NoPadding,
766 DecodeError::InvalidPadding,
767 "no-pad rejects padding",
768 ),
769 (b"A", Alphabet::Rfc4648, DecodeError::InvalidLength, "single char"),
770 (
771 b"D1JPRV!!",
772 Alphabet::Crockford,
773 DecodeError::InvalidInput,
774 "Crockford invalid chars",
775 ),
776 ];
777
778 const ENCODED_LENGTH_VECTORS: &[(usize, bool, Option<usize>, &str)] = &[
780 (0, true, Some(0), "empty padded"),
781 (1, true, Some(8), "1 byte padded"),
782 (5, true, Some(8), "5 bytes padded"),
783 (6, true, Some(16), "6 bytes padded"),
784 (10, true, Some(16), "10 bytes padded"),
785 (0, false, Some(0), "empty unpadded"),
786 (1, false, Some(2), "1 byte unpadded"),
787 (5, false, Some(8), "5 bytes unpadded"),
788 (6, false, Some(10), "6 bytes unpadded"),
789 ];
790
791 const ENCODE_INTO_STRING_VECTORS: &[(&str, &[u8], Alphabet, &str, &str)] = &[
793 ("", b"", Alphabet::Rfc4648, "", "empty"),
794 ("prefix", b"", Alphabet::Rfc4648, "prefix", "empty data with prefix"),
795 (
796 "",
797 b"hello world",
798 Alphabet::Rfc4648,
799 "NBSWY3DPEB3W64TMMQ======",
800 "hello world padded",
801 ),
802 (
803 "",
804 b"hello world",
805 Alphabet::Rfc4648NoPadding,
806 "NBSWY3DPEB3W64TMMQ",
807 "hello world unpadded",
808 ),
809 (
810 "data: ",
811 b"hello world",
812 Alphabet::Rfc4648,
813 "data: NBSWY3DPEB3W64TMMQ======",
814 "append to prefix",
815 ),
816 ("", b"foobar", Alphabet::Rfc4648Hex, "CPNMUOJ1E8======", "hex alphabet"),
817 ];
818
819 const ALL_ALPHABETS: &[Alphabet] = &[
820 Alphabet::Rfc4648,
821 Alphabet::Rfc4648NoPadding,
822 Alphabet::Rfc4648Lower,
823 Alphabet::Rfc4648Hex,
824 Alphabet::Rfc4648HexLower,
825 Alphabet::Crockford,
826 ];
827
828 const ROUNDTRIP_SIZES: &[usize] = &[
829 0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129,
830 ];
831
832 const SIMD_BOUNDARY_SIZES: &[usize] = &[38, 39, 40, 41, 42, 45, 50, 62, 63, 64, 65, 66, 84, 85, 100];
833
834 #[test]
835 fn test_encode() {
836 for &(input, alphabet, expected, desc) in ENCODE_VECTORS {
837 let result = encode(input, alphabet);
838 assert_eq!(result, expected, "encode: {desc}");
839 }
840 }
841
842 #[test]
843 fn test_decode() {
844 for &(encoded, alphabet, expected, desc) in DECODE_VECTORS {
845 let result = decode(encoded, alphabet).unwrap();
846 assert_eq!(&result, expected, "decode: {desc}");
847 }
848 }
849
850 #[test]
851 fn test_decode_error() {
852 for &(encoded, alphabet, expected_err, desc) in DECODE_ERROR_VECTORS {
853 let result = decode(encoded, alphabet);
854 assert_eq!(result, Err(expected_err), "decode error: {desc}");
855 }
856
857 let mut data = alloc::vec![b'A'; 256];
859 data[255] = b'!';
860 assert_eq!(decode(&data, Alphabet::Rfc4648), Err(DecodeError::InvalidInput));
861 }
862
863 #[test]
864 fn test_encoded_length() {
865 for &(data_len, padding, expected, desc) in ENCODED_LENGTH_VECTORS {
866 let result = encoded_length(data_len, padding);
867 assert_eq!(result, expected, "encoded_length: {desc}");
868 }
869 }
870
871 #[test]
872 fn test_encode_into_string() {
873 for &(initial, input, alphabet, expected, desc) in ENCODE_INTO_STRING_VECTORS {
874 let mut s = alloc::string::String::from(initial);
875 encode_into_string(&mut s, input, alphabet);
876 assert_eq!(s, expected, "encode_into_string: {desc}");
877 }
878
879 let mut s = alloc::string::String::from("~~");
881 encode_into_string(&mut s, b"hello", Alphabet::Rfc4648);
882 assert_eq!(s, "~~NBSWY3DP");
883 encode_into_string(&mut s, b"foo", Alphabet::Rfc4648);
884 assert_eq!(s, "~~NBSWY3DPMZXW6===");
885
886 for alphabet in ALL_ALPHABETS {
888 let expected = encode(b"hello world", *alphabet);
889 let mut s = alloc::string::String::new();
890 encode_into_string(&mut s, b"hello world", *alphabet);
891 assert_eq!(s, expected, "encode_into_string alphabet {alphabet:?}");
892 }
893 }
894
895 #[test]
896 fn test_roundtrip() {
897 for &len in ROUNDTRIP_SIZES {
898 let data: Vec<u8> = (0..len as u8).collect();
899 for alphabet in ALL_ALPHABETS {
900 let encoded = encode(&data, *alphabet);
901 let decoded = decode(encoded.as_bytes(), *alphabet).unwrap();
902 assert_eq!(decoded, data, "roundtrip len={len} alphabet={alphabet:?}");
903 }
904 }
905 }
906
907 #[test]
908 fn test_roundtrip_large() {
909 let size = 4096;
910
911 let data = alloc::vec![0x00u8; size];
912 let elen = encoded_length(size, true).expect("encoded_len overflow");
913 let mut encoded = alloc::vec![0u8; elen];
914 encode_into_constant_time(&mut encoded, &data, Alphabet::Rfc4648).unwrap();
915 let mut decoded = alloc::vec![0u8; size];
916 decode_into_constant_time(&mut decoded, &encoded, Alphabet::Rfc4648).unwrap();
917 assert_eq!(decoded, data, "4096 zeroes constant-time");
918
919 let data = alloc::vec![0xFFu8; size];
920 let mut encoded = alloc::vec![0u8; elen];
921 encode_into_constant_time(&mut encoded, &data, Alphabet::Rfc4648).unwrap();
922 decode_into_constant_time(&mut decoded, &encoded, Alphabet::Rfc4648).unwrap();
923 assert_eq!(decoded, data, "4096 0xFF constant-time");
924
925 let data: Vec<u8> = (0..=255).cycle().take(size).collect();
926 let encoded = encode(&data, Alphabet::Rfc4648);
927 let decoded = decode(encoded.as_bytes(), Alphabet::Rfc4648).unwrap();
928 assert_eq!(decoded, data, "4096 cycle dispatch");
929
930 let data: Vec<u8> = (0..=255).collect();
931 let mut s = alloc::string::String::new();
932 encode_into_string(&mut s, &data, Alphabet::Rfc4648);
933 let decoded = decode(s.as_bytes(), Alphabet::Rfc4648).unwrap();
934 assert_eq!(decoded, data, "256-byte encode_into_string roundtrip");
935
936 let data: Vec<u8> = (0..255).cycle().take(4096).collect();
937 let expected = encode(&data, Alphabet::Rfc4648);
938 let mut s = alloc::string::String::new();
939 encode_into_string(&mut s, &data, Alphabet::Rfc4648);
940 assert_eq!(s, expected, "4096-byte encode_into_string");
941 }
942
943 #[test]
944 fn test_encode_all_single_bytes() {
945 for byte in 0..=255u8 {
946 for alphabet in &[
947 Alphabet::Rfc4648,
948 Alphabet::Rfc4648Lower,
949 Alphabet::Rfc4648Hex,
950 Alphabet::Rfc4648HexLower,
951 Alphabet::Crockford,
952 ] {
953 let padding = alphabet.is_padded();
954 let elen = encoded_length(1, padding).unwrap();
955 let mut encoded = alloc::vec![0u8; elen];
956 encode_into_constant_time(&mut encoded, &[byte], *alphabet).unwrap();
957 let mut decoded = [0u8; 1];
958 decode_into_constant_time(&mut decoded, &encoded, *alphabet).unwrap();
959 assert_eq!(decoded[0], byte, "single byte roundtrip {byte:#04x} alphabet={alphabet:?}");
960 }
961 }
962 }
963
964 #[test]
965 fn test_decode_invalid_char_every_position() {
966 let mut out = [0u8; 128];
967
968 for pos in 0..8 {
970 let mut input = [b'A'; 8];
971 input[pos] = b'!';
972 assert_eq!(
973 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648),
974 Err(DecodeError::InvalidInput),
975 "invalid char at position {pos} in 8-char input"
976 );
977 }
978
979 for pos in 0..64 {
981 let mut input = [b'A'; 64];
982 input[pos] = b'!';
983 assert_eq!(
984 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648),
985 Err(DecodeError::InvalidInput),
986 "invalid char at position {pos} in 64-char input"
987 );
988 }
989
990 for pos in 0..72 {
992 let mut input = [b'A'; 72];
993 input[pos] = b'!';
994 assert_eq!(
995 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648),
996 Err(DecodeError::InvalidInput),
997 "invalid char at position {pos} in 72-char input"
998 );
999 }
1000
1001 for pos in 0..8 {
1003 let mut input = [b'a'; 8];
1004 input[pos] = b'!';
1005 assert_eq!(
1006 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648Lower),
1007 Err(DecodeError::InvalidInput)
1008 );
1009 }
1010 for pos in 0..8 {
1011 let mut input = [b'0'; 8];
1012 input[pos] = b'!';
1013 assert_eq!(
1014 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648Hex),
1015 Err(DecodeError::InvalidInput)
1016 );
1017 }
1018 for pos in 0..8 {
1019 let mut input = [b'0'; 8];
1020 input[pos] = b'!';
1021 assert_eq!(
1022 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648HexLower),
1023 Err(DecodeError::InvalidInput)
1024 );
1025 }
1026 for pos in 0..8 {
1027 let mut input = [b'0'; 8];
1028 input[pos] = b'!';
1029 assert_eq!(
1030 decode_into_constant_time(&mut out, &input, Alphabet::Crockford),
1031 Err(DecodeError::InvalidInput)
1032 );
1033 }
1034 }
1035
1036 #[test]
1037 fn test_decode_non_canonical_trailing_bits() {
1038 let mut out = [0u8; 8];
1039
1040 for &(input, expected) in &[
1042 (b"AA======" as &[u8], Ok(())),
1043 (b"AB======" as &[u8], Err(DecodeError::InvalidInput)),
1044 (b"AC======" as &[u8], Err(DecodeError::InvalidInput)),
1045 (b"AD======" as &[u8], Err(DecodeError::InvalidInput)),
1046 ] {
1047 assert_eq!(
1048 decode_into_constant_time(&mut out, input, Alphabet::Rfc4648),
1049 expected,
1050 "non-canonical (rem=2): {:?}",
1051 core::str::from_utf8(input)
1052 );
1053 }
1054
1055 for &(input, expected) in &[
1057 (b"MZXQ====" as &[u8], Ok(())),
1058 (b"MZXR====" as &[u8], Err(DecodeError::InvalidInput)),
1059 ] {
1060 assert_eq!(
1061 decode_into_constant_time(&mut out, input, Alphabet::Rfc4648),
1062 expected,
1063 "non-canonical (rem=4): {:?}",
1064 core::str::from_utf8(input)
1065 );
1066 }
1067
1068 for &(input, expected) in &[
1070 (b"MZXW6===" as &[u8], Ok(())),
1071 (b"MZXW7===" as &[u8], Err(DecodeError::InvalidInput)),
1072 ] {
1073 assert_eq!(
1074 decode_into_constant_time(&mut out, input, Alphabet::Rfc4648),
1075 expected,
1076 "non-canonical (rem=5): {:?}",
1077 core::str::from_utf8(input)
1078 );
1079 }
1080
1081 assert_eq!(decode_into_constant_time(&mut out, b"NBSWY3DP", Alphabet::Rfc4648), Ok(()));
1083 }
1084
1085 #[test]
1086 fn test_decode_rejects_interior_padding() {
1087 let mut out = [0u8; 8];
1088 assert_eq!(
1089 decode_into_constant_time(&mut out, b"=AAA====", Alphabet::Rfc4648),
1090 Err(DecodeError::InvalidInput)
1091 );
1092 assert_eq!(
1093 decode_into_constant_time(&mut out, b"A=AA====", Alphabet::Rfc4648),
1094 Err(DecodeError::InvalidInput)
1095 );
1096 assert_eq!(
1097 decode_into_constant_time(&mut out, b"AA=A====", Alphabet::Rfc4648),
1098 Err(DecodeError::InvalidInput)
1099 );
1100 assert_eq!(
1101 decode_into_constant_time(&mut out, b"AAA=====", Alphabet::Rfc4648),
1102 Err(DecodeError::InvalidPadding)
1103 );
1104 }
1105
1106 #[test]
1107 fn test_roundtrip_simd_boundary_sizes() {
1108 let mut data_buf = Vec::new();
1109 let mut enc_buf = Vec::new();
1110
1111 for &input_len in SIMD_BOUNDARY_SIZES {
1112 data_buf.clear();
1113 for b in 0..input_len {
1114 data_buf.push(b as u8);
1115 }
1116
1117 for alphabet in &[Alphabet::Rfc4648, Alphabet::Rfc4648NoPadding] {
1118 let padding = alphabet.is_padded();
1119 let elen = encoded_length(input_len, padding).expect("encoded_len overflow");
1120 enc_buf.resize(elen, 0);
1121 encode_into_constant_time(&mut enc_buf, &data_buf, *alphabet).unwrap();
1122
1123 let mut decoded = alloc::vec![0u8; input_len];
1124 assert_eq!(
1125 decode_into_constant_time(&mut decoded, &enc_buf, *alphabet),
1126 Ok(()),
1127 "decode failed len={input_len} alphabet={alphabet:?}"
1128 );
1129 assert_eq!(&decoded, &data_buf, "roundtrip mismatch len={input_len} alphabet={alphabet:?}");
1130 }
1131 }
1132 }
1133
1134 #[test]
1135 fn test_const_encode() {
1136 const RESULT: [u8; 8] = encode_array::<8>(b"hello", Alphabet::Rfc4648);
1137 assert_eq!(&RESULT, b"NBSWY3DP");
1138
1139 const RESULT_EMPTY: [u8; 0] = encode_array::<0>(b"", Alphabet::Rfc4648);
1140 assert_eq!(RESULT_EMPTY.len(), 0);
1141
1142 const RESULT_CROCKFORD: [u8; 8] = encode_array::<8>(b"hello", Alphabet::Crockford);
1143 assert_eq!(&RESULT_CROCKFORD, b"D1JPRV3F");
1144 }
1145
1146 #[test]
1147 fn test_const_decode() {
1148 const RESULT: Result<[u8; 5], DecodeError> = decode_array::<5>(b"NBSWY3DP", Alphabet::Rfc4648);
1149 assert_eq!(RESULT.unwrap(), *b"hello");
1150
1151 const RESULT_EMPTY: Result<[u8; 0], DecodeError> = decode_array::<0>(b"", Alphabet::Rfc4648);
1152 assert_eq!(RESULT_EMPTY.unwrap().len(), 0);
1153 }
1154
1155 #[test]
1156 fn test_const_decode_error() {
1157 const ERR_INVALID: Result<[u8; 5], DecodeError> = decode_array::<5>(b"D1JPRV!!", Alphabet::Crockford);
1158 assert_eq!(ERR_INVALID, Err(DecodeError::InvalidInput));
1159 }
1160
1161 #[test]
1162 fn test_buffer_management() {
1163 let mut out = [0u8; 1];
1164 assert_eq!(
1165 encode_into(&mut out, b"hello", Alphabet::Rfc4648),
1166 Err(EncodeError::InvalidOutputLength)
1167 );
1168
1169 let mut out = [0u8; 5];
1170 decode_into(&mut out, b"NBSWY3DP", Alphabet::Rfc4648).unwrap();
1171 assert_eq!(&out, b"hello");
1172
1173 let mut out = [0u8; 1];
1174 assert_eq!(
1175 decode_into(&mut out, b"NBSWY3DP", Alphabet::Rfc4648),
1176 Err(DecodeError::InvalidLength)
1177 );
1178
1179 let mut remainders = [0u8; 80];
1181 let mut output = [0u8; 80];
1182 for len in 1..=80 {
1183 for i in 0..len {
1184 remainders[i] = (i * 7 + 3) as u8;
1185 }
1186 let encoded = encode(&remainders[..len], Alphabet::Rfc4648);
1187 let expected_output_len = len;
1188 let r = decode_into(&mut output[..expected_output_len], encoded.as_bytes(), Alphabet::Rfc4648);
1189 assert!(r.is_ok(), "decode_into failed at len {}", len);
1190 assert_eq!(&output[..expected_output_len], &remainders[..len], "mismatch at len {}", len);
1191 }
1192 }
1193
1194 #[test]
1195 fn test_display_error() {
1196 assert_eq!(format!("{}", DecodeError::InvalidInput), "invalid base32 character");
1197 assert_eq!(format!("{}", DecodeError::InvalidLength), "invalid base32 length");
1198 assert_eq!(format!("{}", DecodeError::InvalidPadding), "invalid base32 padding");
1199 assert_eq!(
1200 format!("{}", EncodeError::InvalidOutputLength),
1201 "output buffer size is not valid"
1202 );
1203 }
1204
1205 #[cfg(feature = "serde")]
1206 #[test]
1207 fn test_serde() {
1208 #[derive(::serde::Serialize, ::serde::Deserialize)]
1209 struct Data(#[serde(with = "crate::serde")] Vec<u8>);
1210
1211 let data = Data(b"hello world".to_vec());
1212 let json = ::serde_json::to_string(&data).unwrap();
1213 assert_eq!(json, "\"NBSWY3DPEB3W64TMMQ======\"");
1214 let deserialized: Data = ::serde_json::from_str(&json).unwrap();
1215 assert_eq!(deserialized.0, b"hello world");
1216 }
1217}