1use sha2::{Digest, Sha256};
18use std::collections::HashMap;
19use std::sync::Mutex;
20
21type CacheKey = [u8; 32];
23
24struct CacheEntry {
27 verified: bool,
28 seq: u64,
29}
30
31pub struct VerificationCache {
33 inner: Mutex<CacheInner>,
34}
35
36struct CacheInner {
37 entries: HashMap<CacheKey, CacheEntry>,
38 max_entries: usize,
39 next_seq: u64,
40}
41
42impl VerificationCache {
43 pub fn new(max_entries: usize) -> Self {
45 Self {
46 inner: Mutex::new(CacheInner {
47 entries: HashMap::with_capacity(max_entries),
48 max_entries,
49 next_seq: 0,
50 }),
51 }
52 }
53
54 pub fn get(
57 &self,
58 algorithm: &str,
59 public_key: &[u8],
60 message: &[u8],
61 signature: &[u8],
62 ) -> Option<bool> {
63 let key = make_key(algorithm, public_key, message, signature);
64 let mut inner = self.inner.lock().unwrap();
65 let next_seq = inner.next_seq;
66 inner.next_seq += 1;
67 if let Some(entry) = inner.entries.get_mut(&key) {
68 entry.seq = next_seq;
69 Some(entry.verified)
70 } else {
71 None
72 }
73 }
74
75 pub fn put(
77 &self,
78 algorithm: &str,
79 public_key: &[u8],
80 message: &[u8],
81 signature: &[u8],
82 verified: bool,
83 ) {
84 let key = make_key(algorithm, public_key, message, signature);
85 let mut inner = self.inner.lock().unwrap();
86 let seq = inner.next_seq;
87 inner.next_seq += 1;
88
89 inner.entries.insert(key, CacheEntry { verified, seq });
90
91 if inner.entries.len() > inner.max_entries {
92 evict_oldest(&mut inner.entries);
93 }
94 }
95
96 pub fn len(&self) -> usize {
98 self.inner.lock().unwrap().entries.len()
99 }
100
101 pub fn is_empty(&self) -> bool {
103 self.len() == 0
104 }
105
106 pub fn clear(&self) {
108 self.inner.lock().unwrap().entries.clear();
109 }
110}
111
112impl Default for VerificationCache {
113 fn default() -> Self {
114 Self::new(1024)
115 }
116}
117
118fn make_key(algorithm: &str, public_key: &[u8], message: &[u8], signature: &[u8]) -> CacheKey {
119 let mut hasher = Sha256::new();
120 hasher.update(algorithm.as_bytes());
121 hasher.update(public_key);
122 hasher.update(message);
123 hasher.update(signature);
124 let result = hasher.finalize();
125 let mut key = [0u8; 32];
126 key.copy_from_slice(&result);
127 key
128}
129
130fn evict_oldest(entries: &mut HashMap<CacheKey, CacheEntry>) {
131 if let Some((&oldest_key, _)) = entries.iter().min_by_key(|(_, v)| v.seq) {
132 entries.remove(&oldest_key);
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn empty_cache_returns_none() {
142 let cache = VerificationCache::new(100);
143 assert_eq!(cache.get("Ed25519", &[1; 32], &[2; 32], &[3; 64]), None);
144 }
145
146 #[test]
147 fn put_then_get_returns_cached() {
148 let cache = VerificationCache::new(100);
149 cache.put("Ed25519", &[1; 32], &[2; 32], &[3; 64], true);
150 assert_eq!(
151 cache.get("Ed25519", &[1; 32], &[2; 32], &[3; 64]),
152 Some(true)
153 );
154 }
155
156 #[test]
157 fn different_message_is_miss() {
158 let cache = VerificationCache::new(100);
159 cache.put("Ed25519", &[1; 32], &[2; 32], &[3; 64], true);
160 assert_eq!(cache.get("Ed25519", &[1; 32], &[9; 32], &[3; 64]), None);
161 }
162
163 #[test]
164 fn different_signature_is_miss() {
165 let cache = VerificationCache::new(100);
166 cache.put("Ed25519", &[1; 32], &[2; 32], &[3; 64], true);
167 assert_eq!(cache.get("Ed25519", &[1; 32], &[2; 32], &[9; 64]), None);
168 }
169
170 #[test]
171 fn lru_eviction_removes_oldest() {
172 let cache = VerificationCache::new(3);
173 cache.put("a", &[1], &[1], &[1], true);
174 cache.put("b", &[2], &[2], &[2], true);
175 cache.put("c", &[3], &[3], &[3], true);
176
177 cache.get("a", &[1], &[1], &[1]);
179
180 cache.put("d", &[4], &[4], &[4], true);
182
183 assert_eq!(cache.get("a", &[1], &[1], &[1]), Some(true));
184 assert_eq!(cache.get("b", &[2], &[2], &[2]), None); assert_eq!(cache.get("c", &[3], &[3], &[3]), Some(true));
186 assert_eq!(cache.get("d", &[4], &[4], &[4]), Some(true));
187 assert_eq!(cache.len(), 3);
188 }
189
190 #[test]
191 fn clear_empties_cache() {
192 let cache = VerificationCache::new(100);
193 cache.put("a", &[1], &[1], &[1], true);
194 assert_eq!(cache.len(), 1);
195 cache.clear();
196 assert_eq!(cache.len(), 0);
197 }
198
199 #[test]
200 fn overwrite_updates_value() {
201 let cache = VerificationCache::new(100);
202 cache.put("Ed25519", &[1], &[2], &[3], false);
203 assert_eq!(cache.get("Ed25519", &[1], &[2], &[3]), Some(false));
204 cache.put("Ed25519", &[1], &[2], &[3], true);
205 assert_eq!(cache.get("Ed25519", &[1], &[2], &[3]), Some(true));
206 }
207
208 #[test]
209 fn capacity_one_always_evicts() {
210 let cache = VerificationCache::new(1);
211 cache.put("a", &[1], &[1], &[1], true);
212 assert_eq!(cache.len(), 1);
213 cache.put("b", &[2], &[2], &[2], true);
214 assert_eq!(cache.len(), 1);
215 assert_eq!(cache.get("a", &[1], &[1], &[1]), None);
216 assert_eq!(cache.get("b", &[2], &[2], &[2]), Some(true));
217 }
218
219 #[test]
220 fn default_capacity_is_1024() {
221 let cache = VerificationCache::default();
222 assert_eq!(cache.len(), 0);
223 for i in 0..100 {
224 cache.put("alg", &[i], &[i], &[i], true);
225 }
226 assert_eq!(cache.len(), 100);
227 }
228
229 #[test]
230 fn thread_safe_concurrent_access() {
231 use std::sync::Arc;
232 use std::thread;
233
234 let cache = Arc::new(VerificationCache::new(100));
235 let mut handles = Vec::new();
236
237 for i in 0..4 {
238 let cache = Arc::clone(&cache);
239 handles.push(thread::spawn(move || {
240 for j in 0..10 {
241 let val = i * 10 + j;
242 cache.put("alg", &[val as u8], &[val as u8], &[val as u8], true);
243 cache.get("alg", &[val as u8], &[val as u8], &[val as u8]);
244 }
245 }));
246 }
247
248 for h in handles {
249 h.join().unwrap();
250 }
251 assert!(cache.len() <= 100);
252 }
253}