use std::collections::HashMap;
use std::collections::HashSet;
use crate::error::{Error, ErrorKind};
pub(crate) const RADIX_MIN: u32 = 2;
pub(crate) const RADIX_MAX_EXCLUSIVE: u32 = 1 << 16;
pub(crate) const MAX_LEN: u64 = (1 << 32) - 1;
pub(crate) const MAX_TWEAK_LEN: u64 = (1 << 32) - 1;
const MIN_DOMAIN: u64 = 1_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Operand {
Plaintext,
Ciphertext,
}
impl Operand {
fn as_str(self) -> &'static str {
match self {
Operand::Plaintext => "plaintext",
Operand::Ciphertext => "ciphertext",
}
}
}
pub(crate) fn key(key: &[u8]) -> Result<(), Error> {
match key.len() {
16 | 24 | 32 => Ok(()),
n => Err(Error::new(
ErrorKind::KeyLength,
format!("key must be 16, 24, or 32 bytes, got {n}"),
)),
}
}
pub(crate) fn radix(radix: u32) -> Result<(), Error> {
if (RADIX_MIN..RADIX_MAX_EXCLUSIVE).contains(&radix) {
Ok(())
} else {
Err(Error::new(
ErrorKind::Radix,
format!("radix must satisfy {RADIX_MIN} <= radix < {RADIX_MAX_EXCLUSIVE}, got {radix}"),
))
}
}
pub(crate) fn min_length(radix: u32) -> usize {
let radix = u64::from(radix);
let mut n = 1;
let mut value = radix;
while value < MIN_DOMAIN {
value *= radix;
n += 1;
}
n
}
pub(crate) fn length(
n: usize,
min_length: usize,
radix: u32,
operand: Operand,
) -> Result<(), Error> {
let side = operand.as_str();
if n < min_length {
return Err(Error::new(
ErrorKind::Length,
format!("{side} length {n} below minimum {min_length} for radix {radix}"),
));
}
if n as u64 > MAX_LEN {
return Err(Error::new(
ErrorKind::Length,
format!("{side} length {n} above maximum {MAX_LEN}"),
));
}
Ok(())
}
pub(crate) fn numerals(x: &[u16], radix: u32, operand: Operand) -> Result<(), Error> {
match x.iter().position(|&v| u32::from(v) >= radix) {
None => Ok(()),
Some(idx) => Err(Error::new(
ErrorKind::ValueRange,
format!(
"{}[{idx}] is out of range for radix {radix}",
operand.as_str()
),
)),
}
}
pub(crate) fn tweak_bounds(min: Option<usize>, max: Option<usize>) -> Result<(), Error> {
if let Some(low) = min {
if low as u64 > MAX_TWEAK_LEN {
return Err(Error::new(
ErrorKind::TweakLength,
format!(
"min_tweak_len {low} above encodable maximum tweak length {MAX_TWEAK_LEN}; \
no tweak could satisfy it"
),
));
}
}
if let Some(high) = max {
if high as u64 > MAX_TWEAK_LEN {
return Err(Error::new(
ErrorKind::TweakLength,
format!(
"max_tweak_len {high} above encodable maximum tweak length {MAX_TWEAK_LEN}; \
bounds are rejected, not clamped"
),
));
}
}
if let (Some(low), Some(high)) = (min, max) {
if low > high {
return Err(Error::new(
ErrorKind::TweakLength,
format!(
"min_tweak_len {low} exceeds max_tweak_len {high}; \
no tweak length could satisfy both bounds"
),
));
}
}
Ok(())
}
pub(crate) fn tweak(len: usize, min: Option<usize>, max: Option<usize>) -> Result<(), Error> {
if len as u64 > MAX_TWEAK_LEN {
return Err(Error::new(
ErrorKind::TweakLength,
format!("tweak length {len} above encodable maximum {MAX_TWEAK_LEN}"),
));
}
if let Some(low) = min {
if len < low {
return Err(Error::new(
ErrorKind::TweakLength,
format!("tweak length {len} below minimum {low}"),
));
}
}
if let Some(high) = max {
if len > high {
return Err(Error::new(
ErrorKind::TweakLength,
format!("tweak length {len} above maximum {high}"),
));
}
}
Ok(())
}
pub(crate) fn alphabet(alphabet: &str, radix: u32) -> Result<Vec<char>, Error> {
let chars: Vec<char> = alphabet.chars().collect();
if chars.len() as u64 != u64::from(radix) {
return Err(Error::new(
ErrorKind::Alphabet,
format!(
"alphabet length {} does not match radix {radix}",
chars.len()
),
));
}
let distinct: HashSet<char> = chars.iter().copied().collect();
if distinct.len() != chars.len() {
return Err(Error::new(
ErrorKind::Alphabet,
"alphabet contains duplicate characters",
));
}
Ok(chars)
}
pub(crate) fn decode(s: &str, lookup: &HashMap<char, u16>) -> Result<Vec<u16>, Error> {
s.chars()
.enumerate()
.map(|(idx, ch)| {
lookup.get(&ch).copied().ok_or_else(|| {
Error::new(
ErrorKind::ValueRange,
format!("character at index {idx} is not in the alphabet"),
)
})
})
.collect()
}
pub(crate) fn alphabet_required(numeral_method: &str) -> Error {
Error::new(
ErrorKind::AlphabetRequired,
format!(
"alphabet required for string interface; use {numeral_method} for the numeral interface"
),
)
}