1use crate::{DecodeError, EncodeError};
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum LineEnding {
8 Lf,
10 CrLf,
12}
13
14impl LineEnding {
15 #[must_use]
17 pub const fn name(self) -> &'static str {
18 match self {
19 Self::Lf => "LF",
20 Self::CrLf => "CRLF",
21 }
22 }
23
24 #[must_use]
26 pub const fn as_str(self) -> &'static str {
27 match self {
28 Self::Lf => "\n",
29 Self::CrLf => "\r\n",
30 }
31 }
32
33 #[must_use]
35 pub const fn as_bytes(self) -> &'static [u8] {
36 self.as_str().as_bytes()
37 }
38
39 #[must_use]
41 pub const fn byte_len(self) -> usize {
42 match self {
43 Self::Lf => 1,
44 Self::CrLf => 2,
45 }
46 }
47}
48
49impl core::fmt::Display for LineEnding {
50 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51 formatter.write_str(self.name())
52 }
53}
54
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub struct LineWrap {
62 pub line_len: usize,
64 pub line_ending: LineEnding,
66}
67
68impl LineWrap {
69 pub const MIME: Self = Self::new(76, LineEnding::CrLf);
71 pub const PEM: Self = Self::new(64, LineEnding::Lf);
73 pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
75
76 #[must_use]
91 pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
92 assert!(line_len != 0, "base64 line wrap length must be non-zero");
93 Self {
94 line_len,
95 line_ending,
96 }
97 }
98
99 #[must_use]
106 pub const fn checked_new(line_len: usize, line_ending: LineEnding) -> Option<Self> {
107 if line_len == 0 {
108 None
109 } else {
110 Some(Self::new(line_len, line_ending))
111 }
112 }
113
114 #[must_use]
116 pub const fn line_len(self) -> usize {
117 self.line_len
118 }
119
120 #[must_use]
122 pub const fn line_ending(self) -> LineEnding {
123 self.line_ending
124 }
125
126 #[must_use]
128 pub const fn is_valid(self) -> bool {
129 self.line_len != 0
130 }
131}
132
133impl core::fmt::Display for LineWrap {
134 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135 write!(formatter, "{}:{}", self.line_len, self.line_ending.name())
136 }
137}
138
139pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
154 match checked_encoded_len(input_len, padded) {
155 Some(len) => Ok(len),
156 None => Err(EncodeError::LengthOverflow),
157 }
158}
159
160pub const fn wrapped_encoded_len(
174 input_len: usize,
175 padded: bool,
176 wrap: LineWrap,
177) -> Result<usize, EncodeError> {
178 if wrap.line_len == 0 {
179 return Err(EncodeError::InvalidLineWrap { line_len: 0 });
180 }
181
182 let Some(encoded) = checked_encoded_len(input_len, padded) else {
183 return Err(EncodeError::LengthOverflow);
184 };
185 if encoded == 0 {
186 return Ok(0);
187 }
188
189 let breaks = (encoded - 1) / wrap.line_len;
190 let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
191 return Err(EncodeError::LengthOverflow);
192 };
193 match encoded.checked_add(line_ending_bytes) {
194 Some(len) => Ok(len),
195 None => Err(EncodeError::LengthOverflow),
196 }
197}
198
199#[must_use]
215pub const fn checked_wrapped_encoded_len(
216 input_len: usize,
217 padded: bool,
218 wrap: LineWrap,
219) -> Option<usize> {
220 if wrap.line_len == 0 {
221 return None;
222 }
223
224 let Some(encoded) = checked_encoded_len(input_len, padded) else {
225 return None;
226 };
227 if encoded == 0 {
228 return Some(0);
229 }
230
231 let breaks = (encoded - 1) / wrap.line_len;
232 let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
233 return None;
234 };
235 encoded.checked_add(line_ending_bytes)
236}
237
238#[must_use]
249pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
250 let groups = input_len / 3;
251 if groups > usize::MAX / 4 {
252 return None;
253 }
254 let full = groups * 4;
255 let rem = input_len % 3;
256 if rem == 0 {
257 Some(full)
258 } else if padded {
259 full.checked_add(4)
260 } else {
261 full.checked_add(rem + 1)
262 }
263}
264
265#[must_use]
276pub const fn decoded_capacity(encoded_len: usize) -> usize {
277 let rem = encoded_len % 4;
278 encoded_len / 4 * 3
279 + if rem == 2 {
280 1
281 } else if rem == 3 {
282 2
283 } else {
284 0
285 }
286}
287
288pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
302 if padded {
303 decoded_len_padded(input)
304 } else {
305 decoded_len_unpadded(input)
306 }
307}
308
309pub(crate) fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
310 if input.is_empty() {
311 return Ok(0);
312 }
313 if !input.len().is_multiple_of(4) {
314 return Err(DecodeError::InvalidLength);
315 }
316
317 let Some((&last, before_last_prefix)) = input.split_last() else {
318 return Ok(0);
319 };
320 let Some(&before_last) = before_last_prefix.last() else {
321 return Err(DecodeError::InvalidLength);
322 };
323
324 let mut padding = 0;
325 if last == b'=' {
326 padding += 1;
327 }
328 if before_last == b'=' {
329 padding += 1;
330 }
331 if padding == 0
332 && let Some(index) = input.iter().position(|byte| *byte == b'=')
333 {
334 return Err(DecodeError::InvalidPadding { index });
335 }
336 if padding > 0 {
337 let first_pad = input.len() - padding;
338 if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
339 return Err(DecodeError::InvalidPadding { index });
340 }
341 }
342 Ok(input.len() / 4 * 3 - padding)
343}
344
345pub(crate) fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
346 if input.len() % 4 == 1 {
347 return Err(DecodeError::InvalidLength);
348 }
349 if let Some(index) = input.iter().position(|byte| *byte == b'=') {
350 return Err(DecodeError::InvalidPadding { index });
351 }
352 Ok(decoded_capacity(input.len()))
353}