1use super::{
4 Authenticated, BoundDelta, CombinedEntry, EntryLookup, Freshness, Reasons, ScopeFailure,
5 ScopeOutcome,
6};
7use crate::temporal::{LeapTable, Reading};
8use crate::{Certificate, Error, Result, oid};
9use structured_asn1::memory::{Memory, MemoryError, OwnedVec, Work};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum SourceOrigin {
14 Cached,
16 Replacement,
18}
19
20pub struct Evaluation<'p, E: Work = Memory> {
22 pub now: Reading,
24 pub utc: &'p LeapTable<'p>,
26 pub execution: &'p E,
28 pub origin: SourceOrigin,
30}
31
32#[derive(Debug, Clone, Copy)]
35pub enum EvidenceSource<'r, 'a> {
36 Complete(Authenticated<'r, 'a>),
38 Delta {
40 base: Authenticated<'r, 'a>,
42 delta: Authenticated<'r, 'a>,
44 },
45}
46
47#[derive(Debug, Clone, Copy)]
51pub struct AbsenceEvidence<'r, 'a> {
52 target: &'r Certificate<'a>,
53 now: Reading,
54 reasons: Reasons,
55 source: EvidenceSource<'r, 'a>,
56}
57impl<'r, 'a> AbsenceEvidence<'r, 'a> {
58 #[must_use]
61 pub fn target(self) -> &'r Certificate<'a> {
62 self.target
63 }
64 #[must_use]
66 pub fn evaluated_at(self) -> Reading {
67 self.now
68 }
69 #[must_use]
71 pub fn source(self) -> EvidenceSource<'r, 'a> {
72 self.source
73 }
74 #[must_use]
76 pub fn reasons(self) -> Reasons {
77 self.reasons
78 }
79}
80
81fn current(freshness: Freshness, origin: SourceOrigin) -> bool {
82 freshness == Freshness::Current
83 || (freshness == Freshness::AtNextUpdate && origin == SourceOrigin::Cached)
84}
85
86#[derive(Debug, Clone, Copy)]
88pub struct CompleteFacts<'b, 'r, 'a> {
89 target: &'r Certificate<'a>,
90 now: Reading,
91 source: &'b Authenticated<'r, 'a>,
92 reasons: Reasons,
93 freshness: Freshness,
94 origin: SourceOrigin,
95 entry: EntryLookup<'r, 'a>,
96}
97impl<'b, 'r, 'a> CompleteFacts<'b, 'r, 'a> {
98 #[must_use]
101 pub fn target(self) -> &'r Certificate<'a> {
102 self.target
103 }
104 #[must_use]
106 pub fn evaluated_at(self) -> Reading {
107 self.now
108 }
109 #[must_use]
111 pub fn source(self) -> &'b Authenticated<'r, 'a> {
112 self.source
113 }
114 #[must_use]
116 pub fn reasons(self) -> Reasons {
117 self.reasons
118 }
119 #[must_use]
121 pub fn freshness(self) -> Freshness {
122 self.freshness
123 }
124 #[must_use]
126 pub fn entry(self) -> EntryLookup<'r, 'a> {
127 self.entry
128 }
129 #[must_use]
132 pub fn absence(self) -> Option<AbsenceEvidence<'r, 'a>> {
133 if !current(self.freshness, self.origin) || !matches!(self.entry, EntryLookup::Absent) {
134 return None;
135 }
136 Some(AbsenceEvidence {
137 target: self.target,
138 now: self.now,
139 reasons: self.reasons,
140 source: EvidenceSource::Complete(*self.source),
141 })
142 }
143}
144
145#[derive(Debug, Clone, Copy)]
147pub enum CompleteEvidence<'b, 'r, 'a> {
148 DeltaNeedsBase,
150 NotApplicable(ScopeFailure),
152 Facts(CompleteFacts<'b, 'r, 'a>),
154}
155
156impl<'r, 'a> Authenticated<'r, 'a> {
157 pub fn evaluate<'b, E: Work>(
161 &'b self,
162 target: &'r Certificate<'a>,
163 time: &Evaluation<'_, E>,
164 ) -> Result<CompleteEvidence<'b, 'r, 'a>> {
165 if self
166 .crl()
167 .tbs()
168 .extensions()
169 .get(oid::DELTA_CRL_INDICATOR, time.execution)?
170 .is_some()
171 {
172 return Ok(CompleteEvidence::DeltaNeedsBase);
173 }
174 let reasons = match self.scope(target, time.execution)? {
175 ScopeOutcome::Applicable(reasons) => reasons,
176 ScopeOutcome::NotApplicable(reason) => {
177 return Ok(CompleteEvidence::NotApplicable(reason));
178 }
179 };
180 let entry = self.entry(target, time.execution)?;
181 let freshness = self.freshness(time.now, time.utc, time.execution)?;
182 Ok(CompleteEvidence::Facts(CompleteFacts {
183 target,
184 now: time.now,
185 source: self,
186 reasons,
187 entry,
188 freshness,
189 origin: time.origin,
190 }))
191 }
192}
193
194#[derive(Debug)]
196pub struct DeltaFacts<'b, 'r, 'a> {
197 target: &'r Certificate<'a>,
198 now: Reading,
199 pair: BoundDelta<'b, 'r, 'a>,
200 reasons: Reasons,
201 base_freshness: Freshness,
202 delta_freshness: Freshness,
203 origin: SourceOrigin,
204 entry: CombinedEntry<'r, 'a>,
205}
206impl<'b, 'r, 'a> DeltaFacts<'b, 'r, 'a> {
207 #[must_use]
210 pub fn target(&self) -> &'r Certificate<'a> {
211 self.target
212 }
213 #[must_use]
215 pub fn evaluated_at(&self) -> Reading {
216 self.now
217 }
218 #[must_use]
220 pub fn pair(&self) -> BoundDelta<'b, 'r, 'a> {
221 self.pair
222 }
223 #[must_use]
225 pub fn base_freshness(&self) -> Freshness {
226 self.base_freshness
227 }
228 #[must_use]
230 pub fn delta_freshness(&self) -> Freshness {
231 self.delta_freshness
232 }
233 #[must_use]
235 pub fn entry(&self) -> &CombinedEntry<'r, 'a> {
236 &self.entry
237 }
238 #[must_use]
240 pub fn absence(&self) -> Option<AbsenceEvidence<'r, 'a>> {
241 if !current(self.delta_freshness, self.origin)
242 || matches!(
243 self.base_freshness,
244 Freshness::NotYetCurrent | Freshness::InvalidUpdateOrder | Freshness::Uncertain(_)
245 )
246 || !matches!(
247 self.entry,
248 CombinedEntry::Absent | CombinedEntry::Removed { .. }
249 )
250 {
251 return None;
252 }
253 Some(AbsenceEvidence {
254 target: self.target,
255 now: self.now,
256 reasons: self.reasons,
257 source: EvidenceSource::Delta {
258 base: *self.pair.base(),
259 delta: *self.pair.delta(),
260 },
261 })
262 }
263}
264
265#[derive(Debug)]
267pub enum DeltaEvidence<'b, 'r, 'a> {
268 NotApplicable(ScopeFailure),
270 Facts(DeltaFacts<'b, 'r, 'a>),
272}
273impl<'b, 'r, 'a> BoundDelta<'b, 'r, 'a> {
274 pub fn evaluate<E: Work>(
277 self,
278 target: &'r Certificate<'a>,
279 time: &Evaluation<'_, E>,
280 ) -> Result<DeltaEvidence<'b, 'r, 'a>> {
281 let reasons = match self.base().scope(target, time.execution)? {
282 ScopeOutcome::Applicable(reasons) => reasons,
283 ScopeOutcome::NotApplicable(reason) => return Ok(DeltaEvidence::NotApplicable(reason)),
284 };
285 let entry = self.entry(target, time.execution)?;
286 let base_freshness = self.base().freshness(time.now, time.utc, time.execution)?;
287 let delta_freshness = self.delta().freshness(time.now, time.utc, time.execution)?;
288 Ok(DeltaEvidence::Facts(DeltaFacts {
289 target,
290 now: time.now,
291 pair: self,
292 reasons,
293 entry,
294 base_freshness,
295 delta_freshness,
296 origin: time.origin,
297 }))
298 }
299}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303pub enum CoverageAdmission {
304 Added,
306 Redundant,
308 DifferentTarget,
310 DifferentTime,
312 DifferentExecution,
315}
316
317#[derive(Debug)]
321pub struct Coverage<'r, 'a> {
322 reasons: Reasons,
323 sources: Option<OwnedVec<AbsenceEvidence<'r, 'a>>>,
326}
327impl<'r, 'a> Coverage<'r, 'a> {
328 pub fn new<W: Work>(first: AbsenceEvidence<'r, 'a>, execution: &W) -> Result<Self> {
330 let mut sources = execution.vector();
331 sources
332 .try_push(first)
333 .map_err(|reason| first.target.tbs().subject().resource(reason))?;
334 Ok(Self {
335 reasons: first.reasons,
336 sources: Some(sources.into_owned()),
337 })
338 }
339 #[must_use]
341 pub fn reasons(&self) -> Reasons {
342 self.reasons
343 }
344 #[must_use]
349 pub fn target(&self) -> &'r Certificate<'a> {
350 self.sources()[0].target()
351 }
352 #[must_use]
357 pub fn evaluated_at(&self) -> Reading {
358 self.sources()[0].evaluated_at()
359 }
360 #[must_use]
366 pub fn sources(&self) -> &[AbsenceEvidence<'r, 'a>] {
367 self.sources
368 .as_deref()
369 .expect("coverage growth restores its buffer")
370 }
371 pub fn add<W: Work>(
380 &mut self,
381 evidence: AbsenceEvidence<'r, 'a>,
382 execution: &W,
383 ) -> Result<CoverageAdmission> {
384 if !self
385 .sources
386 .as_ref()
387 .expect("coverage owns its buffer")
388 .memory()
389 .same_account(execution.memory())
390 {
391 return Ok(CoverageAdmission::DifferentExecution);
392 }
393 let first = &self.sources()[0];
394 execution
395 .charge_work(1)
396 .map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
397 if first.now != evidence.now {
398 return Ok(CoverageAdmission::DifferentTime);
399 }
400 let left = first.target.as_bytes();
401 let right = evidence.target.as_bytes();
402 let work = u64::try_from(left.len().max(right.len()))
403 .ok()
404 .and_then(|n| n.checked_mul(2))
405 .and_then(|n| n.checked_add(1))
406 .ok_or_else(|| {
407 Error::resource(
408 MemoryError::WorkExceeded,
409 "certificateList.coverage",
410 evidence.target.span().start,
411 )
412 })?;
413 execution
414 .charge_work(work)
415 .map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
416 if left != right {
417 return Ok(CoverageAdmission::DifferentTarget);
418 }
419 let union = self.reasons.union(evidence.reasons);
420 if union == self.reasons {
421 return Ok(CoverageAdmission::Redundant);
422 }
423 let sources = self.sources.take().expect("coverage owns its buffer");
424 let Ok(mut scoped) = sources.with_execution(execution) else {
425 unreachable!("the account was checked before any coverage work")
426 };
427 let admitted = scoped.try_push(evidence);
428 self.sources = Some(scoped.into_owned());
429 admitted.map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
430 self.reasons = union;
431 Ok(CoverageAdmission::Added)
432 }
433}