1use {
30 alloc::{
31 string::String,
32 vec::Vec,
33 },
34 crate::Result,
35};
36
37mod encoder;
38mod decoder;
39
40const BASE62_DIGITS: [char; 62] = [
42 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
43 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
44 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
45];
46
47const PAD_CHAR: char = '=';
49
50#[derive(Debug, Eq, PartialEq)]
52struct LastChars {
53
54 first: char,
56
57 last: char,
59
60}
61
62#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
66pub enum Debt64 {
67
68 Standard,
74
75 IMAP,
81
82 MIME,
94
95 URL,
106
107 FreenetURL,
119
120}
121
122impl Debt64 {
123
124 const MIME_LINE_SEPARATORS: [char; 2] = ['\r', '\n'];
125
126 const fn last_chars(&self) -> &LastChars {
128 match self {
129 Self::Standard => &LastChars { first: '+', last: '/' },
130 Self::IMAP => &LastChars { first: '+', last: ',' },
131 Self::MIME => &LastChars { first: '+', last: '/' },
132 Self::URL => &LastChars { first: '-', last: '_' },
133 Self::FreenetURL => &LastChars { first: '~', last: '-' },
134 }
135 }
136
137 const fn line_separators(&self) -> Option<&[char]> {
139 match self {
140 Self::Standard => None,
141 Self::IMAP => None,
142 Self::MIME => Some(&Self::MIME_LINE_SEPARATORS),
143 Self::URL => None,
144 Self::FreenetURL => None,
145 }
146 }
147
148 const fn max_line_len(&self) -> Option<usize> {
150 match self {
151 Self::Standard => None,
152 Self::IMAP => None,
153 Self::MIME => Some(76),
154 Self::URL => None,
155 Self::FreenetURL => None,
156 }
157 }
158
159 const fn must_use_pad(&self) -> bool {
161 match self {
162 Self::Standard => true,
163 Self::IMAP => false,
164 Self::MIME => true,
165 Self::URL => false,
166 Self::FreenetURL => true,
167 }
168 }
169
170 const fn can_use_pad(&self) -> bool {
172 match self {
173 Self::Standard => true,
174 Self::IMAP => false,
175 Self::MIME => true,
176 Self::URL => true,
177 Self::FreenetURL => true,
178 }
179 }
180
181 fn get_char(index: usize, last_chars: &LastChars) -> &char {
183 match index {
184 0..=61 => &BASE62_DIGITS[index],
185 62 => &last_chars.first,
186 _ => &last_chars.last,
187 }
188 }
189
190 const fn allows_invalid_chars(&self) -> bool {
192 match self {
193 Self::Standard => false,
194 Self::IMAP => false,
195 Self::MIME => true,
196 Self::URL => false,
197 Self::FreenetURL => false,
198 }
199 }
200
201 pub fn encode<B>(&self, bytes: B) -> String where B: AsRef<[u8]> {
203 encoder::encode(bytes, self)
204 }
205
206 pub fn decode<B>(&self, bytes: B) -> Result<Vec<u8>> where B: AsRef<[u8]> {
208 decoder::decode(bytes, self)
209 }
210
211 pub fn estimate_encoding_capacity<B>(&self, bytes: B) -> usize where B: AsRef<[u8]> {
213 let result = bytes.as_ref().len();
214 if result == 0 {
215 return 0;
216 }
217
218 let result = (result / 3).saturating_mul(4).saturating_add(match result % 3 {
219 0 => 0,
220 other => if self.must_use_pad() { 4 } else { other + 1 },
221 });
222 match (self.max_line_len().as_ref(), self.line_separators().as_ref()) {
223 (Some(max_line_len), Some(line_separators)) => result.saturating_add(
224 (result / max_line_len).saturating_sub(if result % max_line_len == 0 { 1 } else { 0 }).saturating_mul(line_separators.len())
225 ),
226 _ => result,
227 }
228 }
229
230 pub fn estimate_decoding_capacity<B>(&self, bytes: B) -> usize where B: AsRef<[u8]> {
232 let result = bytes.as_ref().len();
233 if result == 0 {
234 return 0;
235 }
236
237 let line_separators = match (self.max_line_len().as_ref(), self.line_separators().as_ref()) {
238 (Some(max_line_len), Some(line_separators)) => {
239 let line_len = max_line_len.saturating_add(line_separators.len());
240 (result / line_len).saturating_mul(line_separators.len())
241 },
242 _ => 0,
243 };
244
245 let result = result.saturating_sub(line_separators);
246 let result = (result / 4).saturating_mul(3).saturating_add(match result % 4 {
247 0 | 1 => 0,
248 2 => 1,
249 _ => 2,
250 });
251
252 result
253 }
254
255}
256
257#[test]
258fn test_base62_digits() {
259 for (i, b) in (b'A'..b'Z').enumerate() {
260 assert_eq!(b as char, BASE62_DIGITS[i]);
261 }
262 for (i, b) in (b'a'..b'z').enumerate() {
263 assert_eq!(b as char, BASE62_DIGITS[i + 26]);
264 }
265 for (i, b) in (b'0'..b'9').enumerate() {
266 assert_eq!(b as char, BASE62_DIGITS[i + 52]);
267 }
268}
269
270#[test]
271fn test_last_chars() {
272 assert_eq!(Debt64::Standard.last_chars(), &LastChars { first: '+', last: '/' });
273 assert_eq!(Debt64::IMAP.last_chars(), &LastChars { first: '+', last: ',' });
274 assert_eq!(Debt64::MIME.last_chars(), &LastChars { first: '+', last: '/' });
275 assert_eq!(Debt64::URL.last_chars(), &LastChars { first: '-', last: '_' });
276 assert_eq!(Debt64::FreenetURL.last_chars(), &LastChars { first: '~', last: '-' });
277}
278
279#[test]
280fn test_line_separators() {
281 assert_eq!(Debt64::Standard.line_separators(), None);
282 assert_eq!(Debt64::IMAP.line_separators(), None);
283 assert_eq!(Debt64::MIME.line_separators().unwrap(), &['\r', '\n']);
284 assert_eq!(Debt64::URL.line_separators(), None);
285 assert_eq!(Debt64::FreenetURL.line_separators(), None);
286}
287
288#[test]
289fn test_max_line_len() {
290 assert_eq!(Debt64::Standard.max_line_len(), None);
291 assert_eq!(Debt64::IMAP.max_line_len(), None);
292 assert_eq!(Debt64::MIME.max_line_len(), Some(76));
293 assert_eq!(Debt64::URL.max_line_len(), None);
294 assert_eq!(Debt64::FreenetURL.max_line_len(), None);
295}
296
297#[test]
298fn test_pad_char() {
299 assert_eq!(Debt64::Standard.must_use_pad(), true);
300 assert_eq!(Debt64::Standard.can_use_pad(), true);
301
302 assert_eq!(Debt64::IMAP.must_use_pad(), false);
303 assert_eq!(Debt64::IMAP.can_use_pad(), false);
304
305 assert_eq!(Debt64::MIME.must_use_pad(), true);
306 assert_eq!(Debt64::MIME.can_use_pad(), true);
307
308 assert_eq!(Debt64::URL.must_use_pad(), false);
309 assert_eq!(Debt64::URL.can_use_pad(), true);
310
311 assert_eq!(Debt64::FreenetURL.must_use_pad(), true);
312 assert_eq!(Debt64::FreenetURL.can_use_pad(), true);
313}
314
315#[test]
316fn test_invalid_chars() {
317 assert_eq!(Debt64::Standard.allows_invalid_chars(), false);
318 assert_eq!(Debt64::IMAP.allows_invalid_chars(), false);
319 assert_eq!(Debt64::MIME.allows_invalid_chars(), true);
320 assert_eq!(Debt64::URL.allows_invalid_chars(), false);
321 assert_eq!(Debt64::FreenetURL.allows_invalid_chars(), false);
322}