use crate::{DecodeError, EncodeError};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LineEnding {
Lf,
CrLf,
}
impl LineEnding {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Lf => "LF",
Self::CrLf => "CRLF",
}
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Lf => "\n",
Self::CrLf => "\r\n",
}
}
#[must_use]
pub const fn as_bytes(self) -> &'static [u8] {
self.as_str().as_bytes()
}
#[must_use]
pub const fn byte_len(self) -> usize {
match self {
Self::Lf => 1,
Self::CrLf => 2,
}
}
}
impl core::fmt::Display for LineEnding {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str(self.name())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LineWrap {
pub line_len: usize,
pub line_ending: LineEnding,
}
impl LineWrap {
pub const MIME: Self = Self::new(76, LineEnding::CrLf);
pub const PEM: Self = Self::new(64, LineEnding::Lf);
pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
#[must_use]
pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
assert!(line_len != 0, "base64 line wrap length must be non-zero");
Self {
line_len,
line_ending,
}
}
#[must_use]
pub const fn checked_new(line_len: usize, line_ending: LineEnding) -> Option<Self> {
if line_len == 0 {
None
} else {
Some(Self::new(line_len, line_ending))
}
}
#[must_use]
pub const fn line_len(self) -> usize {
self.line_len
}
#[must_use]
pub const fn line_ending(self) -> LineEnding {
self.line_ending
}
#[must_use]
pub const fn is_valid(self) -> bool {
self.line_len != 0
}
}
impl core::fmt::Display for LineWrap {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, "{}:{}", self.line_len, self.line_ending.name())
}
}
pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
match checked_encoded_len(input_len, padded) {
Some(len) => Ok(len),
None => Err(EncodeError::LengthOverflow),
}
}
pub const fn wrapped_encoded_len(
input_len: usize,
padded: bool,
wrap: LineWrap,
) -> Result<usize, EncodeError> {
if wrap.line_len == 0 {
return Err(EncodeError::InvalidLineWrap { line_len: 0 });
}
let Some(encoded) = checked_encoded_len(input_len, padded) else {
return Err(EncodeError::LengthOverflow);
};
if encoded == 0 {
return Ok(0);
}
let breaks = (encoded - 1) / wrap.line_len;
let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
return Err(EncodeError::LengthOverflow);
};
match encoded.checked_add(line_ending_bytes) {
Some(len) => Ok(len),
None => Err(EncodeError::LengthOverflow),
}
}
#[must_use]
pub const fn checked_wrapped_encoded_len(
input_len: usize,
padded: bool,
wrap: LineWrap,
) -> Option<usize> {
if wrap.line_len == 0 {
return None;
}
let Some(encoded) = checked_encoded_len(input_len, padded) else {
return None;
};
if encoded == 0 {
return Some(0);
}
let breaks = (encoded - 1) / wrap.line_len;
let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
return None;
};
encoded.checked_add(line_ending_bytes)
}
#[must_use]
pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
let groups = input_len / 3;
if groups > usize::MAX / 4 {
return None;
}
let full = groups * 4;
let rem = input_len % 3;
if rem == 0 {
Some(full)
} else if padded {
full.checked_add(4)
} else {
full.checked_add(rem + 1)
}
}
#[must_use]
pub const fn decoded_capacity(encoded_len: usize) -> usize {
let rem = encoded_len % 4;
encoded_len / 4 * 3
+ if rem == 2 {
1
} else if rem == 3 {
2
} else {
0
}
}
pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
if padded {
decoded_len_padded(input)
} else {
decoded_len_unpadded(input)
}
}
pub(crate) fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
if input.is_empty() {
return Ok(0);
}
if !input.len().is_multiple_of(4) {
return Err(DecodeError::InvalidLength);
}
let Some((&last, before_last_prefix)) = input.split_last() else {
return Ok(0);
};
let Some(&before_last) = before_last_prefix.last() else {
return Err(DecodeError::InvalidLength);
};
let mut padding = 0;
if last == b'=' {
padding += 1;
}
if before_last == b'=' {
padding += 1;
}
if padding == 0
&& let Some(index) = input.iter().position(|byte| *byte == b'=')
{
return Err(DecodeError::InvalidPadding { index });
}
if padding > 0 {
let first_pad = input.len() - padding;
if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
return Err(DecodeError::InvalidPadding { index });
}
}
Ok(input.len() / 4 * 3 - padding)
}
pub(crate) fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
if input.len() % 4 == 1 {
return Err(DecodeError::InvalidLength);
}
if let Some(index) = input.iter().position(|byte| *byte == b'=') {
return Err(DecodeError::InvalidPadding { index });
}
Ok(decoded_capacity(input.len()))
}