//! Object IDs: SHA-256 over the object's kind byte and its bytes. use std::fmt; use sha2::{Digest, Sha256}; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Id(pub [u8; 32]); 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")); } }