confium_signatif/
discovery.rs1use std::collections::HashMap;
14
15use serde::{Deserialize, Serialize};
16
17use crate::error::{SignatifError, SignatifResult};
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct LogRef {
22 pub log: String,
24 pub sequence: u64,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(tag = "strategy", rename_all = "snake_case")]
31pub enum ChainDelivery {
32 Embedded {
34 chain: Vec<Vec<u8>>,
36 },
37 LogReference {
39 refs: Vec<LogRef>,
41 },
42 Hybrid {
44 immediate: Vec<Vec<u8>>,
46 refs: Vec<LogRef>,
48 },
49}
50
51impl ChainDelivery {
52 pub fn is_offline_capable(&self) -> bool {
55 matches!(self, ChainDelivery::Embedded { .. })
56 }
57}
58
59pub trait LogResolver {
63 fn resolve(&mut self, r: &LogRef) -> SignatifResult<Vec<u8>>;
69}
70
71#[derive(Debug, Default)]
75pub struct CachingResolver<R> {
76 inner: R,
77 cache: HashMap<(String, u64), Vec<u8>>,
78}
79
80impl<R: LogResolver> LogResolver for CachingResolver<R> {
81 fn resolve(&mut self, r: &LogRef) -> SignatifResult<Vec<u8>> {
82 CachingResolver::resolve(self, r)
83 }
84}
85
86impl<R: LogResolver> CachingResolver<R> {
87 pub fn new(inner: R) -> Self {
89 Self {
90 inner,
91 cache: HashMap::new(),
92 }
93 }
94
95 pub fn resolve(&mut self, r: &LogRef) -> SignatifResult<Vec<u8>> {
101 if let Some(hit) = self.cache.get(&(r.log.clone(), r.sequence)) {
102 return Ok(hit.clone());
103 }
104 let fetched = self.inner.resolve(r)?;
105 self.cache
106 .insert((r.log.clone(), r.sequence), fetched.clone());
107 Ok(fetched)
108 }
109
110 pub fn cache_len(&self) -> usize {
112 self.cache.len()
113 }
114}
115
116pub fn reconstruct_chain(
124 delivery: &ChainDelivery,
125 resolver: Option<&mut dyn LogResolver>,
126) -> SignatifResult<Vec<Vec<u8>>> {
127 match delivery {
128 ChainDelivery::Embedded { chain } => Ok(chain.clone()),
129 ChainDelivery::LogReference { refs } => {
130 let resolver = resolver.ok_or_else(|| {
131 SignatifError::Encoding("log-reference delivery requires a resolver".into())
132 })?;
133 refs.iter().map(|r| resolver.resolve(r)).collect()
134 }
135 ChainDelivery::Hybrid { immediate, refs } => {
136 let mut chain = immediate.clone();
137 let resolver = resolver.ok_or_else(|| {
138 SignatifError::Encoding("hybrid delivery requires a resolver".into())
139 })?;
140 for r in refs {
141 chain.push(resolver.resolve(r)?);
142 }
143 Ok(chain)
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 struct FakeLog {
153 entries: HashMap<(String, u64), Vec<u8>>,
154 fetches: std::cell::Cell<u32>,
155 }
156
157 impl LogResolver for &FakeLog {
158 fn resolve(&mut self, r: &LogRef) -> SignatifResult<Vec<u8>> {
159 self.fetches.set(self.fetches.get() + 1);
160 self.entries
161 .get(&(r.log.clone(), r.sequence))
162 .cloned()
163 .ok_or_else(|| SignatifError::Encoding("miss".into()))
164 }
165 }
166
167 #[test]
168 fn embedded_is_offline() {
169 let d = ChainDelivery::Embedded {
170 chain: vec![vec![1]],
171 };
172 assert!(d.is_offline_capable());
173 assert_eq!(reconstruct_chain(&d, None).unwrap(), vec![vec![1]]);
174 }
175
176 #[test]
177 fn log_reference_resolves_and_caches() {
178 let log = FakeLog {
179 entries: [("pharma-log".to_string(), 7u64)]
180 .iter()
181 .map(|(l, s)| ((l.clone(), *s), vec![9, 9]))
182 .collect(),
183 fetches: std::cell::Cell::new(0),
184 };
185 let d = ChainDelivery::LogReference {
186 refs: vec![LogRef {
187 log: "pharma-log".into(),
188 sequence: 7,
189 }],
190 };
191 let mut cache = CachingResolver::new(&log);
192 let chain = reconstruct_chain(&d, Some(&mut cache)).unwrap();
193 assert_eq!(chain, vec![vec![9, 9]]);
194 assert_eq!(cache.cache_len(), 1);
195 reconstruct_chain(&d, Some(&mut cache)).unwrap();
197 assert_eq!(log.fetches.get(), 1);
198 }
199
200 #[test]
201 fn hybrid_combines_inline_and_resolved() {
202 let log = FakeLog {
203 entries: [("log".to_string(), 1u64)]
204 .iter()
205 .map(|(l, s)| ((l.clone(), *s), vec![2]))
206 .collect(),
207 fetches: std::cell::Cell::new(0),
208 };
209 let d = ChainDelivery::Hybrid {
210 immediate: vec![vec![1]],
211 refs: vec![LogRef {
212 log: "log".into(),
213 sequence: 1,
214 }],
215 };
216 assert_eq!(
217 reconstruct_chain(&d, Some(&mut &log)).unwrap(),
218 vec![vec![1], vec![2]]
219 );
220 }
221}