#[cfg(feature = "alloc")]
use crate::SecretBuffer;
use crate::{Alphabet, DecodeError, DecodedBuffer, Standard, UrlSafe};
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,
}
}
#[must_use]
pub const fn is_padded(&self) -> bool {
PAD
}
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 decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
ct_decoded_len::<A, PAD>(input)
}
#[must_use = "handle decode errors; use decode_slice_staged_clear_tail for shared-memory or HSM-style threat models"]
pub fn decode_slice_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
let written = match ct_decode_slice::<A, PAD>(input, output) {
Ok(written) => written,
Err(err) => {
crate::wipe_bytes(output);
return Err(err);
}
};
crate::wipe_tail(output, written);
Ok(written)
}
#[must_use = "handle decode errors; staged decode is for shared-memory or HSM-style threat models"]
pub fn decode_slice_staged_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
staging: &mut [u8],
) -> Result<usize, DecodeError> {
ct_decode_slice_staged_clear_tail::<A, PAD>(input, output, staging)
}
pub fn decode_buffer<const CAP: usize>(
&self,
input: &[u8],
) -> Result<DecodedBuffer<CAP>, DecodeError> {
let mut output = DecodedBuffer::new();
let written = match self.decode_slice_clear_tail(input, output.as_mut_capacity()) {
Ok(written) => written,
Err(err) => {
output.clear();
return Err(err);
}
};
output.set_filled(written)?;
Ok(output)
}
#[cfg(feature = "alloc")]
#[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
let required = self.decoded_len(input)?;
let mut output = alloc::vec![0; required];
let written = self.decode_slice_clear_tail(input, &mut output)?;
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_secret_staged<const STAGE: usize>(
&self,
input: &[u8],
) -> Result<SecretBuffer, DecodeError> {
let required = self.decoded_len(input)?;
let mut staging = DecodedBuffer::<STAGE>::new();
let mut output = alloc::vec![0; required];
let written =
self.decode_slice_staged_clear_tail(input, &mut output, staging.as_mut_capacity())?;
output.truncate(written);
Ok(SecretBuffer::from_vec(output))
}
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])
}
}
impl<A, const PAD: bool> core::fmt::Display for CtEngine<A, PAD> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, "ct padded={PAD}")
}
}
mod decode;
mod equality;
mod padded;
mod unpadded;
use decode::{
ct_decode_in_place, ct_decode_slice, ct_decode_slice_staged_clear_tail, ct_decoded_len,
ct_validate_decode,
};
#[cfg(test)]
pub(crate) use equality::report_ct_error;
pub(crate) use equality::{
constant_time_eq_fixed_width_array, constant_time_eq_public_len, ct_mask_eq_u8, ct_mask_lt_u8,
};
#[cfg(test)]
pub(crate) use padded::ct_padded_final_quantum;