Skip to main content

ic_host_artifacts/artifact/hashing/
mod.rs

1//! Hash accepted bytes from a push producer without buffering its complete output.
2
3#[cfg(test)]
4mod tests;
5
6use super::{ArtifactIdentity, BoundedWriter, Sha256Digest};
7use sha2::{Digest, Sha256};
8use std::io::{self, Write};
9
10/// A bounded caller-owned sink with a raw SHA-256 of successful writes.
11///
12/// Counts and hashes only the bytes reported as accepted by the sink. Short
13/// writes, interrupted writes and byte allowances use [`BoundedWriter`]'s
14/// existing contract; [`Write::write_all`] handles retries through this wrapper.
15/// Sink errors can have unreported partial effects, which cannot be identified.
16/// Encoding, flush/sync, publication and recovery remain caller-owned.
17///
18/// Accept a complete artifact identity only after the producer/serializer
19/// succeeds. Recovering parts after failure describes an observed prefix,
20/// not a completed or durable artifact. This wrapper never publishes output.
21pub struct HashingWriter<W> {
22    writer: BoundedWriter<W>,
23    hasher: Sha256,
24}
25
26impl<W> HashingWriter<W> {
27    /// Select the sink and inclusive allowance for accepted bytes.
28    #[must_use]
29    pub fn new(writer: W, max_bytes: u64) -> Self {
30        Self {
31            writer: BoundedWriter::new(writer, max_bytes),
32            hasher: Sha256::new(),
33        }
34    }
35
36    /// Number of bytes reported as accepted by successful writes.
37    #[must_use]
38    pub const fn bytes_written(&self) -> u64 {
39        self.writer.bytes_written()
40    }
41
42    /// Whether an offered buffer has exceeded the selected allowance.
43    #[must_use]
44    pub const fn limit_exceeded(&self) -> bool {
45        self.writer.limit_exceeded()
46    }
47
48    /// Recover the sink and identity of the observed accepted bytes.
49    ///
50    /// Does not flush, certify producer success or discard partial output.
51    /// A failed producer must not use this prefix identity to publish an artifact.
52    #[must_use]
53    pub fn into_parts(self) -> (W, ArtifactIdentity) {
54        let identity = ArtifactIdentity {
55            bytes: self.writer.bytes_written(),
56            sha256: Sha256Digest::from_bytes(self.hasher.finalize().into()),
57        };
58        (self.writer.into_inner(), identity)
59    }
60}
61
62impl<W: Write> Write for HashingWriter<W> {
63    fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
64        let accepted = self.writer.write(buffer)?;
65        self.hasher.update(&buffer[..accepted]);
66        Ok(accepted)
67    }
68
69    fn flush(&mut self) -> io::Result<()> {
70        self.writer.flush()
71    }
72}