Skip to main content

trine_kv/content/identity/
content_id.rs

1use super::{CONTENT_ID_SHA256_TAG, Digest, Error, Result, Sha256, fmt, write_hex};
2
3/// Cryptographic algorithm carried by a [`ContentId`].
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
5#[non_exhaustive]
6pub enum ContentHashAlgorithm {
7    /// SHA-256 over the complete original byte sequence.
8    Sha256,
9}
10
11impl ContentHashAlgorithm {
12    pub(in crate::content) const fn tag(self) -> u8 {
13        match self {
14            Self::Sha256 => CONTENT_ID_SHA256_TAG,
15        }
16    }
17
18    pub(in crate::content) fn from_tag(tag: u8) -> Result<Self> {
19        match tag {
20            CONTENT_ID_SHA256_TAG => Ok(Self::Sha256),
21            _ => Err(Error::UnsupportedFormat {
22                message: format!("unsupported content hash algorithm tag {tag}"),
23            }),
24        }
25    }
26}
27
28/// Algorithm-tagged identity of one immutable original byte sequence.
29///
30/// Equality means byte identity under the carried cryptographic algorithm. It
31/// does not identify a file name, owner, directory entry, or physical object.
32#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct ContentId {
34    pub(in crate::content) algorithm: ContentHashAlgorithm,
35    pub(in crate::content) digest: [u8; 32],
36}
37
38impl ContentId {
39    /// Decodes the portable 33-byte content identity.
40    ///
41    /// Byte zero selects the digest algorithm and the remaining 32 bytes carry
42    /// its digest. Unknown algorithm tags fail closed so stored catalog values
43    /// are never reinterpreted under a different hash scheme.
44    ///
45    /// # Errors
46    ///
47    /// Returns [`Error::UnsupportedFormat`] when the algorithm tag is not
48    /// supported by this build.
49    pub fn from_bytes(bytes: [u8; 33]) -> Result<Self> {
50        let algorithm = ContentHashAlgorithm::from_tag(bytes[0])?;
51        let mut digest = [0_u8; 32];
52        digest.copy_from_slice(&bytes[1..]);
53        Ok(Self { algorithm, digest })
54    }
55
56    /// Returns the portable algorithm-tagged 33-byte content identity.
57    #[must_use]
58    pub fn to_bytes(self) -> [u8; 33] {
59        let mut bytes = [0_u8; 33];
60        bytes[0] = self.algorithm.tag();
61        bytes[1..].copy_from_slice(&self.digest);
62        bytes
63    }
64
65    /// Computes the identity of an in-memory byte slice.
66    ///
67    /// Incremental uploads compute the same value without retaining all bytes;
68    /// this convenience constructor is intended for expected-digest checks and
69    /// small inputs.
70    #[must_use]
71    pub fn for_bytes(bytes: &[u8]) -> Self {
72        let digest: [u8; 32] = Sha256::digest(bytes).into();
73        Self {
74            algorithm: ContentHashAlgorithm::Sha256,
75            digest,
76        }
77    }
78
79    /// Returns the hash algorithm carried by this identity.
80    #[must_use]
81    pub const fn algorithm(self) -> ContentHashAlgorithm {
82        self.algorithm
83    }
84
85    /// Returns the 32-byte digest without its algorithm tag.
86    #[must_use]
87    pub const fn digest(self) -> [u8; 32] {
88        self.digest
89    }
90
91    pub(crate) fn from_sha256(digest: [u8; 32]) -> Self {
92        Self {
93            algorithm: ContentHashAlgorithm::Sha256,
94            digest,
95        }
96    }
97
98    pub(in crate::content) fn encode_into(self, bytes: &mut Vec<u8>) {
99        bytes.push(self.algorithm.tag());
100        bytes.extend_from_slice(&self.digest);
101    }
102}
103
104impl fmt::Debug for ContentId {
105    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(formatter, "ContentId({self})")
107    }
108}
109
110impl fmt::Display for ContentId {
111    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
112        formatter.write_str("sha256:")?;
113        write_hex(formatter, &self.digest)
114    }
115}