use super::alphabet::{
STANDARD_ALPHABET, URL_SAFE_ALPHABET, ValidatedAlphabet, ValidatedAlphabetError,
};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum EncodePadding {
Padded,
Unpadded,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum DecodePadding {
RequireCanonical,
Forbid,
Indifferent,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum TrailingBits {
RequireCanonical,
AllowNonCanonical,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct CodecSettings {
alphabet: ValidatedAlphabet,
encode_padding: EncodePadding,
decode_padding: DecodePadding,
trailing_bits: TrailingBits,
}
impl CodecSettings {
const fn new(
alphabet: ValidatedAlphabet,
encode_padding: EncodePadding,
decode_padding: DecodePadding,
trailing_bits: TrailingBits,
) -> Self {
Self {
alphabet,
encode_padding,
decode_padding,
trailing_bits,
}
}
#[must_use]
pub const fn alphabet(&self) -> &ValidatedAlphabet {
&self.alphabet
}
#[must_use]
pub const fn encode_padding(&self) -> EncodePadding {
self.encode_padding
}
#[must_use]
pub const fn decode_padding(&self) -> DecodePadding {
self.decode_padding
}
#[must_use]
pub const fn trailing_bits(&self) -> TrailingBits {
self.trailing_bits
}
#[must_use]
pub const fn permits_secret_processing(&self) -> bool {
!matches!(self.decode_padding, DecodePadding::Indifferent)
&& matches!(self.trailing_bits, TrailingBits::RequireCanonical)
}
}
mod sealed {
pub trait Sealed {}
}
pub trait Codec: sealed::Sealed + Send + Sync {
fn settings(&self) -> CodecSettings;
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Base64<S> {
specification: S,
}
impl<S: Codec> Base64<S> {
pub const fn new(specification: S) -> Self {
Self { specification }
}
pub const fn specification(&self) -> &S {
&self.specification
}
pub fn settings(&self) -> CodecSettings {
self.specification.settings()
}
}
macro_rules! strict_specification {
($name:ident, $alphabet:expr, $encode:expr, $decode:expr) => {
#[doc = "A sealed strict RFC 4648 built-in specification."]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct $name;
impl $name {
const SETTINGS: CodecSettings =
CodecSettings::new($alphabet, $encode, $decode, TrailingBits::RequireCanonical);
pub(crate) const fn const_settings() -> CodecSettings {
Self::SETTINGS
}
}
impl sealed::Sealed for $name {}
impl Codec for $name {
fn settings(&self) -> CodecSettings {
Self::SETTINGS
}
}
};
}
strict_specification!(
StrictStandardPadded,
STANDARD_ALPHABET,
EncodePadding::Padded,
DecodePadding::RequireCanonical
);
strict_specification!(
StrictStandardUnpadded,
STANDARD_ALPHABET,
EncodePadding::Unpadded,
DecodePadding::Forbid
);
strict_specification!(
StrictUrlSafePadded,
URL_SAFE_ALPHABET,
EncodePadding::Padded,
DecodePadding::RequireCanonical
);
strict_specification!(
StrictUrlSafeUnpadded,
URL_SAFE_ALPHABET,
EncodePadding::Unpadded,
DecodePadding::Forbid
);
pub const STRICT_STANDARD_PADDED: Base64<StrictStandardPadded> = Base64::new(StrictStandardPadded);
pub const STRICT_STANDARD_UNPADDED: Base64<StrictStandardUnpadded> =
Base64::new(StrictStandardUnpadded);
pub const STRICT_URL_SAFE_PADDED: Base64<StrictUrlSafePadded> = Base64::new(StrictUrlSafePadded);
pub const STRICT_URL_SAFE_UNPADDED: Base64<StrictUrlSafeUnpadded> =
Base64::new(StrictUrlSafeUnpadded);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct RuntimeSpec {
settings: CodecSettings,
}
impl RuntimeSpec {
pub(crate) const fn const_settings(&self) -> CodecSettings {
self.settings
}
}
pub(crate) const fn runtime_codec(
alphabet: ValidatedAlphabet,
encode_padding: EncodePadding,
decode_padding: DecodePadding,
trailing_bits: TrailingBits,
) -> Base64<RuntimeSpec> {
Base64::new(RuntimeSpec {
settings: CodecSettings::new(alphabet, encode_padding, decode_padding, trailing_bits),
})
}
pub(crate) const fn compatibility_codec(
alphabet: ValidatedAlphabet,
encode_padding: EncodePadding,
decode_padding: DecodePadding,
trailing_bits: TrailingBits,
) -> Base64<RuntimeSpec> {
runtime_codec(alphabet, encode_padding, decode_padding, trailing_bits)
}
impl sealed::Sealed for RuntimeSpec {}
impl Codec for RuntimeSpec {
fn settings(&self) -> CodecSettings {
self.settings
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CodecBuilderError {
EncodedPaddingRejected,
EncodedPaddingRequired,
}
impl core::fmt::Display for CodecBuilderError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::EncodedPaddingRejected => {
formatter.write_str("encoding emits padding rejected by decode policy")
}
Self::EncodedPaddingRequired => {
formatter.write_str("encoding omits padding required by decode policy")
}
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CodecBuilder {
settings: CodecSettings,
}
impl CodecBuilder {
#[must_use]
pub const fn new(alphabet: ValidatedAlphabet) -> Self {
Self {
settings: CodecSettings::new(
alphabet,
EncodePadding::Padded,
DecodePadding::RequireCanonical,
TrailingBits::RequireCanonical,
),
}
}
pub const fn from_table(table: [u8; 64]) -> Result<Self, ValidatedAlphabetError> {
match ValidatedAlphabet::new(table) {
Ok(alphabet) => Ok(Self::new(alphabet)),
Err(error) => Err(error),
}
}
pub const fn from_slice(bytes: &[u8]) -> Result<Self, ValidatedAlphabetError> {
match ValidatedAlphabet::try_from_slice(bytes) {
Ok(alphabet) => Ok(Self::new(alphabet)),
Err(error) => Err(error),
}
}
#[must_use]
pub const fn encode_padding(mut self, policy: EncodePadding) -> Self {
self.settings.encode_padding = policy;
self
}
#[must_use]
pub const fn decode_padding(mut self, policy: DecodePadding) -> Self {
self.settings.decode_padding = policy;
self
}
#[must_use]
pub const fn trailing_bits(mut self, policy: TrailingBits) -> Self {
self.settings.trailing_bits = policy;
self
}
pub const fn build(self) -> Result<Base64<RuntimeSpec>, CodecBuilderError> {
match (self.settings.encode_padding, self.settings.decode_padding) {
(EncodePadding::Padded, DecodePadding::Forbid) => {
Err(CodecBuilderError::EncodedPaddingRejected)
}
(EncodePadding::Unpadded, DecodePadding::RequireCanonical) => {
Err(CodecBuilderError::EncodedPaddingRequired)
}
_ => Ok(Base64::new(RuntimeSpec {
settings: self.settings,
})),
}
}
}