Skip to main content

oci_image/
digest.rs

1use std::fmt;
2use std::io::{self, Read};
3use std::str::FromStr;
4
5use sha2::{Digest as _, Sha256};
6
7use crate::{Error, Result};
8
9/// A `sha256:<hex>` content digest. Parsing is strict (64 lowercase hex
10/// digits), so a digest is always safe to use as a file name.
11#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
12pub struct Digest {
13    hex: String,
14}
15
16impl Digest {
17    /// The digest of `bytes`.
18    pub fn of(bytes: &[u8]) -> Self {
19        Self::from_hasher(Sha256::new().chain_update(bytes))
20    }
21
22    /// The digest of everything `reader` yields.
23    pub fn of_reader(mut reader: impl Read) -> io::Result<Self> {
24        let mut h = Hasher::default();
25        let mut buf = vec![0u8; 1 << 16];
26        loop {
27            let n = reader.read(&mut buf)?;
28            if n == 0 {
29                return Ok(h.finish());
30            }
31            h.update(&buf[..n]);
32        }
33    }
34
35    fn from_hasher(h: Sha256) -> Self {
36        let hex = h.finalize().iter().map(|b| format!("{b:02x}")).collect();
37        Self { hex }
38    }
39
40    /// The 64 hex digits.
41    pub fn hex(&self) -> &str {
42        &self.hex
43    }
44
45    /// `Ok` when `actual` is this digest, a [`Error::DigestMismatch`] otherwise.
46    pub fn verify(&self, actual: &Digest) -> Result<()> {
47        if self == actual {
48            Ok(())
49        } else {
50            Err(Error::DigestMismatch {
51                expected: self.clone(),
52                actual: actual.clone(),
53            })
54        }
55    }
56}
57
58impl fmt::Display for Digest {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(f, "sha256:{}", self.hex)
61    }
62}
63
64impl FromStr for Digest {
65    type Err = Error;
66
67    fn from_str(s: &str) -> Result<Self> {
68        let hex = s
69            .strip_prefix("sha256:")
70            .filter(|h| h.len() == 64 && h.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f')))
71            .ok_or_else(|| Error::BadDigest(s.to_string()))?;
72        Ok(Self {
73            hex: hex.to_string(),
74        })
75    }
76}
77
78impl serde::Serialize for Digest {
79    fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
80        s.collect_str(self)
81    }
82}
83
84impl<'de> serde::Deserialize<'de> for Digest {
85    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
86        let s = String::deserialize(d)?;
87        s.parse().map_err(serde::de::Error::custom)
88    }
89}
90
91/// Incremental sha256, for blobs that arrive in chunks.
92#[derive(Default, Clone)]
93pub struct Hasher(Sha256);
94
95impl Hasher {
96    pub fn update(&mut self, bytes: &[u8]) {
97        self.0.update(bytes);
98    }
99
100    pub fn finish(self) -> Digest {
101        Digest::from_hasher(self.0)
102    }
103}