#[must_use]
pub const fn armor_char_to_sixbit(c: u8) -> u8 {
let v = c.wrapping_sub(48);
if v > 40 { v.wrapping_sub(8) } else { v }
}
#[must_use]
pub const fn sixbit_to_armor_char(v: u8) -> u8 {
let c = v + 48;
if c > 87 { c + 8 } else { c }
}
#[must_use]
pub const fn is_valid_armor_byte(c: u8) -> bool {
matches!(c, 48..=87 | 96..=119)
}
#[must_use]
pub const fn sixbit_to_ascii(v: u8) -> u8 {
if v < 32 { v + 64 } else { v }
}
#[must_use]
pub const fn ascii_to_sixbit(c: u8) -> u8 {
match c {
b'@'..=b'_' => c - 64,
b' '..=b'?' => c,
_ => 32,
}
}
#[derive(Debug, Clone, Copy)]
pub struct FixedStr<const N: usize> {
buf: [u8; N],
len: usize,
}
impl<const N: usize> PartialEq for FixedStr<N> {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> Eq for FixedStr<N> {}
impl<const N: usize> FixedStr<N> {
#[must_use]
pub const fn from_raw(buf: [u8; N], len: usize) -> Self {
Self { buf, len }
}
#[must_use]
pub fn as_str(&self) -> &str {
let mut end = self.len;
while end > 0 && matches!(self.buf[end - 1], b'@' | b' ') {
end -= 1;
}
core::str::from_utf8(&self.buf[..end]).unwrap_or_default()
}
}
impl<const N: usize> core::fmt::Display for FixedStr<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
pub(crate) fn test_padded<const N: usize>(s: &str) -> FixedStr<N> {
let mut buf = [b'@'; N];
let bytes = s.as_bytes();
buf[..bytes.len()].copy_from_slice(bytes);
FixedStr::from_raw(buf, N)
}
#[cfg(kani)]
mod kani_proofs {
use super::*;
#[kani::proof]
fn armor_roundtrip() {
let v: u8 = kani::any();
kani::assume(v < 64);
let c = sixbit_to_armor_char(v);
assert!(is_valid_armor_byte(c));
assert_eq!(armor_char_to_sixbit(c), v);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn armor_roundtrip_covers_full_range() {
for v in 0u8..64 {
let c = sixbit_to_armor_char(v);
assert!(is_valid_armor_byte(c), "char {c} for value {v} not valid");
assert_eq!(armor_char_to_sixbit(c), v);
}
}
#[test]
fn armor_known_values() {
assert_eq!(sixbit_to_armor_char(0), b'0');
assert_eq!(sixbit_to_armor_char(39), b'W');
assert_eq!(sixbit_to_armor_char(40), b'`');
assert_eq!(sixbit_to_armor_char(63), b'w');
}
#[test]
fn text_alphabet_roundtrip_covers_full_range() {
for v in 0u8..64 {
let c = sixbit_to_ascii(v);
assert_eq!(ascii_to_sixbit(c), v);
}
}
#[test]
fn fixed_str_trims_padding() {
let mut buf = [0u8; 8];
buf[..5].copy_from_slice(b"ABC@@");
let s: FixedStr<8> = FixedStr::from_raw(buf, 5);
assert_eq!(s.as_str(), "ABC");
}
}