Skip to main content

confium_api/plugin/
hash.rs

1//! `HashPlugin` trait — the Rust-side counterpart of the hash v0 wire
2//! protocol.
3//!
4//! Plugin authors implement this trait on their hash state type, then
5//! apply `#[plugin_interface(name = "hash", version = 0)]` to the impl
6//! block. The macro emits the eight canonical `cfmp_hash_*` FFI symbols,
7//! one per trait method.
8//!
9//! Method → symbol mapping (see `crates/confium-core/src/ffi/hash.rs`
10//! for the loader-side wire types):
11//!
12//! | trait method | FFI symbol | purpose |
13//! |--------------|------------|---------|
14//! | [`HashPlugin::create_with_opts`] | `cfmp_hash_create`         | construct a new instance |
15//! | [`HashPlugin::output_size`]      | `cfmp_hash_output_size`    | output length in bytes |
16//! | [`HashPlugin::block_size`]       | `cfmp_hash_block_size`     | internal block size in bytes |
17//! | [`HashPlugin::update`]           | `cfmp_hash_update`         | absorb input bytes |
18//! | [`HashPlugin::reset`]            | `cfmp_hash_reset`          | reset to initial state |
19//! | [`HashPlugin::try_clone`]        | `cfmp_hash_clone`          | duplicate the state |
20//! | [`HashPlugin::finalize`]         | `cfmp_hash_finalize`       | emit digest into caller buffer |
21//! | `Drop`                           | `cfmp_hash_destroy`        | reclaim the boxed state |
22
23use crate::error::{PluginError, PluginResult};
24use crate::options::OptionView;
25
26/// Trait implemented by hash plugins. The macro-generated
27/// `cfmp_hash_create` calls [`HashPlugin::create_with_opts`]; all other
28/// symbols dispatch through `OpaqueHandle::<Self>::borrow_raw` and the
29/// corresponding trait method.
30///
31/// `update`, `reset`, `try_clone`, and `finalize` may return a
32/// [`PluginError`]; the macro maps the error into the wire status code
33/// (non-zero) that the loader surfaces via its `Error::PluginInternalError`
34/// variant.
35pub trait HashPlugin: Sized {
36    /// Construct a new hash instance for the named algorithm.
37    ///
38    /// `name` is the algorithm name the caller passed to
39    /// `cfm_hash_create` (e.g. `"sha-256"`). `opts` is the caller's
40    /// option map (or `None` if the caller passed NULL). Plugins that
41    /// don't take options can ignore both.
42    fn create_with_opts(name: &str, opts: Option<OptionView<'_>>) -> PluginResult<Self>;
43
44    /// Output length in bytes for this instance. The caller allocates a
45    /// buffer of this size before calling `finalize`.
46    fn output_size(&self) -> u32;
47
48    /// Internal block size in bytes. Used by callers that want to feed
49    /// input in aligned chunks; safe to return a constant (e.g. 64) if
50    /// the algorithm doesn't have a meaningful block size.
51    fn block_size(&self) -> u32;
52
53    /// Absorb `data` into the hash state.
54    fn update(&mut self, data: &[u8]) -> PluginResult<()>;
55
56    /// Reset to the initial state (post-`create`).
57    fn reset(&mut self) -> PluginResult<()>;
58
59    /// Duplicate the hash state. The clone must be independent: updates
60    /// to one must not affect the other.
61    fn try_clone(&self) -> PluginResult<Self>;
62
63    /// Write the finalized digest into `out`. The caller guarantees
64    /// `out.len() == self.output_size()`. Plugins that need to pad or
65    /// finalize internally should do so here.
66    fn finalize(&mut self, out: &mut [u8]) -> PluginResult<()>;
67}
68
69/// Trivial constructor entry point for plugin authors who don't need
70/// `opts`. Wraps [`HashPlugin::create_with_opts`] with `None`.
71///
72/// Plugin authors do not normally call this — the macro-generated
73/// `cfmp_hash_create` calls `create_with_opts` directly. It's provided
74/// as a convenience for tests and hand-written plugins.
75pub fn create_simple<T: HashPlugin>(name: &str) -> PluginResult<T> {
76    T::create_with_opts(name, None)
77}
78
79/// Default `block_size` implementation for hash plugins that don't have
80/// a meaningful block size. Returns 64, the common block size for
81/// SHA-2 family hashes.
82pub fn default_block_size() -> u32 {
83    64
84}
85
86#[doc(hidden)]
87/// Convenience used by macro-generated code to convert a `Result<T, E>`
88/// where `E: ToString` into a [`PluginResult<T>`].
89pub fn err_with_message<E: std::fmt::Display>(code: crate::ErrorCode, e: E) -> PluginError {
90    PluginError::new(code, e.to_string())
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    /// A trivial XOR-fold hash used by the test suite. Each byte of
98    /// input is XOR-folded into a single u8 state; `finalize` writes
99    /// that single byte to the first byte of the output buffer.
100    struct XorHash {
101        acc: u8,
102    }
103
104    impl HashPlugin for XorHash {
105        fn create_with_opts(_name: &str, _opts: Option<OptionView<'_>>) -> PluginResult<Self> {
106            Ok(XorHash { acc: 0 })
107        }
108
109        fn output_size(&self) -> u32 {
110            1
111        }
112
113        fn block_size(&self) -> u32 {
114            default_block_size()
115        }
116
117        fn update(&mut self, data: &[u8]) -> PluginResult<()> {
118            for &b in data {
119                self.acc ^= b;
120            }
121            Ok(())
122        }
123
124        fn reset(&mut self) -> PluginResult<()> {
125            self.acc = 0;
126            Ok(())
127        }
128
129        fn try_clone(&self) -> PluginResult<Self> {
130            Ok(XorHash { acc: self.acc })
131        }
132
133        fn finalize(&mut self, out: &mut [u8]) -> PluginResult<()> {
134            if out.is_empty() {
135                return Err(crate::error::PluginError::new(
136                    crate::ErrorCode::INSUFFICIENT_BUFFER,
137                    "output buffer too small",
138                ));
139            }
140            out[0] = self.acc;
141            Ok(())
142        }
143    }
144
145    #[test]
146    fn xor_hash_produces_xor_of_inputs() {
147        let mut h = XorHash::create_with_opts("xor", None).unwrap();
148        h.update(&[1, 2, 4]).unwrap();
149        let mut out = [0u8];
150        h.finalize(&mut out).unwrap();
151        assert_eq!(out[0], 1 ^ 2 ^ 4);
152    }
153
154    #[test]
155    fn xor_hash_clone_is_independent() {
156        let mut a = XorHash::create_with_opts("xor", None).unwrap();
157        a.update(&[0xFF]).unwrap();
158        let mut b = a.try_clone().unwrap();
159        a.update(&[0x01]).unwrap();
160        b.update(&[0x02]).unwrap();
161        let mut oa = [0u8];
162        let mut ob = [0u8];
163        a.finalize(&mut oa).unwrap();
164        b.finalize(&mut ob).unwrap();
165        assert_eq!(oa[0], 0xFF ^ 0x01);
166        assert_eq!(ob[0], 0xFF ^ 0x02);
167    }
168
169    #[test]
170    fn xor_hash_reset_clears_state() {
171        let mut h = XorHash::create_with_opts("xor", None).unwrap();
172        h.update(&[0xFF]).unwrap();
173        h.reset().unwrap();
174        let mut out = [0u8];
175        h.finalize(&mut out).unwrap();
176        assert_eq!(out[0], 0);
177    }
178}