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}