Skip to main content

confium_store/backends/
memory.rs

1//! In-memory HashMap backend.
2//!
3//! For development and tests: holds everything in process memory, no
4//! persistence, no hardware backing. The structure mirrors the
5//! compartment design from `TODO.finalize/12-keystore-interface.md`:
6//!
7//! - outer map keyed by `(module_id, app_id)`
8//! - each entry has two inner maps:
9//!   - private: `key_id → *mut c_void`
10//!   - public:  `identity → (*mut c_void, Vec<u8> signature)`
11//!
12//! Key handles are opaque `*mut c_void`; the backend does not interpret
13//! them. Ownership of the underlying memory stays with whoever produced
14//! the handle (typically the Engine's keyfmt interface).
15
16use std::collections::HashMap;
17use std::ffi::c_void;
18
19use crate::backend::{Compartment, Options, StoreBackend, StoreInstance};
20use crate::error::{Result, ValueNotFoundSnafu};
21use crate::register_backend;
22
23/// One `(module_id, app_id)` scope's two compartments.
24#[derive(Default)]
25struct Scope {
26    private: HashMap<String, *mut c_void>,
27    public: HashMap<String, (*mut c_void, Vec<u8>)>,
28}
29
30/// Factory for the in-memory backend. Stateless — all state lives in
31/// [`MemoryInstance`].
32pub struct MemoryBackend;
33
34impl StoreBackend for MemoryBackend {
35    fn name(&self) -> &'static str {
36        "memory"
37    }
38
39    fn open(&self, _opts: &Options) -> Result<Box<dyn StoreInstance>> {
40        Ok(Box::new(MemoryInstance::default()))
41    }
42}
43
44register_backend!(MemoryBackend);
45
46#[derive(Default)]
47pub struct MemoryInstance {
48    scopes: HashMap<(String, String), Scope>,
49}
50
51impl MemoryInstance {
52    fn scope(&self, module: &str, app: &str) -> Option<&Scope> {
53        self.scopes.get(&(module.to_string(), app.to_string()))
54    }
55
56    fn scope_mut(&mut self, module: &str, app: &str) -> &mut Scope {
57        self.scopes
58            .entry((module.to_string(), app.to_string()))
59            .or_default()
60    }
61}
62
63impl StoreInstance for MemoryInstance {
64    fn put_secret(
65        &mut self,
66        module: &str,
67        app: &str,
68        key_id: &str,
69        key: *mut c_void,
70    ) -> Result<()> {
71        self.scope_mut(module, app)
72            .private
73            .insert(key_id.to_string(), key);
74        Ok(())
75    }
76
77    fn get_secret(&self, module: &str, app: &str, key_id: &str) -> Result<*mut c_void> {
78        self.scope(module, app)
79            .and_then(|s| s.private.get(key_id))
80            .copied()
81            .ok_or_else(|| ValueNotFoundSnafu.build())
82    }
83
84    fn put_public(
85        &mut self,
86        module: &str,
87        app: &str,
88        identity: &str,
89        key: *mut c_void,
90        sig: &[u8],
91    ) -> Result<()> {
92        self.scope_mut(module, app)
93            .public
94            .insert(identity.to_string(), (key, sig.to_vec()));
95        Ok(())
96    }
97
98    fn get_public(
99        &self,
100        module: &str,
101        app: &str,
102        identity: &str,
103    ) -> Result<(*mut c_void, Vec<u8>)> {
104        self.scope(module, app)
105            .and_then(|s| s.public.get(identity))
106            .map(|(k, sig)| (*k, sig.clone()))
107            .ok_or_else(|| ValueNotFoundSnafu.build())
108    }
109
110    fn enumerate(
111        &self,
112        module: &str,
113        app: &str,
114        compartment: Compartment,
115    ) -> Result<Vec<(*mut c_void, String)>> {
116        let Some(scope) = self.scope(module, app) else {
117            return Ok(Vec::new());
118        };
119        let entries: Vec<(*mut c_void, String)> = match compartment {
120            Compartment::Private => scope.private.iter().map(|(k, v)| (*v, k.clone())).collect(),
121            Compartment::Public => scope
122                .public
123                .iter()
124                .map(|(id, (k, _))| (*k, id.clone()))
125                .collect(),
126        };
127        Ok(entries)
128    }
129}
130
131// SAFETY: the backend stores raw `*mut c_void` handles provided by the
132// caller. Those handles represent opaque key material owned by the
133// Engine; the Store never dereferences them, only stores and returns
134// them. Sending/sharing the handle across threads is sound because no
135// thread reads through the pointer — it is an opaque token to the
136// backend.
137unsafe impl Send for MemoryInstance {}
138unsafe impl Sync for MemoryInstance {}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::backend::{StoreBackend, StoreInstance};
144
145    fn open() -> Box<dyn StoreInstance> {
146        MemoryBackend
147            .open(&Options::new())
148            .expect("memory backend opens")
149    }
150
151    // Use distinct non-null sentinel pointers so the tests can assert on
152    // identity without allocating real key material. The backend treats
153    // these as opaque tokens.
154    fn sentinel(n: usize) -> *mut c_void {
155        n as *mut c_void
156    }
157
158    #[test]
159    fn put_get_secret_round_trip() {
160        let mut ks = open();
161        let key = sentinel(0x1000);
162        ks.put_secret("mod", "app", "key-1", key)
163            .expect("put_secret");
164        let got = ks.get_secret("mod", "app", "key-1").expect("get_secret");
165        assert_eq!(got, key);
166    }
167
168    #[test]
169    fn put_get_public_round_trip() {
170        let mut ks = open();
171        let key = sentinel(0x2000);
172        let sig = vec![0xDE, 0xAD, 0xBE, 0xEF];
173        ks.put_public("mod", "app", "email:alice@example.com", key, &sig)
174            .expect("put_public");
175        let (got_key, got_sig) = ks
176            .get_public("mod", "app", "email:alice@example.com")
177            .expect("get_public");
178        assert_eq!(got_key, key);
179        assert_eq!(got_sig, sig);
180    }
181
182    #[test]
183    fn wrong_module_returns_value_not_found() {
184        let mut ks = open();
185        ks.put_secret("mod", "app", "key-1", sentinel(0x10))
186            .expect("put_secret");
187        let err = ks.get_secret("other", "app", "key-1").unwrap_err();
188        assert!(matches!(err, crate::error::Error::ValueNotFound));
189    }
190
191    #[test]
192    fn wrong_app_returns_value_not_found() {
193        let mut ks = open();
194        ks.put_secret("mod", "app", "key-1", sentinel(0x10))
195            .expect("put_secret");
196        let err = ks.get_secret("mod", "other", "key-1").unwrap_err();
197        assert!(matches!(err, crate::error::Error::ValueNotFound));
198    }
199
200    #[test]
201    fn wrong_key_id_returns_value_not_found() {
202        let mut ks = open();
203        ks.put_secret("mod", "app", "key-1", sentinel(0x10))
204            .expect("put_secret");
205        let err = ks.get_secret("mod", "app", "missing").unwrap_err();
206        assert!(matches!(err, crate::error::Error::ValueNotFound));
207    }
208
209    #[test]
210    fn wrong_identity_returns_value_not_found() {
211        let mut ks = open();
212        ks.put_public(
213            "mod",
214            "app",
215            "email:alice@example.com",
216            sentinel(0x20),
217            &[1, 2, 3],
218        )
219        .expect("put_public");
220        let err = ks
221            .get_public("mod", "app", "email:bob@example.com")
222            .unwrap_err();
223        assert!(matches!(err, crate::error::Error::ValueNotFound));
224    }
225
226    #[test]
227    fn compartments_are_isolated() {
228        let mut ks = open();
229        // Put a secret in the private compartment.
230        let secret = sentinel(0x30);
231        ks.put_secret("mod", "app", "key-1", secret)
232            .expect("put_secret");
233
234        // The private key must not be visible via the public lookup
235        // path, even using the same index string.
236        let err = ks.get_public("mod", "app", "key-1").unwrap_err();
237        assert!(matches!(err, crate::error::Error::ValueNotFound));
238
239        // And vice versa: a public entry is not visible via get_secret.
240        let pub_key = sentinel(0x40);
241        ks.put_public("mod", "app", "email:alice@example.com", pub_key, &[9])
242            .expect("put_public");
243        let err = ks
244            .get_secret("mod", "app", "email:alice@example.com")
245            .unwrap_err();
246        assert!(matches!(err, crate::error::Error::ValueNotFound));
247    }
248
249    #[test]
250    fn enumerate_partitions_by_compartment() {
251        let mut ks = open();
252        ks.put_secret("mod", "app", "key-a", sentinel(0x1))
253            .expect("put_secret");
254        ks.put_secret("mod", "app", "key-b", sentinel(0x2))
255            .expect("put_secret");
256        ks.put_public("mod", "app", "email:a@b", sentinel(0x3), &[0])
257            .expect("put_public");
258
259        let private = ks
260            .enumerate("mod", "app", Compartment::Private)
261            .expect("enumerate private");
262        assert_eq!(private.len(), 2, "two private entries expected");
263
264        let public = ks
265            .enumerate("mod", "app", Compartment::Public)
266            .expect("enumerate public");
267        assert_eq!(public.len(), 1, "one public entry expected");
268        assert_eq!(public[0].1, "email:a@b");
269    }
270
271    #[test]
272    fn put_secret_overwrites() {
273        let mut ks = open();
274        let first = sentinel(0x100);
275        let second = sentinel(0x200);
276        ks.put_secret("mod", "app", "key-1", first)
277            .expect("put_secret first");
278        ks.put_secret("mod", "app", "key-1", second)
279            .expect("put_secret second");
280        let got = ks.get_secret("mod", "app", "key-1").expect("get_secret");
281        assert_eq!(got, second, "second put should win");
282    }
283
284    #[test]
285    fn distinct_scopes_do_not_leak() {
286        let mut ks = open();
287        ks.put_secret("mod", "app-a", "key-1", sentinel(0x1))
288            .expect("put_secret");
289        let err = ks.get_secret("mod", "app-b", "key-1").unwrap_err();
290        assert!(matches!(err, crate::error::Error::ValueNotFound));
291    }
292
293    #[test]
294    fn backend_is_registered() {
295        // The link-time registry must surface the memory backend by its
296        // wire name so the FFI create path can find it.
297        let backend = crate::backend::find("memory").expect("memory backend registered");
298        assert_eq!(backend.name(), "memory");
299    }
300}