1use crate::error::{Error, Result};
14
15const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
17
18#[must_use]
28pub const fn encoded_len(len: usize) -> usize {
29 len.div_ceil(3) * 4
30}
31
32pub fn base64_encode(data: &[u8], out: &mut [u8]) -> Result<()> {
49 let needed = encoded_len(data.len());
50 if out.len() < needed {
51 return Err(Error::too_large("base64 output", needed));
52 }
53
54 let mut chunks = data.chunks_exact(3);
55 let mut cursor = 0usize;
56 for chunk in chunks.by_ref() {
57 let n = (u32::from(chunk[0]) << 16) | (u32::from(chunk[1]) << 8) | u32::from(chunk[2]);
58 out[cursor] = ALPHABET[((n >> 18) & 0x3f) as usize];
59 out[cursor + 1] = ALPHABET[((n >> 12) & 0x3f) as usize];
60 out[cursor + 2] = ALPHABET[((n >> 6) & 0x3f) as usize];
61 out[cursor + 3] = ALPHABET[(n & 0x3f) as usize];
62 cursor += 4;
63 }
64 let rem = chunks.remainder();
65 match rem.len() {
66 0 => {}
67 1 => {
68 let n = u32::from(rem[0]) << 16;
69 out[cursor] = ALPHABET[((n >> 18) & 0x3f) as usize];
70 out[cursor + 1] = ALPHABET[((n >> 12) & 0x3f) as usize];
71 out[cursor + 2] = b'=';
72 out[cursor + 3] = b'=';
73 }
74 _ => {
75 let n = (u32::from(rem[0]) << 16) | (u32::from(rem[1]) << 8);
76 out[cursor] = ALPHABET[((n >> 18) & 0x3f) as usize];
77 out[cursor + 1] = ALPHABET[((n >> 12) & 0x3f) as usize];
78 out[cursor + 2] = ALPHABET[((n >> 6) & 0x3f) as usize];
79 out[cursor + 3] = b'=';
80 }
81 }
82 Ok(())
83}
84
85fn sextet(byte: u8) -> Result<u32> {
87 let value = match byte {
88 b'A'..=b'Z' => byte - b'A',
89 b'a'..=b'z' => byte - b'a' + 26,
90 b'0'..=b'9' => byte - b'0' + 52,
91 b'+' => 62,
92 b'/' => 63,
93 _ => return Err(Error::BadValue("base64 input")),
94 };
95 Ok(u32::from(value))
96}
97
98pub fn base64_decode(encoded: &[u8], out: &mut [u8]) -> Result<usize> {
119 if encoded.len() % 4 != 0 {
120 return Err(Error::BadValue("base64 input"));
121 }
122 let worst = encoded.len() / 4 * 3;
123 if out.len() < worst {
124 return Err(Error::too_large("base64 output", worst));
125 }
126 let pad = match encoded {
128 [.., b'=', b'='] => 2,
129 [.., b'='] => 1,
130 _ => 0,
131 };
132 let body = encoded.len() - pad;
133
134 let mut written = 0usize;
135 let mut acc: u32 = 0;
136 let mut bits: u32 = 0;
137 for (i, &byte) in encoded.iter().enumerate() {
138 if byte == b'=' {
139 if i < body {
141 return Err(Error::BadValue("base64 input"));
142 }
143 continue;
144 }
145 acc = (acc << 6) | sextet(byte)?;
146 bits += 6;
147 if bits >= 8 {
148 bits -= 8;
149 out[written] = ((acc >> bits) & 0xff) as u8;
150 written += 1;
151 }
152 }
153 if acc & ((1 << bits) - 1) != 0 {
156 return Err(Error::BadValue("base64 input"));
157 }
158 Ok(written)
159}
160
161#[cfg(test)]
162mod tests {
163 use super::{base64_decode, base64_encode, encoded_len};
164
165 #[test]
167 fn rfc_4648_s10_vectors() {
168 for (plain, encoded) in [
169 (&b""[..], &b""[..]),
170 (b"f", b"Zg=="),
171 (b"fo", b"Zm8="),
172 (b"foo", b"Zm9v"),
173 (b"foob", b"Zm9vYg=="),
174 (b"fooba", b"Zm9vYmE="),
175 (b"foobar", b"Zm9vYmFy"),
176 ] {
177 let mut out = [0u8; 16];
178 base64_encode(plain, &mut out[..encoded_len(plain.len())]).unwrap();
179 assert_eq!(&out[..encoded_len(plain.len())], encoded, "{plain:?}");
180 let mut back = [0u8; 16];
181 let n = base64_decode(encoded, &mut back).unwrap();
182 assert_eq!(&back[..n], plain, "{encoded:?}");
183 }
184 }
185
186 #[test]
189 fn binary_round_trips() {
190 for len in 0..64usize {
191 let data: Vec<u8> = (0..len).map(|i| (i * 37 + 11) as u8).collect();
192 let mut out = [0u8; 128];
193 base64_encode(&data, &mut out[..encoded_len(len)]).unwrap();
194 let mut back = [0u8; 128];
195 let n = base64_decode(&out[..encoded_len(len)], &mut back).unwrap();
196 assert_eq!(&back[..n], &data[..], "len {len}");
197 }
198 }
199
200 #[test]
202 fn short_output_is_too_large() {
203 let mut out = [0u8; 3];
204 assert!(matches!(
205 base64_encode(b"foobar", &mut out),
206 Err(crate::Error::TooLarge { .. })
207 ));
208 assert!(matches!(
209 base64_decode(b"Zm9vYmFy", &mut out),
210 Err(crate::Error::TooLarge { .. })
211 ));
212 }
213
214 #[test]
217 fn non_canonical_inputs_are_refused() {
218 let mut out = [0u8; 16];
219 assert!(base64_decode(b"Zm9*YmFy", &mut out).is_err());
221 assert!(base64_decode(b"Zm=vYmFy", &mut out).is_err());
223 assert!(base64_decode(b"Zg+=", &mut out).is_err());
226 assert!(base64_decode(b"Zm9", &mut out).is_err());
228 assert!(base64_decode(b"Zg==", &mut out).is_ok());
230 assert!(base64_decode(b"Zg=", &mut out).is_err());
232 }
233}