1use std::collections::BTreeMap;
36
37use acdp_did::WebResolver;
38use acdp_primitives::error::AcdpError;
39use acdp_types::lifecycle::retraction_state;
40use acdp_types::trust_list::{
41 advance_floor, evaluate, select_version, validate_list_version, IssuerPin, ListRejected,
42 ListVersion, NodeTrust, PinState, ReceiptInput, TrustPass, TrustReport, TrustSubject,
43 VerifiedListBody,
44};
45use chrono::{DateTime, Utc};
46use serde_json::Value;
47
48use crate::revocation_verdict::RevocationVerdict;
49use crate::VerifiedContext;
50
51pub type Series = (String, String);
53
54#[derive(Debug, Clone, Default)]
56pub struct TrustLists {
57 pins: Vec<IssuerPin>,
58 floors: BTreeMap<Series, u64>,
59 versions: Vec<Vec<ListVersion>>,
60 refuse_unknown_issuer_revocation: bool,
61}
62
63fn series(pin: &IssuerPin) -> Series {
64 (pin.issuer.clone(), pin.list_id.clone())
65}
66
67fn rejected(step: u8, e: AcdpError) -> ListRejected {
68 ListRejected {
69 step,
70 reason: e.to_string(),
71 }
72}
73
74impl TrustLists {
75 pub fn new(pins: Vec<IssuerPin>) -> Self {
77 let versions = vec![Vec::new(); pins.len()];
78 Self {
79 pins,
80 floors: BTreeMap::new(),
81 versions,
82 refuse_unknown_issuer_revocation: false,
83 }
84 }
85
86 pub fn refuse_unknown_issuer_revocation(mut self, refuse: bool) -> Self {
91 self.refuse_unknown_issuer_revocation = refuse;
92 self
93 }
94
95 pub fn with_floors(mut self, floors: BTreeMap<Series, u64>) -> Self {
97 self.floors = floors;
98 self
99 }
100
101 pub fn pins(&self) -> &[IssuerPin] {
103 &self.pins
104 }
105
106 pub fn floors(&self) -> &BTreeMap<Series, u64> {
109 &self.floors
110 }
111
112 fn hold(
115 &mut self,
116 mut check: impl FnMut(&IssuerPin) -> Result<ListVersion, ListRejected>,
117 ) -> Result<usize, ListRejected> {
118 let mut accepted = 0;
119 let mut first_err = None;
120 for (pin, held) in self.pins.iter().zip(self.versions.iter_mut()) {
121 match check(pin) {
122 Ok(v) => {
123 accepted += 1;
124 let key = series(pin);
125 if let Some(f) = advance_floor(self.floors.get(&key).copied(), [&v]) {
126 self.floors.insert(key, f);
127 }
128 held.retain(|h| (&h.content_hash, &h.ctx_id) != (&v.content_hash, &v.ctx_id));
131 held.push(v);
132 }
133 Err(e) => {
134 first_err.get_or_insert(e);
135 }
136 }
137 }
138 match (accepted, first_err) {
139 (0, Some(e)) => Err(e),
140 (0, None) => Err(ListRejected {
141 step: 2,
142 reason: "no pins are configured".into(),
143 }),
144 (n, _) => Ok(n),
145 }
146 }
147
148 pub async fn add_verified(
157 &mut self,
158 list: &VerifiedContext,
159 resolver: &WebResolver,
160 ) -> Result<usize, ListRejected> {
161 let body = list.body();
162 let fingerprint = acdp_crypto::fingerprint::fingerprint_for_key_id(
163 &body.signature.key_id,
164 &body.signature.algorithm,
165 resolver,
166 )
167 .await
168 .map_err(|e| rejected(1, e))?;
169 let mut issuer_events = Vec::new();
170 for e in list.registry_state().lifecycle_events.iter().flatten() {
171 if e.actor != body.agent_id {
172 continue;
173 }
174 let Ok(raw) = serde_json::to_value(e) else {
175 continue;
176 };
177 if let Ok(ev) = acdp_verify::verify_lifecycle_event(
178 &raw,
179 &body.ctx_id,
180 &body.agent_id,
181 None,
182 resolver,
183 )
184 .await
185 {
186 issuer_events.push(ev);
187 }
188 }
189 let verdict = list.revocation_verdict();
192 let unknown = matches!(
193 verdict,
194 RevocationVerdict::Unknown { .. }
195 | RevocationVerdict::NotRevoked {
196 complete: false,
197 ..
198 }
199 );
200 let revoked = verdict.is_refusal() || (self.refuse_unknown_issuer_revocation && unknown);
201 let ty = context_type(&body.context_type);
202 let candidate = VerifiedListBody {
203 agent_id: body.agent_id.as_str(),
204 context_type: &ty,
205 metadata: body.metadata.as_ref(),
206 content_hash: body.content_hash.as_str(),
207 ctx_id: Some(body.ctx_id.as_str()),
208 issuer_key_fingerprint: Some(&fingerprint),
209 issuer_key_revoked: revoked,
210 retracted: retraction_state(&issuer_events),
211 };
212 self.hold(|pin| validate_list_version(pin, &candidate))
213 }
214
215 pub fn add_offline(
220 &mut self,
221 raw: &Value,
222 retractions: &[Value],
223 issuer_key_revoked: bool,
224 ) -> Result<usize, ListRejected> {
225 self.hold(|pin| {
226 acdp_verify::trust::verify_list_offline(raw, pin, retractions, issuer_key_revoked)
227 })
228 }
229
230 pub fn select(&self, t: DateTime<Utc>) -> Vec<PinState> {
232 self.pins
233 .iter()
234 .zip(&self.versions)
235 .map(|(pin, held)| {
236 select_version(
237 pin,
238 held,
239 t,
240 TrustPass::Latest,
241 self.floors.get(&series(pin)).copied(),
242 )
243 })
244 .collect()
245 }
246
247 pub fn evaluate(
252 &self,
253 body: &VerifiedContext,
254 t: DateTime<Utc>,
255 capabilities: Option<&[String]>,
256 ) -> NodeTrust {
257 evaluate_against(&self.select(t), body, t, capabilities)
258 }
259
260 pub fn report(
263 &self,
264 bodies: &[&VerifiedContext],
265 t: DateTime<Utc>,
266 capabilities: &BTreeMap<String, Vec<String>>,
267 ) -> TrustReport {
268 let states = self.select(t);
269 let nodes = bodies
270 .iter()
271 .map(|b| {
272 let caps = capabilities
273 .get(&b.body().origin_registry.to_ascii_lowercase())
274 .map(Vec::as_slice);
275 evaluate_against(&states, b, t, caps)
276 })
277 .collect();
278 TrustReport::new(TrustPass::Latest, t, &states, nodes, None, None)
279 }
280}
281
282fn context_type(t: &acdp_types::primitives::ContextType) -> String {
283 serde_json::to_value(t)
284 .ok()
285 .and_then(|v| v.as_str().map(str::to_string))
286 .unwrap_or_default()
287}
288
289fn evaluate_against(
290 states: &[PinState],
291 b: &VerifiedContext,
292 t: DateTime<Utc>,
293 capabilities: Option<&[String]>,
294) -> NodeTrust {
295 let body = b.body();
296 let receipt = b.verified_receipt().and_then(|r| {
297 Some(ReceiptInput {
298 registry_did: r.registry_did.clone(),
299 registry_key_fingerprint: b.receipt_registry_key_fingerprint()?.to_string(),
300 })
301 });
302 let ty = context_type(&body.context_type);
303 evaluate(
304 &TrustSubject::of(body, &ty),
305 states,
306 t,
307 receipt.as_ref(),
308 capabilities,
309 )
310}