Skip to main content

confium_mock_plugin/
lib.rs

1//! Mock plugin built entirely with the `confium-api` SDK proc-macros.
2//!
3//! This is the proof-of-concept that the macro-generated FFI symbols
4//! load through the standard Confium plugin loader. The plugin
5//! implements two interfaces:
6//!
7//! - A trivial XOR-fold hash: the digest is one byte, the XOR of every
8//!   input byte. Fully deterministic and dependency-free.
9//! - A trivial XOR-stream cipher: each input byte is XOR-folded with a
10//!   running key byte derived from the key material. The cipher is a
11//!   stand-in for a real symmetric cipher — it exercises the full
12//!   `cfmp_cipher_*` symbol set (create, block/key/iv size, update,
13//!   finalize, reset, destroy) end to end.
14//!
15//! The crate compiles to a `cdylib` so the loader can `dlopen` it.
16//! The `cfmp_*` symbols are emitted by:
17//!
18//! - `#[plugin_interface(name = "hash", version = 0)]` on the
19//!   `impl HashPlugin for XorHash` block (eight `cfmp_hash_*` symbols).
20//! - `#[plugin_interface(name = "cipher", version = 0)]` on the
21//!   `impl CipherPlugin for XorCipher` block (eight `cfmp_cipher_*`
22//!   symbols).
23//! - `#[export(metadata(...))]` on the plugin marker struct. Emits the
24//!   lifecycle + metadata symbols. The interface list is auto-discovered
25//!   from the `#[plugin_interface]` attributes above; no explicit
26//!   `interfaces(...)` argument is needed.
27//!
28//! The integration test in `tests/loader.rs` loads this crate's
29//! cdylib artifact and runs an end-to-end hash and cipher through the
30//! loader.
31
32use confium_api::CipherPlugin;
33use confium_api::HashPlugin;
34use confium_api::error::PluginResult;
35use confium_api::options::OptionView;
36use confium_macros::{export, plugin_interface};
37
38// =====================================================================
39// Hash interface — XOR-fold hash
40// =====================================================================
41
42/// One-byte XOR-fold hash. State is a single accumulator byte.
43pub struct XorHash {
44    acc: u8,
45}
46
47impl XorHash {
48    /// Construct an empty accumulator. Exposed for tests that want to
49    /// bypass the trait `create_with_opts` path.
50    pub fn new() -> Self {
51        Self { acc: 0 }
52    }
53}
54
55impl Default for XorHash {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61#[plugin_interface(name = "hash", version = 0)]
62impl HashPlugin for XorHash {
63    fn create_with_opts(_name: &str, _opts: Option<OptionView<'_>>) -> PluginResult<Self> {
64        Ok(Self::new())
65    }
66
67    fn output_size(&self) -> u32 {
68        1
69    }
70
71    fn block_size(&self) -> u32 {
72        // The XOR hash has no meaningful block size. Return 1 so callers
73        // that try to chunk input still make progress.
74        1
75    }
76
77    fn update(&mut self, data: &[u8]) -> PluginResult<()> {
78        for &b in data {
79            self.acc ^= b;
80        }
81        Ok(())
82    }
83
84    fn reset(&mut self) -> PluginResult<()> {
85        self.acc = 0;
86        Ok(())
87    }
88
89    fn try_clone(&self) -> PluginResult<Self> {
90        Ok(Self { acc: self.acc })
91    }
92
93    fn finalize(&mut self, out: &mut [u8]) -> PluginResult<()> {
94        if out.is_empty() {
95            return Err(confium_api::PluginError::new(
96                confium_api::ErrorCode::INSUFFICIENT_BUFFER,
97                "xor hash needs at least 1 byte of output buffer",
98            ));
99        }
100        out[0] = self.acc;
101        Ok(())
102    }
103}
104
105// =====================================================================
106// Cipher interface — XOR-stream cipher
107// =====================================================================
108
109/// Trivial XOR-stream cipher used as a mock. The "key schedule" is a
110/// single running byte derived by XOR-folding the supplied key; each
111/// input byte is XORed with that running byte. This is not secure
112/// cryptography — it exists only to exercise the full cipher FFI
113/// surface without pulling in a real cipher library.
114pub struct XorCipher {
115    /// The running key byte. XORed against every input byte.
116    keystream: u8,
117}
118
119impl XorCipher {
120    /// Construct from key + iv. The key is XOR-folded into a single
121    /// byte; the IV is folded in on top so different IVs produce
122    /// different keystreams for the same key.
123    pub fn from_key_iv(key: &[u8], iv: &[u8]) -> Self {
124        let mut k: u8 = 0;
125        for &b in key {
126            k ^= b;
127        }
128        for &b in iv {
129            k ^= b;
130        }
131        Self { keystream: k }
132    }
133}
134
135#[plugin_interface(name = "cipher", version = 0)]
136impl CipherPlugin for XorCipher {
137    fn create_with_key(
138        _algorithm: &str,
139        key: &[u8],
140        iv: &[u8],
141        _opts: Option<OptionView<'_>>,
142    ) -> PluginResult<Self> {
143        Ok(Self::from_key_iv(key, iv))
144    }
145
146    fn block_size(&self) -> u32 {
147        // A stream cipher has no meaningful block size. Return 1 so the
148        // loader's buffer-allocation logic doesn't divide by zero.
149        1
150    }
151
152    fn key_size(&self) -> u32 {
153        // The mock accepts any key length; report 0 to signal "variable".
154        0
155    }
156
157    fn iv_size(&self) -> u32 {
158        // The mock accepts any IV length; report 0 to signal "variable".
159        0
160    }
161
162    fn update(&mut self, input: &[u8], output: &mut [u8]) -> PluginResult<usize> {
163        let n = input.len().min(output.len());
164        for i in 0..n {
165            output[i] = input[i] ^ self.keystream;
166        }
167        Ok(n)
168    }
169
170    fn finalize(&mut self, _output: &mut [u8]) -> PluginResult<usize> {
171        // A stream cipher has no buffered final block.
172        Ok(0)
173    }
174
175    fn reset(&mut self) -> PluginResult<()> {
176        // Reset is a no-op for this mock: the keystream byte is derived
177        // from the key, not from accumulated state.
178        Ok(())
179    }
180}
181
182// `#[export]` emits the four plugin lifecycle symbols plus the optional
183// `cfmp_metadata` symbol (because `metadata(...)` is supplied). The
184// interface list is auto-discovered from the `#[plugin_interface]`
185// attributes above — `hash` and `symmetric` (cipher's wire name) are
186// registered at link time and surfaced through
187// `cfmp_query_interfaces` without an explicit `interfaces(...)` arg.
188#[export(metadata(
189    name = "confium-mock-plugin",
190    version = "0.1.0",
191    vendor = "confium",
192    license = "BSD-2-Clause",
193    description = "XOR-fold mock hash + cipher for SDK loader tests",
194))]
195pub struct Plugin;