#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "stream")]
pub mod stream {
use super::{Alphabet, DecodeError, EncodeError, Engine};
use std::collections::VecDeque;
use std::io::{self, Read, Write};
pub struct Encoder<W, A, const PAD: bool>
where
A: Alphabet,
{
inner: W,
engine: Engine<A, PAD>,
pending: [u8; 2],
pending_len: usize,
}
impl<W, A, const PAD: bool> Encoder<W, A, PAD>
where
A: Alphabet,
{
#[must_use]
pub const fn new(inner: W, engine: Engine<A, PAD>) -> Self {
Self {
inner,
engine,
pending: [0; 2],
pending_len: 0,
}
}
#[must_use]
pub const fn get_ref(&self) -> &W {
&self.inner
}
pub fn get_mut(&mut self) -> &mut W {
&mut self.inner
}
#[must_use]
pub fn into_inner(self) -> W {
self.inner
}
}
impl<W, A, const PAD: bool> Encoder<W, A, PAD>
where
W: Write,
A: Alphabet,
{
pub fn finish(mut self) -> io::Result<W> {
self.write_pending_final()?;
self.inner.flush()?;
Ok(self.inner)
}
fn write_pending_final(&mut self) -> io::Result<()> {
if self.pending_len == 0 {
return Ok(());
}
let mut encoded = [0u8; 4];
let written = self
.engine
.encode_slice(&self.pending[..self.pending_len], &mut encoded)
.map_err(encode_error_to_io)?;
self.inner.write_all(&encoded[..written])?;
self.pending_len = 0;
Ok(())
}
}
impl<W, A, const PAD: bool> Write for Encoder<W, A, PAD>
where
W: Write,
A: Alphabet,
{
fn write(&mut self, input: &[u8]) -> io::Result<usize> {
if input.is_empty() {
return Ok(0);
}
let mut consumed = 0;
if self.pending_len > 0 {
let needed = 3 - self.pending_len;
if input.len() < needed {
self.pending[self.pending_len..self.pending_len + input.len()]
.copy_from_slice(input);
self.pending_len += input.len();
return Ok(input.len());
}
let mut chunk = [0u8; 3];
chunk[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
chunk[self.pending_len..].copy_from_slice(&input[..needed]);
let mut encoded = [0u8; 4];
let written = self
.engine
.encode_slice(&chunk, &mut encoded)
.map_err(encode_error_to_io)?;
self.inner.write_all(&encoded[..written])?;
self.pending_len = 0;
consumed += needed;
}
let remaining = &input[consumed..];
let full_len = remaining.len() / 3 * 3;
let mut offset = 0;
let mut encoded = [0u8; 1024];
while offset < full_len {
let mut take = core::cmp::min(full_len - offset, 768);
take -= take % 3;
debug_assert!(take > 0);
let written = self
.engine
.encode_slice(&remaining[offset..offset + take], &mut encoded)
.map_err(encode_error_to_io)?;
self.inner.write_all(&encoded[..written])?;
offset += take;
}
let tail = &remaining[full_len..];
self.pending[..tail.len()].copy_from_slice(tail);
self.pending_len = tail.len();
Ok(input.len())
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
fn encode_error_to_io(err: EncodeError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, err)
}
pub struct Decoder<W, A, const PAD: bool>
where
A: Alphabet,
{
inner: W,
engine: Engine<A, PAD>,
pending: [u8; 4],
pending_len: usize,
finished: bool,
}
impl<W, A, const PAD: bool> Decoder<W, A, PAD>
where
A: Alphabet,
{
#[must_use]
pub const fn new(inner: W, engine: Engine<A, PAD>) -> Self {
Self {
inner,
engine,
pending: [0; 4],
pending_len: 0,
finished: false,
}
}
#[must_use]
pub const fn get_ref(&self) -> &W {
&self.inner
}
pub fn get_mut(&mut self) -> &mut W {
&mut self.inner
}
#[must_use]
pub fn into_inner(self) -> W {
self.inner
}
}
impl<W, A, const PAD: bool> Decoder<W, A, PAD>
where
W: Write,
A: Alphabet,
{
pub fn finish(mut self) -> io::Result<W> {
self.write_pending_final()?;
self.inner.flush()?;
Ok(self.inner)
}
fn write_pending_final(&mut self) -> io::Result<()> {
if self.pending_len == 0 {
return Ok(());
}
let mut decoded = [0u8; 3];
let written = self
.engine
.decode_slice(&self.pending[..self.pending_len], &mut decoded)
.map_err(decode_error_to_io)?;
self.inner.write_all(&decoded[..written])?;
self.pending_len = 0;
Ok(())
}
fn write_full_quad(&mut self, input: [u8; 4]) -> io::Result<()> {
let mut decoded = [0u8; 3];
let written = self
.engine
.decode_slice(&input, &mut decoded)
.map_err(decode_error_to_io)?;
self.inner.write_all(&decoded[..written])?;
if written < 3 {
self.finished = true;
}
Ok(())
}
}
impl<W, A, const PAD: bool> Write for Decoder<W, A, PAD>
where
W: Write,
A: Alphabet,
{
fn write(&mut self, input: &[u8]) -> io::Result<usize> {
if input.is_empty() {
return Ok(0);
}
if self.finished {
return Err(trailing_input_after_padding_error());
}
let mut consumed = 0;
if self.pending_len > 0 {
let needed = 4 - self.pending_len;
if input.len() < needed {
self.pending[self.pending_len..self.pending_len + input.len()]
.copy_from_slice(input);
self.pending_len += input.len();
return Ok(input.len());
}
let mut quad = [0u8; 4];
quad[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
quad[self.pending_len..].copy_from_slice(&input[..needed]);
self.write_full_quad(quad)?;
self.pending_len = 0;
consumed += needed;
if self.finished && consumed < input.len() {
return Err(trailing_input_after_padding_error());
}
}
let remaining = &input[consumed..];
let full_len = remaining.len() / 4 * 4;
let mut offset = 0;
while offset < full_len {
let quad = [
remaining[offset],
remaining[offset + 1],
remaining[offset + 2],
remaining[offset + 3],
];
self.write_full_quad(quad)?;
offset += 4;
if self.finished && offset < remaining.len() {
return Err(trailing_input_after_padding_error());
}
}
let tail = &remaining[full_len..];
self.pending[..tail.len()].copy_from_slice(tail);
self.pending_len = tail.len();
Ok(input.len())
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
fn decode_error_to_io(err: DecodeError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, err)
}
fn trailing_input_after_padding_error() -> io::Error {
io::Error::new(
io::ErrorKind::InvalidInput,
"base64 decoder received trailing input after padding",
)
}
pub struct DecoderReader<R, A, const PAD: bool>
where
A: Alphabet,
{
inner: R,
engine: Engine<A, PAD>,
pending: [u8; 4],
pending_len: usize,
output: VecDeque<u8>,
finished: bool,
terminal_seen: bool,
}
impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
where
A: Alphabet,
{
#[must_use]
pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
Self {
inner,
engine,
pending: [0; 4],
pending_len: 0,
output: VecDeque::new(),
finished: false,
terminal_seen: false,
}
}
#[must_use]
pub const fn get_ref(&self) -> &R {
&self.inner
}
pub fn get_mut(&mut self) -> &mut R {
&mut self.inner
}
#[must_use]
pub fn into_inner(self) -> R {
self.inner
}
}
impl<R, A, const PAD: bool> Read for DecoderReader<R, A, PAD>
where
R: Read,
A: Alphabet,
{
fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
if output.is_empty() {
return Ok(0);
}
while self.output.is_empty() && !self.finished {
self.fill_output()?;
}
let mut written = 0;
while written < output.len() {
let Some(byte) = self.output.pop_front() else {
break;
};
output[written] = byte;
written += 1;
}
Ok(written)
}
}
impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
where
R: Read,
A: Alphabet,
{
fn fill_output(&mut self) -> io::Result<()> {
if self.terminal_seen {
self.finished = true;
return Ok(());
}
let mut input = [0u8; 4];
let read = self.inner.read(&mut input[..4 - self.pending_len])?;
if read == 0 {
self.finished = true;
self.push_final_pending()?;
return Ok(());
}
self.pending[self.pending_len..self.pending_len + read].copy_from_slice(&input[..read]);
self.pending_len += read;
if self.pending_len < 4 {
return Ok(());
}
let quad = self.pending;
self.pending_len = 0;
self.push_decoded(&quad)?;
if self.terminal_seen {
self.finished = true;
}
Ok(())
}
fn push_final_pending(&mut self) -> io::Result<()> {
if self.pending_len == 0 {
return Ok(());
}
let mut pending = [0u8; 4];
pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
let pending_len = self.pending_len;
self.pending_len = 0;
self.push_decoded(&pending[..pending_len])
}
fn push_decoded(&mut self, input: &[u8]) -> io::Result<()> {
let mut decoded = [0u8; 3];
let written = self
.engine
.decode_slice(input, &mut decoded)
.map_err(decode_error_to_io)?;
self.output.extend(&decoded[..written]);
if input.len() == 4 && written < 3 {
self.terminal_seen = true;
}
Ok(())
}
}
pub struct EncoderReader<R, A, const PAD: bool>
where
A: Alphabet,
{
inner: R,
engine: Engine<A, PAD>,
pending: [u8; 2],
pending_len: usize,
output: VecDeque<u8>,
finished: bool,
}
impl<R, A, const PAD: bool> EncoderReader<R, A, PAD>
where
A: Alphabet,
{
#[must_use]
pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
Self {
inner,
engine,
pending: [0; 2],
pending_len: 0,
output: VecDeque::new(),
finished: false,
}
}
#[must_use]
pub const fn get_ref(&self) -> &R {
&self.inner
}
pub fn get_mut(&mut self) -> &mut R {
&mut self.inner
}
#[must_use]
pub fn into_inner(self) -> R {
self.inner
}
}
impl<R, A, const PAD: bool> Read for EncoderReader<R, A, PAD>
where
R: Read,
A: Alphabet,
{
fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
if output.is_empty() {
return Ok(0);
}
while self.output.is_empty() && !self.finished {
self.fill_output()?;
}
let mut written = 0;
while written < output.len() {
let Some(byte) = self.output.pop_front() else {
break;
};
output[written] = byte;
written += 1;
}
Ok(written)
}
}
impl<R, A, const PAD: bool> EncoderReader<R, A, PAD>
where
R: Read,
A: Alphabet,
{
fn fill_output(&mut self) -> io::Result<()> {
let mut input = [0u8; 768];
let read = self.inner.read(&mut input)?;
if read == 0 {
self.finished = true;
self.push_final_pending()?;
return Ok(());
}
let mut consumed = 0;
if self.pending_len > 0 {
let needed = 3 - self.pending_len;
if read < needed {
self.pending[self.pending_len..self.pending_len + read]
.copy_from_slice(&input[..read]);
self.pending_len += read;
return Ok(());
}
let mut chunk = [0u8; 3];
chunk[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
chunk[self.pending_len..].copy_from_slice(&input[..needed]);
self.push_encoded(&chunk)?;
self.pending_len = 0;
consumed += needed;
}
let remaining = &input[consumed..read];
let full_len = remaining.len() / 3 * 3;
if full_len > 0 {
self.push_encoded(&remaining[..full_len])?;
}
let tail = &remaining[full_len..];
self.pending[..tail.len()].copy_from_slice(tail);
self.pending_len = tail.len();
Ok(())
}
fn push_final_pending(&mut self) -> io::Result<()> {
if self.pending_len == 0 {
return Ok(());
}
let mut pending = [0u8; 2];
pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
let pending_len = self.pending_len;
self.pending_len = 0;
self.push_encoded(&pending[..pending_len])
}
fn push_encoded(&mut self, input: &[u8]) -> io::Result<()> {
let mut encoded = [0u8; 1024];
let written = self
.engine
.encode_slice(input, &mut encoded)
.map_err(encode_error_to_io)?;
self.output.extend(&encoded[..written]);
Ok(())
}
}
}
pub const STANDARD: Engine<Standard, true> = Engine::new();
pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
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),
}
}
#[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 trait Alphabet {
const ENCODE: [u8; 64];
fn decode(byte: u8) -> Option<u8>;
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Standard;
impl Alphabet for Standard {
const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#[inline]
fn decode(byte: u8) -> Option<u8> {
decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct UrlSafe;
impl Alphabet for UrlSafe {
const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
#[inline]
fn decode(byte: u8) -> Option<u8> {
decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
}
}
#[inline]
const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
encode_ascii_base64(value, A::ENCODE[62], A::ENCODE[63])
}
#[inline]
const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
let upper = mask_if(value < 26);
let lower = mask_if(value.wrapping_sub(26) < 26);
let digit = mask_if(value.wrapping_sub(52) < 10);
let value_62 = mask_if(value == 0x3e);
let value_63 = mask_if(value == 0x3f);
(value.wrapping_add(b'A') & upper)
| (value.wrapping_sub(26).wrapping_add(b'a') & lower)
| (value.wrapping_sub(52).wrapping_add(b'0') & digit)
| (value_62_byte & value_62)
| (value_63_byte & value_63)
}
#[inline]
fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
let upper = mask_if(byte.wrapping_sub(b'A') <= b'Z' - b'A');
let lower = mask_if(byte.wrapping_sub(b'a') <= b'z' - b'a');
let digit = mask_if(byte.wrapping_sub(b'0') <= b'9' - b'0');
let value_62 = mask_if(byte == value_62_byte);
let value_63 = mask_if(byte == value_63_byte);
let valid = upper | lower | digit | value_62 | value_63;
let decoded = (byte.wrapping_sub(b'A') & upper)
| (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
| (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
| (0x3e & value_62)
| (0x3f & value_63);
if valid == 0 { None } else { Some(decoded) }
}
#[inline]
const fn mask_if(condition: bool) -> u8 {
0u8.wrapping_sub(condition as u8)
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Engine<A, const PAD: bool> {
alphabet: core::marker::PhantomData<A>,
}
impl<A, const PAD: bool> Engine<A, PAD>
where
A: Alphabet,
{
#[must_use]
pub const fn new() -> Self {
Self {
alphabet: core::marker::PhantomData,
}
}
pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
encoded_len(input_len, PAD)
}
#[must_use]
pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
checked_encoded_len(input_len, PAD)
}
pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
decoded_len(input, PAD)
}
#[must_use]
pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
&self,
input: &[u8; INPUT_LEN],
) -> [u8; OUTPUT_LEN] {
let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
panic!("encoded base64 length overflows usize");
};
assert!(
required == OUTPUT_LEN,
"base64 output array has incorrect length"
);
let mut output = [0u8; OUTPUT_LEN];
let mut read = 0;
let mut write = 0;
while INPUT_LEN - read >= 3 {
let b0 = input[read];
let b1 = input[read + 1];
let b2 = input[read + 2];
output[write] = encode_base64_value::<A>(b0 >> 2);
output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
read += 3;
write += 4;
}
match INPUT_LEN - read {
0 => {}
1 => {
let b0 = input[read];
output[write] = encode_base64_value::<A>(b0 >> 2);
output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
write += 2;
if PAD {
output[write] = b'=';
output[write + 1] = b'=';
}
}
2 => {
let b0 = input[read];
let b1 = input[read + 1];
output[write] = encode_base64_value::<A>(b0 >> 2);
output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
if PAD {
output[write + 3] = b'=';
}
}
_ => unreachable!(),
}
output
}
pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
if output.len() < required {
return Err(EncodeError::OutputTooSmall {
required,
available: output.len(),
});
}
let mut read = 0;
let mut write = 0;
while read + 3 <= input.len() {
let b0 = input[read];
let b1 = input[read + 1];
let b2 = input[read + 2];
output[write] = encode_base64_value::<A>(b0 >> 2);
output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
read += 3;
write += 4;
}
match input.len() - read {
0 => {}
1 => {
let b0 = input[read];
output[write] = encode_base64_value::<A>(b0 >> 2);
output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
write += 2;
if PAD {
output[write] = b'=';
output[write + 1] = b'=';
write += 2;
}
}
2 => {
let b0 = input[read];
let b1 = input[read + 1];
output[write] = encode_base64_value::<A>(b0 >> 2);
output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
write += 3;
if PAD {
output[write] = b'=';
write += 1;
}
}
_ => unreachable!(),
}
Ok(write)
}
#[cfg(feature = "alloc")]
pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
let mut output = alloc::vec![0; required];
let written = self.encode_slice(input, &mut output)?;
output.truncate(written);
Ok(output)
}
#[cfg(feature = "alloc")]
pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
let output = self.encode_vec(input)?;
match alloc::string::String::from_utf8(output) {
Ok(output) => Ok(output),
Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
}
}
pub fn encode_in_place<'a>(
&self,
buffer: &'a mut [u8],
input_len: usize,
) -> Result<&'a mut [u8], EncodeError> {
if input_len > buffer.len() {
return Err(EncodeError::InputTooLarge {
input_len,
buffer_len: buffer.len(),
});
}
let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
if buffer.len() < required {
return Err(EncodeError::OutputTooSmall {
required,
available: buffer.len(),
});
}
let mut read = input_len;
let mut write = required;
match input_len % 3 {
0 => {}
1 => {
read -= 1;
let b0 = buffer[read];
if PAD {
write -= 4;
buffer[write] = encode_base64_value::<A>(b0 >> 2);
buffer[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
buffer[write + 2] = b'=';
buffer[write + 3] = b'=';
} else {
write -= 2;
buffer[write] = encode_base64_value::<A>(b0 >> 2);
buffer[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
}
}
2 => {
read -= 2;
let b0 = buffer[read];
let b1 = buffer[read + 1];
if PAD {
write -= 4;
buffer[write] = encode_base64_value::<A>(b0 >> 2);
buffer[write + 1] =
encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
buffer[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
buffer[write + 3] = b'=';
} else {
write -= 3;
buffer[write] = encode_base64_value::<A>(b0 >> 2);
buffer[write + 1] =
encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
buffer[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
}
}
_ => unreachable!(),
}
while read > 0 {
read -= 3;
write -= 4;
let b0 = buffer[read];
let b1 = buffer[read + 1];
let b2 = buffer[read + 2];
buffer[write] = encode_base64_value::<A>(b0 >> 2);
buffer[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
buffer[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
buffer[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
}
debug_assert_eq!(write, 0);
Ok(&mut buffer[..required])
}
pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
if input.is_empty() {
return Ok(0);
}
if PAD {
decode_padded::<A>(input, output)
} else {
decode_unpadded::<A>(input, output)
}
}
#[cfg(feature = "alloc")]
pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
let required = validate_decode::<A, PAD>(input)?;
let mut output = alloc::vec![0; required];
let written = match self.decode_slice(input, &mut output) {
Ok(written) => written,
Err(err) => {
output.fill(0);
return Err(err);
}
};
output.truncate(written);
Ok(output)
}
pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
let len = Self::decode_slice_to_start(buffer)?;
Ok(&mut buffer[..len])
}
fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
let input_len = buffer.len();
let mut read = 0;
let mut write = 0;
while read + 4 <= input_len {
let chunk = [
buffer[read],
buffer[read + 1],
buffer[read + 2],
buffer[read + 3],
];
let written = decode_chunk::<A, PAD>(&chunk, &mut buffer[write..])
.map_err(|err| err.with_index_offset(read))?;
read += 4;
write += written;
if written < 3 {
if read != input_len {
return Err(DecodeError::InvalidPadding { index: read - 4 });
}
return Ok(write);
}
}
let rem = input_len - read;
if rem == 0 {
return Ok(write);
}
if PAD {
return Err(DecodeError::InvalidLength);
}
let mut tail = [0u8; 3];
tail[..rem].copy_from_slice(&buffer[read..input_len]);
decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
.map_err(|err| err.with_index_offset(read))
.map(|n| write + n)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EncodeError {
LengthOverflow,
InputTooLarge {
input_len: usize,
buffer_len: usize,
},
OutputTooSmall {
required: usize,
available: usize,
},
}
impl core::fmt::Display for EncodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
Self::InputTooLarge {
input_len,
buffer_len,
} => write!(
f,
"base64 input length {input_len} exceeds buffer length {buffer_len}"
),
Self::OutputTooSmall {
required,
available,
} => write!(
f,
"base64 output buffer too small: required {required}, available {available}"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for EncodeError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DecodeError {
InvalidLength,
InvalidByte {
index: usize,
byte: u8,
},
InvalidPadding {
index: usize,
},
OutputTooSmall {
required: usize,
available: usize,
},
}
impl core::fmt::Display for DecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidLength => f.write_str("invalid base64 input length"),
Self::InvalidByte { index, byte } => {
write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
}
Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
Self::OutputTooSmall {
required,
available,
} => write!(
f,
"base64 decode output buffer too small: required {required}, available {available}"
),
}
}
}
impl DecodeError {
fn with_index_offset(self, offset: usize) -> Self {
match self {
Self::InvalidByte { index, byte } => Self::InvalidByte {
index: index + offset,
byte,
},
Self::InvalidPadding { index } => Self::InvalidPadding {
index: index + offset,
},
Self::InvalidLength | Self::OutputTooSmall { .. } => self,
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for DecodeError {}
fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
if !input.len().is_multiple_of(4) {
return Err(DecodeError::InvalidLength);
}
let required = decoded_len_padded(input)?;
if output.len() < required {
return Err(DecodeError::OutputTooSmall {
required,
available: output.len(),
});
}
let mut read = 0;
let mut write = 0;
while read < input.len() {
let written = decode_chunk::<A, true>(&input[read..read + 4], &mut output[write..])
.map_err(|err| err.with_index_offset(read))?;
read += 4;
write += written;
if written < 3 && read != input.len() {
return Err(DecodeError::InvalidPadding { index: read - 4 });
}
}
Ok(write)
}
#[cfg(feature = "alloc")]
fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
if input.is_empty() {
return Ok(0);
}
if PAD {
validate_padded::<A>(input)
} else {
validate_unpadded::<A>(input)
}
}
#[cfg(feature = "alloc")]
fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
if !input.len().is_multiple_of(4) {
return Err(DecodeError::InvalidLength);
}
let required = decoded_len_padded(input)?;
let mut read = 0;
while read < input.len() {
let written = validate_chunk::<A, true>(&input[read..read + 4])
.map_err(|err| err.with_index_offset(read))?;
read += 4;
if written < 3 && read != input.len() {
return Err(DecodeError::InvalidPadding { index: read - 4 });
}
}
Ok(required)
}
#[cfg(feature = "alloc")]
fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
let required = decoded_len_unpadded(input)?;
let mut read = 0;
while read + 4 <= input.len() {
validate_chunk::<A, false>(&input[read..read + 4])
.map_err(|err| err.with_index_offset(read))?;
read += 4;
}
validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
Ok(required)
}
fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
let required = decoded_len_unpadded(input)?;
if output.len() < required {
return Err(DecodeError::OutputTooSmall {
required,
available: output.len(),
});
}
let mut read = 0;
let mut write = 0;
while read + 4 <= input.len() {
let written = decode_chunk::<A, false>(&input[read..read + 4], &mut output[write..])
.map_err(|err| err.with_index_offset(read))?;
read += 4;
write += written;
}
decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
.map_err(|err| err.with_index_offset(read))
.map(|n| write + n)
}
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 mut padding = 0;
if input[input.len() - 1] == b'=' {
padding += 1;
}
if input[input.len() - 2] == 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)
}
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()))
}
#[cfg(feature = "alloc")]
fn validate_chunk<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
debug_assert_eq!(input.len(), 4);
let _v0 = decode_byte::<A>(input[0], 0)?;
let v1 = decode_byte::<A>(input[1], 1)?;
match (input[2], input[3]) {
(b'=', b'=') if PAD => {
if v1 & 0b0000_1111 != 0 {
return Err(DecodeError::InvalidPadding { index: 1 });
}
Ok(1)
}
(b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
(_, b'=') if PAD => {
let v2 = decode_byte::<A>(input[2], 2)?;
if v2 & 0b0000_0011 != 0 {
return Err(DecodeError::InvalidPadding { index: 2 });
}
Ok(2)
}
(b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
index: input.iter().position(|byte| *byte == b'=').unwrap_or(0),
}),
_ => {
decode_byte::<A>(input[2], 2)?;
decode_byte::<A>(input[3], 3)?;
Ok(3)
}
}
}
fn decode_chunk<A: Alphabet, const PAD: bool>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
debug_assert_eq!(input.len(), 4);
let v0 = decode_byte::<A>(input[0], 0)?;
let v1 = decode_byte::<A>(input[1], 1)?;
match (input[2], input[3]) {
(b'=', b'=') if PAD => {
if output.is_empty() {
return Err(DecodeError::OutputTooSmall {
required: 1,
available: output.len(),
});
}
if v1 & 0b0000_1111 != 0 {
return Err(DecodeError::InvalidPadding { index: 1 });
}
output[0] = (v0 << 2) | (v1 >> 4);
Ok(1)
}
(b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
(_, b'=') if PAD => {
if output.len() < 2 {
return Err(DecodeError::OutputTooSmall {
required: 2,
available: output.len(),
});
}
let v2 = decode_byte::<A>(input[2], 2)?;
if v2 & 0b0000_0011 != 0 {
return Err(DecodeError::InvalidPadding { index: 2 });
}
output[0] = (v0 << 2) | (v1 >> 4);
output[1] = (v1 << 4) | (v2 >> 2);
Ok(2)
}
(b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
index: input.iter().position(|byte| *byte == b'=').unwrap_or(0),
}),
_ => {
if output.len() < 3 {
return Err(DecodeError::OutputTooSmall {
required: 3,
available: output.len(),
});
}
let v2 = decode_byte::<A>(input[2], 2)?;
let v3 = decode_byte::<A>(input[3], 3)?;
output[0] = (v0 << 2) | (v1 >> 4);
output[1] = (v1 << 4) | (v2 >> 2);
output[2] = (v2 << 6) | v3;
Ok(3)
}
}
}
#[cfg(feature = "alloc")]
fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
match input.len() {
0 => Ok(()),
2 => {
decode_byte::<A>(input[0], 0)?;
let v1 = decode_byte::<A>(input[1], 1)?;
if v1 & 0b0000_1111 != 0 {
return Err(DecodeError::InvalidPadding { index: 1 });
}
Ok(())
}
3 => {
decode_byte::<A>(input[0], 0)?;
decode_byte::<A>(input[1], 1)?;
let v2 = decode_byte::<A>(input[2], 2)?;
if v2 & 0b0000_0011 != 0 {
return Err(DecodeError::InvalidPadding { index: 2 });
}
Ok(())
}
_ => Err(DecodeError::InvalidLength),
}
}
fn decode_tail_unpadded<A: Alphabet>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
match input.len() {
0 => Ok(0),
2 => {
if output.is_empty() {
return Err(DecodeError::OutputTooSmall {
required: 1,
available: output.len(),
});
}
let v0 = decode_byte::<A>(input[0], 0)?;
let v1 = decode_byte::<A>(input[1], 1)?;
if v1 & 0b0000_1111 != 0 {
return Err(DecodeError::InvalidPadding { index: 1 });
}
output[0] = (v0 << 2) | (v1 >> 4);
Ok(1)
}
3 => {
if output.len() < 2 {
return Err(DecodeError::OutputTooSmall {
required: 2,
available: output.len(),
});
}
let v0 = decode_byte::<A>(input[0], 0)?;
let v1 = decode_byte::<A>(input[1], 1)?;
let v2 = decode_byte::<A>(input[2], 2)?;
if v2 & 0b0000_0011 != 0 {
return Err(DecodeError::InvalidPadding { index: 2 });
}
output[0] = (v0 << 2) | (v1 >> 4);
output[1] = (v1 << 4) | (v2 >> 2);
Ok(2)
}
_ => Err(DecodeError::InvalidLength),
}
}
fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encodes_standard_vectors() {
let vectors = [
(&b""[..], &b""[..]),
(&b"f"[..], &b"Zg=="[..]),
(&b"fo"[..], &b"Zm8="[..]),
(&b"foo"[..], &b"Zm9v"[..]),
(&b"foob"[..], &b"Zm9vYg=="[..]),
(&b"fooba"[..], &b"Zm9vYmE="[..]),
(&b"foobar"[..], &b"Zm9vYmFy"[..]),
];
for (input, expected) in vectors {
let mut output = [0u8; 16];
let written = STANDARD.encode_slice(input, &mut output).unwrap();
assert_eq!(&output[..written], expected);
}
}
#[test]
fn decodes_standard_vectors() {
let vectors = [
(&b""[..], &b""[..]),
(&b"Zg=="[..], &b"f"[..]),
(&b"Zm8="[..], &b"fo"[..]),
(&b"Zm9v"[..], &b"foo"[..]),
(&b"Zm9vYg=="[..], &b"foob"[..]),
(&b"Zm9vYmE="[..], &b"fooba"[..]),
(&b"Zm9vYmFy"[..], &b"foobar"[..]),
];
for (input, expected) in vectors {
let mut output = [0u8; 16];
let written = STANDARD.decode_slice(input, &mut output).unwrap();
assert_eq!(&output[..written], expected);
}
}
#[test]
fn supports_unpadded_url_safe() {
let mut encoded = [0u8; 16];
let written = URL_SAFE_NO_PAD
.encode_slice(b"\xfb\xff", &mut encoded)
.unwrap();
assert_eq!(&encoded[..written], b"-_8");
let mut decoded = [0u8; 2];
let written = URL_SAFE_NO_PAD
.decode_slice(&encoded[..written], &mut decoded)
.unwrap();
assert_eq!(&decoded[..written], b"\xfb\xff");
}
#[test]
fn decodes_in_place() {
let mut buffer = *b"Zm9vYmFy";
let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
assert_eq!(decoded, b"foobar");
}
#[test]
fn rejects_non_canonical_padding_bits() {
let mut output = [0u8; 4];
assert_eq!(
STANDARD.decode_slice(b"Zh==", &mut output),
Err(DecodeError::InvalidPadding { index: 1 })
);
assert_eq!(
STANDARD.decode_slice(b"Zm9=", &mut output),
Err(DecodeError::InvalidPadding { index: 2 })
);
}
}