use std::fmt;
use zeroize::Zeroize;
#[derive(Clone, PartialEq, Eq)]
pub struct SmallKey {
bytes: [u8; 32],
len: u8,
}
impl SmallKey {
pub const MAX_LEN: usize = 32;
#[must_use]
pub(crate) fn from_prefix(source: &[u8], len: usize) -> Self {
let len = len.min(Self::MAX_LEN);
let mut bytes = [0u8; Self::MAX_LEN];
let copied = len.min(source.len());
if let (Some(head), Some(from)) = (bytes.get_mut(..copied), source.get(..copied)) {
head.copy_from_slice(from);
}
#[expect(
clippy::cast_possible_truncation,
reason = "len <= 32 by the line above"
)]
Self {
bytes,
len: len as u8,
}
}
#[must_use]
pub(crate) const fn from_full(bytes: [u8; 32]) -> Self {
#[expect(clippy::cast_possible_truncation, reason = "32 fits in a u8")]
Self {
bytes,
len: Self::MAX_LEN as u8,
}
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
self.bytes.get(..self.len()).unwrap_or_default()
}
#[must_use]
pub fn len(&self) -> usize {
usize::from(self.len)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl Drop for SmallKey {
fn drop(&mut self) {
self.bytes.zeroize();
self.len = 0;
}
}
impl fmt::Debug for SmallKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SmallKey(<{} bytes redacted>)", self.len())
}
}
#[cfg(test)]
mod tests {
use super::SmallKey;
#[test]
fn a_prefix_shorter_than_requested_is_zero_filled() {
let key = SmallKey::from_prefix(b"abc", 5);
assert_eq!(key.bytes(), b"abc\0\0");
assert_eq!(key.len(), 5);
}
#[test]
fn a_key_never_exceeds_thirty_two_bytes() {
let key = SmallKey::from_prefix(&[0xFFu8; 64], 64);
assert_eq!(key.len(), SmallKey::MAX_LEN);
assert_eq!(key.bytes().len(), SmallKey::MAX_LEN);
}
#[test]
fn debug_redacts_the_material() {
let key = SmallKey::from_full([0xAB; 32]);
let dump = format!("{key:?}");
assert_eq!(dump, "SmallKey(<32 bytes redacted>)");
assert!(!dump.contains("ab"), "{dump}");
assert!(!dump.contains("171"), "{dump}");
}
#[test]
fn an_empty_key_is_representable_and_says_so() {
let key = SmallKey::from_prefix(b"", 0);
assert!(key.is_empty());
assert_eq!(key.bytes(), b"");
}
}