use crate::MeshError;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Fingerprint(pub [u8; 32]);
impl Fingerprint {
#[must_use]
pub fn of_bytes(data: &[u8]) -> Self {
let h = blake3::hash(data);
Self(*h.as_bytes())
}
#[must_use]
pub fn short(&self) -> String {
hex::encode(&self.0[..6])
}
#[must_use]
pub fn hex(&self) -> String {
hex::encode(self.0)
}
}
impl fmt::Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.short())
}
}
impl std::str::FromStr for Fingerprint {
type Err = MeshError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(s).map_err(|e| MeshError::Encoding(e.to_string()))?;
if bytes.len() != 32 {
return Err(MeshError::Encoding(format!(
"expected 32 bytes, got {}",
bytes.len()
)));
}
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Ok(Self(arr))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn short_truncates_to_12_hex_chars() {
let fp = Fingerprint::of_bytes(b"hello world");
let s = fp.short();
assert_eq!(s.len(), 12, "short should be 12 hex chars (6 bytes)");
assert!(s.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn roundtrip_hex() {
let fp = Fingerprint::of_bytes(b"some bytes");
let h = fp.hex();
assert_eq!(h.len(), 64);
let parsed = Fingerprint::from_str(&h).expect("parse roundtrip");
assert_eq!(fp, parsed);
}
#[test]
fn equality() {
let a = Fingerprint::of_bytes(b"x");
let b = Fingerprint::of_bytes(b"x");
let c = Fingerprint::of_bytes(b"y");
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn from_str_rejects_wrong_length() {
let err = Fingerprint::from_str("deadbeef").expect_err("too short");
match err {
MeshError::Encoding(_) => {}
other => panic!("expected Encoding, got {other:?}"),
}
}
#[test]
fn from_str_rejects_non_hex() {
let err = Fingerprint::from_str("zz").expect_err("not hex");
match err {
MeshError::Encoding(_) => {}
other => panic!("expected Encoding, got {other:?}"),
}
}
#[test]
fn display_matches_short() {
let fp = Fingerprint::of_bytes(b"display test");
assert_eq!(format!("{fp}"), fp.short());
}
#[test]
fn debug_does_not_panic() {
let fp = Fingerprint::of_bytes(b"debug");
let _ = format!("{fp:?}");
}
#[test]
fn hash_in_collection() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(Fingerprint::of_bytes(b"a"));
set.insert(Fingerprint::of_bytes(b"a"));
set.insert(Fingerprint::of_bytes(b"b"));
assert_eq!(set.len(), 2);
}
}