ctoolbox/formats/
base64.rs

1use std::collections::HashMap;
2
3use anyhow::{Result, anyhow};
4use base64::Engine;
5use base64::prelude::BASE64_STANDARD;
6
7use crate::bail_if_none;
8
9/// Converts bytes to standard RFC 4648 base64 representation.
10/// <https://datatracker.ietf.org/doc/html/rfc4648#section-4>
11pub fn bytes_to_standard_base64(bytes: &[u8]) -> String {
12    BASE64_STANDARD.encode(bytes)
13}
14
15pub fn standard_base64_to_bytes(base64: String) -> Result<Vec<u8>> {
16    BASE64_STANDARD
17        .decode(base64)
18        .map_err(|e| anyhow!("Failed to decode base64: {e}"))
19}
20
21/// Converts base64 input string to decimal representation. Padding becomes 64.
22pub fn standard_base64_to_decimal(base64: String) -> Result<Vec<u8>> {
23    let alphabet = base64::alphabet::STANDARD.as_str();
24
25    // build an index of the base64 alphabet
26    let index: HashMap<u8, u8> = alphabet
27        .chars()
28        .enumerate()
29        .map(|(i, c)| (u8::try_from(c).unwrap(), u8::try_from(i).unwrap()))
30        .collect();
31
32    let mut out = Vec::new();
33    // convert the decoded bytes to decimal using the index
34    for byte in base64.bytes() {
35        if byte == 61 {
36            out.push(64);
37        } else {
38            out.push(bail_if_none!(index.get(&byte).copied()));
39        }
40    }
41    Ok(out)
42}
43
44pub fn decimal_to_standard_base64(decimal: Vec<u8>) -> Result<String> {
45    let alphabet = base64::alphabet::STANDARD.as_str();
46    let mut out = String::new();
47
48    for &value in &decimal {
49        let v = value;
50        if v == 64 {
51            out.push('=');
52            continue;
53        }
54        out.push(bail_if_none!(alphabet.chars().nth(usize::from(v))));
55    }
56
57    Ok(out)
58}
59
60#[cfg(test)]
61mod tests {
62    use crate::formats::assert_string_ok_eq;
63    use crate::utilities::{assert_vec_u8_eq, assert_vec_u8_ok_eq};
64
65    use super::*;
66
67    #[crate::ctb_test]
68    fn test_bytes_to_standard_base64() {
69        let input = b"Hello, world!";
70        let expected = "SGVsbG8sIHdvcmxkIQ==";
71        let result = bytes_to_standard_base64(input);
72        assert_eq!(result, expected);
73    }
74
75    #[crate::ctb_test]
76    fn test_standard_base64_to_bytes() {
77        let input = "SGVsbG8sIHdvcmxkIQ==".to_string();
78        let expected = b"Hello, world!";
79        let result = standard_base64_to_bytes(input).unwrap();
80        assert_vec_u8_eq(expected, &result);
81    }
82
83    #[crate::ctb_test]
84    fn test_base64_to_decimal() {
85        let input = "SGVsbG8sIHdvcmxkIQ==";
86        let expected = vec![
87            18, 6, 21, 44, 27, 6, 60, 44, 8, 7, 29, 47, 28, 38, 49, 36, 8, 16,
88            64, 64,
89        ];
90        let result = standard_base64_to_decimal(input.to_string());
91        assert_vec_u8_ok_eq(&expected, result);
92    }
93
94    #[crate::ctb_test]
95    fn test_decimal_to_base64() {
96        let input = vec![
97            18, 6, 21, 44, 27, 6, 60, 44, 8, 7, 29, 47, 28, 38, 49, 36, 8, 16,
98            64, 64,
99        ];
100        let expected = "SGVsbG8sIHdvcmxkIQ==";
101        let result = decimal_to_standard_base64(input);
102        assert_string_ok_eq(expected, result);
103    }
104}