oxihuman_core/
base64_codec.rs1#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct Base64Config {
9 pub url_safe: bool,
10}
11
12#[allow(dead_code)]
13pub fn default_base64_config() -> Base64Config {
14 Base64Config { url_safe: false }
15}
16
17const STANDARD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
18const URL_SAFE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
19
20#[allow(dead_code)]
21fn alphabet(url_safe: bool) -> &'static [u8; 64] {
22 if url_safe {
23 URL_SAFE
24 } else {
25 STANDARD
26 }
27}
28
29#[allow(dead_code)]
30pub fn base64_encode(bytes: &[u8]) -> String {
31 base64_encode_with_config(bytes, false)
32}
33
34#[allow(dead_code)]
35fn base64_encode_with_config(bytes: &[u8], url_safe: bool) -> String {
36 let table = alphabet(url_safe);
37 let mut out = Vec::with_capacity(base64_encoded_len(bytes.len()));
38 let mut i = 0;
39 while i + 3 <= bytes.len() {
40 let b0 = bytes[i] as u32;
41 let b1 = bytes[i + 1] as u32;
42 let b2 = bytes[i + 2] as u32;
43 out.push(table[((b0 >> 2) & 0x3f) as usize]);
44 out.push(table[((b0 << 4 | b1 >> 4) & 0x3f) as usize]);
45 out.push(table[((b1 << 2 | b2 >> 6) & 0x3f) as usize]);
46 out.push(table[(b2 & 0x3f) as usize]);
47 i += 3;
48 }
49 let rem = bytes.len() - i;
50 if rem == 1 {
51 let b0 = bytes[i] as u32;
52 out.push(table[((b0 >> 2) & 0x3f) as usize]);
53 out.push(table[((b0 << 4) & 0x3f) as usize]);
54 out.push(b'=');
55 out.push(b'=');
56 } else if rem == 2 {
57 let b0 = bytes[i] as u32;
58 let b1 = bytes[i + 1] as u32;
59 out.push(table[((b0 >> 2) & 0x3f) as usize]);
60 out.push(table[((b0 << 4 | b1 >> 4) & 0x3f) as usize]);
61 out.push(table[((b1 << 2) & 0x3f) as usize]);
62 out.push(b'=');
63 }
64 unsafe { String::from_utf8_unchecked(out) }
66}
67
68#[allow(dead_code)]
69pub fn base64_decode(s: &str) -> Result<Vec<u8>, String> {
70 let s = s.trim_end_matches('=');
71 let mut out = Vec::with_capacity(base64_decoded_len(s.len() + (4 - s.len() % 4) % 4));
72 let table: [i8; 256] = {
73 let mut t = [-1i8; 256];
74 for (i, &b) in STANDARD.iter().enumerate() {
75 t[b as usize] = i as i8;
76 }
77 t[b'-' as usize] = 62;
79 t[b'_' as usize] = 63;
80 t
81 };
82 let bytes = s.as_bytes();
83 let mut i = 0;
84 while i + 4 <= bytes.len() {
85 let v: Vec<i8> = (0..4).map(|j| table[bytes[i + j] as usize]).collect();
86 if v.iter().any(|&x| x < 0) {
87 return Err("invalid base64 character".to_string());
88 }
89 let (c0, c1, c2, c3) = (v[0] as u8, v[1] as u8, v[2] as u8, v[3] as u8);
90 out.push((c0 << 2) | (c1 >> 4));
91 out.push((c1 << 4) | (c2 >> 2));
92 out.push((c2 << 6) | c3);
93 i += 4;
94 }
95 let rem = bytes.len() - i;
96 if rem == 2 {
97 let (c0, c1) = (table[bytes[i] as usize], table[bytes[i + 1] as usize]);
98 if c0 < 0 || c1 < 0 {
99 return Err("invalid base64 character".to_string());
100 }
101 out.push(((c0 as u8) << 2) | ((c1 as u8) >> 4));
102 } else if rem == 3 {
103 let (c0, c1, c2) = (
104 table[bytes[i] as usize],
105 table[bytes[i + 1] as usize],
106 table[bytes[i + 2] as usize],
107 );
108 if c0 < 0 || c1 < 0 || c2 < 0 {
109 return Err("invalid base64 character".to_string());
110 }
111 out.push(((c0 as u8) << 2) | ((c1 as u8) >> 4));
112 out.push(((c1 as u8) << 4) | ((c2 as u8) >> 2));
113 } else if rem == 1 {
114 return Err("invalid base64 length".to_string());
115 }
116 Ok(out)
117}
118
119#[allow(dead_code)]
120pub fn base64_encode_str(s: &str) -> String {
121 base64_encode(s.as_bytes())
122}
123
124#[allow(dead_code)]
125pub fn base64_is_valid(s: &str) -> bool {
126 base64_decode(s).is_ok()
127}
128
129#[allow(dead_code)]
130pub fn base64_encoded_len(byte_len: usize) -> usize {
131 byte_len.div_ceil(3) * 4
132}
133
134#[allow(dead_code)]
135pub fn base64_decoded_len(encoded_len: usize) -> usize {
136 (encoded_len / 4) * 3
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_default_config() {
145 let cfg = default_base64_config();
146 assert!(!cfg.url_safe);
147 }
148
149 #[test]
150 fn test_encode_empty() {
151 assert_eq!(base64_encode(b""), "");
152 }
153
154 #[test]
155 fn test_encode_hello() {
156 assert_eq!(base64_encode(b"hello"), "aGVsbG8=");
157 }
158
159 #[test]
160 fn test_roundtrip_ascii() {
161 let original = b"Hello, World!";
162 let encoded = base64_encode(original);
163 let decoded = base64_decode(&encoded).expect("should succeed");
164 assert_eq!(decoded, original);
165 }
166
167 #[test]
168 fn test_roundtrip_binary() {
169 let original: Vec<u8> = (0u8..=255).collect();
170 let encoded = base64_encode(&original);
171 let decoded = base64_decode(&encoded).expect("should succeed");
172 assert_eq!(decoded, original);
173 }
174
175 #[test]
176 fn test_encode_str() {
177 let s = "test";
178 let encoded = base64_encode_str(s);
179 let decoded = base64_decode(&encoded).expect("should succeed");
180 assert_eq!(decoded, s.as_bytes());
181 }
182
183 #[test]
184 fn test_is_valid() {
185 assert!(base64_is_valid("aGVsbG8="));
186 assert!(!base64_is_valid("!!!invalid!!!"));
187 }
188
189 #[test]
190 fn test_encoded_len() {
191 assert_eq!(base64_encoded_len(3), 4);
192 assert_eq!(base64_encoded_len(4), 8);
193 }
194
195 #[test]
196 fn test_decoded_len() {
197 assert_eq!(base64_decoded_len(4), 3);
198 assert_eq!(base64_decoded_len(8), 6);
199 }
200}