//! Object IDs: SHA-256 over the object's kind byte and its bytes. use std::collections::{HashMap, HashSet}; use std::fmt; use std::hash::{BuildHasherDefault, Hash, Hasher}; use sha2::{Digest, Sha256}; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Id(pub [u8; 32]); /// By its first bytes: an ID is a SHA-256, as good as random already. impl Hash for Id { fn hash(&self, state: &mut H) { state.write_u64(u64::from_le_bytes(self.0[..8].try_into().unwrap())); } } /// A hasher for IDs (see `Hash for Id`): their bytes, mixed once, rather /// than SipHash over them, which a busy host spent a tenth of its landing in. #[derive(Default, Clone, Copy)] pub struct IdHasher(u64); impl Hasher for IdHasher { fn finish(&self) -> u64 { self.0 } fn write(&mut self, bytes: &[u8]) { for &b in bytes { self.0 = (self.0.rotate_left(8) ^ u64::from(b)).wrapping_mul(0x9e37_79b9_7f4a_7c15); } } fn write_u64(&mut self, n: u64) { self.0 = (self.0 ^ n).wrapping_mul(0x9e37_79b9_7f4a_7c15); } } /// Maps and sets keyed by ID. pub type IdMap = HashMap>; pub type IdSet = HashSet>; impl Id { pub const ZERO: Id = Id([0; 32]); /// The ID of an object of `kind` holding `data`. pub fn of(kind: u8, data: &[u8]) -> Id { let mut h = Sha256::new(); h.update([kind]); h.update(data); Id(h.finalize().into()) } pub fn hex(&self) -> String { let mut s = String::with_capacity(64); for b in self.0 { s.push(char::from_digit((b >> 4) as u32, 16).unwrap()); s.push(char::from_digit((b & 15) as u32, 16).unwrap()); } s } pub fn parse(text: &str) -> Option { if text.len() != 64 { return None; } let mut out = [0u8; 32]; for (i, chunk) in text.as_bytes().chunks(2).enumerate() { let hi = (chunk[0] as char).to_digit(16)?; let lo = (chunk[1] as char).to_digit(16)?; out[i] = (hi * 16 + lo) as u8; } Some(Id(out)) } pub fn from_slice(bytes: &[u8]) -> Option { Some(Id(bytes.try_into().ok()?)) } pub fn short(&self) -> String { self.hex()[..12].to_string() } } impl fmt::Display for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.hex()) } } impl fmt::Debug for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Id({})", self.short()) } } impl serde::Serialize for Id { fn serialize(&self, s: S) -> Result { s.serialize_str(&self.hex()) } } impl<'de> serde::Deserialize<'de> for Id { fn deserialize>(d: D) -> Result { let text = String::deserialize(d)?; Id::parse(&text).ok_or_else(|| serde::de::Error::custom("not an object id")) } } /// SHA-256 of arbitrary bytes, for version IDs. pub fn sha256(parts: &[&[u8]]) -> Id { let mut h = Sha256::new(); for p in parts { h.update(p); } Id(h.finalize().into()) } #[cfg(test)] mod tests { use super::*; #[test] fn hex_round_trip() { let id = Id::of(1, b"hello\n"); assert_eq!(Id::parse(&id.hex()), Some(id)); assert_ne!(Id::of(1, b"x"), Id::of(2, b"x")); } }