Skip to main content

dynomite/crypto/
base64.rs

1//! Base64 encoding helpers.
2//!
3//! Wraps the workspace `base64` crate with the standard alphabet.
4//! Encoding emits padded output with no embedded newlines (the
5//! trailing `=` padding is preserved). Decoding accepts both padded
6//! and unpadded inputs.
7
8use base64::engine::general_purpose::STANDARD;
9use base64::engine::general_purpose::STANDARD_NO_PAD;
10use base64::Engine;
11
12use crate::crypto::CryptoError;
13
14/// Encode `bytes` to a base64 string using the standard alphabet
15/// with trailing `=` padding (RFC 4648).
16///
17/// # Examples
18///
19/// ```
20/// use dynomite::crypto::base64_encode;
21/// assert_eq!(base64_encode(b"hi"), "aGk=");
22/// assert_eq!(base64_encode(b"Hello"), "SGVsbG8=");
23/// assert_eq!(base64_encode(b""), "");
24/// ```
25pub fn base64_encode(bytes: &[u8]) -> String {
26    STANDARD.encode(bytes)
27}
28
29/// Decode a base64 string. Accepts both padded and unpadded inputs
30/// using the standard alphabet.
31///
32/// # Examples
33///
34/// ```
35/// use dynomite::crypto::base64_decode;
36/// assert_eq!(base64_decode("aGk=").unwrap(), b"hi");
37/// assert_eq!(base64_decode("aGk").unwrap(), b"hi");
38/// assert!(base64_decode("not base64!@#").is_err());
39/// ```
40pub fn base64_decode(s: &str) -> Result<Vec<u8>, CryptoError> {
41    if s.contains('=') {
42        STANDARD
43            .decode(s.as_bytes())
44            .map_err(|e| CryptoError::Base64(e.to_string()))
45    } else {
46        STANDARD_NO_PAD
47            .decode(s.as_bytes())
48            .map_err(|e| CryptoError::Base64(e.to_string()))
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn empty_round_trip() {
58        assert_eq!(base64_encode(b""), "");
59        assert_eq!(base64_decode("").unwrap(), Vec::<u8>::new());
60    }
61
62    #[test]
63    fn standard_vectors() {
64        // RFC 4648 test vectors with padding.
65        assert_eq!(base64_encode(b"f"), "Zg==");
66        assert_eq!(base64_encode(b"fo"), "Zm8=");
67        assert_eq!(base64_encode(b"foo"), "Zm9v");
68        assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
69        assert_eq!(base64_encode(b"fooba"), "Zm9vYmE=");
70        assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
71    }
72
73    #[test]
74    fn unpadded_decodes_too() {
75        assert_eq!(base64_decode("Zg").unwrap(), b"f");
76        assert_eq!(base64_decode("Zm8").unwrap(), b"fo");
77    }
78
79    #[test]
80    fn invalid_input_errors() {
81        assert!(base64_decode("@@@").is_err());
82        assert!(base64_decode("####").is_err());
83    }
84}