use heapless::{String, Vec};
use zeroize::Zeroizing;
use crate::{crc::checksum, error::DecodeError};
pub const fn binary_len(payload_len: usize) -> usize {
1 + payload_len + 2
}
pub const fn encode_len(binary_len: usize) -> usize {
(binary_len * 8 + 4) / 5
}
pub fn encode<const P: usize, const B: usize, const E: usize>(
ver: u8,
payload: &[u8],
) -> String<E> {
const {
assert!(B == binary_len(P), "B must be exactly binary_len(P)");
assert!(E == encode_len(B), "E must be exactly encode_len(B)");
}
let mut d: Vec<u8, B> = Vec::new();
d.push(ver).unwrap();
d.extend_from_slice(payload).unwrap();
d.extend_from_slice(&checksum(&d)).unwrap();
let mut encoded: Vec<u8, E> = Vec::new();
let encoded_len = data_encoding::BASE32_NOPAD.encode_len(d.len());
encoded.resize_default(encoded_len).unwrap();
data_encoding::BASE32_NOPAD.encode_mut(&d, &mut encoded);
unsafe { String::from_utf8_unchecked(encoded) }
}
pub fn encode_zeroizing<const P: usize, const B: usize, const E: usize>(
ver: u8,
payload: &[u8],
out: &mut Zeroizing<String<E>>,
) {
const {
assert!(B == binary_len(P), "B must be exactly binary_len(P)");
assert!(E == encode_len(B), "E must be exactly encode_len(B)");
}
out.clear();
let mut d: Zeroizing<Vec<u8, B>> = Zeroizing::new(Vec::new());
d.push(ver).unwrap();
d.extend_from_slice(payload).unwrap();
let cs = checksum(&d);
d.extend_from_slice(&cs).unwrap();
let mut encoded: Zeroizing<Vec<u8, E>> = Zeroizing::new(Vec::new());
let encoded_len = data_encoding::BASE32_NOPAD.encode_len(d.len());
encoded.resize_default(encoded_len).unwrap();
data_encoding::BASE32_NOPAD.encode_mut(&d, &mut encoded);
out.push_str(unsafe { core::str::from_utf8_unchecked(&encoded) })
.unwrap();
}
pub fn decode<const P: usize, const B: usize>(s: &[u8]) -> Result<(u8, Vec<u8, P>), DecodeError> {
const {
assert!(B == binary_len(P), "B must be exactly binary_len(P)");
}
let mut data: Vec<u8, B> = Vec::new();
let data_len = data_encoding::BASE32_NOPAD
.decode_len(s.len())
.map_err(|_| DecodeError::InvalidBase32)?;
if data_len < 3 {
return Err(DecodeError::TooShort);
}
data.resize_default(data_len)
.map_err(|_| DecodeError::TooLong)?;
data_encoding::BASE32_NOPAD
.decode_mut(s, &mut data)
.map_err(|_| DecodeError::InvalidBase32)?;
let ver = data[0];
let data_len = data.len();
let (data_without_crc, crc_actual) = data.split_at(data_len - 2);
let crc_expect = checksum(data_without_crc);
if crc_actual != crc_expect {
return Err(DecodeError::ChecksumMismatch);
}
let payload_data = &data_without_crc[1..];
let payload: Vec<u8, P> = Vec::from_slice(payload_data).unwrap();
Ok((ver, payload))
}
pub fn decode_zeroizing<const P: usize, const B: usize>(
s: &[u8],
out: &mut Zeroizing<Vec<u8, P>>,
) -> Result<u8, DecodeError> {
const {
assert!(B == binary_len(P), "B must be exactly binary_len(P)");
}
out.clear();
let mut data: Zeroizing<Vec<u8, B>> = Zeroizing::new(Vec::new());
let data_len = data_encoding::BASE32_NOPAD
.decode_len(s.len())
.map_err(|_| DecodeError::InvalidBase32)?;
if data_len < 3 {
return Err(DecodeError::TooShort);
}
data.resize_default(data_len)
.map_err(|_| DecodeError::TooLong)?;
data_encoding::BASE32_NOPAD
.decode_mut(s, &mut data)
.map_err(|_| DecodeError::InvalidBase32)?;
let ver = data[0];
let data_len = data.len();
let (data_without_crc, crc_actual) = data.split_at(data_len - 2);
let crc_expect = checksum(data_without_crc);
if crc_actual != crc_expect {
return Err(DecodeError::ChecksumMismatch);
}
let payload_data = &data_without_crc[1..];
out.extend_from_slice(payload_data).unwrap();
Ok(ver)
}
#[cfg(test)]
mod tests {
use super::{
binary_len, decode, decode_zeroizing, encode, encode_len, encode_zeroizing, DecodeError,
};
use heapless::{String, Vec};
use zeroize::Zeroizing;
#[test]
fn test_binary_len() {
for payload_len in 0..=100 {
let expected = 1 + payload_len + 2;
let actual = binary_len(payload_len);
assert_eq!(actual, expected);
}
}
#[test]
fn test_encode_len() {
for payload_len in 0..=100 {
let bin_len = binary_len(payload_len);
let expected = data_encoding::BASE32_NOPAD.encode_len(bin_len);
let actual = encode_len(bin_len);
assert_eq!(actual, expected);
let payload = [0u8; 100];
let encoded = encode::<100, 103, 165>(0x00, &payload[..payload_len]);
assert_eq!(encoded.len(), expected);
}
}
#[test]
fn test_decode_minimum_length() {
assert_eq!(decode::<0, 3>(b""), Err(DecodeError::TooShort));
assert_eq!(decode::<0, 3>(b"AA"), Err(DecodeError::TooShort)); assert_eq!(decode::<0, 3>(b"AAAA"), Err(DecodeError::TooShort));
let result = decode::<0, 3>(b"AAAAA");
assert!(
result.is_ok(),
"decode should accept 3 binary bytes (empty payload)"
);
let (ver, payload) = result.unwrap();
assert_eq!(ver, 0x00);
assert!(payload.is_empty());
}
#[test]
fn test_encode_zeroizing_matches_encode() {
let payload = [0xab_u8; 32];
let plain: String<56> = encode::<32, 35, 56>(0x90, &payload);
let mut zeroizing: Zeroizing<String<56>> = Zeroizing::new(String::new());
encode_zeroizing::<32, 35, 56>(0x90, &payload, &mut zeroizing);
assert_eq!(plain.as_str(), zeroizing.as_str());
}
#[test]
fn test_decode_zeroizing_matches_decode() {
let payload = [0xab_u8; 32];
let strkey: String<56> = encode::<32, 35, 56>(0x90, &payload);
let (ver_plain, payload_plain) = decode::<32, 35>(strkey.as_bytes()).unwrap();
let mut payload_zeroizing: Zeroizing<Vec<u8, 32>> = Zeroizing::new(Vec::new());
let ver_zeroizing =
decode_zeroizing::<32, 35>(strkey.as_bytes(), &mut payload_zeroizing).unwrap();
assert_eq!(ver_plain, ver_zeroizing);
assert_eq!(payload_plain.as_slice(), payload_zeroizing.as_slice());
}
#[test]
fn test_encode_zeroizing_resets_output_on_reuse() {
let payload_a = [0xab_u8; 32];
let payload_b = [0xcd_u8; 32];
let mut buf: Zeroizing<String<56>> = Zeroizing::new(String::new());
encode_zeroizing::<32, 35, 56>(0x90, &payload_a, &mut buf);
let expected_a: String<56> = encode::<32, 35, 56>(0x90, &payload_a);
assert_eq!(buf.as_str(), expected_a.as_str());
encode_zeroizing::<32, 35, 56>(0x90, &payload_b, &mut buf);
let expected_b: String<56> = encode::<32, 35, 56>(0x90, &payload_b);
assert_eq!(buf.as_str(), expected_b.as_str());
}
#[test]
fn test_decode_zeroizing_resets_output_on_reuse() {
let payload_a = [0xab_u8; 32];
let payload_b = [0xcd_u8; 32];
let strkey_a: String<56> = encode::<32, 35, 56>(0x90, &payload_a);
let strkey_b: String<56> = encode::<32, 35, 56>(0x90, &payload_b);
let mut buf: Zeroizing<Vec<u8, 32>> = Zeroizing::new(Vec::new());
let ver_a = decode_zeroizing::<32, 35>(strkey_a.as_bytes(), &mut buf).unwrap();
assert_eq!(ver_a, 0x90);
assert_eq!(buf.as_slice(), &payload_a);
let ver_b = decode_zeroizing::<32, 35>(strkey_b.as_bytes(), &mut buf).unwrap();
assert_eq!(ver_b, 0x90);
assert_eq!(buf.as_slice(), &payload_b);
}
#[test]
fn test_decode_zeroizing_resets_output_on_error() {
let payload = [0xab_u8; 32];
let strkey: String<56> = encode::<32, 35, 56>(0x90, &payload);
let mut buf: Zeroizing<Vec<u8, 32>> = Zeroizing::new(Vec::new());
decode_zeroizing::<32, 35>(strkey.as_bytes(), &mut buf).unwrap();
assert_eq!(buf.as_slice(), &payload);
let err = decode_zeroizing::<32, 35>(b"", &mut buf);
assert_eq!(err, Err(DecodeError::TooShort));
assert!(buf.is_empty());
}
}