1use super::{
4 Authenticated, BoundDelta, CombinedEntry, EntryLookup, Freshness, Reasons, RevocationTarget,
5 ScopeFailure, ScopeOutcome,
6};
7use crate::temporal::{LeapTable, Reading};
8use crate::{Certificate, Result, oid};
9use structured_asn1::memory::{Memory, 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)]
51pub struct AbsenceEvidence<'r, 'a, T = Certificate<'a>> {
52 target: &'r T,
53 now: Reading,
54 reasons: Reasons,
55 source: EvidenceSource<'r, 'a>,
56}
57impl<T> Copy for AbsenceEvidence<'_, '_, T> {}
58impl<T> Clone for AbsenceEvidence<'_, '_, T> {
59 fn clone(&self) -> Self {
60 *self
61 }
62}
63impl<'r, 'a, T> AbsenceEvidence<'r, 'a, T> {
64 #[must_use]
67 pub fn target(self) -> &'r T {
68 self.target
69 }
70 #[must_use]
72 pub fn evaluated_at(self) -> Reading {
73 self.now
74 }
75 #[must_use]
77 pub fn source(self) -> EvidenceSource<'r, 'a> {
78 self.source
79 }
80 #[must_use]
82 pub fn reasons(self) -> Reasons {
83 self.reasons
84 }
85}
86
87fn current(freshness: Freshness, origin: SourceOrigin) -> bool {
88 freshness == Freshness::Current
89 || (freshness == Freshness::AtNextUpdate && origin == SourceOrigin::Cached)
90}
91
92#[derive(Debug)]
94pub struct CompleteFacts<'b, 'r, 'a, T = Certificate<'a>> {
95 target: &'r T,
96 now: Reading,
97 source: &'b Authenticated<'r, 'a>,
98 reasons: Reasons,
99 freshness: Freshness,
100 origin: SourceOrigin,
101 entry: EntryLookup<'r, 'a>,
102}
103impl<T> Copy for CompleteFacts<'_, '_, '_, T> {}
104impl<T> Clone for CompleteFacts<'_, '_, '_, T> {
105 fn clone(&self) -> Self {
106 *self
107 }
108}
109impl<'b, 'r, 'a, T> CompleteFacts<'b, 'r, 'a, T> {
110 #[must_use]
113 pub fn target(self) -> &'r T {
114 self.target
115 }
116 #[must_use]
118 pub fn evaluated_at(self) -> Reading {
119 self.now
120 }
121 #[must_use]
123 pub fn source(self) -> &'b Authenticated<'r, 'a> {
124 self.source
125 }
126 #[must_use]
128 pub fn reasons(self) -> Reasons {
129 self.reasons
130 }
131 #[must_use]
133 pub fn freshness(self) -> Freshness {
134 self.freshness
135 }
136 #[must_use]
138 pub fn entry(self) -> EntryLookup<'r, 'a> {
139 self.entry
140 }
141 #[must_use]
144 pub fn absence(self) -> Option<AbsenceEvidence<'r, 'a, T>> {
145 if !current(self.freshness, self.origin) || !matches!(self.entry, EntryLookup::Absent) {
146 return None;
147 }
148 Some(AbsenceEvidence {
149 target: self.target,
150 now: self.now,
151 reasons: self.reasons,
152 source: EvidenceSource::Complete(*self.source),
153 })
154 }
155}
156
157#[derive(Debug)]
159pub enum CompleteEvidence<'b, 'r, 'a, T = Certificate<'a>> {
160 DeltaNeedsBase,
162 NotApplicable(ScopeFailure),
164 Facts(CompleteFacts<'b, 'r, 'a, T>),
166}
167impl<T> Copy for CompleteEvidence<'_, '_, '_, T> {}
168impl<T> Clone for CompleteEvidence<'_, '_, '_, T> {
169 fn clone(&self) -> Self {
170 *self
171 }
172}
173
174impl<'r, 'a> Authenticated<'r, 'a> {
175 pub fn evaluate<'b, T: RevocationTarget<'a>, E: Work>(
179 &'b self,
180 target: &'r T,
181 time: &Evaluation<'_, E>,
182 ) -> Result<CompleteEvidence<'b, 'r, 'a, T>> {
183 if self
184 .crl()
185 .tbs()
186 .extensions()
187 .get(oid::DELTA_CRL_INDICATOR, time.execution)?
188 .is_some()
189 {
190 return Ok(CompleteEvidence::DeltaNeedsBase);
191 }
192 let reasons = match self.scope(target, time.execution)? {
193 ScopeOutcome::Applicable(reasons) => reasons,
194 ScopeOutcome::NotApplicable(reason) => {
195 return Ok(CompleteEvidence::NotApplicable(reason));
196 }
197 };
198 let entry = self.entry(target, time.execution)?;
199 let freshness = self.freshness(time.now, time.utc, time.execution)?;
200 Ok(CompleteEvidence::Facts(CompleteFacts {
201 target,
202 now: time.now,
203 source: self,
204 reasons,
205 entry,
206 freshness,
207 origin: time.origin,
208 }))
209 }
210}
211
212#[derive(Debug)]
214pub struct DeltaFacts<'b, 'r, 'a, T = Certificate<'a>> {
215 target: &'r T,
216 now: Reading,
217 pair: BoundDelta<'b, 'r, 'a>,
218 reasons: Reasons,
219 base_freshness: Freshness,
220 delta_freshness: Freshness,
221 origin: SourceOrigin,
222 entry: CombinedEntry<'r, 'a>,
223}
224impl<'b, 'r, 'a, T> DeltaFacts<'b, 'r, 'a, T> {
225 #[must_use]
228 pub fn target(&self) -> &'r T {
229 self.target
230 }
231 #[must_use]
233 pub fn evaluated_at(&self) -> Reading {
234 self.now
235 }
236 #[must_use]
238 pub fn pair(&self) -> BoundDelta<'b, 'r, 'a> {
239 self.pair
240 }
241 #[must_use]
243 pub fn base_freshness(&self) -> Freshness {
244 self.base_freshness
245 }
246 #[must_use]
248 pub fn delta_freshness(&self) -> Freshness {
249 self.delta_freshness
250 }
251 #[must_use]
253 pub fn entry(&self) -> &CombinedEntry<'r, 'a> {
254 &self.entry
255 }
256 #[must_use]
258 pub fn absence(&self) -> Option<AbsenceEvidence<'r, 'a, T>> {
259 if !current(self.delta_freshness, self.origin)
260 || matches!(
261 self.base_freshness,
262 Freshness::NotYetCurrent | Freshness::InvalidUpdateOrder | Freshness::Uncertain(_)
263 )
264 || !matches!(
265 self.entry,
266 CombinedEntry::Absent | CombinedEntry::Removed { .. }
267 )
268 {
269 return None;
270 }
271 Some(AbsenceEvidence {
272 target: self.target,
273 now: self.now,
274 reasons: self.reasons,
275 source: EvidenceSource::Delta {
276 base: *self.pair.base(),
277 delta: *self.pair.delta(),
278 },
279 })
280 }
281}
282
283#[derive(Debug)]
285pub enum DeltaEvidence<'b, 'r, 'a, T = Certificate<'a>> {
286 NotApplicable(ScopeFailure),
288 Facts(DeltaFacts<'b, 'r, 'a, T>),
290}
291impl<'b, 'r, 'a> BoundDelta<'b, 'r, 'a> {
292 pub fn evaluate<T: RevocationTarget<'a>, E: Work>(
295 self,
296 target: &'r T,
297 time: &Evaluation<'_, E>,
298 ) -> Result<DeltaEvidence<'b, 'r, 'a, T>> {
299 let reasons = match self.base().scope(target, time.execution)? {
300 ScopeOutcome::Applicable(reasons) => reasons,
301 ScopeOutcome::NotApplicable(reason) => return Ok(DeltaEvidence::NotApplicable(reason)),
302 };
303 let entry = self.entry(target, time.execution)?;
304 let base_freshness = self.base().freshness(time.now, time.utc, time.execution)?;
305 let delta_freshness = self.delta().freshness(time.now, time.utc, time.execution)?;
306 Ok(DeltaEvidence::Facts(DeltaFacts {
307 target,
308 now: time.now,
309 pair: self,
310 reasons,
311 entry,
312 base_freshness,
313 delta_freshness,
314 origin: time.origin,
315 }))
316 }
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321pub enum CoverageAdmission {
322 Added,
324 Redundant,
326 DifferentTarget,
328 DifferentTime,
330 DifferentExecution,
333}
334
335#[derive(Debug)]
339pub struct Coverage<'r, 'a, T = Certificate<'a>> {
340 reasons: Reasons,
341 sources: Option<OwnedVec<AbsenceEvidence<'r, 'a, T>>>,
344}
345impl<'r, 'a, T: RevocationTarget<'a>> Coverage<'r, 'a, T> {
346 pub fn new<W: Work>(first: AbsenceEvidence<'r, 'a, T>, execution: &W) -> Result<Self> {
348 let mut sources = execution.vector();
349 sources
350 .try_push(first)
351 .map_err(|reason| first.target.issuer().resource(reason))?;
352 Ok(Self {
353 reasons: first.reasons,
354 sources: Some(sources.into_owned()),
355 })
356 }
357 #[must_use]
359 pub fn reasons(&self) -> Reasons {
360 self.reasons
361 }
362 #[must_use]
367 pub fn target(&self) -> &'r T {
368 self.sources()[0].target()
369 }
370 #[must_use]
375 pub fn evaluated_at(&self) -> Reading {
376 self.sources()[0].evaluated_at()
377 }
378 #[must_use]
384 pub fn sources(&self) -> &[AbsenceEvidence<'r, 'a, T>] {
385 self.sources
386 .as_deref()
387 .expect("coverage growth restores its buffer")
388 }
389 pub fn add<W: Work>(
398 &mut self,
399 evidence: AbsenceEvidence<'r, 'a, T>,
400 execution: &W,
401 ) -> Result<CoverageAdmission> {
402 if !self
403 .sources
404 .as_ref()
405 .expect("coverage owns its buffer")
406 .memory()
407 .same_account(execution.memory())
408 {
409 return Ok(CoverageAdmission::DifferentExecution);
410 }
411 let first = &self.sources()[0];
412 execution
413 .charge_work(1)
414 .map_err(|reason| evidence.target.issuer().resource(reason))?;
415 if first.now != evidence.now {
416 return Ok(CoverageAdmission::DifferentTime);
417 }
418 let left = first.target.encoded();
419 let right = evidence.target.encoded();
420 for target in [first.target, evidence.target] {
421 target
422 .execution_in(execution)
423 .charge_work(1)
424 .map_err(|reason| target.issuer().resource(reason))?;
425 }
426 if first.target.kind() != evidence.target.kind() || left.len() != right.len() {
427 return Ok(CoverageAdmission::DifferentTarget);
428 }
429 if !core::ptr::eq(first.target, evidence.target) {
430 for target in [first.target, evidence.target] {
431 target
432 .execution_in(execution)
433 .charge_work(target.encoded().len() as u64)
434 .map_err(|reason| target.issuer().resource(reason))?;
435 }
436 if left != right {
437 return Ok(CoverageAdmission::DifferentTarget);
438 }
439 }
440 let union = self.reasons.union(evidence.reasons);
441 if union == self.reasons {
442 return Ok(CoverageAdmission::Redundant);
443 }
444 let sources = self.sources.take().expect("coverage owns its buffer");
445 let Ok(mut scoped) = sources.with_execution(execution) else {
446 unreachable!("the account was checked before any coverage work")
447 };
448 let admitted = scoped.try_push(evidence);
449 self.sources = Some(scoped.into_owned());
450 admitted.map_err(|reason| evidence.target.issuer().resource(reason))?;
451 self.reasons = union;
452 Ok(CoverageAdmission::Added)
453 }
454}