Skip to main content

ct_codecs/
base32.rs

1use crate::error::*;
2use crate::{Decoder, Encoder};
3
4struct Base32Impl;
5
6#[derive(Copy, Clone, Debug, Eq, PartialEq)]
7enum Base32Variant {
8    Standard = 1,
9    StandardNoPadding = 3,
10    Hex = 5,
11    HexNoPadding = 7,
12}
13
14enum VariantMask {
15    NoPadding = 2,
16    Hex = 4,
17}
18
19impl Base32Impl {
20    #[inline]
21    fn is_no_padding(variant: Base32Variant) -> bool {
22        (variant as u16 & VariantMask::NoPadding as u16) != 0
23    }
24
25    #[inline]
26    fn is_hex(variant: Base32Variant) -> bool {
27        (variant as u16 & VariantMask::Hex as u16) != 0
28    }
29
30    #[inline]
31    fn _eq(x: u8, y: u8) -> u8 {
32        !(((0u16.wrapping_sub((x as u16) ^ (y as u16))) >> 8) as u8)
33    }
34
35    #[inline]
36    fn _gt(x: u8, y: u8) -> u8 {
37        (((y as u16).wrapping_sub(x as u16)) >> 8) as u8
38    }
39
40    #[inline]
41    fn _ge(x: u8, y: u8) -> u8 {
42        !Self::_gt(y, x)
43    }
44
45    #[inline]
46    fn _lt(x: u8, y: u8) -> u8 {
47        Self::_gt(y, x)
48    }
49
50    #[inline]
51    fn _le(x: u8, y: u8) -> u8 {
52        Self::_ge(y, x)
53    }
54
55    #[inline]
56    fn b32_byte_to_char(x: u8) -> u8 {
57        (Self::_lt(x, 26) & (x.wrapping_add(b'A')))
58            | (Self::_ge(x, 26) & Self::_lt(x, 32) & (x.wrapping_add(b'2'.wrapping_sub(26))))
59    }
60
61    #[inline]
62    fn b32_char_to_byte(c: u8) -> u8 {
63        let x = (Self::_ge(c, b'A') & Self::_le(c, b'Z') & (c.wrapping_sub(b'A')))
64            | (Self::_ge(c, b'2') & Self::_le(c, b'7') & (c.wrapping_sub(b'2').wrapping_add(26)));
65        x | (Self::_eq(x, 0) & (Self::_eq(c, b'A') ^ 0xff))
66    }
67
68    #[inline]
69    fn b32_hex_byte_to_char(x: u8) -> u8 {
70        (Self::_lt(x, 10) & (x.wrapping_add(b'0')))
71            | (Self::_ge(x, 10) & Self::_lt(x, 32) & (x.wrapping_add(b'A'.wrapping_sub(10))))
72    }
73
74    #[inline]
75    fn b32_hex_char_to_byte(c: u8) -> u8 {
76        let x = (Self::_ge(c, b'0') & Self::_le(c, b'9') & (c.wrapping_sub(b'0')))
77            | (Self::_ge(c, b'A') & Self::_le(c, b'V') & (c.wrapping_sub(b'A').wrapping_add(10)));
78        x | (Self::_eq(x, 0) & ((Self::_eq(c, b'0') | Self::_eq(c, b'A')) ^ 0xff))
79    }
80
81    #[inline]
82    #[allow(clippy::manual_div_ceil)]
83    fn encoded_len(bin_len: usize, variant: Base32Variant) -> Result<usize, Error> {
84        let groups = bin_len / 5;
85        let remainder = bin_len - 5 * groups;
86        let mut b32_len = groups.checked_mul(8).ok_or(Error::Overflow)?;
87        if remainder != 0 {
88            let remainder_len = if Self::is_no_padding(variant) {
89                (remainder * 8 + 4) / 5
90            } else {
91                8
92            };
93            b32_len = b32_len.checked_add(remainder_len).ok_or(Error::Overflow)?;
94        }
95        Ok(b32_len)
96    }
97
98    #[allow(clippy::manual_div_ceil)]
99    pub fn encode<'t>(
100        b32: &'t mut [u8],
101        bin: &[u8],
102        variant: Base32Variant,
103    ) -> Result<&'t [u8], Error> {
104        let b32_len = Self::encoded_len(bin.len(), variant)?;
105        let b32_maxlen = b32.len();
106        let mut acc_len = 0usize;
107        let mut b32_pos = 0usize;
108        let mut acc = 0u16;
109
110        if b32_maxlen < b32_len {
111            return Err(Error::Overflow);
112        }
113        if Self::is_hex(variant) {
114            for &v in bin {
115                acc = (acc << 8) + v as u16;
116                acc_len += 8;
117                while acc_len >= 5 {
118                    acc_len -= 5;
119                    b32[b32_pos] = Self::b32_hex_byte_to_char(((acc >> acc_len) & 0x1f) as u8);
120                    b32_pos += 1;
121                }
122            }
123            if acc_len > 0 {
124                b32[b32_pos] = Self::b32_hex_byte_to_char(((acc << (5 - acc_len)) & 0x1f) as u8);
125                b32_pos += 1;
126            }
127        } else {
128            for &v in bin {
129                acc = (acc << 8) + v as u16;
130                acc_len += 8;
131                while acc_len >= 5 {
132                    acc_len -= 5;
133                    b32[b32_pos] = Self::b32_byte_to_char(((acc >> acc_len) & 0x1f) as u8);
134                    b32_pos += 1;
135                }
136            }
137            if acc_len > 0 {
138                b32[b32_pos] = Self::b32_byte_to_char(((acc << (5 - acc_len)) & 0x1f) as u8);
139                b32_pos += 1;
140            }
141        }
142        while b32_pos < b32_len {
143            b32[b32_pos] = b'=';
144            b32_pos += 1
145        }
146        Ok(&b32[..b32_pos])
147    }
148
149    fn skip_padding<'t>(
150        b32: &'t [u8],
151        mut padding_len: usize,
152        ignore: Option<&[u8]>,
153    ) -> Result<&'t [u8], Error> {
154        let b32_len = b32.len();
155        let mut b32_pos = 0usize;
156        while padding_len > 0 {
157            if b32_pos >= b32_len {
158                return Err(Error::InvalidInput);
159            }
160            let c = b32[b32_pos];
161            if c == b'=' {
162                padding_len -= 1
163            } else {
164                match ignore {
165                    Some(ignore) if ignore.contains(&c) => {}
166                    _ => return Err(Error::InvalidInput),
167                }
168            }
169            b32_pos += 1
170        }
171        Ok(&b32[b32_pos..])
172    }
173
174    pub fn decode<'t>(
175        bin: &'t mut [u8],
176        b32: &[u8],
177        ignore: Option<&[u8]>,
178        variant: Base32Variant,
179    ) -> Result<&'t [u8], Error> {
180        let bin_maxlen = bin.len();
181        let is_hex = Self::is_hex(variant);
182        let is_no_padding = Self::is_no_padding(variant);
183        let mut acc = 0u16;
184        let mut acc_len = 0usize;
185        let mut bin_pos = 0usize;
186        let mut premature_end = None;
187        for (b32_pos, &c) in b32.iter().enumerate() {
188            let d = if is_hex {
189                Self::b32_hex_char_to_byte(c)
190            } else {
191                Self::b32_char_to_byte(c)
192            };
193            if d == 0xff {
194                match ignore {
195                    Some(ignore) if ignore.contains(&c) => continue,
196                    _ => {
197                        premature_end = Some(b32_pos);
198                        break;
199                    }
200                }
201            }
202            acc = (acc << 5) + d as u16;
203            acc_len += 5;
204            if acc_len >= 8 {
205                acc_len -= 8;
206                if bin_pos >= bin_maxlen {
207                    return Err(Error::Overflow);
208                }
209                bin[bin_pos] = (acc >> acc_len) as u8;
210                bin_pos += 1;
211            }
212        }
213        if acc_len >= 5 || (acc & ((1u16 << acc_len).wrapping_sub(1))) != 0 {
214            return Err(Error::InvalidInput);
215        }
216        let padding_len = [0, 3, 6, 1, 4][acc_len];
217        if let Some(premature_end) = premature_end {
218            let remaining = if !is_no_padding {
219                Self::skip_padding(&b32[premature_end..], padding_len, ignore)?
220            } else {
221                &b32[premature_end..]
222            };
223            match ignore {
224                None => {
225                    if !remaining.is_empty() {
226                        return Err(Error::InvalidInput);
227                    }
228                }
229                Some(ignore) => {
230                    for &c in remaining {
231                        if !ignore.contains(&c) {
232                            return Err(Error::InvalidInput);
233                        }
234                    }
235                }
236            }
237        } else if !is_no_padding && padding_len != 0 {
238            return Err(Error::InvalidInput);
239        }
240        Ok(&bin[..bin_pos])
241    }
242}
243
244/// Standard Base32 encoder and decoder with padding.
245///
246/// This implementation follows the standard Base32 encoding as defined in RFC 4648,
247/// and includes padding characters ('=') when needed.
248///
249/// # Standard Base32 Alphabet
250///
251/// The standard Base32 alphabet uses characters:
252/// - 'A' to 'Z' (26 characters, values 0-25)
253/// - '2' to '7' (6 characters, values 26-31)
254/// - '=' (padding character)
255///
256/// # Examples
257///
258/// ```
259/// use ct_codecs::{Base32, Encoder, Decoder};
260///
261/// fn example() -> Result<(), ct_codecs::Error> {
262///     let data = b"foobar";
263///     let encoded = Base32::encode_to_string(data)?;
264///     assert_eq!(encoded, "MZXW6YTBOI======");
265///
266///     let decoded = Base32::decode_to_vec(&encoded, None)?;
267///     assert_eq!(decoded, data);
268///     Ok(())
269/// }
270/// # example().unwrap();
271/// ```
272pub struct Base32;
273
274/// Standard Base32 encoder and decoder without padding.
275///
276/// This implementation follows the standard Base32 encoding as defined in RFC 4648,
277/// but omits padding characters ('=').
278///
279/// # Examples
280///
281/// ```
282/// use ct_codecs::{Base32NoPadding, Encoder, Decoder};
283///
284/// fn example() -> Result<(), ct_codecs::Error> {
285///     let data = b"foobar";
286///     let encoded = Base32NoPadding::encode_to_string(data)?;
287///     assert_eq!(encoded, "MZXW6YTBOI");
288///
289///     let decoded = Base32NoPadding::decode_to_vec(&encoded, None)?;
290///     assert_eq!(decoded, data);
291///     Ok(())
292/// }
293/// # example().unwrap();
294/// ```
295pub struct Base32NoPadding;
296
297/// Base32 Hex encoder and decoder with padding.
298///
299/// This implementation follows the Base32hex encoding variant as defined in RFC 4648.
300/// It uses the extended hex alphabet (0-9, A-V) instead of the standard base32 alphabet.
301/// Padding characters ('=') are included when needed.
302///
303/// # Base32hex Alphabet
304///
305/// The Base32hex alphabet uses characters:
306/// - '0' to '9' (10 characters, values 0-9)
307/// - 'A' to 'V' (22 characters, values 10-31)
308/// - '=' (padding character)
309///
310/// # Examples
311///
312/// ```
313/// use ct_codecs::{Base32Hex, Encoder, Decoder};
314///
315/// fn example() -> Result<(), ct_codecs::Error> {
316///     let data = b"foobar";
317///     let encoded = Base32Hex::encode_to_string(data)?;
318///     assert_eq!(encoded, "CPNMUOJ1E8======");
319///
320///     let decoded = Base32Hex::decode_to_vec(&encoded, None)?;
321///     assert_eq!(decoded, data);
322///     Ok(())
323/// }
324/// # example().unwrap();
325/// ```
326pub struct Base32Hex;
327
328/// Base32 Hex encoder and decoder without padding.
329///
330/// This implementation follows the Base32hex encoding variant as defined in RFC 4648,
331/// but omits padding characters ('='). This is particularly useful for identifiers
332/// and other cases where the padding is unnecessary.
333///
334/// # Examples
335///
336/// ```
337/// use ct_codecs::{Base32HexNoPadding, Encoder, Decoder};
338///
339/// fn example() -> Result<(), ct_codecs::Error> {
340///     let data = b"foobar";
341///     let encoded = Base32HexNoPadding::encode_to_string(data)?;
342///     assert_eq!(encoded, "CPNMUOJ1E8");
343///
344///     let decoded = Base32HexNoPadding::decode_to_vec(&encoded, None)?;
345///     assert_eq!(decoded, data);
346///     Ok(())
347/// }
348/// # example().unwrap();
349/// ```
350pub struct Base32HexNoPadding;
351
352impl Encoder for Base32 {
353    #[inline]
354    fn encoded_len(bin_len: usize) -> Result<usize, Error> {
355        Base32Impl::encoded_len(bin_len, Base32Variant::Standard)
356    }
357
358    #[inline]
359    fn encode<IN: AsRef<[u8]>>(b32: &mut [u8], bin: IN) -> Result<&[u8], Error> {
360        Base32Impl::encode(b32, bin.as_ref(), Base32Variant::Standard)
361    }
362}
363
364impl Decoder for Base32 {
365    #[inline]
366    fn decode<'t, IN: AsRef<[u8]>>(
367        bin: &'t mut [u8],
368        b32: IN,
369        ignore: Option<&[u8]>,
370    ) -> Result<&'t [u8], Error> {
371        Base32Impl::decode(bin, b32.as_ref(), ignore, Base32Variant::Standard)
372    }
373}
374
375impl Encoder for Base32NoPadding {
376    #[inline]
377    fn encoded_len(bin_len: usize) -> Result<usize, Error> {
378        Base32Impl::encoded_len(bin_len, Base32Variant::StandardNoPadding)
379    }
380
381    #[inline]
382    fn encode<IN: AsRef<[u8]>>(b32: &mut [u8], bin: IN) -> Result<&[u8], Error> {
383        Base32Impl::encode(b32, bin.as_ref(), Base32Variant::StandardNoPadding)
384    }
385}
386
387impl Decoder for Base32NoPadding {
388    #[inline]
389    fn decode<'t, IN: AsRef<[u8]>>(
390        bin: &'t mut [u8],
391        b32: IN,
392        ignore: Option<&[u8]>,
393    ) -> Result<&'t [u8], Error> {
394        Base32Impl::decode(bin, b32.as_ref(), ignore, Base32Variant::StandardNoPadding)
395    }
396}
397
398impl Encoder for Base32Hex {
399    #[inline]
400    fn encoded_len(bin_len: usize) -> Result<usize, Error> {
401        Base32Impl::encoded_len(bin_len, Base32Variant::Hex)
402    }
403
404    #[inline]
405    fn encode<IN: AsRef<[u8]>>(b32: &mut [u8], bin: IN) -> Result<&[u8], Error> {
406        Base32Impl::encode(b32, bin.as_ref(), Base32Variant::Hex)
407    }
408}
409
410impl Decoder for Base32Hex {
411    #[inline]
412    fn decode<'t, IN: AsRef<[u8]>>(
413        bin: &'t mut [u8],
414        b32: IN,
415        ignore: Option<&[u8]>,
416    ) -> Result<&'t [u8], Error> {
417        Base32Impl::decode(bin, b32.as_ref(), ignore, Base32Variant::Hex)
418    }
419}
420
421impl Encoder for Base32HexNoPadding {
422    #[inline]
423    fn encoded_len(bin_len: usize) -> Result<usize, Error> {
424        Base32Impl::encoded_len(bin_len, Base32Variant::HexNoPadding)
425    }
426
427    #[inline]
428    fn encode<IN: AsRef<[u8]>>(b32: &mut [u8], bin: IN) -> Result<&[u8], Error> {
429        Base32Impl::encode(b32, bin.as_ref(), Base32Variant::HexNoPadding)
430    }
431}
432
433impl Decoder for Base32HexNoPadding {
434    #[inline]
435    fn decode<'t, IN: AsRef<[u8]>>(
436        bin: &'t mut [u8],
437        b32: IN,
438        ignore: Option<&[u8]>,
439    ) -> Result<&'t [u8], Error> {
440        Base32Impl::decode(bin, b32.as_ref(), ignore, Base32Variant::HexNoPadding)
441    }
442}
443
444#[cfg(feature = "std")]
445#[test]
446fn test_base32() {
447    let test_vectors: &[(&[u8], &str)] = &[
448        (b"", ""),
449        (b"f", "MY======"),
450        (b"fo", "MZXQ===="),
451        (b"foo", "MZXW6==="),
452        (b"foob", "MZXW6YQ="),
453        (b"fooba", "MZXW6YTB"),
454        (b"foobar", "MZXW6YTBOI======"),
455    ];
456    for &(bin, expected) in test_vectors {
457        let b32 = Base32::encode_to_string(bin).unwrap();
458        assert_eq!(b32, expected);
459        let decoded = Base32::decode_to_vec(&b32, None).unwrap();
460        assert_eq!(decoded, bin);
461    }
462}
463
464#[cfg(feature = "std")]
465#[test]
466fn test_base32_no_padding() {
467    let test_vectors: &[(&[u8], &str)] = &[
468        (b"", ""),
469        (b"f", "MY"),
470        (b"fo", "MZXQ"),
471        (b"foo", "MZXW6"),
472        (b"foob", "MZXW6YQ"),
473        (b"fooba", "MZXW6YTB"),
474        (b"foobar", "MZXW6YTBOI"),
475    ];
476    for &(bin, expected) in test_vectors {
477        let b32 = Base32NoPadding::encode_to_string(bin).unwrap();
478        assert_eq!(b32, expected);
479        let decoded = Base32NoPadding::decode_to_vec(&b32, None).unwrap();
480        assert_eq!(decoded, bin);
481    }
482}
483
484#[cfg(feature = "std")]
485#[test]
486fn test_base32_hex() {
487    let test_vectors: &[(&[u8], &str)] = &[
488        (b"", ""),
489        (b"f", "CO======"),
490        (b"fo", "CPNG===="),
491        (b"foo", "CPNMU==="),
492        (b"foob", "CPNMUOG="),
493        (b"fooba", "CPNMUOJ1"),
494        (b"foobar", "CPNMUOJ1E8======"),
495    ];
496    for &(bin, expected) in test_vectors {
497        let b32 = Base32Hex::encode_to_string(bin).unwrap();
498        assert_eq!(b32, expected);
499        let decoded = Base32Hex::decode_to_vec(&b32, None).unwrap();
500        assert_eq!(decoded, bin);
501    }
502}
503
504#[cfg(feature = "std")]
505#[test]
506fn test_base32_hex_no_padding() {
507    let test_vectors: &[(&[u8], &str)] = &[
508        (b"", ""),
509        (b"f", "CO"),
510        (b"fo", "CPNG"),
511        (b"foo", "CPNMU"),
512        (b"foob", "CPNMUOG"),
513        (b"fooba", "CPNMUOJ1"),
514        (b"foobar", "CPNMUOJ1E8"),
515    ];
516    for &(bin, expected) in test_vectors {
517        let b32 = Base32HexNoPadding::encode_to_string(bin).unwrap();
518        assert_eq!(b32, expected);
519        let decoded = Base32HexNoPadding::decode_to_vec(&b32, None).unwrap();
520        assert_eq!(decoded, bin);
521    }
522}
523
524#[test]
525fn test_base32_no_std() {
526    let bin = [1u8, 5, 11, 15, 19, 131, 122];
527    let mut b32 = [0u8; 17];
528    let b32 = Base32::encode(&mut b32, bin).unwrap();
529    let expected = b"AECQWDYTQN5A====";
530    assert_eq!(b32, expected);
531    let mut bin2 = [0u8; 7];
532    let bin2 = Base32::decode(&mut bin2, b32, None).unwrap();
533    assert_eq!(bin, bin2);
534}
535
536#[test]
537fn test_base32_encoded_len() {
538    let test_vectors: &[(usize, usize, usize)] = &[
539        (0, 0, 0),
540        (1, 8, 2),
541        (2, 8, 4),
542        (3, 8, 5),
543        (4, 8, 7),
544        (5, 8, 8),
545        (6, 16, 10),
546    ];
547    for &(bin_len, padded_len, unpadded_len) in test_vectors {
548        assert_eq!(Base32::encoded_len(bin_len), Ok(padded_len));
549        assert_eq!(Base32NoPadding::encoded_len(bin_len), Ok(unpadded_len));
550        assert_eq!(Base32Hex::encoded_len(bin_len), Ok(padded_len));
551        assert_eq!(Base32HexNoPadding::encoded_len(bin_len), Ok(unpadded_len));
552    }
553}
554
555#[test]
556fn test_base32_encoded_len_overflow() {
557    let mult_overflow_bin_len = (usize::MAX / 8 + 1).checked_mul(5).unwrap();
558    assert_eq!(
559        Base32::encoded_len(mult_overflow_bin_len),
560        Err(Error::Overflow)
561    );
562    assert_eq!(
563        Base32NoPadding::encoded_len(mult_overflow_bin_len),
564        Err(Error::Overflow)
565    );
566    assert_eq!(
567        Base32Hex::encoded_len(mult_overflow_bin_len),
568        Err(Error::Overflow)
569    );
570    assert_eq!(
571        Base32HexNoPadding::encoded_len(mult_overflow_bin_len),
572        Err(Error::Overflow)
573    );
574
575    let add_overflow_bin_len = (usize::MAX / 8).checked_mul(5).unwrap() + 1;
576    assert_eq!(
577        Base32::encoded_len(add_overflow_bin_len),
578        Err(Error::Overflow)
579    );
580    assert_eq!(
581        Base32Hex::encoded_len(add_overflow_bin_len),
582        Err(Error::Overflow)
583    );
584}
585
586#[cfg(feature = "std")]
587#[test]
588fn test_base32_missing_padding() {
589    let missing_padding = "MY";
590    assert!(Base32::decode_to_vec(missing_padding, None).is_err());
591    assert!(Base32NoPadding::decode_to_vec(missing_padding, None).is_ok());
592    let missing_padding = "MZXQ";
593    assert!(Base32::decode_to_vec(missing_padding, None).is_err());
594    assert!(Base32NoPadding::decode_to_vec(missing_padding, None).is_ok());
595}
596
597#[cfg(feature = "std")]
598#[test]
599fn test_base32_invalid_padding() {
600    let valid_padding = "MY======";
601    assert_eq!(Base32::decode_to_vec(valid_padding, None), Ok(vec![b'f']));
602    let invalid_padding = "MY=====";
603    assert_eq!(
604        Base32::decode_to_vec(invalid_padding, None),
605        Err(Error::InvalidInput)
606    );
607    let invalid_padding = "MY=";
608    assert_eq!(
609        Base32::decode_to_vec(invalid_padding, None),
610        Err(Error::InvalidInput)
611    );
612}
613
614#[cfg(feature = "std")]
615#[test]
616fn test_base32_non_canonical() {
617    assert!(Base32::decode_to_vec("MZ======", None).is_err());
618    assert!(Base32NoPadding::decode_to_vec("MZ", None).is_err());
619}
620
621#[cfg(feature = "std")]
622#[test]
623fn test_base32_no_padding_rejects_padding() {
624    assert!(Base32NoPadding::decode_to_vec("MY======", None).is_err());
625    assert!(Base32NoPadding::decode_to_vec("MZXQ====", None).is_err());
626}
627
628#[cfg(feature = "std")]
629#[test]
630fn test_base32_hex_rejects_lowercase() {
631    assert!(Base32Hex::decode_to_vec("cpnmuoj1e8======", None).is_err());
632    assert!(Base32HexNoPadding::decode_to_vec("cpnmuoj1e8", None).is_err());
633}