1#![no_std]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Base64Error {
11 Invalid,
13 OutputFull,
15 Truncated,
17}
18
19impl fmt::Display for Base64Error {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 f.write_str(match self {
22 Self::Invalid => "invalid Base64 input or state",
23 Self::OutputFull => "Base64 output buffer is too small",
24 Self::Truncated => "incomplete Base64 group",
25 })
26 }
27}
28
29impl core::error::Error for Base64Error {}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct Base64Codec {
37 pub encode: bool,
39 pub columns: usize,
41 pub carry: u8,
43 pub position: usize,
45 pub line_position: usize,
47 pub finished: bool,
49}
50
51impl Base64Codec {
52 #[must_use]
54 pub const fn new(encode: bool, columns: usize) -> Self {
55 Self {
56 encode,
57 columns,
58 carry: 0,
59 position: 0,
60 line_position: 0,
61 finished: false,
62 }
63 }
64
65 #[must_use]
68 pub fn encoded_capacity(columns: usize, size: usize) -> Option<usize> {
69 let symbols = (size.checked_mul(4)? / 3).checked_add(4)?;
70 let with_breaks = if let Some(breaks) = symbols.checked_div(columns) {
71 symbols.checked_add(breaks)?.checked_add(4)?
72 } else {
73 symbols
74 };
75 with_breaks.checked_add(1)
76 }
77
78 #[must_use]
81 pub fn decoded_capacity(size: usize) -> Option<usize> {
82 (size.checked_mul(3)? / 4).checked_add(8)
83 }
84
85 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, Base64Error> {
94 self.validate()?;
95 if self.encode {
96 self.encode_input(input, output)
97 } else {
98 self.decode_input(input, output)
99 }
100 }
101
102 pub fn finish(&mut self, output: &mut [u8]) -> Result<usize, Base64Error> {
114 self.validate()?;
115 let (_, output) = output.split_last_mut().ok_or(Base64Error::OutputFull)?;
116 let mut written = 0;
117 if self.position != 0 {
118 if !self.encode {
119 return Err(Base64Error::Truncated);
120 }
121 let shift = if self.position == 1 { 4 } else { 2 };
122 self.emit(
123 ALPHABET[usize::from((self.carry << shift) & 63)],
124 output,
125 &mut written,
126 )?;
127 for _ in self.position..3 {
128 self.emit(b'=', output, &mut written)?;
129 }
130 }
131 self.carry = 0;
132 self.position = 0;
133 self.finished = true;
134 Ok(written)
135 }
136
137 fn validate(&self) -> Result<(), Base64Error> {
138 let valid = if self.encode {
139 self.valid_encoder()
140 } else {
141 self.line_position == 0
142 && self.carry < 64
143 && match (self.finished, self.position) {
144 (false, 0) => self.carry == 0,
145 (false, 1..=3) | (true, 0 | 3) => true,
146 _ => false,
147 }
148 };
149 if valid {
150 Ok(())
151 } else {
152 Err(Base64Error::Invalid)
153 }
154 }
155
156 fn valid_encoder(&self) -> bool {
157 if self.position > 2
158 || (self.position == 0 && self.carry != 0)
159 || (self.finished && self.position != 0)
160 {
161 return false;
162 }
163 if self.columns == 0 {
164 return self.line_position % 4 == self.position;
165 }
166 if self.line_position == 0 {
167 return self.position == 0;
168 }
169 if self.line_position > self.columns {
170 return false;
171 }
172 let mask = match self.columns % 4 {
175 0 => 3,
176 2 => 1,
177 _ => 0,
178 };
179 self.line_position & mask == self.position & mask
180 }
181
182 fn encode_input(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, Base64Error> {
183 if self.finished && !input.is_empty() {
184 return Err(Base64Error::Invalid);
185 }
186 let mut written = 0;
187 for &byte in input {
188 let sextet = match self.position {
189 0 => byte >> 2,
190 1 => (self.carry & 3) << 4 | byte >> 4,
191 _ => (self.carry & 15) << 2 | byte >> 6,
192 };
193 self.emit(ALPHABET[usize::from(sextet)], output, &mut written)?;
194 if self.position == 2 {
195 self.emit(ALPHABET[usize::from(byte & 63)], output, &mut written)?;
196 self.position = 0;
197 self.carry = 0;
198 } else {
199 self.position += 1;
200 self.carry = byte;
201 }
202 }
203 Ok(written)
204 }
205
206 fn emit(
207 &mut self,
208 byte: u8,
209 output: &mut [u8],
210 written: &mut usize,
211 ) -> Result<(), Base64Error> {
212 if self.columns != 0 && self.line_position == self.columns {
213 write_byte(output, written, b'\n')?;
214 self.line_position = 0;
215 }
216 let next = self
217 .line_position
218 .checked_add(1)
219 .ok_or(Base64Error::Invalid)?;
220 write_byte(output, written, byte)?;
221 self.line_position = next;
222 Ok(())
223 }
224
225 fn decode_input(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, Base64Error> {
226 let mut written = 0;
227 for &byte in input {
228 if matches!(byte, b' ' | b'\t' | b'\r' | b'\n') {
229 continue;
230 }
231 if written == output.len() {
234 return Err(Base64Error::OutputFull);
235 }
236 if byte == b'=' {
237 self.position = match (self.finished, self.position) {
238 (false, 2) => 3,
239 (_, 3) => 0,
240 _ => return Err(Base64Error::Invalid),
241 };
242 self.finished = true;
243 continue;
244 }
245 if self.finished {
246 return Err(Base64Error::Invalid);
247 }
248 let value = sextet(byte).ok_or(Base64Error::Invalid)?;
249 if self.position != 0 {
250 let shift = self.position * 2;
251 write_byte(
252 output,
253 &mut written,
254 (self.carry << shift) | (value >> (6 - shift)),
255 )?;
256 }
257 self.position = (self.position + 1) % 4;
258 self.carry = if self.position == 0 { 0 } else { value };
259 }
260 Ok(written)
261 }
262}
263
264fn sextet(byte: u8) -> Option<u8> {
265 Some(match byte {
266 b'A'..=b'Z' => byte - b'A',
267 b'a'..=b'z' => byte - b'a' + 26,
268 b'0'..=b'9' => byte - b'0' + 52,
269 b'+' => 62,
270 b'/' => 63,
271 _ => return None,
272 })
273}
274
275fn write_byte(output: &mut [u8], written: &mut usize, byte: u8) -> Result<(), Base64Error> {
276 *output.get_mut(*written).ok_or(Base64Error::OutputFull)? = byte;
277 *written += 1;
278 Ok(())
279}