1pub fn to_hex(bytes: &[u8]) -> String {
3 let mut s = String::with_capacity(bytes.len() * 2);
4 for &b in bytes {
5 s.push(HEX_CHARS[(b >> 4) as usize]);
6 s.push(HEX_CHARS[(b & 0x0f) as usize]);
7 }
8 s
9}
10
11const HEX_CHARS: [char; 16] = [
12 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
13];
14
15pub fn from_hex(s: &str) -> Option<Vec<u8>> {
17 if s.len() % 2 != 0 {
18 return None;
19 }
20 let mut out = Vec::with_capacity(s.len() / 2);
21 let bytes = s.as_bytes();
22 let mut i = 0;
23 while i < bytes.len() {
24 let hi = hex_val(bytes[i])?;
25 let lo = hex_val(bytes[i + 1])?;
26 out.push((hi << 4) | lo);
27 i += 2;
28 }
29 Some(out)
30}
31
32fn hex_val(b: u8) -> Option<u8> {
33 match b {
34 b'0'..=b'9' => Some(b - b'0'),
35 b'a'..=b'f' => Some(b - b'a' + 10),
36 b'A'..=b'F' => Some(b - b'A' + 10),
37 _ => None,
38 }
39}
40
41pub fn hex_to_array<const N: usize>(s: &str) -> Option<[u8; N]> {
43 let v = from_hex(s)?;
44 if v.len() != N {
45 return None;
46 }
47 let mut arr = [0u8; N];
48 arr.copy_from_slice(&v);
49 Some(arr)
50}
51
52const B64_CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
53
54pub fn to_base64(data: &[u8]) -> String {
56 let mut out = String::with_capacity((data.len() + 2) / 3 * 4);
57 let chunks = data.chunks(3);
58 for chunk in chunks {
59 let b0 = chunk[0] as u32;
60 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
61 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
62 let triple = (b0 << 16) | (b1 << 8) | b2;
63
64 out.push(B64_CHARS[((triple >> 18) & 0x3F) as usize] as char);
65 out.push(B64_CHARS[((triple >> 12) & 0x3F) as usize] as char);
66 if chunk.len() > 1 {
67 out.push(B64_CHARS[((triple >> 6) & 0x3F) as usize] as char);
68 } else {
69 out.push('=');
70 }
71 if chunk.len() > 2 {
72 out.push(B64_CHARS[(triple & 0x3F) as usize] as char);
73 } else {
74 out.push('=');
75 }
76 }
77 out
78}
79
80pub fn from_base64(s: &str) -> Option<Vec<u8>> {
82 let s = s.trim_end_matches('=');
83 let mut out = Vec::with_capacity(s.len() * 3 / 4);
84 let bytes = s.as_bytes();
85 let chunks = bytes.chunks(4);
86 for chunk in chunks {
87 let mut vals = [0u32; 4];
88 let n = chunk.len();
89 for i in 0..n {
90 vals[i] = b64_val(chunk[i])? as u32;
91 }
92 if n >= 2 {
93 out.push(((vals[0] << 2) | (vals[1] >> 4)) as u8);
94 }
95 if n >= 3 {
96 out.push(((vals[1] << 4) | (vals[2] >> 2)) as u8);
97 }
98 if n >= 4 {
99 out.push(((vals[2] << 6) | vals[3]) as u8);
100 }
101 }
102 Some(out)
103}
104
105fn b64_val(b: u8) -> Option<u8> {
106 match b {
107 b'A'..=b'Z' => Some(b - b'A'),
108 b'a'..=b'z' => Some(b - b'a' + 26),
109 b'0'..=b'9' => Some(b - b'0' + 52),
110 b'+' => Some(62),
111 b'/' => Some(63),
112 _ => None,
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn hex_roundtrip() {
122 let data = b"\x00\x01\x0a\xff\xde\xad";
123 assert_eq!(to_hex(data), "00010affdead");
124 assert_eq!(from_hex("00010affDEAD").unwrap(), data);
125 }
126
127 #[test]
128 fn hex_empty() {
129 assert_eq!(to_hex(&[]), "");
130 assert_eq!(from_hex("").unwrap(), Vec::<u8>::new());
131 }
132
133 #[test]
134 fn hex_invalid() {
135 assert!(from_hex("0").is_none()); assert!(from_hex("zz").is_none()); }
138
139 #[test]
140 fn hex_to_array_works() {
141 let arr: [u8; 3] = hex_to_array("aabbcc").unwrap();
142 assert_eq!(arr, [0xaa, 0xbb, 0xcc]);
143 assert!(hex_to_array::<4>("aabb").is_none()); }
145
146 #[test]
147 fn base64_roundtrip() {
148 let data = b"Hello, World!";
149 let encoded = to_base64(data);
150 assert_eq!(encoded, "SGVsbG8sIFdvcmxkIQ==");
151 assert_eq!(from_base64(&encoded).unwrap(), data);
152 }
153
154 #[test]
155 fn base64_empty() {
156 assert_eq!(to_base64(&[]), "");
157 assert_eq!(from_base64("").unwrap(), Vec::<u8>::new());
158 }
159
160 #[test]
161 fn base64_no_padding() {
162 assert_eq!(to_base64(b"abc"), "YWJj");
164 assert_eq!(from_base64("YWJj").unwrap(), b"abc");
165 }
166
167 #[test]
168 fn base64_one_pad() {
169 assert_eq!(to_base64(b"ab"), "YWI=");
171 assert_eq!(from_base64("YWI=").unwrap(), b"ab");
172 assert_eq!(from_base64("YWI").unwrap(), b"ab"); }
174}