Skip to main content

veil_engine/
vault.rs

1//! The value-to-placeholder mapping. The engine only needs this trait; the
2//! encrypted on-disk implementation lives in the `veil-vault` crate.
3
4use std::collections::HashMap;
5use std::sync::{Mutex, MutexGuard};
6
7use crate::{EntityKind, ScopeId, VaultError};
8
9/// Where the engine keeps which value each placeholder stands for.
10///
11/// These are the only three operations redaction and restoration need.
12/// Storage, expiry and admin operations belong to the implementation; the
13/// `veil-vault` crate has an encrypted one on disk.
14pub trait Vault: Send + Sync {
15    /// Returns the placeholder number for `value`, creating one if needed. The
16    /// same value of the same kind in the same scope always gets the same number.
17    fn get_or_create(&self, scope: &ScopeId, kind: &EntityKind, value: &str) -> Result<u64, VaultError>;
18    /// Returns the value behind placeholder number `n`, if this scope knows it.
19    fn lookup(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<Option<String>, VaultError>;
20    /// Makes sure number `n` is never handed out for a new value. Used for
21    /// placeholder-shaped text the user wrote themselves.
22    fn reserve(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<(), VaultError>;
23}
24
25/// RAM-only vault: nothing touches disk and everything is lost on restart.
26#[derive(Default)]
27pub struct MemoryVault {
28    inner: Mutex<Inner>,
29}
30
31#[derive(Default)]
32struct Inner {
33    by_value: HashMap<(ScopeId, EntityKind, String), u64>,
34    by_number: HashMap<(ScopeId, EntityKind, u64), String>,
35    counters: HashMap<(ScopeId, EntityKind), u64>,
36    created: Vec<i64>,
37}
38
39impl MemoryVault {
40    /// How many values it holds.
41    pub fn len(&self) -> usize {
42        self.lock().by_number.len()
43    }
44
45    /// True if it holds no values.
46    pub fn is_empty(&self) -> bool {
47        self.len() == 0
48    }
49
50    /// Unix seconds when the oldest value it holds was added.
51    pub fn oldest_created_at(&self) -> Option<i64> {
52        self.lock().created.iter().min().copied()
53    }
54
55    /// Forgets every value. Numbers already handed out are never given to
56    /// another value, so an old placeholder can't restore to the wrong one.
57    pub fn clear(&self) {
58        let mut inner = self.lock();
59        inner.by_value.clear();
60        inner.by_number.clear();
61        inner.created.clear();
62    }
63
64    /// A panic while holding the lock leaves the maps consistent (each
65    /// change is a few inserts), so a poisoned lock is still usable.
66    fn lock(&self) -> MutexGuard<'_, Inner> {
67        self.inner.lock().unwrap_or_else(|e| e.into_inner())
68    }
69}
70
71impl Vault for MemoryVault {
72    fn get_or_create(&self, scope: &ScopeId, kind: &EntityKind, value: &str) -> Result<u64, VaultError> {
73        let mut inner = self.lock();
74        let key = (scope.clone(), kind.clone(), value.to_owned());
75        if let Some(&n) = inner.by_value.get(&key) {
76            return Ok(n);
77        }
78        let counter = inner.counters.entry((scope.clone(), kind.clone())).or_insert(0);
79        *counter += 1;
80        let n = *counter;
81        inner.by_number.insert((scope.clone(), kind.clone(), n), value.to_owned());
82        inner.by_value.insert(key, n);
83        inner.created.push(now());
84        Ok(n)
85    }
86
87    fn lookup(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<Option<String>, VaultError> {
88        Ok(self.lock().by_number.get(&(scope.clone(), kind.clone(), n)).cloned())
89    }
90
91    fn reserve(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<(), VaultError> {
92        let mut inner = self.lock();
93        let counter = inner.counters.entry((scope.clone(), kind.clone())).or_insert(0);
94        *counter = (*counter).max(n);
95        Ok(())
96    }
97}
98
99fn now() -> i64 {
100    std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_secs() as i64)
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn clear_keeps_numbers_used() {
109        let v = MemoryVault::default();
110        let (scope, kind) = (ScopeId::new("s"), EntityKind::new("EMAIL").unwrap());
111        assert_eq!(v.get_or_create(&scope, &kind, "a@example.com").unwrap(), 1);
112        assert_eq!((v.len(), v.oldest_created_at().is_some()), (1, true));
113        v.clear();
114        assert!(v.is_empty() && v.oldest_created_at().is_none());
115        assert_eq!(v.lookup(&scope, &kind, 1).unwrap(), None);
116        assert_eq!(v.get_or_create(&scope, &kind, "a@example.com").unwrap(), 2);
117    }
118}