protoflow_blocks/blocks/hash/
hash.rs

1// This is free and unencumbered software released into the public domain.
2
3use crate::{
4    prelude::{vec, Bytes},
5    types::HashAlgorithm,
6    StdioConfig, StdioError, StdioSystem, System,
7};
8use blake3::Hasher;
9use protoflow_core::{Block, BlockResult, BlockRuntime, InputPort, OutputPort, Port, PortError};
10use protoflow_derive::Block;
11use simple_mermaid::mermaid;
12
13/// Computes the cryptographic hash of a byte stream, while optionally
14/// passing it through.
15///
16/// # Block Diagram
17#[doc = mermaid!("../../../doc/hash/hash.mmd")]
18///
19/// # Sequence Diagram
20#[doc = mermaid!("../../../doc/hash/hash.seq.mmd" framed)]
21///
22/// # Examples
23///
24/// ## Using the block in a system
25///
26/// ```rust
27/// # use protoflow_blocks::*;
28/// # fn main() {
29/// System::build(|s| {
30///     let stdin = s.read_stdin();
31///     let hasher = s.hash_blake3();
32///     let hex_encoder = s.encode_hex();
33///     let stdout = s.write_stdout();
34///     s.connect(&stdin.output, &hasher.input);
35///     s.connect(&hasher.hash, &hex_encoder.input);
36///     s.connect(&hex_encoder.output, &stdout.input);
37/// });
38/// # }
39/// ```
40///
41/// ## Running the block via the CLI
42///
43/// ```console
44/// $ protoflow execute Hash algorithm=blake3
45/// ```
46///
47#[derive(Block, Clone)]
48pub struct Hash {
49    /// The input byte stream.
50    #[input]
51    pub input: InputPort<Bytes>,
52
53    /// The (optional) output target for the stream being passed through.
54    #[output]
55    pub output: OutputPort<Bytes>,
56
57    /// The output port for the computed hash.
58    #[output]
59    pub hash: OutputPort<Bytes>,
60
61    /// A configuration parameter for which algorithm to use.
62    #[parameter]
63    pub algorithm: HashAlgorithm,
64
65    /// The internal state for computing the hash.
66    #[state]
67    hasher: Hasher,
68}
69
70impl Hash {
71    pub fn new(
72        input: InputPort<Bytes>,
73        output: OutputPort<Bytes>,
74        hash: OutputPort<Bytes>,
75    ) -> Self {
76        Self::with_params(input, output, hash, None)
77    }
78
79    pub fn with_params(
80        input: InputPort<Bytes>,
81        output: OutputPort<Bytes>,
82        hash: OutputPort<Bytes>,
83        algorithm: Option<HashAlgorithm>,
84    ) -> Self {
85        Self {
86            input,
87            output,
88            hash,
89            algorithm: algorithm.unwrap_or_default(),
90            hasher: Hasher::new(),
91        }
92    }
93
94    pub fn with_system(system: &System, algorithm: Option<HashAlgorithm>) -> Self {
95        use crate::SystemBuilding;
96        Self::with_params(system.input(), system.output(), system.output(), algorithm)
97    }
98}
99
100impl Block for Hash {
101    fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult {
102        while let Some(message) = self.input.recv()? {
103            self.hasher.update(&message);
104
105            if self.output.is_connected() {
106                self.output.send(&message)?;
107            } else {
108                drop(message);
109            }
110        }
111        self.output.close()?;
112
113        runtime.wait_for(&self.hash)?;
114
115        let hash = Bytes::from(self.hasher.finalize().as_bytes().to_vec());
116        match self.hash.send(&hash) {
117            Ok(()) => {}
118            Err(PortError::Closed | PortError::Disconnected) => {
119                // TODO: log the error
120            }
121            Err(e) => return Err(e)?,
122        };
123
124        Ok(())
125    }
126}
127
128#[cfg(feature = "std")]
129impl StdioSystem for Hash {
130    fn build_system(config: StdioConfig) -> Result<System, StdioError> {
131        use crate::{HashBlocks, IoBlocks, SystemBuilding};
132
133        // TODO: parse the algorithm parameter
134        config.allow_only(vec!["algorithm"])?;
135
136        Ok(System::build(|s| {
137            let stdin = config.read_stdin(s);
138            let hasher = s.hash_blake3();
139            let hex_encoder = s.encode_hex();
140            let stdout = config.write_stdout(s);
141            s.connect(&stdin.output, &hasher.input);
142            s.connect(&hasher.hash, &hex_encoder.input);
143            s.connect(&hex_encoder.output, &stdout.input);
144        }))
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::Hash;
151    use crate::{System, SystemBuilding};
152
153    #[test]
154    fn instantiate_block() {
155        // Check that the block is constructible:
156        let _ = System::build(|s| {
157            let _ = s.block(Hash::new(s.input(), s.output(), s.output()));
158        });
159    }
160}