use core::fmt;
use subtle::ConstantTimeEq;
#[derive(Debug, Clone)]
pub struct Fingerprint {
pub hash_func: String,
pub bytes: Vec<u8>,
}
impl PartialEq for Fingerprint {
fn eq(&self, other: &Self) -> bool {
self.hash_func == other.hash_func && self.bytes[..].ct_eq(&other.bytes[..]).into()
}
}
impl Eq for Fingerprint {}
impl std::hash::Hash for Fingerprint {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.hash_func.hash(state);
self.bytes.hash(state);
}
}
impl fmt::Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ", self.hash_func)?;
for (i, b) in self.bytes.iter().enumerate() {
if i > 0 {
write!(f, ":")?;
}
write!(f, "{:02X}", b)?;
}
Ok(())
}
}
impl std::str::FromStr for Fingerprint {
type Err = String;
fn from_str(hex_string: &str) -> Result<Self, Self::Err> {
let (hash_func, hex_with_colons) = hex_string
.split_once(' ')
.ok_or_else(|| "Failed to split once".to_owned())?;
let mut bytes = Vec::new();
for hex in hex_with_colons.split(':') {
let byte = u8::from_str_radix(hex, 16)
.map_err(|e| format!("Failed to parse fingerprint: {}", e))?;
bytes.push(byte);
}
Ok(Self {
hash_func: hash_func.to_owned(),
bytes,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn fingerprint_format() {
let f = Fingerprint {
hash_func: "foo".to_string(),
bytes: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
};
assert_eq!(
f.to_string(),
"foo 00:01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F:10:11"
);
}
}