modo/encoding/
base64url.rs1const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
4
5pub fn encode(bytes: &[u8]) -> String {
20 if bytes.is_empty() {
21 return String::new();
22 }
23 let mut result = String::with_capacity((bytes.len() * 4).div_ceil(3));
24 let mut buffer: u32 = 0;
25 let mut bits_left = 0;
26
27 for &byte in bytes {
28 buffer = (buffer << 8) | byte as u32;
29 bits_left += 8;
30 while bits_left >= 6 {
31 bits_left -= 6;
32 let idx = ((buffer >> bits_left) & 0x3F) as usize;
33 result.push(ALPHABET[idx] as char);
34 }
35 }
36 if bits_left > 0 {
37 let idx = ((buffer << (6 - bits_left)) & 0x3F) as usize;
38 result.push(ALPHABET[idx] as char);
39 }
40 result
41}
42
43pub fn decode(encoded: &str) -> crate::Result<Vec<u8>> {
63 if encoded.is_empty() {
64 return Ok(Vec::new());
65 }
66 let mut result = Vec::with_capacity(encoded.len() * 3 / 4);
67 let mut buffer: u32 = 0;
68 let mut bits_left = 0;
69
70 for ch in encoded.chars() {
71 let val = decode_char(ch)?;
72 buffer = (buffer << 6) | val as u32;
73 bits_left += 6;
74 if bits_left >= 8 {
75 bits_left -= 8;
76 result.push((buffer >> bits_left) as u8);
77 }
78 }
79 Ok(result)
80}
81
82fn decode_char(ch: char) -> crate::Result<u8> {
83 match ch {
84 'A'..='Z' => Ok(ch as u8 - b'A'),
85 'a'..='z' => Ok(ch as u8 - b'a' + 26),
86 '0'..='9' => Ok(ch as u8 - b'0' + 52),
87 '-' => Ok(62),
88 '_' => Ok(63),
89 _ => Err(crate::Error::bad_request(format!(
90 "invalid base64url character: '{ch}'"
91 ))),
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn encode_empty() {
101 assert_eq!(encode(b""), "");
102 }
103
104 #[test]
105 fn encode_basic() {
106 assert_eq!(encode(b"Hello"), "SGVsbG8");
108 }
109
110 #[test]
111 fn encode_uses_url_safe_chars() {
112 let bytes = [0xfb, 0xff, 0xfe];
114 let encoded = encode(&bytes);
115 assert!(!encoded.contains('+'), "should use - not +");
116 assert!(!encoded.contains('/'), "should use _ not /");
117 assert!(encoded.contains('-') || encoded.contains('_'));
118 }
119
120 #[test]
121 fn decode_basic() {
122 assert_eq!(decode("SGVsbG8").unwrap(), b"Hello");
123 }
124
125 #[test]
126 fn roundtrip_random_bytes() {
127 let bytes: Vec<u8> = (0..=255).collect();
128 let encoded = encode(&bytes);
129 let decoded = decode(&encoded).unwrap();
130 assert_eq!(decoded, bytes);
131 }
132
133 #[test]
134 fn decode_invalid_char() {
135 assert!(decode("SGVs!G8").is_err());
136 }
137
138 #[test]
139 fn encode_32_bytes_pkce() {
140 let bytes = [0xABu8; 32];
141 let encoded = encode(&bytes);
142 let decoded = decode(&encoded).unwrap();
143 assert_eq!(decoded, bytes);
144 }
145}