1use crate::prelude::*;
2use super::{ ChunkedSlice, UnsafeBufWriteGuard };
3
4pub const BINARY_FRAME_LEN: usize = 5;
10pub const STRING_FRAME_LEN: usize = 8;
11
12#[inline]
17pub fn encode_base32(bytes: &[u8]) -> String {
18 _encode::<25, b'A', { b'2' - 26 }>(bytes)
19}
20
21#[inline]
26pub fn encode_base32hex(bytes: &[u8]) -> String {
27 _encode::<9, b'0', { b'A' - 10 }>(bytes)
28}
29
30fn _encode<
38 const BREAKPOINT: u8,
39 const LOWER: u8,
40 const UPPER_ADJUSTED: u8
41>(bytes: &[u8]) -> String {
42 let frames = bytes.len() / BINARY_FRAME_LEN;
44 let remainder = bytes.len() % BINARY_FRAME_LEN;
45
46 let capacity = if remainder == 0 {
47 frames * STRING_FRAME_LEN
48 } else {
49 (frames + 1) * STRING_FRAME_LEN
50 };
51
52 let mut frames_iter = ChunkedSlice::<BINARY_FRAME_LEN>::new(bytes);
53 let mut dest = UnsafeBufWriteGuard::with_capacity(capacity);
54
55 for _ in 0..frames {
56 let frame = unsafe { frames_iter.next_frame_unchecked() };
58
59 unsafe { encode_frame::<BREAKPOINT, LOWER, UPPER_ADJUSTED>(frame, &mut dest) }
61 }
62
63 if remainder > 0 {
64 let padding_amount = match remainder {
66 1 => { 6 }
67 2 => { 4 }
68 3 => { 3 }
69 4 => { 1 }
70 _ => {
71 unsafe { hint::unreachable_unchecked() }
76 }
77 };
78
79 let f = |frame: &[u8; 5]| {
80 static PADDING: &[u8; 6] = b"======";
81
82 unsafe { encode_frame::<BREAKPOINT, LOWER, UPPER_ADJUSTED>(frame, &mut dest) }
84
85 let ptr = unsafe { dest.as_mut_ptr().sub(padding_amount) };
87
88 unsafe { ptr::copy_nonoverlapping(PADDING.as_ptr(), ptr, padding_amount) }
90 };
91
92 unsafe { frames_iter.with_remainder_unchecked(f) }
94 }
95
96 let vec = unsafe { dest.into_full_vec() };
98
99 unsafe {
101 debug_assert!(str::from_utf8(&vec).is_ok(), "output bytes valid utf-8");
102 String::from_utf8_unchecked(vec)
103 }
104}
105
106unsafe fn encode_frame<
107 const BREAKPOINT: u8,
108 const LOWER: u8,
109 const UPPER_ADJUSTED: u8
110>(frame: &[u8; BINARY_FRAME_LEN], dest: &mut UnsafeBufWriteGuard) {
111 let byte1 = frame[0] >> 3;
113
114 let byte2 = ((frame[0] << 2) & 0b11100) | (frame[1] >> 6);
116
117 let byte3 = (frame[1] >> 1) & 0b11111;
119
120 let byte4 = ((frame[1] << 4) & 0b10000) | (frame[2] >> 4);
122
123 let byte5 = ((frame[2] << 1) & 0b11110) | (frame[3] >> 7);
125
126 let byte6 = (frame[3] >> 2) & 0b11111;
128
129 let byte7 = ((frame[3] << 3) & 0b11000) | (frame[4] >> 5);
131
132 let byte8 = frame[4] & 0b11111;
134
135 let bytes = [
136 if byte1 > BREAKPOINT { byte1 + UPPER_ADJUSTED } else { byte1 + LOWER },
138 if byte2 > BREAKPOINT { byte2 + UPPER_ADJUSTED } else { byte2 + LOWER },
139 if byte3 > BREAKPOINT { byte3 + UPPER_ADJUSTED } else { byte3 + LOWER },
140 if byte4 > BREAKPOINT { byte4 + UPPER_ADJUSTED } else { byte4 + LOWER },
141 if byte5 > BREAKPOINT { byte5 + UPPER_ADJUSTED } else { byte5 + LOWER },
142 if byte6 > BREAKPOINT { byte6 + UPPER_ADJUSTED } else { byte6 + LOWER },
143 if byte7 > BREAKPOINT { byte7 + UPPER_ADJUSTED } else { byte7 + LOWER },
144 if byte8 > BREAKPOINT { byte8 + UPPER_ADJUSTED } else { byte8 + LOWER }
145 ];
146
147 unsafe { dest.write_bytes_const::<8>(bytes.as_ptr()) }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn rfc_provided_examples() {
157 let examples = [
158 ("", ""),
159 ("f", "MY======"),
160 ("fo", "MZXQ===="),
161 ("foo", "MZXW6==="),
162 ("foob", "MZXW6YQ="),
163 ("fooba", "MZXW6YTB"),
164 ("foobar", "MZXW6YTBOI======")
165 ];
166
167 for (bytes, encoded) in examples {
168 assert_eq!(encoded, encode_base32(bytes.as_bytes()));
169 }
170 }
171
172 #[test]
173 fn rfc_provided_examples_base32hex() {
174 let examples = [
175 ("", ""),
176 ("f", "CO======"),
177 ("fo", "CPNG===="),
178 ("foo", "CPNMU==="),
179 ("foob", "CPNMUOG="),
180 ("fooba", "CPNMUOJ1"),
181 ("foobar", "CPNMUOJ1E8======")
182 ];
183
184 for (bytes, encoded) in examples {
185 assert_eq!(encoded, encode_base32hex(bytes.as_bytes()));
186 }
187 }
188}