#![cfg_attr(not(feature = "std"), no_std)]
#![deny(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "simd")]
mod simd;
pub mod runtime {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Backend {
Scalar,
Avx512Vbmi,
Avx2,
Neon,
}
impl Backend {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Scalar => "scalar",
Self::Avx512Vbmi => "avx512-vbmi",
Self::Avx2 => "avx2",
Self::Neon => "neon",
}
}
#[must_use]
pub const fn required_cpu_features(self) -> &'static [&'static str] {
match self {
Self::Scalar => &[],
Self::Avx512Vbmi => &["avx512f", "avx512bw", "avx512vl", "avx512vbmi"],
Self::Avx2 => &["avx2"],
Self::Neon => &["neon"],
}
}
}
impl core::fmt::Display for Backend {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum SecurityPosture {
ScalarOnly,
SimdCandidateScalarActive,
Accelerated,
}
impl SecurityPosture {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ScalarOnly => "scalar-only",
Self::SimdCandidateScalarActive => "simd-candidate-scalar-active",
Self::Accelerated => "accelerated",
}
}
}
impl core::fmt::Display for SecurityPosture {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum BackendPolicy {
ScalarExecutionOnly,
SimdFeatureDisabled,
NoDetectedSimdCandidate,
HighAssuranceScalarOnly,
}
impl BackendPolicy {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ScalarExecutionOnly => "scalar-execution-only",
Self::SimdFeatureDisabled => "simd-feature-disabled",
Self::NoDetectedSimdCandidate => "no-detected-simd-candidate",
Self::HighAssuranceScalarOnly => "high-assurance-scalar-only",
}
}
}
impl core::fmt::Display for BackendPolicy {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BackendPolicyError {
pub policy: BackendPolicy,
pub report: BackendReport,
}
impl core::fmt::Display for BackendPolicyError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
formatter,
"runtime backend policy `{}` was not satisfied ({})",
self.policy, self.report,
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for BackendPolicyError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BackendReport {
pub active: Backend,
pub candidate: Backend,
pub simd_feature_enabled: bool,
pub accelerated_backend_active: bool,
pub unsafe_boundary_enforced: bool,
pub security_posture: SecurityPosture,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BackendSnapshot {
pub active: &'static str,
pub candidate: &'static str,
pub candidate_required_cpu_features: &'static [&'static str],
pub simd_feature_enabled: bool,
pub accelerated_backend_active: bool,
pub unsafe_boundary_enforced: bool,
pub security_posture: &'static str,
}
impl core::fmt::Display for BackendReport {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
formatter,
"active={} candidate={} candidate_required_cpu_features=",
self.active, self.candidate,
)?;
write_feature_list(formatter, self.candidate_required_cpu_features())?;
write!(
formatter,
" simd_feature_enabled={} accelerated_backend_active={} unsafe_boundary_enforced={} security_posture={}",
self.simd_feature_enabled,
self.accelerated_backend_active,
self.unsafe_boundary_enforced,
self.security_posture,
)
}
}
impl BackendReport {
#[must_use]
pub const fn satisfies(self, policy: BackendPolicy) -> bool {
match policy {
BackendPolicy::ScalarExecutionOnly => {
matches!(self.active, Backend::Scalar) && !self.accelerated_backend_active
}
BackendPolicy::SimdFeatureDisabled => !self.simd_feature_enabled,
BackendPolicy::NoDetectedSimdCandidate => matches!(self.candidate, Backend::Scalar),
BackendPolicy::HighAssuranceScalarOnly => {
matches!(self.active, Backend::Scalar)
&& matches!(self.candidate, Backend::Scalar)
&& !self.simd_feature_enabled
&& !self.accelerated_backend_active
&& self.unsafe_boundary_enforced
}
}
}
#[must_use]
pub const fn candidate_required_cpu_features(self) -> &'static [&'static str] {
self.candidate.required_cpu_features()
}
#[must_use]
pub const fn snapshot(self) -> BackendSnapshot {
BackendSnapshot {
active: self.active.as_str(),
candidate: self.candidate.as_str(),
candidate_required_cpu_features: self.candidate_required_cpu_features(),
simd_feature_enabled: self.simd_feature_enabled,
accelerated_backend_active: self.accelerated_backend_active,
unsafe_boundary_enforced: self.unsafe_boundary_enforced,
security_posture: self.security_posture.as_str(),
}
}
}
#[must_use]
pub fn backend_report() -> BackendReport {
let active = active_backend();
let candidate = detected_candidate();
let accelerated_backend_active = active != Backend::Scalar;
let security_posture = if accelerated_backend_active {
SecurityPosture::Accelerated
} else if candidate != Backend::Scalar {
SecurityPosture::SimdCandidateScalarActive
} else {
SecurityPosture::ScalarOnly
};
BackendReport {
active,
candidate,
simd_feature_enabled: cfg!(feature = "simd"),
accelerated_backend_active,
unsafe_boundary_enforced: true,
security_posture,
}
}
pub fn require_backend_policy(policy: BackendPolicy) -> Result<(), BackendPolicyError> {
let report = backend_report();
if report.satisfies(policy) {
Ok(())
} else {
Err(BackendPolicyError { policy, report })
}
}
fn write_feature_list(
formatter: &mut core::fmt::Formatter<'_>,
features: &[&str],
) -> core::fmt::Result {
formatter.write_str("[")?;
let mut index = 0;
while index < features.len() {
if index != 0 {
formatter.write_str(",")?;
}
formatter.write_str(features[index])?;
index += 1;
}
formatter.write_str("]")
}
#[cfg(feature = "simd")]
fn active_backend() -> Backend {
match super::simd::active_backend() {
super::simd::ActiveBackend::Scalar => Backend::Scalar,
}
}
#[cfg(not(feature = "simd"))]
const fn active_backend() -> Backend {
Backend::Scalar
}
#[cfg(feature = "simd")]
fn detected_candidate() -> Backend {
match super::simd::detected_candidate() {
super::simd::Candidate::Scalar => Backend::Scalar,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
super::simd::Candidate::Avx512Vbmi => Backend::Avx512Vbmi,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
super::simd::Candidate::Avx2 => Backend::Avx2,
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
super::simd::Candidate::Neon => Backend::Neon,
}
}
#[cfg(not(feature = "simd"))]
const fn detected_candidate() -> Backend {
Backend::Scalar
}
}
#[cfg(feature = "stream")]
pub mod stream {
use super::{Alphabet, DecodeError, EncodeError, Engine};
use std::io::{self, Read, Write};
struct OutputQueue<const CAP: usize> {
buffer: [u8; CAP],
start: usize,
len: usize,
}
impl<const CAP: usize> OutputQueue<CAP> {
const fn new() -> Self {
Self {
buffer: [0; CAP],
start: 0,
len: 0,
}
}
const fn is_empty(&self) -> bool {
self.len == 0
}
fn push_slice(&mut self, input: &[u8]) -> io::Result<()> {
if input.len() > self.available_capacity() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"base64 stream output queue capacity exceeded",
));
}
let mut read = 0;
while read < input.len() {
let write = (self.start + self.len) % CAP;
self.buffer[write] = input[read];
self.len += 1;
read += 1;
}
Ok(())
}
fn pop_front(&mut self) -> Option<u8> {
if self.len == 0 {
return None;
}
let byte = self.buffer[self.start];
self.buffer[self.start] = 0;
self.start = (self.start + 1) % CAP;
self.len -= 1;
if self.len == 0 {
self.start = 0;
}
Some(byte)
}
fn clear_all(&mut self) {
crate::wipe_bytes(&mut self.buffer);
self.start = 0;
self.len = 0;
}
const fn available_capacity(&self) -> usize {
CAP - self.len
}
}
pub struct Encoder<W, A, const PAD: bool>
where
A: Alphabet,
{
inner: Option<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: Some(inner),
engine,
pending: [0; 2],
pending_len: 0,
}
}
#[must_use]
pub fn get_ref(&self) -> &W {
self.inner_ref()
}
pub fn get_mut(&mut self) -> &mut W {
self.inner_mut()
}
#[must_use]
pub fn into_inner(mut self) -> W {
self.take_inner()
}
fn inner_ref(&self) -> &W {
match &self.inner {
Some(inner) => inner,
None => unreachable!("stream encoder inner writer was already taken"),
}
}
fn inner_mut(&mut self) -> &mut W {
match &mut self.inner {
Some(inner) => inner,
None => unreachable!("stream encoder inner writer was already taken"),
}
}
fn take_inner(&mut self) -> W {
match self.inner.take() {
Some(inner) => inner,
None => unreachable!("stream encoder inner writer was already taken"),
}
}
fn clear_pending(&mut self) {
crate::wipe_bytes(&mut self.pending);
self.pending_len = 0;
}
}
impl<W, A, const PAD: bool> Drop for Encoder<W, A, PAD>
where
A: Alphabet,
{
fn drop(&mut self) {
self.clear_pending();
}
}
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_mut().flush()?;
Ok(self.take_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_mut().write_all(&encoded[..written])?;
self.clear_pending();
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_mut().write_all(&encoded[..written])?;
self.clear_pending();
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_mut().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_mut().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: Option<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: Some(inner),
engine,
pending: [0; 4],
pending_len: 0,
finished: false,
}
}
#[must_use]
pub fn get_ref(&self) -> &W {
self.inner_ref()
}
pub fn get_mut(&mut self) -> &mut W {
self.inner_mut()
}
#[must_use]
pub fn into_inner(mut self) -> W {
self.take_inner()
}
fn inner_ref(&self) -> &W {
match &self.inner {
Some(inner) => inner,
None => unreachable!("stream decoder inner writer was already taken"),
}
}
fn inner_mut(&mut self) -> &mut W {
match &mut self.inner {
Some(inner) => inner,
None => unreachable!("stream decoder inner writer was already taken"),
}
}
fn take_inner(&mut self) -> W {
match self.inner.take() {
Some(inner) => inner,
None => unreachable!("stream decoder inner writer was already taken"),
}
}
fn clear_pending(&mut self) {
crate::wipe_bytes(&mut self.pending);
self.pending_len = 0;
}
}
impl<W, A, const PAD: bool> Drop for Decoder<W, A, PAD>
where
A: Alphabet,
{
fn drop(&mut self) {
self.clear_pending();
}
}
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_mut().flush()?;
Ok(self.take_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_mut().write_all(&decoded[..written])?;
self.clear_pending();
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_mut().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.clear_pending();
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_mut().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: Option<R>,
engine: Engine<A, PAD>,
pending: [u8; 4],
pending_len: usize,
output: OutputQueue<3>,
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: Some(inner),
engine,
pending: [0; 4],
pending_len: 0,
output: OutputQueue::new(),
finished: false,
terminal_seen: false,
}
}
#[must_use]
pub fn get_ref(&self) -> &R {
self.inner_ref()
}
pub fn get_mut(&mut self) -> &mut R {
self.inner_mut()
}
#[must_use]
pub fn into_inner(mut self) -> R {
self.take_inner()
}
fn inner_ref(&self) -> &R {
match &self.inner {
Some(inner) => inner,
None => unreachable!("stream decoder reader inner reader was already taken"),
}
}
fn inner_mut(&mut self) -> &mut R {
match &mut self.inner {
Some(inner) => inner,
None => unreachable!("stream decoder reader inner reader was already taken"),
}
}
fn take_inner(&mut self) -> R {
match self.inner.take() {
Some(inner) => inner,
None => unreachable!("stream decoder reader inner reader was already taken"),
}
}
fn clear_pending(&mut self) {
crate::wipe_bytes(&mut self.pending);
self.pending_len = 0;
}
}
impl<R, A, const PAD: bool> Drop for DecoderReader<R, A, PAD>
where
A: Alphabet,
{
fn drop(&mut self) {
self.clear_pending();
self.output.clear_all();
}
}
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 available = 4 - self.pending_len;
let read = self.inner_mut().read(&mut input[..available])?;
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.clear_pending();
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.clear_pending();
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.push_slice(&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: Option<R>,
engine: Engine<A, PAD>,
pending: [u8; 2],
pending_len: usize,
output: OutputQueue<1024>,
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: Some(inner),
engine,
pending: [0; 2],
pending_len: 0,
output: OutputQueue::new(),
finished: false,
}
}
#[must_use]
pub fn get_ref(&self) -> &R {
self.inner_ref()
}
pub fn get_mut(&mut self) -> &mut R {
self.inner_mut()
}
#[must_use]
pub fn into_inner(mut self) -> R {
self.take_inner()
}
fn inner_ref(&self) -> &R {
match &self.inner {
Some(inner) => inner,
None => unreachable!("stream encoder reader inner reader was already taken"),
}
}
fn inner_mut(&mut self) -> &mut R {
match &mut self.inner {
Some(inner) => inner,
None => unreachable!("stream encoder reader inner reader was already taken"),
}
}
fn take_inner(&mut self) -> R {
match self.inner.take() {
Some(inner) => inner,
None => unreachable!("stream encoder reader inner reader was already taken"),
}
}
fn clear_pending(&mut self) {
crate::wipe_bytes(&mut self.pending);
self.pending_len = 0;
}
}
impl<R, A, const PAD: bool> Drop for EncoderReader<R, A, PAD>
where
A: Alphabet,
{
fn drop(&mut self) {
self.clear_pending();
self.output.clear_all();
}
}
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_mut().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.clear_pending();
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.clear_pending();
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.push_slice(&encoded[..written])?;
Ok(())
}
}
}
pub mod ct {
use super::{
Alphabet, DecodeError, Standard, UrlSafe, ct_decode_in_place, ct_decode_slice,
ct_validate_decode,
};
use core::marker::PhantomData;
pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct CtEngine<A, const PAD: bool> {
alphabet: PhantomData<A>,
}
impl<A, const PAD: bool> CtEngine<A, PAD>
where
A: Alphabet,
{
#[must_use]
pub const fn new() -> Self {
Self {
alphabet: PhantomData,
}
}
pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
ct_validate_decode::<A, PAD>(input)
}
#[must_use]
pub fn validate(&self, input: &[u8]) -> bool {
self.validate_result(input).is_ok()
}
pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
ct_decode_slice::<A, PAD>(input, output)
}
pub fn decode_slice_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
let written = match self.decode_slice(input, output) {
Ok(written) => written,
Err(err) => {
crate::wipe_bytes(output);
return Err(err);
}
};
crate::wipe_tail(output, written);
Ok(written)
}
pub fn decode_in_place<'a>(
&self,
buffer: &'a mut [u8],
) -> Result<&'a mut [u8], DecodeError> {
let len = ct_decode_in_place::<A, PAD>(buffer)?;
Ok(&mut buffer[..len])
}
pub fn decode_in_place_clear_tail<'a>(
&self,
buffer: &'a mut [u8],
) -> Result<&'a mut [u8], DecodeError> {
let len = match ct_decode_in_place::<A, PAD>(buffer) {
Ok(len) => len,
Err(err) => {
crate::wipe_bytes(buffer);
return Err(err);
}
};
crate::wipe_tail(buffer, len);
Ok(&mut buffer[..len])
}
}
}
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 BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LineEnding {
Lf,
CrLf,
}
impl LineEnding {
#[must_use]
pub const fn as_bytes(self) -> &'static [u8] {
match self {
Self::Lf => b"\n",
Self::CrLf => b"\r\n",
}
}
#[must_use]
pub const fn byte_len(self) -> usize {
match self {
Self::Lf => 1,
Self::CrLf => 2,
}
}
}
#[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 {
Self {
line_len,
line_ending,
}
}
}
#[allow(unsafe_code)]
fn wipe_bytes(bytes: &mut [u8]) {
for byte in bytes {
unsafe {
core::ptr::write_volatile(byte, 0);
}
}
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
fn wipe_tail(bytes: &mut [u8], start: usize) {
wipe_bytes(&mut bytes[start..]);
}
pub struct EncodedBuffer<const CAP: usize> {
bytes: [u8; CAP],
len: usize,
}
impl<const CAP: usize> EncodedBuffer<CAP> {
#[must_use]
pub const fn new() -> Self {
Self {
bytes: [0u8; CAP],
len: 0,
}
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub const fn capacity(&self) -> usize {
CAP
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
#[must_use]
pub fn as_str(&self) -> &str {
match core::str::from_utf8(self.as_bytes()) {
Ok(output) => output,
Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
}
}
pub fn clear(&mut self) {
wipe_bytes(&mut self.bytes);
self.len = 0;
}
pub fn clear_tail(&mut self) {
wipe_tail(&mut self.bytes, self.len);
}
}
impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
fn clone(&self) -> Self {
let mut output = Self::new();
output.bytes[..self.len].copy_from_slice(self.as_bytes());
output.len = self.len;
output
}
}
impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("EncodedBuffer")
.field("bytes", &"<redacted>")
.field("len", &self.len)
.field("capacity", &CAP)
.finish()
}
}
impl<const CAP: usize> Default for EncodedBuffer<CAP> {
fn default() -> Self {
Self::new()
}
}
impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
fn drop(&mut self) {
self.clear();
}
}
impl<const CAP: usize> Eq for EncodedBuffer<CAP> {}
impl<const CAP: usize> PartialEq for EncodedBuffer<CAP> {
fn eq(&self, other: &Self) -> bool {
self.as_bytes() == other.as_bytes()
}
}
#[cfg(feature = "alloc")]
pub struct SecretBuffer {
bytes: alloc::vec::Vec<u8>,
}
#[cfg(feature = "alloc")]
impl SecretBuffer {
#[must_use]
pub fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
Self { bytes }
}
#[must_use]
pub fn from_slice(bytes: &[u8]) -> Self {
Self {
bytes: bytes.to_vec(),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.bytes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[must_use]
pub fn expose_secret(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn expose_secret_mut(&mut self) -> &mut [u8] {
&mut self.bytes
}
pub fn clear(&mut self) {
wipe_bytes(&mut self.bytes);
self.bytes.clear();
}
}
#[cfg(feature = "alloc")]
impl Clone for SecretBuffer {
fn clone(&self) -> Self {
Self::from_slice(self.expose_secret())
}
}
#[cfg(feature = "alloc")]
impl core::fmt::Debug for SecretBuffer {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("SecretBuffer")
.field("bytes", &"<redacted>")
.field("len", &self.len())
.finish()
}
}
#[cfg(feature = "alloc")]
impl core::fmt::Display for SecretBuffer {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str("<redacted>")
}
}
#[cfg(feature = "alloc")]
impl Drop for SecretBuffer {
fn drop(&mut self) {
wipe_bytes(&mut self.bytes);
}
}
#[cfg(feature = "alloc")]
impl Eq for SecretBuffer {}
#[cfg(feature = "alloc")]
impl PartialEq for SecretBuffer {
fn eq(&self, other: &Self) -> bool {
self.expose_secret() == other.expose_secret()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Profile<A, const PAD: bool> {
engine: Engine<A, PAD>,
wrap: Option<LineWrap>,
}
impl<A, const PAD: bool> Profile<A, PAD>
where
A: Alphabet,
{
#[must_use]
pub const fn new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Self {
Self { engine, wrap }
}
#[must_use]
pub const fn engine(&self) -> Engine<A, PAD> {
self.engine
}
#[must_use]
pub const fn line_wrap(&self) -> Option<LineWrap> {
self.wrap
}
pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
match self.wrap {
Some(wrap) => wrapped_encoded_len(input_len, PAD, wrap),
None => encoded_len(input_len, PAD),
}
}
pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
match self.wrap {
Some(wrap) => self.engine.decoded_len_wrapped(input, wrap),
None => self.engine.decoded_len(input),
}
}
pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
match self.wrap {
Some(wrap) => self.engine.validate_wrapped_result(input, wrap),
None => self.engine.validate_result(input),
}
}
#[must_use]
pub fn validate(&self, input: &[u8]) -> bool {
self.validate_result(input).is_ok()
}
pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
match self.wrap {
Some(wrap) => self.engine.encode_slice_wrapped(input, output, wrap),
None => self.engine.encode_slice(input, output),
}
}
pub fn encode_slice_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, EncodeError> {
match self.wrap {
Some(wrap) => self
.engine
.encode_slice_wrapped_clear_tail(input, output, wrap),
None => self.engine.encode_slice_clear_tail(input, output),
}
}
pub fn encode_buffer<const CAP: usize>(
&self,
input: &[u8],
) -> Result<EncodedBuffer<CAP>, EncodeError> {
let mut output = EncodedBuffer::new();
let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
Ok(written) => written,
Err(err) => {
output.clear();
return Err(err);
}
};
output.len = written;
Ok(output)
}
pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
match self.wrap {
Some(wrap) => self.engine.decode_slice_wrapped(input, output, wrap),
None => self.engine.decode_slice(input, output),
}
}
pub fn decode_slice_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
match self.wrap {
Some(wrap) => self
.engine
.decode_slice_wrapped_clear_tail(input, output, wrap),
None => self.engine.decode_slice_clear_tail(input, output),
}
}
#[cfg(feature = "alloc")]
pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
match self.wrap {
Some(wrap) => self.engine.encode_wrapped_vec(input, wrap),
None => self.engine.encode_vec(input),
}
}
#[cfg(feature = "alloc")]
pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
self.encode_vec(input).map(SecretBuffer::from_vec)
}
#[cfg(feature = "alloc")]
pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
match self.wrap {
Some(wrap) => self.engine.encode_wrapped_string(input, wrap),
None => self.engine.encode_string(input),
}
}
#[cfg(feature = "alloc")]
pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
match self.wrap {
Some(wrap) => self.engine.decode_wrapped_vec(input, wrap),
None => self.engine.decode_vec(input),
}
}
#[cfg(feature = "alloc")]
pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
self.decode_vec(input).map(SecretBuffer::from_vec)
}
}
pub const MIME: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::MIME));
pub const PEM: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM));
pub const PEM_CRLF: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM_CRLF));
pub const BCRYPT: Profile<Bcrypt, false> = Profile::new(BCRYPT_NO_PAD, None);
pub const CRYPT: Profile<Crypt, false> = Profile::new(CRYPT_NO_PAD, None);
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_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 const fn validate_alphabet(encode: &[u8; 64]) -> Result<(), AlphabetError> {
let mut index = 0;
while index < encode.len() {
let byte = encode[index];
if !is_visible_ascii(byte) {
return Err(AlphabetError::InvalidByte { index, byte });
}
if byte == b'=' {
return Err(AlphabetError::PaddingByte { index });
}
let mut duplicate = index + 1;
while duplicate < encode.len() {
if encode[duplicate] == byte {
return Err(AlphabetError::DuplicateByte {
first: index,
second: duplicate,
byte,
});
}
duplicate += 1;
}
index += 1;
}
Ok(())
}
#[must_use]
pub const fn decode_alphabet_byte(byte: u8, encode: &[u8; 64]) -> Option<u8> {
let mut index = 0;
let mut value = 0;
while index < encode.len() {
if encode[index] == byte {
return Some(value);
}
index += 1;
value += 1;
}
None
}
pub trait Alphabet {
const ENCODE: [u8; 64];
#[must_use]
fn encode(value: u8) -> u8 {
encode_alphabet_value(value, &Self::ENCODE)
}
fn decode(byte: u8) -> Option<u8>;
}
const fn is_visible_ascii(byte: u8) -> bool {
byte >= 0x21 && byte <= 0x7e
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Standard;
impl Alphabet for Standard {
const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#[inline]
fn encode(value: u8) -> u8 {
encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
}
#[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 encode(value: u8) -> u8 {
encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
}
#[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 Bcrypt;
impl Alphabet for Bcrypt {
const ENCODE: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
#[inline]
fn decode(byte: u8) -> Option<u8> {
decode_alphabet_byte(byte, &Self::ENCODE)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Crypt;
impl Alphabet for Crypt {
const ENCODE: [u8; 64] = *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
#[inline]
fn decode(byte: u8) -> Option<u8> {
decode_alphabet_byte(byte, &Self::ENCODE)
}
}
#[inline]
const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
encode_alphabet_value(value, &A::ENCODE)
}
#[inline]
fn encode_base64_value_runtime<A: Alphabet>(value: u8) -> u8 {
A::encode(value)
}
#[inline]
const fn encode_alphabet_value(value: u8, encode: &[u8; 64]) -> u8 {
let mut output = 0;
let mut index = 0;
let mut candidate = 0;
while index < encode.len() {
output |= encode[index] & ct_mask_eq_u8(value, candidate);
index += 1;
candidate += 1;
}
output
}
#[inline]
const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
let upper = ct_mask_lt_u8(value, 26);
let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
let value_62 = ct_mask_eq_u8(value, 0x3e);
let value_63 = ct_mask_eq_u8(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 = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
let value_62 = ct_mask_eq_u8(byte, value_62_byte);
let value_63 = ct_mask_eq_u8(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 ct_mask_bit(bit: u8) -> u8 {
0u8.wrapping_sub(bit & 1)
}
#[inline]
const fn ct_mask_nonzero_u8(value: u8) -> u8 {
let wide = value as u16;
let negative = 0u16.wrapping_sub(wide);
let nonzero = ((wide | negative) >> 8) as u8;
ct_mask_bit(nonzero)
}
#[inline]
const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
!ct_mask_nonzero_u8(left ^ right)
}
#[inline]
const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
let diff = (left as u16).wrapping_sub(right as u16);
ct_mask_bit((diff >> 8) as u8)
}
mod backend {
use super::{
Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
encode_base64_value_runtime,
};
pub(super) fn encode_slice<A, const PAD: bool>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, EncodeError>
where
A: Alphabet,
{
#[cfg(feature = "simd")]
match super::simd::active_backend() {
super::simd::ActiveBackend::Scalar => {}
}
scalar_encode_slice::<A, PAD>(input, output)
}
pub(super) fn decode_slice<A, const PAD: bool>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError>
where
A: Alphabet,
{
#[cfg(feature = "simd")]
match super::simd::active_backend() {
super::simd::ActiveBackend::Scalar => {}
}
scalar_decode_slice::<A, PAD>(input, output)
}
#[cfg(test)]
pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, EncodeError>
where
A: Alphabet,
{
scalar_encode_slice::<A, PAD>(input, output)
}
#[cfg(test)]
pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError>
where
A: Alphabet,
{
scalar_decode_slice::<A, PAD>(input, output)
}
fn scalar_encode_slice<A, const PAD: bool>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, EncodeError>
where
A: Alphabet,
{
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_runtime::<A>(b0 >> 2);
output[write + 1] =
encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
output[write + 2] =
encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
read += 3;
write += 4;
}
match input.len() - read {
0 => {}
1 => {
let b0 = input[read];
output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
output[write + 1] = encode_base64_value_runtime::<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_runtime::<A>(b0 >> 2);
output[write + 1] =
encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
write += 3;
if PAD {
output[write] = b'=';
write += 1;
}
}
_ => unreachable!(),
}
Ok(write)
}
fn scalar_decode_slice<A, const PAD: bool>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError>
where
A: Alphabet,
{
if input.is_empty() {
return Ok(0);
}
if PAD {
decode_padded::<A>(input, output)
} else {
decode_unpadded::<A>(input, output)
}
}
}
pub struct Engine<A, const PAD: bool> {
alphabet: core::marker::PhantomData<A>,
}
impl<A, const PAD: bool> Clone for Engine<A, PAD> {
fn clone(&self) -> Self {
*self
}
}
impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("Engine")
.field("padded", &PAD)
.finish()
}
}
impl<A, const PAD: bool> Default for Engine<A, PAD> {
fn default() -> Self {
Self {
alphabet: core::marker::PhantomData,
}
}
}
impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
fn eq(&self, _other: &Self) -> bool {
true
}
}
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 const fn wrapped_encoded_len(
&self,
input_len: usize,
wrap: LineWrap,
) -> Result<usize, EncodeError> {
wrapped_encoded_len(input_len, PAD, wrap)
}
pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
decoded_len(input, PAD)
}
pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
validate_legacy_decode::<A, PAD>(input)
}
pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
validate_wrapped_decode::<A, PAD>(input, wrap)
}
pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
validate_decode::<A, PAD>(input).map(|_| ())
}
#[must_use]
pub fn validate(&self, input: &[u8]) -> bool {
self.validate_result(input).is_ok()
}
pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
validate_legacy_decode::<A, PAD>(input).map(|_| ())
}
#[must_use]
pub fn validate_legacy(&self, input: &[u8]) -> bool {
self.validate_legacy_result(input).is_ok()
}
pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
}
#[must_use]
pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
self.validate_wrapped_result(input, wrap).is_ok()
}
#[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> {
backend::encode_slice::<A, PAD>(input, output)
}
pub fn encode_slice_wrapped(
&self,
input: &[u8],
output: &mut [u8],
wrap: LineWrap,
) -> Result<usize, EncodeError> {
let required = self.wrapped_encoded_len(input.len(), wrap)?;
if output.len() < required {
return Err(EncodeError::OutputTooSmall {
required,
available: output.len(),
});
}
let encoded_len =
checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
if encoded_len == 0 {
return Ok(0);
}
if output.len() < required.saturating_add(encoded_len) {
let mut scratch = [0u8; 1024];
let mut input_offset = 0;
let mut output_offset = 0;
let mut column = 0;
while input_offset < input.len() {
let remaining = input.len() - input_offset;
let mut take = remaining.min(768);
if remaining > take {
take -= take % 3;
}
if take == 0 {
take = remaining;
}
let encoded =
self.encode_slice(&input[input_offset..input_offset + take], &mut scratch)?;
write_wrapped_bytes(
&scratch[..encoded],
output,
&mut output_offset,
&mut column,
wrap,
);
wipe_bytes(&mut scratch[..encoded]);
input_offset += take;
}
Ok(output_offset)
} else {
let encoded =
self.encode_slice(input, &mut output[required..required + encoded_len])?;
let mut output_offset = 0;
let mut column = 0;
let mut read = required;
while read < required + encoded {
let byte = output[read];
write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap);
read += 1;
}
wipe_bytes(&mut output[required..required + encoded]);
Ok(output_offset)
}
}
pub fn encode_slice_wrapped_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
wrap: LineWrap,
) -> Result<usize, EncodeError> {
let written = match self.encode_slice_wrapped(input, output, wrap) {
Ok(written) => written,
Err(err) => {
wipe_bytes(output);
return Err(err);
}
};
wipe_tail(output, written);
Ok(written)
}
#[cfg(feature = "alloc")]
pub fn encode_wrapped_vec(
&self,
input: &[u8],
wrap: LineWrap,
) -> Result<alloc::vec::Vec<u8>, EncodeError> {
let required = self.wrapped_encoded_len(input.len(), wrap)?;
let mut output = alloc::vec![0; required];
let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
output.truncate(written);
Ok(output)
}
#[cfg(feature = "alloc")]
pub fn encode_wrapped_string(
&self,
input: &[u8],
wrap: LineWrap,
) -> Result<alloc::string::String, EncodeError> {
let output = self.encode_wrapped_vec(input, wrap)?;
match alloc::string::String::from_utf8(output) {
Ok(output) => Ok(output),
Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
}
}
pub fn encode_slice_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, EncodeError> {
let written = match self.encode_slice(input, output) {
Ok(written) => written,
Err(err) => {
wipe_bytes(output);
return Err(err);
}
};
wipe_tail(output, written);
Ok(written)
}
pub fn encode_buffer<const CAP: usize>(
&self,
input: &[u8],
) -> Result<EncodedBuffer<CAP>, EncodeError> {
let mut output = EncodedBuffer::new();
let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
Ok(written) => written,
Err(err) => {
output.clear();
return Err(err);
}
};
output.len = written;
Ok(output)
}
#[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_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
self.encode_vec(input).map(SecretBuffer::from_vec)
}
#[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_runtime::<A>(b0 >> 2);
buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
buffer[write + 2] = b'=';
buffer[write + 3] = b'=';
} else {
write -= 2;
buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
buffer[write + 1] = encode_base64_value_runtime::<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_runtime::<A>(b0 >> 2);
buffer[write + 1] =
encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
buffer[write + 3] = b'=';
} else {
write -= 3;
buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
buffer[write + 1] =
encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
buffer[write + 2] = encode_base64_value_runtime::<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_runtime::<A>(b0 >> 2);
buffer[write + 1] =
encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
buffer[write + 2] =
encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
}
debug_assert_eq!(write, 0);
Ok(&mut buffer[..required])
}
pub fn encode_in_place_clear_tail<'a>(
&self,
buffer: &'a mut [u8],
input_len: usize,
) -> Result<&'a mut [u8], EncodeError> {
let len = match self.encode_in_place(buffer, input_len) {
Ok(encoded) => encoded.len(),
Err(err) => {
wipe_bytes(buffer);
return Err(err);
}
};
wipe_tail(buffer, len);
Ok(&mut buffer[..len])
}
pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
backend::decode_slice::<A, PAD>(input, output)
}
pub fn decode_slice_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
let written = match self.decode_slice(input, output) {
Ok(written) => written,
Err(err) => {
wipe_bytes(output);
return Err(err);
}
};
wipe_tail(output, written);
Ok(written)
}
pub fn decode_slice_legacy(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
let required = validate_legacy_decode::<A, PAD>(input)?;
if output.len() < required {
return Err(DecodeError::OutputTooSmall {
required,
available: output.len(),
});
}
decode_legacy_to_slice::<A, PAD>(input, output)
}
pub fn decode_slice_legacy_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
let written = match self.decode_slice_legacy(input, output) {
Ok(written) => written,
Err(err) => {
wipe_bytes(output);
return Err(err);
}
};
wipe_tail(output, written);
Ok(written)
}
pub fn decode_slice_wrapped(
&self,
input: &[u8],
output: &mut [u8],
wrap: LineWrap,
) -> Result<usize, DecodeError> {
let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
if output.len() < required {
return Err(DecodeError::OutputTooSmall {
required,
available: output.len(),
});
}
decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
}
pub fn decode_slice_wrapped_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
wrap: LineWrap,
) -> Result<usize, DecodeError> {
let written = match self.decode_slice_wrapped(input, output, wrap) {
Ok(written) => written,
Err(err) => {
wipe_bytes(output);
return Err(err);
}
};
wipe_tail(output, written);
Ok(written)
}
#[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) => {
wipe_bytes(&mut output);
return Err(err);
}
};
output.truncate(written);
Ok(output)
}
#[cfg(feature = "alloc")]
pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
self.decode_vec(input).map(SecretBuffer::from_vec)
}
#[cfg(feature = "alloc")]
pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
let required = validate_legacy_decode::<A, PAD>(input)?;
let mut output = alloc::vec![0; required];
let written = match self.decode_slice_legacy(input, &mut output) {
Ok(written) => written,
Err(err) => {
wipe_bytes(&mut output);
return Err(err);
}
};
output.truncate(written);
Ok(output)
}
#[cfg(feature = "alloc")]
pub fn decode_wrapped_vec(
&self,
input: &[u8],
wrap: LineWrap,
) -> Result<alloc::vec::Vec<u8>, DecodeError> {
let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
let mut output = alloc::vec![0; required];
let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
Ok(written) => written,
Err(err) => {
wipe_bytes(&mut output);
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])
}
pub fn decode_in_place_clear_tail<'a>(
&self,
buffer: &'a mut [u8],
) -> Result<&'a mut [u8], DecodeError> {
let len = match Self::decode_slice_to_start(buffer) {
Ok(len) => len,
Err(err) => {
wipe_bytes(buffer);
return Err(err);
}
};
wipe_tail(buffer, len);
Ok(&mut buffer[..len])
}
pub fn decode_in_place_legacy<'a>(
&self,
buffer: &'a mut [u8],
) -> Result<&'a mut [u8], DecodeError> {
let _required = validate_legacy_decode::<A, PAD>(buffer)?;
let mut write = 0;
let mut read = 0;
while read < buffer.len() {
let byte = buffer[read];
if !is_legacy_whitespace(byte) {
buffer[write] = byte;
write += 1;
}
read += 1;
}
let len = Self::decode_slice_to_start(&mut buffer[..write])?;
Ok(&mut buffer[..len])
}
pub fn decode_in_place_legacy_clear_tail<'a>(
&self,
buffer: &'a mut [u8],
) -> Result<&'a mut [u8], DecodeError> {
if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
wipe_bytes(buffer);
return Err(err);
}
let mut write = 0;
let mut read = 0;
while read < buffer.len() {
let byte = buffer[read];
if !is_legacy_whitespace(byte) {
buffer[write] = byte;
write += 1;
}
read += 1;
}
let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
Ok(len) => len,
Err(err) => {
wipe_bytes(buffer);
return Err(err);
}
};
wipe_tail(buffer, len);
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 = read_quad(buffer, read)?;
let available = buffer.len();
let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
required: write,
available,
})?;
let written = decode_chunk::<A, PAD>(chunk, output_tail)
.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)
}
}
fn write_wrapped_bytes(
input: &[u8],
output: &mut [u8],
output_offset: &mut usize,
column: &mut usize,
wrap: LineWrap,
) {
for byte in input {
write_wrapped_byte(*byte, output, output_offset, column, wrap);
}
}
fn write_wrapped_byte(
byte: u8,
output: &mut [u8],
output_offset: &mut usize,
column: &mut usize,
wrap: LineWrap,
) {
if *column == wrap.line_len {
let line_ending = wrap.line_ending.as_bytes();
let mut index = 0;
while index < line_ending.len() {
output[*output_offset] = line_ending[index];
*output_offset += 1;
index += 1;
}
*column = 0;
}
output[*output_offset] = byte;
*output_offset += 1;
*column += 1;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EncodeError {
LengthOverflow,
InvalidLineWrap {
line_len: usize,
},
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::InvalidLineWrap { line_len } => {
write!(f, "base64 line wrap length {line_len} is invalid")
}
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 AlphabetError {
InvalidByte {
index: usize,
byte: u8,
},
PaddingByte {
index: usize,
},
DuplicateByte {
first: usize,
second: usize,
byte: u8,
},
}
impl core::fmt::Display for AlphabetError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidByte { index, byte } => {
write!(
f,
"invalid base64 alphabet byte 0x{byte:02x} at index {index}"
)
}
Self::PaddingByte { index } => {
write!(f, "base64 alphabet contains padding byte at index {index}")
}
Self::DuplicateByte {
first,
second,
byte,
} => write!(
f,
"base64 alphabet byte 0x{byte:02x} is duplicated at indexes {first} and {second}"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for AlphabetError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DecodeError {
InvalidInput,
InvalidLength,
InvalidByte {
index: usize,
byte: u8,
},
InvalidPadding {
index: usize,
},
InvalidLineWrap {
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::InvalidInput => f.write_str("malformed base64 input"),
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::InvalidLineWrap { index } => {
write!(f, "invalid base64 line wrapping 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::InvalidLineWrap { index } => Self::InvalidLineWrap {
index: index + offset,
},
Self::InvalidInput | Self::InvalidLength | Self::OutputTooSmall { .. } => self,
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for DecodeError {}
fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
input: &[u8],
) -> Result<usize, DecodeError> {
let mut chunk = [0u8; 4];
let mut indexes = [0usize; 4];
let mut chunk_len = 0;
let mut required = 0;
let mut terminal_seen = false;
for (index, byte) in input.iter().copied().enumerate() {
if is_legacy_whitespace(byte) {
continue;
}
if terminal_seen {
return Err(DecodeError::InvalidPadding { index });
}
chunk[chunk_len] = byte;
indexes[chunk_len] = index;
chunk_len += 1;
if chunk_len == 4 {
let written =
validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
required += written;
terminal_seen = written < 3;
chunk_len = 0;
}
}
if chunk_len == 0 {
return Ok(required);
}
if PAD {
return Err(DecodeError::InvalidLength);
}
validate_tail_unpadded::<A>(&chunk[..chunk_len])
.map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
Ok(required + decoded_capacity(chunk_len))
}
fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
let mut chunk = [0u8; 4];
let mut indexes = [0usize; 4];
let mut chunk_len = 0;
let mut write = 0;
let mut terminal_seen = false;
for (index, byte) in input.iter().copied().enumerate() {
if is_legacy_whitespace(byte) {
continue;
}
if terminal_seen {
return Err(DecodeError::InvalidPadding { index });
}
chunk[chunk_len] = byte;
indexes[chunk_len] = index;
chunk_len += 1;
if chunk_len == 4 {
let available = output.len();
let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
required: write,
available,
})?;
let written = decode_chunk::<A, PAD>(chunk, output_tail)
.map_err(|err| map_chunk_error(err, &indexes))?;
write += written;
terminal_seen = written < 3;
chunk_len = 0;
}
}
if chunk_len == 0 {
return Ok(write);
}
if PAD {
return Err(DecodeError::InvalidLength);
}
decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
.map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
.map(|n| write + n)
}
struct WrappedBytes<'a> {
input: &'a [u8],
wrap: LineWrap,
index: usize,
line_len: usize,
}
impl<'a> WrappedBytes<'a> {
const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
if wrap.line_len == 0 {
return Err(DecodeError::InvalidLineWrap { index: 0 });
}
Ok(Self {
input,
wrap,
index: 0,
line_len: 0,
})
}
fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
loop {
if self.index == self.input.len() {
return Ok(None);
}
if self.starts_with_line_ending() {
let line_end_index = self.index;
if self.line_len == 0 {
return Err(DecodeError::InvalidLineWrap {
index: line_end_index,
});
}
self.index += self.wrap.line_ending.byte_len();
if self.index == self.input.len() {
self.line_len = 0;
return Ok(None);
}
if self.line_len != self.wrap.line_len {
return Err(DecodeError::InvalidLineWrap {
index: line_end_index,
});
}
self.line_len = 0;
continue;
}
let byte = self.input[self.index];
if matches!(byte, b'\r' | b'\n') {
return Err(DecodeError::InvalidLineWrap { index: self.index });
}
self.line_len += 1;
if self.line_len > self.wrap.line_len {
return Err(DecodeError::InvalidLineWrap { index: self.index });
}
let index = self.index;
self.index += 1;
return Ok(Some((index, byte)));
}
}
fn starts_with_line_ending(&self) -> bool {
let line_ending = self.wrap.line_ending.as_bytes();
let end = self.index + line_ending.len();
end <= self.input.len() && &self.input[self.index..end] == line_ending
}
}
fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
input: &[u8],
wrap: LineWrap,
) -> Result<usize, DecodeError> {
let mut bytes = WrappedBytes::new(input, wrap)?;
let mut chunk = [0u8; 4];
let mut indexes = [0usize; 4];
let mut chunk_len = 0;
let mut required = 0;
let mut terminal_seen = false;
while let Some((index, byte)) = bytes.next_byte()? {
if terminal_seen {
return Err(DecodeError::InvalidPadding { index });
}
chunk[chunk_len] = byte;
indexes[chunk_len] = index;
chunk_len += 1;
if chunk_len == 4 {
let written =
validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
required += written;
terminal_seen = written < 3;
chunk_len = 0;
}
}
if chunk_len == 0 {
return Ok(required);
}
if PAD {
return Err(DecodeError::InvalidLength);
}
validate_tail_unpadded::<A>(&chunk[..chunk_len])
.map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
Ok(required + decoded_capacity(chunk_len))
}
fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
input: &[u8],
output: &mut [u8],
wrap: LineWrap,
) -> Result<usize, DecodeError> {
let mut bytes = WrappedBytes::new(input, wrap)?;
let mut chunk = [0u8; 4];
let mut indexes = [0usize; 4];
let mut chunk_len = 0;
let mut write = 0;
let mut terminal_seen = false;
while let Some((index, byte)) = bytes.next_byte()? {
if terminal_seen {
return Err(DecodeError::InvalidPadding { index });
}
chunk[chunk_len] = byte;
indexes[chunk_len] = index;
chunk_len += 1;
if chunk_len == 4 {
let available = output.len();
let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
required: write,
available,
})?;
let written = decode_chunk::<A, PAD>(chunk, output_tail)
.map_err(|err| map_chunk_error(err, &indexes))?;
write += written;
terminal_seen = written < 3;
chunk_len = 0;
}
}
if chunk_len == 0 {
return Ok(write);
}
if PAD {
return Err(DecodeError::InvalidLength);
}
decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
.map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
.map(|n| write + n)
}
#[inline]
const fn is_legacy_whitespace(byte: u8) -> bool {
matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
}
fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
match err {
DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
index: indexes[index],
byte,
},
DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
index: indexes[index],
},
DecodeError::InvalidInput
| DecodeError::InvalidLineWrap { .. }
| DecodeError::InvalidLength
| DecodeError::OutputTooSmall { .. } => err,
}
}
fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
match err {
DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
index: indexes[index],
byte,
},
DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
index: indexes[index],
},
DecodeError::InvalidByte { .. }
| DecodeError::InvalidPadding { .. }
| DecodeError::InvalidLineWrap { .. }
| DecodeError::InvalidInput
| DecodeError::InvalidLength
| DecodeError::OutputTooSmall { .. } => err,
}
}
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 chunk = read_quad(input, read)?;
let available = output.len();
let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
required: write,
available,
})?;
let written = decode_chunk::<A, true>(chunk, output_tail)
.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)
}
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)
}
}
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 chunk = read_quad(input, read)?;
let written =
validate_chunk::<A, true>(chunk).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)
}
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() {
let chunk = read_quad(input, read)?;
validate_chunk::<A, false>(chunk).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 chunk = read_quad(input, read)?;
let available = output.len();
let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
required: write,
available,
})?;
let written = decode_chunk::<A, false>(chunk, output_tail)
.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()))
}
fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
match input.get(offset..end) {
Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
_ => Err(DecodeError::InvalidLength),
}
}
fn first_padding_index(input: [u8; 4]) -> usize {
let [b0, b1, b2, b3] = input;
if b0 == b'=' {
0
} else if b1 == b'=' {
1
} else if b2 == b'=' {
2
} else if b3 == b'=' {
3
} else {
0
}
}
fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
let [b0, b1, b2, b3] = input;
let _v0 = decode_byte::<A>(b0, 0)?;
let v1 = decode_byte::<A>(b1, 1)?;
match (b2, b3) {
(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>(b2, 2)?;
if v2 & 0b0000_0011 != 0 {
return Err(DecodeError::InvalidPadding { index: 2 });
}
Ok(2)
}
(b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
index: first_padding_index(input),
}),
_ => {
decode_byte::<A>(b2, 2)?;
decode_byte::<A>(b3, 3)?;
Ok(3)
}
}
}
fn decode_chunk<A: Alphabet, const PAD: bool>(
input: [u8; 4],
output: &mut [u8],
) -> Result<usize, DecodeError> {
let [b0, b1, b2, b3] = input;
let v0 = decode_byte::<A>(b0, 0)?;
let v1 = decode_byte::<A>(b1, 1)?;
match (b2, b3) {
(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>(b2, 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: first_padding_index(input),
}),
_ => {
if output.len() < 3 {
return Err(DecodeError::OutputTooSmall {
required: 3,
available: output.len(),
});
}
let v2 = decode_byte::<A>(b2, 2)?;
let v3 = decode_byte::<A>(b3, 3)?;
output[0] = (v0 << 2) | (v1 >> 4);
output[1] = (v1 << 4) | (v2 >> 2);
output[2] = (v2 << 6) | v3;
Ok(3)
}
}
}
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 })
}
fn ct_decode_slice<A: Alphabet, const PAD: bool>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
if input.is_empty() {
return Ok(0);
}
if PAD {
ct_decode_padded::<A>(input, output)
} else {
ct_decode_unpadded::<A>(input, output)
}
}
fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
buffer: &mut [u8],
) -> Result<usize, DecodeError> {
if buffer.is_empty() {
return Ok(0);
}
if PAD {
ct_decode_padded_in_place::<A>(buffer)
} else {
ct_decode_unpadded_in_place::<A>(buffer)
}
}
fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
if input.is_empty() {
return Ok(());
}
if PAD {
ct_validate_padded::<A>(input)
} else {
ct_validate_unpadded::<A>(input)
}
}
fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
if !input.len().is_multiple_of(4) {
return Err(DecodeError::InvalidLength);
}
let padding = ct_padding_len(input);
let mut invalid_byte = 0u8;
let mut invalid_padding = 0u8;
let mut read = 0;
while read + 4 < input.len() {
let [b0, b1, b2, b3] = read_quad(input, read)?;
let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
let (_, valid1) = ct_decode_ascii_base64::<A>(b1);
let (_, valid2) = ct_decode_ascii_base64::<A>(b2);
let (_, valid3) = ct_decode_ascii_base64::<A>(b3);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_byte |= !valid2;
invalid_byte |= !valid3;
invalid_padding |= ct_mask_eq_u8(b2, b'=');
invalid_padding |= ct_mask_eq_u8(b3, b'=');
read += 4;
}
let final_chunk = read_quad(input, read)?;
let (_, final_invalid_byte, final_invalid_padding, _) =
ct_padded_final_quantum::<A>(final_chunk, padding);
invalid_byte |= final_invalid_byte;
invalid_padding |= final_invalid_padding;
report_ct_error(invalid_byte, invalid_padding)
}
fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
if input.len() % 4 == 1 {
return Err(DecodeError::InvalidLength);
}
let mut invalid_byte = 0u8;
let mut invalid_padding = 0u8;
let mut read = 0;
while read + 4 <= input.len() {
let b0 = input[read];
let b1 = input[read + 1];
let b2 = input[read + 2];
let b3 = input[read + 3];
let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
let (_, valid1) = ct_decode_ascii_base64::<A>(b1);
let (_, valid2) = ct_decode_ascii_base64::<A>(b2);
let (_, valid3) = ct_decode_ascii_base64::<A>(b3);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_byte |= !valid2;
invalid_byte |= !valid3;
invalid_padding |= ct_mask_eq_u8(b0, b'=');
invalid_padding |= ct_mask_eq_u8(b1, b'=');
invalid_padding |= ct_mask_eq_u8(b2, b'=');
invalid_padding |= ct_mask_eq_u8(b3, b'=');
read += 4;
}
match input.len() - read {
0 => {}
2 => {
let b0 = input[read];
let b1 = input[read + 1];
let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_padding |= ct_mask_eq_u8(b0, b'=');
invalid_padding |= ct_mask_eq_u8(b1, b'=');
invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
}
3 => {
let b0 = input[read];
let b1 = input[read + 1];
let b2 = input[read + 2];
let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
let (_, valid1) = ct_decode_ascii_base64::<A>(b1);
let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_byte |= !valid2;
invalid_padding |= ct_mask_eq_u8(b0, b'=');
invalid_padding |= ct_mask_eq_u8(b1, b'=');
invalid_padding |= ct_mask_eq_u8(b2, b'=');
invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
}
_ => return Err(DecodeError::InvalidLength),
}
report_ct_error(invalid_byte, invalid_padding)
}
fn ct_padded_final_quantum<A: Alphabet>(
input: [u8; 4],
padding: usize,
) -> ([u8; 3], u8, u8, usize) {
let [b0, b1, b2, b3] = input;
let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
let padding_byte = padding.to_le_bytes()[0];
let no_padding = ct_mask_eq_u8(padding_byte, 0);
let one_padding = ct_mask_eq_u8(padding_byte, 1);
let two_padding = ct_mask_eq_u8(padding_byte, 2);
let require_v2 = no_padding | one_padding;
let require_v3 = no_padding;
let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
| ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
| ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
(
[(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
invalid_byte,
invalid_padding,
3 - padding,
)
}
fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
if !input.len().is_multiple_of(4) {
return Err(DecodeError::InvalidLength);
}
let padding = ct_padding_len(input);
let required = input.len() / 4 * 3 - padding;
if output.len() < required {
return Err(DecodeError::OutputTooSmall {
required,
available: output.len(),
});
}
let mut invalid_byte = 0u8;
let mut invalid_padding = 0u8;
let mut write = 0;
let mut read = 0;
while read + 4 < input.len() {
let [b0, b1, b2, b3] = read_quad(input, read)?;
let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_byte |= !valid2;
invalid_byte |= !valid3;
invalid_padding |= ct_mask_eq_u8(b2, b'=');
invalid_padding |= ct_mask_eq_u8(b3, b'=');
output[write] = (v0 << 2) | (v1 >> 4);
output[write + 1] = (v1 << 4) | (v2 >> 2);
output[write + 2] = (v2 << 6) | v3;
write += 3;
read += 4;
}
let final_chunk = read_quad(input, read)?;
let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
ct_padded_final_quantum::<A>(final_chunk, padding);
invalid_byte |= final_invalid_byte;
invalid_padding |= final_invalid_padding;
output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
write += final_written;
report_ct_error(invalid_byte, invalid_padding)?;
Ok(write)
}
fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
if !buffer.len().is_multiple_of(4) {
return Err(DecodeError::InvalidLength);
}
let padding = ct_padding_len(buffer);
let required = buffer.len() / 4 * 3 - padding;
debug_assert!(required <= buffer.len());
let mut invalid_byte = 0u8;
let mut invalid_padding = 0u8;
let mut write = 0;
let mut read = 0;
while read + 4 < buffer.len() {
let [b0, b1, b2, b3] = read_quad(buffer, read)?;
let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_byte |= !valid2;
invalid_byte |= !valid3;
invalid_padding |= ct_mask_eq_u8(b2, b'=');
invalid_padding |= ct_mask_eq_u8(b3, b'=');
buffer[write] = (v0 << 2) | (v1 >> 4);
buffer[write + 1] = (v1 << 4) | (v2 >> 2);
buffer[write + 2] = (v2 << 6) | v3;
write += 3;
read += 4;
}
let final_chunk = read_quad(buffer, read)?;
let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
ct_padded_final_quantum::<A>(final_chunk, padding);
invalid_byte |= final_invalid_byte;
invalid_padding |= final_invalid_padding;
buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
write += final_written;
debug_assert_eq!(write, required);
report_ct_error(invalid_byte, invalid_padding)?;
Ok(write)
}
fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
if input.len() % 4 == 1 {
return Err(DecodeError::InvalidLength);
}
let required = decoded_capacity(input.len());
if output.len() < required {
return Err(DecodeError::OutputTooSmall {
required,
available: output.len(),
});
}
let mut invalid_byte = 0u8;
let mut invalid_padding = 0u8;
let mut write = 0;
let mut read = 0;
while read + 4 <= input.len() {
let b0 = input[read];
let b1 = input[read + 1];
let b2 = input[read + 2];
let b3 = input[read + 3];
let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_byte |= !valid2;
invalid_byte |= !valid3;
invalid_padding |= ct_mask_eq_u8(b0, b'=');
invalid_padding |= ct_mask_eq_u8(b1, b'=');
invalid_padding |= ct_mask_eq_u8(b2, b'=');
invalid_padding |= ct_mask_eq_u8(b3, b'=');
output[write] = (v0 << 2) | (v1 >> 4);
output[write + 1] = (v1 << 4) | (v2 >> 2);
output[write + 2] = (v2 << 6) | v3;
read += 4;
write += 3;
}
match input.len() - read {
0 => {}
2 => {
let b0 = input[read];
let b1 = input[read + 1];
let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_padding |= ct_mask_eq_u8(b0, b'=');
invalid_padding |= ct_mask_eq_u8(b1, b'=');
invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
output[write] = (v0 << 2) | (v1 >> 4);
write += 1;
}
3 => {
let b0 = input[read];
let b1 = input[read + 1];
let b2 = input[read + 2];
let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_byte |= !valid2;
invalid_padding |= ct_mask_eq_u8(b0, b'=');
invalid_padding |= ct_mask_eq_u8(b1, b'=');
invalid_padding |= ct_mask_eq_u8(b2, b'=');
invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
output[write] = (v0 << 2) | (v1 >> 4);
output[write + 1] = (v1 << 4) | (v2 >> 2);
write += 2;
}
_ => return Err(DecodeError::InvalidLength),
}
report_ct_error(invalid_byte, invalid_padding)?;
Ok(write)
}
fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
if buffer.len() % 4 == 1 {
return Err(DecodeError::InvalidLength);
}
let required = decoded_capacity(buffer.len());
debug_assert!(required <= buffer.len());
let mut invalid_byte = 0u8;
let mut invalid_padding = 0u8;
let mut write = 0;
let mut read = 0;
while read + 4 <= buffer.len() {
let b0 = buffer[read];
let b1 = buffer[read + 1];
let b2 = buffer[read + 2];
let b3 = buffer[read + 3];
let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_byte |= !valid2;
invalid_byte |= !valid3;
invalid_padding |= ct_mask_eq_u8(b0, b'=');
invalid_padding |= ct_mask_eq_u8(b1, b'=');
invalid_padding |= ct_mask_eq_u8(b2, b'=');
invalid_padding |= ct_mask_eq_u8(b3, b'=');
buffer[write] = (v0 << 2) | (v1 >> 4);
buffer[write + 1] = (v1 << 4) | (v2 >> 2);
buffer[write + 2] = (v2 << 6) | v3;
read += 4;
write += 3;
}
match buffer.len() - read {
0 => {}
2 => {
let b0 = buffer[read];
let b1 = buffer[read + 1];
let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_padding |= ct_mask_eq_u8(b0, b'=');
invalid_padding |= ct_mask_eq_u8(b1, b'=');
invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
buffer[write] = (v0 << 2) | (v1 >> 4);
write += 1;
}
3 => {
let b0 = buffer[read];
let b1 = buffer[read + 1];
let b2 = buffer[read + 2];
let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
invalid_byte |= !valid0;
invalid_byte |= !valid1;
invalid_byte |= !valid2;
invalid_padding |= ct_mask_eq_u8(b0, b'=');
invalid_padding |= ct_mask_eq_u8(b1, b'=');
invalid_padding |= ct_mask_eq_u8(b2, b'=');
invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
buffer[write] = (v0 << 2) | (v1 >> 4);
buffer[write + 1] = (v1 << 4) | (v2 >> 2);
write += 2;
}
_ => return Err(DecodeError::InvalidLength),
}
debug_assert_eq!(write, required);
report_ct_error(invalid_byte, invalid_padding)?;
Ok(write)
}
#[inline]
fn ct_decode_ascii_base64<A: Alphabet>(byte: u8) -> (u8, u8) {
let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
let value_62 = ct_mask_eq_u8(byte, A::ENCODE[62]);
let value_63 = ct_mask_eq_u8(byte, A::ENCODE[63]);
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);
(decoded, valid)
}
fn ct_padding_len(input: &[u8]) -> usize {
let last = input[input.len() - 1];
let before_last = input[input.len() - 2];
usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
}
fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
if (invalid_byte | invalid_padding) != 0 {
Err(DecodeError::InvalidInput)
} else {
Ok(())
}
}
#[cfg(kani)]
mod kani_proofs {
use super::{STANDARD, checked_encoded_len, decoded_capacity};
#[kani::proof]
fn checked_encoded_len_is_bounded_for_small_inputs() {
let len = usize::from(kani::any::<u8>());
let padded = kani::any::<bool>();
let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
assert!(encoded >= len);
assert!(encoded <= len / 3 * 4 + 4);
}
#[kani::proof]
fn decoded_capacity_is_bounded_for_small_inputs() {
let len = usize::from(kani::any::<u8>());
let capacity = decoded_capacity(len);
assert!(capacity <= len / 4 * 3 + 2);
}
#[kani::proof]
#[kani::unwind(3)]
fn standard_in_place_decode_returns_prefix_within_buffer() {
let mut buffer = kani::any::<[u8; 8]>();
let result = STANDARD.decode_in_place(&mut buffer);
if let Ok(decoded) = result {
assert!(decoded.len() <= 8);
}
}
#[kani::proof]
#[kani::unwind(3)]
fn standard_decode_slice_returns_written_within_output() {
let input = kani::any::<[u8; 4]>();
let mut output = kani::any::<[u8; 3]>();
let result = STANDARD.decode_slice(&input, &mut output);
if let Ok(written) = result {
assert!(written <= output.len());
}
}
#[kani::proof]
#[kani::unwind(3)]
fn standard_decode_slice_clear_tail_clears_output_on_error() {
let input = kani::any::<[u8; 4]>();
let mut output = kani::any::<[u8; 3]>();
let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
if result.is_err() {
assert!(output.iter().all(|byte| *byte == 0));
}
}
#[kani::proof]
#[kani::unwind(3)]
fn standard_encode_slice_returns_written_within_output() {
let input = kani::any::<[u8; 3]>();
let mut output = kani::any::<[u8; 4]>();
let result = STANDARD.encode_slice(&input, &mut output);
if let Ok(written) = result {
assert!(written <= output.len());
}
}
#[kani::proof]
#[kani::unwind(4)]
fn standard_encode_in_place_returns_prefix_within_buffer() {
let mut buffer = kani::any::<[u8; 8]>();
let input_len = usize::from(kani::any::<u8>() % 9);
let result = STANDARD.encode_in_place(&mut buffer, input_len);
if let Ok(encoded) = result {
assert!(encoded.len() <= 8);
}
}
#[kani::proof]
#[kani::unwind(3)]
fn standard_clear_tail_decode_clears_buffer_on_error() {
let mut buffer = kani::any::<[u8; 4]>();
let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
if result.is_err() {
assert!(buffer.iter().all(|byte| *byte == 0));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fill_pattern(output: &mut [u8], seed: usize) {
for (index, byte) in output.iter_mut().enumerate() {
let value = (index * 73 + seed * 19) % 256;
*byte = u8::try_from(value).unwrap();
}
}
fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
where
A: Alphabet,
{
let engine = Engine::<A, PAD>::new();
let mut dispatched = [0x55; 256];
let mut scalar = [0xaa; 256];
let dispatched_result = engine.encode_slice(input, &mut dispatched);
let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
assert_eq!(dispatched_result, scalar_result);
if let Ok(written) = dispatched_result {
assert_eq!(&dispatched[..written], &scalar[..written]);
}
let required = checked_encoded_len(input.len(), PAD).unwrap();
if required > 0 {
let mut dispatched_short = [0x55; 256];
let mut scalar_short = [0xaa; 256];
let available = required - 1;
assert_eq!(
engine.encode_slice(input, &mut dispatched_short[..available]),
backend::scalar_reference_encode_slice::<A, PAD>(
input,
&mut scalar_short[..available],
)
);
}
}
fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
where
A: Alphabet,
{
let engine = Engine::<A, PAD>::new();
let mut dispatched = [0x55; 128];
let mut scalar = [0xaa; 128];
let dispatched_result = engine.decode_slice(input, &mut dispatched);
let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
assert_eq!(dispatched_result, scalar_result);
if let Ok(written) = dispatched_result {
assert_eq!(&dispatched[..written], &scalar[..written]);
if written > 0 {
let mut dispatched_short = [0x55; 128];
let mut scalar_short = [0xaa; 128];
let available = written - 1;
assert_eq!(
engine.decode_slice(input, &mut dispatched_short[..available]),
backend::scalar_reference_decode_slice::<A, PAD>(
input,
&mut scalar_short[..available],
)
);
}
}
}
fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
where
A: Alphabet,
{
assert_encode_backend_matches_scalar::<A, PAD>(input);
let mut encoded = [0; 256];
let encoded_len =
backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
}
#[test]
fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
let mut input = [0; 128];
for input_len in 0..=input.len() {
fill_pattern(&mut input[..input_len], input_len);
let input = &input[..input_len];
assert_backend_round_trip_matches_scalar::<Standard, true>(input);
assert_backend_round_trip_matches_scalar::<Standard, false>(input);
assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
}
}
#[test]
fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
for input in [
&b"Z"[..],
b"====",
b"AA=A",
b"Zh==",
b"Zm9=",
b"Zm9v$g==",
b"Zm9vZh==",
] {
assert_decode_backend_matches_scalar::<Standard, true>(input);
}
for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
assert_decode_backend_matches_scalar::<Standard, false>(input);
}
assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
}
#[cfg(feature = "simd")]
#[test]
fn simd_dispatch_scaffold_keeps_scalar_active() {
assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
let _candidate = simd::detected_candidate();
}
#[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 })
);
}
}