#![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) {
self.buffer.fill(0);
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) {
self.pending.fill(0);
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) {
self.pending.fill(0);
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) {
self.pending.fill(0);
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) {
self.pending.fill(0);
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};
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 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) => {
output.fill(0);
return Err(err);
}
};
output[written..].fill(0);
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) => {
buffer.fill(0);
return Err(err);
}
};
buffer[len..].fill(0);
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 fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
match checked_encoded_len(input_len, padded) {
Some(len) => Ok(len),
None => Err(EncodeError::LengthOverflow),
}
}
#[must_use]
pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
let groups = input_len / 3;
if groups > usize::MAX / 4 {
return None;
}
let full = groups * 4;
let rem = input_len % 3;
if rem == 0 {
Some(full)
} else if padded {
full.checked_add(4)
} else {
full.checked_add(rem + 1)
}
}
#[must_use]
pub const fn decoded_capacity(encoded_len: usize) -> usize {
let rem = encoded_len % 4;
encoded_len / 4 * 3
+ if rem == 2 {
1
} else if rem == 3 {
2
} else {
0
}
}
pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
if padded {
decoded_len_padded(input)
} else {
decoded_len_unpadded(input)
}
}
pub trait Alphabet {
const ENCODE: [u8; 64];
fn decode(byte: u8) -> Option<u8>;
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Standard;
impl Alphabet for Standard {
const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#[inline]
fn decode(byte: u8) -> Option<u8> {
decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct UrlSafe;
impl Alphabet for UrlSafe {
const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
#[inline]
fn decode(byte: u8) -> Option<u8> {
decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
}
}
#[inline]
const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
encode_ascii_base64(value, A::ENCODE[62], A::ENCODE[63])
}
#[inline]
const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
let upper = 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,
};
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::<A>(b0 >> 2);
output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
read += 3;
write += 4;
}
match input.len() - read {
0 => {}
1 => {
let b0 = input[read];
output[write] = encode_base64_value::<A>(b0 >> 2);
output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
write += 2;
if PAD {
output[write] = b'=';
output[write + 1] = b'=';
write += 2;
}
}
2 => {
let b0 = input[read];
let b1 = input[read + 1];
output[write] = encode_base64_value::<A>(b0 >> 2);
output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
write += 3;
if PAD {
output[write] = b'=';
write += 1;
}
}
_ => unreachable!(),
}
Ok(write)
}
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)
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Engine<A, const PAD: bool> {
alphabet: core::marker::PhantomData<A>,
}
impl<A, const PAD: bool> Engine<A, PAD>
where
A: Alphabet,
{
#[must_use]
pub const fn new() -> Self {
Self {
alphabet: core::marker::PhantomData,
}
}
pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
encoded_len(input_len, PAD)
}
#[must_use]
pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
checked_encoded_len(input_len, PAD)
}
pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
decoded_len(input, PAD)
}
pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
validate_legacy_decode::<A, PAD>(input)
}
#[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_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, EncodeError> {
let written = match self.encode_slice(input, output) {
Ok(written) => written,
Err(err) => {
output.fill(0);
return Err(err);
}
};
output[written..].fill(0);
Ok(written)
}
#[cfg(feature = "alloc")]
pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
let mut output = alloc::vec![0; required];
let written = self.encode_slice(input, &mut output)?;
output.truncate(written);
Ok(output)
}
#[cfg(feature = "alloc")]
pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
let output = self.encode_vec(input)?;
match alloc::string::String::from_utf8(output) {
Ok(output) => Ok(output),
Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
}
}
pub fn encode_in_place<'a>(
&self,
buffer: &'a mut [u8],
input_len: usize,
) -> Result<&'a mut [u8], EncodeError> {
if input_len > buffer.len() {
return Err(EncodeError::InputTooLarge {
input_len,
buffer_len: buffer.len(),
});
}
let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
if buffer.len() < required {
return Err(EncodeError::OutputTooSmall {
required,
available: buffer.len(),
});
}
let mut read = input_len;
let mut write = required;
match input_len % 3 {
0 => {}
1 => {
read -= 1;
let b0 = buffer[read];
if PAD {
write -= 4;
buffer[write] = encode_base64_value::<A>(b0 >> 2);
buffer[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
buffer[write + 2] = b'=';
buffer[write + 3] = b'=';
} else {
write -= 2;
buffer[write] = encode_base64_value::<A>(b0 >> 2);
buffer[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
}
}
2 => {
read -= 2;
let b0 = buffer[read];
let b1 = buffer[read + 1];
if PAD {
write -= 4;
buffer[write] = encode_base64_value::<A>(b0 >> 2);
buffer[write + 1] =
encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
buffer[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
buffer[write + 3] = b'=';
} else {
write -= 3;
buffer[write] = encode_base64_value::<A>(b0 >> 2);
buffer[write + 1] =
encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
buffer[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
}
}
_ => unreachable!(),
}
while read > 0 {
read -= 3;
write -= 4;
let b0 = buffer[read];
let b1 = buffer[read + 1];
let b2 = buffer[read + 2];
buffer[write] = encode_base64_value::<A>(b0 >> 2);
buffer[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
buffer[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
buffer[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
}
debug_assert_eq!(write, 0);
Ok(&mut buffer[..required])
}
pub fn 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) => {
buffer.fill(0);
return Err(err);
}
};
buffer[len..].fill(0);
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) => {
output.fill(0);
return Err(err);
}
};
output[written..].fill(0);
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) => {
output.fill(0);
return Err(err);
}
};
output[written..].fill(0);
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) => {
output.fill(0);
return Err(err);
}
};
output.truncate(written);
Ok(output)
}
#[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) => {
output.fill(0);
return Err(err);
}
};
output.truncate(written);
Ok(output)
}
pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
let len = Self::decode_slice_to_start(buffer)?;
Ok(&mut buffer[..len])
}
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) => {
buffer.fill(0);
return Err(err);
}
};
buffer[len..].fill(0);
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) {
buffer.fill(0);
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) => {
buffer.fill(0);
return Err(err);
}
};
buffer[len..].fill(0);
Ok(&mut buffer[..len])
}
fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
let input_len = buffer.len();
let mut read = 0;
let mut write = 0;
while read + 4 <= input_len {
let chunk = [
buffer[read],
buffer[read + 1],
buffer[read + 2],
buffer[read + 3],
];
let written = decode_chunk::<A, PAD>(&chunk, &mut buffer[write..])
.map_err(|err| err.with_index_offset(read))?;
read += 4;
write += written;
if written < 3 {
if read != input_len {
return Err(DecodeError::InvalidPadding { index: read - 4 });
}
return Ok(write);
}
}
let rem = input_len - read;
if rem == 0 {
return Ok(write);
}
if PAD {
return Err(DecodeError::InvalidLength);
}
let mut tail = [0u8; 3];
tail[..rem].copy_from_slice(&buffer[read..input_len]);
decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
.map_err(|err| err.with_index_offset(read))
.map(|n| write + n)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EncodeError {
LengthOverflow,
InputTooLarge {
input_len: usize,
buffer_len: usize,
},
OutputTooSmall {
required: usize,
available: usize,
},
}
impl core::fmt::Display for EncodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
Self::InputTooLarge {
input_len,
buffer_len,
} => write!(
f,
"base64 input length {input_len} exceeds buffer length {buffer_len}"
),
Self::OutputTooSmall {
required,
available,
} => write!(
f,
"base64 output buffer too small: required {required}, available {available}"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for EncodeError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DecodeError {
InvalidInput,
InvalidLength,
InvalidByte {
index: usize,
byte: u8,
},
InvalidPadding {
index: usize,
},
OutputTooSmall {
required: usize,
available: usize,
},
}
impl core::fmt::Display for DecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::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::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::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 written = decode_chunk::<A, PAD>(&chunk, &mut output[write..])
.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::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::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 written = decode_chunk::<A, true>(&input[read..read + 4], &mut output[write..])
.map_err(|err| err.with_index_offset(read))?;
read += 4;
write += written;
if written < 3 && read != input.len() {
return Err(DecodeError::InvalidPadding { index: read - 4 });
}
}
Ok(write)
}
#[cfg(feature = "alloc")]
fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
if input.is_empty() {
return Ok(0);
}
if PAD {
validate_padded::<A>(input)
} else {
validate_unpadded::<A>(input)
}
}
#[cfg(feature = "alloc")]
fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
if !input.len().is_multiple_of(4) {
return Err(DecodeError::InvalidLength);
}
let required = decoded_len_padded(input)?;
let mut read = 0;
while read < input.len() {
let written = validate_chunk::<A, true>(&input[read..read + 4])
.map_err(|err| err.with_index_offset(read))?;
read += 4;
if written < 3 && read != input.len() {
return Err(DecodeError::InvalidPadding { index: read - 4 });
}
}
Ok(required)
}
#[cfg(feature = "alloc")]
fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
let required = decoded_len_unpadded(input)?;
let mut read = 0;
while read + 4 <= input.len() {
validate_chunk::<A, false>(&input[read..read + 4])
.map_err(|err| err.with_index_offset(read))?;
read += 4;
}
validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
Ok(required)
}
fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
let required = decoded_len_unpadded(input)?;
if output.len() < required {
return Err(DecodeError::OutputTooSmall {
required,
available: output.len(),
});
}
let mut read = 0;
let mut write = 0;
while read + 4 <= input.len() {
let written = decode_chunk::<A, false>(&input[read..read + 4], &mut output[write..])
.map_err(|err| err.with_index_offset(read))?;
read += 4;
write += written;
}
decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
.map_err(|err| err.with_index_offset(read))
.map(|n| write + n)
}
fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
if input.is_empty() {
return Ok(0);
}
if !input.len().is_multiple_of(4) {
return Err(DecodeError::InvalidLength);
}
let mut padding = 0;
if input[input.len() - 1] == b'=' {
padding += 1;
}
if input[input.len() - 2] == b'=' {
padding += 1;
}
if padding == 0
&& let Some(index) = input.iter().position(|byte| *byte == b'=')
{
return Err(DecodeError::InvalidPadding { index });
}
if padding > 0 {
let first_pad = input.len() - padding;
if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
return Err(DecodeError::InvalidPadding { index });
}
}
Ok(input.len() / 4 * 3 - padding)
}
fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
if input.len() % 4 == 1 {
return Err(DecodeError::InvalidLength);
}
if let Some(index) = input.iter().position(|byte| *byte == b'=') {
return Err(DecodeError::InvalidPadding { index });
}
Ok(decoded_capacity(input.len()))
}
fn validate_chunk<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
debug_assert_eq!(input.len(), 4);
let _v0 = decode_byte::<A>(input[0], 0)?;
let v1 = decode_byte::<A>(input[1], 1)?;
match (input[2], input[3]) {
(b'=', b'=') if PAD => {
if v1 & 0b0000_1111 != 0 {
return Err(DecodeError::InvalidPadding { index: 1 });
}
Ok(1)
}
(b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
(_, b'=') if PAD => {
let v2 = decode_byte::<A>(input[2], 2)?;
if v2 & 0b0000_0011 != 0 {
return Err(DecodeError::InvalidPadding { index: 2 });
}
Ok(2)
}
(b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
index: input.iter().position(|byte| *byte == b'=').unwrap_or(0),
}),
_ => {
decode_byte::<A>(input[2], 2)?;
decode_byte::<A>(input[3], 3)?;
Ok(3)
}
}
}
fn decode_chunk<A: Alphabet, const PAD: bool>(
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
debug_assert_eq!(input.len(), 4);
let v0 = decode_byte::<A>(input[0], 0)?;
let v1 = decode_byte::<A>(input[1], 1)?;
match (input[2], input[3]) {
(b'=', b'=') if PAD => {
if output.is_empty() {
return Err(DecodeError::OutputTooSmall {
required: 1,
available: output.len(),
});
}
if v1 & 0b0000_1111 != 0 {
return Err(DecodeError::InvalidPadding { index: 1 });
}
output[0] = (v0 << 2) | (v1 >> 4);
Ok(1)
}
(b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
(_, b'=') if PAD => {
if output.len() < 2 {
return Err(DecodeError::OutputTooSmall {
required: 2,
available: output.len(),
});
}
let v2 = decode_byte::<A>(input[2], 2)?;
if v2 & 0b0000_0011 != 0 {
return Err(DecodeError::InvalidPadding { index: 2 });
}
output[0] = (v0 << 2) | (v1 >> 4);
output[1] = (v1 << 4) | (v2 >> 2);
Ok(2)
}
(b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
index: input.iter().position(|byte| *byte == b'=').unwrap_or(0),
}),
_ => {
if output.len() < 3 {
return Err(DecodeError::OutputTooSmall {
required: 3,
available: output.len(),
});
}
let v2 = decode_byte::<A>(input[2], 2)?;
let v3 = decode_byte::<A>(input[3], 3)?;
output[0] = (v0 << 2) | (v1 >> 4);
output[1] = (v1 << 4) | (v2 >> 2);
output[2] = (v2 << 6) | v3;
Ok(3)
}
}
}
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_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 < input.len() {
let is_last = 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;
if is_last && padding == 2 {
invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
output[write] = (v0 << 2) | (v1 >> 4);
write += 1;
} else if is_last && padding == 1 {
invalid_byte |= !valid2;
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;
} else {
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;
}
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 < buffer.len() {
let is_last = 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;
if is_last && padding == 2 {
invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
buffer[write] = (v0 << 2) | (v1 >> 4);
write += 1;
} else if is_last && padding == 1 {
invalid_byte |= !valid2;
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;
} else {
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;
}
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_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 })
);
}
}