ct_codecs/
base64.rs

1use crate::error::*;
2use crate::{Decoder, Encoder};
3
4struct Base64Impl;
5
6#[derive(Copy, Clone, Debug, Eq, PartialEq)]
7enum Base64Variant {
8    Original = 1,
9    OriginalNoPadding = 3,
10    UrlSafe = 5,
11    UrlSafeNoPadding = 7,
12}
13
14enum VariantMask {
15    NoPadding = 2,
16    UrlSafe = 4,
17}
18
19impl Base64Impl {
20    #[inline]
21    fn _eq(x: u8, y: u8) -> u8 {
22        !(((0u16.wrapping_sub((x as u16) ^ (y as u16))) >> 8) as u8)
23    }
24
25    #[inline]
26    fn _gt(x: u8, y: u8) -> u8 {
27        (((y as u16).wrapping_sub(x as u16)) >> 8) as u8
28    }
29
30    #[inline]
31    fn _ge(x: u8, y: u8) -> u8 {
32        !Self::_gt(y, x)
33    }
34
35    #[inline]
36    fn _lt(x: u8, y: u8) -> u8 {
37        Self::_gt(y, x)
38    }
39
40    #[inline]
41    fn _le(x: u8, y: u8) -> u8 {
42        Self::_ge(y, x)
43    }
44
45    #[inline]
46    fn b64_byte_to_char(x: u8) -> u8 {
47        (Self::_lt(x, 26) & (x.wrapping_add(b'A')))
48            | (Self::_ge(x, 26) & Self::_lt(x, 52) & (x.wrapping_add(b'a'.wrapping_sub(26))))
49            | (Self::_ge(x, 52) & Self::_lt(x, 62) & (x.wrapping_add(b'0'.wrapping_sub(52))))
50            | (Self::_eq(x, 62) & b'+')
51            | (Self::_eq(x, 63) & b'/')
52    }
53
54    #[inline]
55    fn b64_char_to_byte(c: u8) -> u8 {
56        let x = (Self::_ge(c, b'A') & Self::_le(c, b'Z') & (c.wrapping_sub(b'A')))
57            | (Self::_ge(c, b'a') & Self::_le(c, b'z') & (c.wrapping_sub(b'a'.wrapping_sub(26))))
58            | (Self::_ge(c, b'0') & Self::_le(c, b'9') & (c.wrapping_sub(b'0'.wrapping_sub(52))))
59            | (Self::_eq(c, b'+') & 62)
60            | (Self::_eq(c, b'/') & 63);
61        x | (Self::_eq(x, 0) & (Self::_eq(c, b'A') ^ 0xff))
62    }
63
64    #[inline]
65    fn b64_byte_to_urlsafe_char(x: u8) -> u8 {
66        (Self::_lt(x, 26) & (x.wrapping_add(b'A')))
67            | (Self::_ge(x, 26) & Self::_lt(x, 52) & (x.wrapping_add(b'a'.wrapping_sub(26))))
68            | (Self::_ge(x, 52) & Self::_lt(x, 62) & (x.wrapping_add(b'0'.wrapping_sub(52))))
69            | (Self::_eq(x, 62) & b'-')
70            | (Self::_eq(x, 63) & b'_')
71    }
72
73    #[inline]
74    fn b64_urlsafe_char_to_byte(c: u8) -> u8 {
75        let x = (Self::_ge(c, b'A') & Self::_le(c, b'Z') & (c.wrapping_sub(b'A')))
76            | (Self::_ge(c, b'a') & Self::_le(c, b'z') & (c.wrapping_sub(b'a'.wrapping_sub(26))))
77            | (Self::_ge(c, b'0') & Self::_le(c, b'9') & (c.wrapping_sub(b'0'.wrapping_sub(52))))
78            | (Self::_eq(c, b'-') & 62)
79            | (Self::_eq(c, b'_') & 63);
80        x | (Self::_eq(x, 0) & (Self::_eq(c, b'A') ^ 0xff))
81    }
82
83    #[inline]
84    fn encoded_len(bin_len: usize, variant: Base64Variant) -> Result<usize, Error> {
85        let nibbles = bin_len / 3;
86        let rounded = nibbles * 3;
87        let pad = bin_len - rounded;
88        Ok(nibbles.checked_mul(4).ok_or(Error::Overflow)?
89            + ((pad | (pad >> 1)) & 1)
90                * (4 - (!((((variant as usize) & 2) >> 1).wrapping_sub(1)) & (3 - pad)))
91            + 1)
92    }
93
94    pub fn encode<'t>(
95        b64: &'t mut [u8],
96        bin: &[u8],
97        variant: Base64Variant,
98    ) -> Result<&'t [u8], Error> {
99        let bin_len = bin.len();
100        let b64_maxlen = b64.len();
101        let mut acc_len = 0usize;
102        let mut b64_pos = 0usize;
103        let mut acc = 0u16;
104
105        let nibbles = bin_len / 3;
106        let remainder = bin_len - 3 * nibbles;
107        let mut b64_len = nibbles * 4;
108        if remainder != 0 {
109            if (variant as u16 & VariantMask::NoPadding as u16) == 0 {
110                b64_len += 4;
111            } else {
112                b64_len += 2 + (remainder >> 1);
113            }
114        }
115        if b64_maxlen < b64_len {
116            return Err(Error::Overflow);
117        }
118        if (variant as u16 & VariantMask::UrlSafe as u16) != 0 {
119            for &v in bin {
120                acc = (acc << 8) + v as u16;
121                acc_len += 8;
122                while acc_len >= 6 {
123                    acc_len -= 6;
124                    b64[b64_pos] = Self::b64_byte_to_urlsafe_char(((acc >> acc_len) & 0x3f) as u8);
125                    b64_pos += 1;
126                }
127            }
128            if acc_len > 0 {
129                b64[b64_pos] =
130                    Self::b64_byte_to_urlsafe_char(((acc << (6 - acc_len)) & 0x3f) as u8);
131                b64_pos += 1;
132            }
133        } else {
134            for &v in bin {
135                acc = (acc << 8) + v as u16;
136                acc_len += 8;
137                while acc_len >= 6 {
138                    acc_len -= 6;
139                    b64[b64_pos] = Self::b64_byte_to_char(((acc >> acc_len) & 0x3f) as u8);
140                    b64_pos += 1;
141                }
142            }
143            if acc_len > 0 {
144                b64[b64_pos] = Self::b64_byte_to_char(((acc << (6 - acc_len)) & 0x3f) as u8);
145                b64_pos += 1;
146            }
147        }
148        while b64_pos < b64_len {
149            b64[b64_pos] = b'=';
150            b64_pos += 1
151        }
152        Ok(&b64[..b64_pos])
153    }
154
155    fn skip_padding<'t>(
156        b64: &'t [u8],
157        mut padding_len: usize,
158        ignore: Option<&[u8]>,
159    ) -> Result<&'t [u8], Error> {
160        let b64_len = b64.len();
161        let mut b64_pos = 0usize;
162        while padding_len > 0 {
163            if b64_pos >= b64_len {
164                return Err(Error::InvalidInput);
165            }
166            let c = b64[b64_pos];
167            if c == b'=' {
168                padding_len -= 1
169            } else {
170                match ignore {
171                    Some(ignore) if ignore.contains(&c) => {}
172                    _ => return Err(Error::InvalidInput),
173                }
174            }
175            b64_pos += 1
176        }
177        Ok(&b64[b64_pos..])
178    }
179
180    pub fn decode<'t>(
181        bin: &'t mut [u8],
182        b64: &[u8],
183        ignore: Option<&[u8]>,
184        variant: Base64Variant,
185    ) -> Result<&'t [u8], Error> {
186        let bin_maxlen = bin.len();
187        let is_urlsafe = (variant as u16 & VariantMask::UrlSafe as u16) != 0;
188        let mut acc = 0u16;
189        let mut acc_len = 0usize;
190        let mut bin_pos = 0usize;
191        let mut premature_end = None;
192        for (b64_pos, &c) in b64.iter().enumerate() {
193            let d = if is_urlsafe {
194                Self::b64_urlsafe_char_to_byte(c)
195            } else {
196                Self::b64_char_to_byte(c)
197            };
198            if d == 0xff {
199                match ignore {
200                    Some(ignore) if ignore.contains(&c) => continue,
201                    _ => {
202                        premature_end = Some(b64_pos);
203                        break;
204                    }
205                }
206            }
207            acc = (acc << 6) + d as u16;
208            acc_len += 6;
209            if acc_len >= 8 {
210                acc_len -= 8;
211                if bin_pos >= bin_maxlen {
212                    return Err(Error::Overflow);
213                }
214                bin[bin_pos] = (acc >> acc_len) as u8;
215                bin_pos += 1;
216            }
217        }
218        if acc_len > 4 || (acc & ((1u16 << acc_len).wrapping_sub(1))) != 0 {
219            return Err(Error::InvalidInput);
220        }
221        let padding_len = acc_len / 2;
222        if let Some(premature_end) = premature_end {
223            let remaining = if variant as u16 & VariantMask::NoPadding as u16 == 0 {
224                Self::skip_padding(&b64[premature_end..], padding_len, ignore)?
225            } else {
226                &b64[premature_end..]
227            };
228            match ignore {
229                None => {
230                    if !remaining.is_empty() {
231                        return Err(Error::InvalidInput);
232                    }
233                }
234                Some(ignore) => {
235                    for &c in remaining {
236                        if !ignore.contains(&c) {
237                            return Err(Error::InvalidInput);
238                        }
239                    }
240                }
241            }
242        } else if variant as u16 & VariantMask::NoPadding as u16 == 0 && padding_len != 0 {
243            return Err(Error::InvalidInput);
244        }
245        Ok(&bin[..bin_pos])
246    }
247}
248
249/// Standard Base64 encoder and decoder with padding.
250///
251/// This implementation follows the standard Base64 encoding as defined in RFC 4648,
252/// and includes padding characters ('=') when needed.
253///
254/// # Standard Base64 Alphabet
255///
256/// The standard Base64 alphabet uses characters:
257/// - 'A' to 'Z' (26 characters)
258/// - 'a' to 'z' (26 characters)
259/// - '0' to '9' (10 characters)
260/// - '+' and '/' (2 characters)
261/// - '=' (padding character)
262///
263/// # Examples
264///
265/// ```
266/// use ct_codecs::{Base64, Encoder, Decoder};
267///
268/// let data = b"Hello, world!";
269/// # let result = 
270/// let encoded = Base64::encode_to_string(data)?;
271/// assert_eq!(encoded, "SGVsbG8sIHdvcmxkIQ==");
272///
273/// let decoded = Base64::decode_to_vec(&encoded, None)?;
274/// assert_eq!(decoded, data);
275/// # Ok::<(), ct_codecs::Error>(())
276/// ```
277pub struct Base64;
278
279/// Standard Base64 encoder and decoder without padding.
280///
281/// This implementation follows the standard Base64 encoding as defined in RFC 4648,
282/// but omits padding characters ('=').
283///
284/// # Examples
285///
286/// ```
287/// use ct_codecs::{Base64NoPadding, Encoder, Decoder};
288///
289/// let data = b"Hello, world!";
290/// # let result = 
291/// let encoded = Base64NoPadding::encode_to_string(data)?;
292/// assert_eq!(encoded, "SGVsbG8sIHdvcmxkIQ");
293///
294/// let decoded = Base64NoPadding::decode_to_vec(&encoded, None)?;
295/// assert_eq!(decoded, data);
296/// # Ok::<(), ct_codecs::Error>(())
297/// ```
298pub struct Base64NoPadding;
299
300/// URL-safe Base64 encoder and decoder with padding.
301///
302/// This implementation follows the URL-safe Base64 encoding variant as defined in RFC 4648.
303/// It replaces the '+' and '/' characters with '-' and '_' to make the output URL and
304/// filename safe. Padding characters ('=') are included when needed.
305///
306/// # URL-safe Base64 Alphabet
307///
308/// The URL-safe Base64 alphabet uses characters:
309/// - 'A' to 'Z' (26 characters)
310/// - 'a' to 'z' (26 characters)
311/// - '0' to '9' (10 characters)
312/// - '-' and '_' (2 characters)
313/// - '=' (padding character)
314///
315/// # Examples
316///
317/// ```
318/// use ct_codecs::{Base64UrlSafe, Encoder, Decoder};
319///
320/// let data = b"Hello, world!";
321/// # let result = 
322/// let encoded = Base64UrlSafe::encode_to_string(data)?;
323/// assert_eq!(encoded, "SGVsbG8sIHdvcmxkIQ==");
324///
325/// // If the input contains characters that would be escaped in URLs
326/// let binary_data = &[251, 239, 190, 222];
327/// let encoded = Base64UrlSafe::encode_to_string(binary_data)?;
328/// assert_eq!(encoded, "---e3g==");
329/// # Ok::<(), ct_codecs::Error>(())
330/// ```
331pub struct Base64UrlSafe;
332
333/// URL-safe Base64 encoder and decoder without padding.
334///
335/// This implementation follows the URL-safe Base64 encoding variant as defined in RFC 4648,
336/// but omits padding characters ('='). This is particularly useful for URLs, where the
337/// padding character may need to be percent-encoded.
338///
339/// # Examples
340///
341/// ```
342/// use ct_codecs::{Base64UrlSafeNoPadding, Encoder, Decoder};
343///
344/// let data = b"Hello, world!";
345/// # let result = 
346/// let encoded = Base64UrlSafeNoPadding::encode_to_string(data)?;
347/// assert_eq!(encoded, "SGVsbG8sIHdvcmxkIQ");
348///
349/// // With binary data containing characters that would be escaped in URLs
350/// let binary_data = &[251, 239, 190, 222];
351/// let encoded = Base64UrlSafeNoPadding::encode_to_string(binary_data)?;
352/// assert_eq!(encoded, "---e3g");
353/// # Ok::<(), ct_codecs::Error>(())
354/// ```
355pub struct Base64UrlSafeNoPadding;
356
357impl Encoder for Base64 {
358    #[inline]
359    fn encoded_len(bin_len: usize) -> Result<usize, Error> {
360        Base64Impl::encoded_len(bin_len, Base64Variant::Original)
361    }
362
363    #[inline]
364    fn encode<IN: AsRef<[u8]>>(b64: &mut [u8], bin: IN) -> Result<&[u8], Error> {
365        Base64Impl::encode(b64, bin.as_ref(), Base64Variant::Original)
366    }
367}
368
369impl Decoder for Base64 {
370    #[inline]
371    fn decode<'t, IN: AsRef<[u8]>>(
372        bin: &'t mut [u8],
373        b64: IN,
374        ignore: Option<&[u8]>,
375    ) -> Result<&'t [u8], Error> {
376        Base64Impl::decode(bin, b64.as_ref(), ignore, Base64Variant::Original)
377    }
378}
379
380impl Encoder for Base64NoPadding {
381    #[inline]
382    fn encoded_len(bin_len: usize) -> Result<usize, Error> {
383        Base64Impl::encoded_len(bin_len, Base64Variant::OriginalNoPadding)
384    }
385
386    #[inline]
387    fn encode<IN: AsRef<[u8]>>(b64: &mut [u8], bin: IN) -> Result<&[u8], Error> {
388        Base64Impl::encode(b64, bin.as_ref(), Base64Variant::OriginalNoPadding)
389    }
390}
391
392impl Decoder for Base64NoPadding {
393    #[inline]
394    fn decode<'t, IN: AsRef<[u8]>>(
395        bin: &'t mut [u8],
396        b64: IN,
397        ignore: Option<&[u8]>,
398    ) -> Result<&'t [u8], Error> {
399        Base64Impl::decode(bin, b64.as_ref(), ignore, Base64Variant::OriginalNoPadding)
400    }
401}
402
403impl Encoder for Base64UrlSafe {
404    #[inline]
405    fn encoded_len(bin_len: usize) -> Result<usize, Error> {
406        Base64Impl::encoded_len(bin_len, Base64Variant::UrlSafe)
407    }
408
409    #[inline]
410    fn encode<IN: AsRef<[u8]>>(b64: &mut [u8], bin: IN) -> Result<&[u8], Error> {
411        Base64Impl::encode(b64, bin.as_ref(), Base64Variant::UrlSafe)
412    }
413}
414
415impl Decoder for Base64UrlSafe {
416    #[inline]
417    fn decode<'t, IN: AsRef<[u8]>>(
418        bin: &'t mut [u8],
419        b64: IN,
420        ignore: Option<&[u8]>,
421    ) -> Result<&'t [u8], Error> {
422        Base64Impl::decode(bin, b64.as_ref(), ignore, Base64Variant::UrlSafe)
423    }
424}
425
426impl Encoder for Base64UrlSafeNoPadding {
427    #[inline]
428    fn encoded_len(bin_len: usize) -> Result<usize, Error> {
429        Base64Impl::encoded_len(bin_len, Base64Variant::UrlSafeNoPadding)
430    }
431
432    #[inline]
433    fn encode<IN: AsRef<[u8]>>(b64: &mut [u8], bin: IN) -> Result<&[u8], Error> {
434        Base64Impl::encode(b64, bin.as_ref(), Base64Variant::UrlSafeNoPadding)
435    }
436}
437
438impl Decoder for Base64UrlSafeNoPadding {
439    #[inline]
440    fn decode<'t, IN: AsRef<[u8]>>(
441        bin: &'t mut [u8],
442        b64: IN,
443        ignore: Option<&[u8]>,
444    ) -> Result<&'t [u8], Error> {
445        Base64Impl::decode(bin, b64.as_ref(), ignore, Base64Variant::UrlSafeNoPadding)
446    }
447}
448
449#[cfg(feature = "std")]
450#[test]
451fn test_base64() {
452    let bin = [1u8, 5, 11, 15, 19, 131, 122];
453    let expected = "AQULDxODeg==";
454    let b64 = Base64::encode_to_string(bin).unwrap();
455    assert_eq!(b64, expected);
456    let bin2 = Base64::decode_to_vec(&b64, None).unwrap();
457    assert_eq!(bin, &bin2[..]);
458}
459
460#[cfg(feature = "std")]
461#[test]
462fn test_base64_mising_padding() {
463    let missing_padding = "AA";
464    assert!(Base64::decode_to_vec(missing_padding, None).is_err());
465    assert!(Base64NoPadding::decode_to_vec(missing_padding, None).is_ok());
466    let missing_padding = "AAA";
467    assert!(Base64::decode_to_vec(missing_padding, None).is_err());
468    assert!(Base64NoPadding::decode_to_vec(missing_padding, None).is_ok());
469}
470
471#[test]
472fn test_base64_no_std() {
473    let bin = [1u8, 5, 11, 15, 19, 131, 122];
474    let expected = [65, 81, 85, 76, 68, 120, 79, 68, 101, 103, 61, 61];
475    let mut b64 = [0u8; 12];
476    let b64 = Base64::encode(&mut b64, bin).unwrap();
477    assert_eq!(b64, expected);
478    let mut bin2 = [0u8; 7];
479    let bin2 = Base64::decode(&mut bin2, b64, None).unwrap();
480    assert_eq!(bin, bin2);
481}
482
483#[test]
484fn test_base64_invalid_padding() {
485    let valid_padding = "AA==";
486    assert_eq!(Base64::decode_to_vec(valid_padding, None), Ok(vec![0u8; 1]));
487    let invalid_padding = "AA=";
488    assert_eq!(
489        Base64::decode_to_vec(invalid_padding, None),
490        Err(Error::InvalidInput)
491    );
492}