1use crate::{ensure, Result};
9
10const HEX_LOWER: &[u8; 16] = b"0123456789abcdef";
11
12pub fn hex_encode(input: &[u8], out: &mut [u8]) -> Result<()> {
16 ensure!(
17 out.len() == input.len() * 2,
18 InvalidLength,
19 "hex output buffer"
20 );
21 for (i, &b) in input.iter().enumerate() {
22 out[i * 2] = HEX_LOWER[(b >> 4) as usize];
23 out[i * 2 + 1] = HEX_LOWER[(b & 0x0f) as usize];
24 }
25 Ok(())
26}
27
28pub fn hex_decode(input: &[u8], out: &mut [u8]) -> Result<()> {
32 ensure!(
33 input.len() % 2 == 0,
34 MalformedEncoding,
35 "hex length must be even"
36 );
37 ensure!(
38 out.len() == input.len() / 2,
39 InvalidLength,
40 "hex output buffer"
41 );
42 for i in 0..out.len() {
43 let hi = hex_nibble(input[i * 2])?;
44 let lo = hex_nibble(input[i * 2 + 1])?;
45 out[i] = (hi << 4) | lo;
46 }
47 Ok(())
48}
49
50#[inline]
51fn hex_nibble(c: u8) -> Result<u8> {
52 let digit = c.wrapping_sub(b'0');
55 let lower = c.wrapping_sub(b'a');
56 let upper = c.wrapping_sub(b'A');
57 let m_digit = ((digit < 10) as u8).wrapping_neg();
58 let m_lower = ((lower < 6) as u8).wrapping_neg();
59 let m_upper = ((upper < 6) as u8).wrapping_neg();
60 ensure!(
61 (m_digit | m_lower | m_upper) == 0xFF,
62 MalformedEncoding,
63 "hex digit"
64 );
65 Ok((digit & m_digit) | (lower.wrapping_add(10) & m_lower) | (upper.wrapping_add(10) & m_upper))
66}
67
68const B64_STD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
69
70pub const fn base64_encoded_len(n: usize) -> usize {
72 n.div_ceil(3) * 4
73}
74
75pub fn base64_encode(input: &[u8], out: &mut [u8]) -> Result<()> {
77 ensure!(
78 out.len() == base64_encoded_len(input.len()),
79 InvalidLength,
80 "base64 output buffer"
81 );
82 let mut oi = 0;
83 let mut chunks = input.chunks_exact(3);
84 for c in &mut chunks {
85 let n = ((c[0] as u32) << 16) | ((c[1] as u32) << 8) | c[2] as u32;
86 out[oi] = B64_STD[(n >> 18) as usize & 63];
87 out[oi + 1] = B64_STD[(n >> 12) as usize & 63];
88 out[oi + 2] = B64_STD[(n >> 6) as usize & 63];
89 out[oi + 3] = B64_STD[n as usize & 63];
90 oi += 4;
91 }
92 let rem = chunks.remainder();
93 match rem.len() {
94 0 => {}
95 1 => {
96 let n = (rem[0] as u32) << 16;
97 out[oi] = B64_STD[(n >> 18) as usize & 63];
98 out[oi + 1] = B64_STD[(n >> 12) as usize & 63];
99 out[oi + 2] = b'=';
100 out[oi + 3] = b'=';
101 }
102 _ => {
103 let n = ((rem[0] as u32) << 16) | ((rem[1] as u32) << 8);
104 out[oi] = B64_STD[(n >> 18) as usize & 63];
105 out[oi + 1] = B64_STD[(n >> 12) as usize & 63];
106 out[oi + 2] = B64_STD[(n >> 6) as usize & 63];
107 out[oi + 3] = b'=';
108 }
109 }
110 Ok(())
111}
112
113pub fn base64_decode(input: &[u8], out: &mut [u8]) -> Result<usize> {
115 ensure!(input.len() % 4 == 0, MalformedEncoding, "base64 length");
116 if input.is_empty() {
117 return Ok(0);
118 }
119 let pad = usize::from(input[input.len() - 1] == b'=')
120 + usize::from(input.len() >= 2 && input[input.len() - 2] == b'=');
121 let decoded = input.len() / 4 * 3 - pad;
122 ensure!(out.len() >= decoded, InvalidLength, "base64 output buffer");
123
124 let mut oi = 0;
125 for block in input.chunks_exact(4) {
126 let mut n: u32 = 0;
127 for (j, &c) in block.iter().enumerate() {
128 let v = if c == b'=' { 0 } else { base64_value(c)? };
129 n |= (v as u32) << (18 - 6 * j);
130 }
131 let bytes = [(n >> 16) as u8, (n >> 8) as u8, n as u8];
132 for &b in &bytes {
133 if oi < decoded {
134 out[oi] = b;
135 oi += 1;
136 }
137 }
138 }
139 Ok(decoded)
140}
141
142#[inline]
143fn base64_value(c: u8) -> Result<u8> {
144 let upper = c.wrapping_sub(b'A');
145 let lower = c.wrapping_sub(b'a');
146 let digit = c.wrapping_sub(b'0');
147 let m_upper = ((upper < 26) as u8).wrapping_neg();
148 let m_lower = ((lower < 26) as u8).wrapping_neg();
149 let m_digit = ((digit < 10) as u8).wrapping_neg();
150 let m_plus = ((c == b'+') as u8).wrapping_neg();
151 let m_slash = ((c == b'/') as u8).wrapping_neg();
152 ensure!(
153 (m_upper | m_lower | m_digit | m_plus | m_slash) == 0xFF,
154 MalformedEncoding,
155 "base64 character"
156 );
157 Ok((upper & m_upper)
158 | (lower.wrapping_add(26) & m_lower)
159 | (digit.wrapping_add(52) & m_digit)
160 | (62 & m_plus)
161 | (63 & m_slash))
162}
163
164#[cfg(feature = "std")]
165mod alloc_helpers {
166 use super::*;
167
168 pub fn hex(input: &[u8]) -> String {
170 let mut buf = vec![0u8; input.len() * 2];
171 hex_encode(input, &mut buf).expect("buffer sized exactly");
172 String::from_utf8(buf).expect("hex alphabet is ASCII")
173 }
174
175 pub fn unhex(input: &str) -> Result<Vec<u8>> {
177 let mut buf = vec![0u8; input.len() / 2];
178 hex_decode(input.as_bytes(), &mut buf)?;
179 Ok(buf)
180 }
181
182 pub fn b64(input: &[u8]) -> String {
184 let mut buf = vec![0u8; base64_encoded_len(input.len())];
185 base64_encode(input, &mut buf).expect("buffer sized exactly");
186 String::from_utf8(buf).expect("base64 alphabet is ASCII")
187 }
188
189 pub fn unb64(input: &str) -> Result<Vec<u8>> {
191 let mut buf = vec![0u8; input.len() / 4 * 3];
192 let n = base64_decode(input.as_bytes(), &mut buf)?;
193 buf.truncate(n);
194 Ok(buf)
195 }
196}
197
198#[cfg(feature = "std")]
199pub use alloc_helpers::{b64, hex, unb64, unhex};
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn hex_roundtrip() {
207 let data = [0x00u8, 0x0f, 0xf0, 0xff, 0x42];
208 let mut enc = [0u8; 10];
209 hex_encode(&data, &mut enc).unwrap();
210 assert_eq!(&enc, b"000ff0ff42");
211 let mut dec = [0u8; 5];
212 hex_decode(&enc, &mut dec).unwrap();
213 assert_eq!(dec, data);
214 }
215
216 #[test]
217 fn hex_accepts_uppercase_and_rejects_junk() {
218 let mut dec = [0u8; 2];
219 hex_decode(b"AbCd", &mut dec).unwrap();
220 assert_eq!(dec, [0xab, 0xcd]);
221 assert!(hex_decode(b"zz", &mut dec[..1]).is_err());
222 assert!(hex_decode(b"abc", &mut dec).is_err());
223 }
224
225 #[test]
226 fn base64_matches_rfc4648_vectors() {
227 for (plain, encoded) in [
228 (&b""[..], ""),
229 (&b"f"[..], "Zg=="),
230 (&b"fo"[..], "Zm8="),
231 (&b"foo"[..], "Zm9v"),
232 (&b"foob"[..], "Zm9vYg=="),
233 (&b"fooba"[..], "Zm9vYmE="),
234 (&b"foobar"[..], "Zm9vYmFy"),
235 ] {
236 let mut enc = vec![0u8; base64_encoded_len(plain.len())];
237 base64_encode(plain, &mut enc).unwrap();
238 assert_eq!(
239 core::str::from_utf8(&enc).unwrap(),
240 encoded,
241 "encoding {plain:?}"
242 );
243
244 let mut dec = vec![0u8; plain.len() + 3];
245 let n = base64_decode(encoded.as_bytes(), &mut dec).unwrap();
246 assert_eq!(&dec[..n], plain, "decoding {encoded}");
247 }
248 }
249
250 #[test]
251 fn string_helpers_roundtrip() {
252 assert_eq!(hex(b"\xde\xad\xbe\xef"), "deadbeef");
253 assert_eq!(unhex("deadbeef").unwrap(), b"\xde\xad\xbe\xef");
254 assert_eq!(b64(b"foobar"), "Zm9vYmFy");
255 assert_eq!(unb64("Zm9vYmE=").unwrap(), b"fooba");
256 }
257}