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)]
34pub enum EvidenceSource<'r, 'a> {
35 Complete(Authenticated<'r, 'a>),
37 Delta {
39 base: Authenticated<'r, 'a>,
41 delta: Authenticated<'r, 'a>,
43 },
44}
45
46#[derive(Debug, Clone, Copy)]
50pub struct AbsenceEvidence<'r, 'a> {
51 target: &'r Certificate<'a>,
52 now: Reading,
53 reasons: Reasons,
54 source: EvidenceSource<'r, 'a>,
55}
56impl<'r, 'a> AbsenceEvidence<'r, 'a> {
57 #[must_use]
60 pub fn target(self) -> &'r Certificate<'a> {
61 self.target
62 }
63 #[must_use]
65 pub fn evaluated_at(self) -> Reading {
66 self.now
67 }
68 #[must_use]
70 pub fn source(self) -> EvidenceSource<'r, 'a> {
71 self.source
72 }
73 #[must_use]
75 pub fn reasons(self) -> Reasons {
76 self.reasons
77 }
78}
79
80fn current(freshness: Freshness, origin: SourceOrigin) -> bool {
81 freshness == Freshness::Current
82 || (freshness == Freshness::AtNextUpdate && origin == SourceOrigin::Cached)
83}
84
85#[derive(Debug, Clone, Copy)]
87pub struct CompleteFacts<'b, 'r, 'a> {
88 target: &'r Certificate<'a>,
89 now: Reading,
90 source: &'b Authenticated<'r, 'a>,
91 reasons: Reasons,
92 freshness: Freshness,
93 origin: SourceOrigin,
94 entry: EntryLookup<'r, 'a>,
95}
96impl<'b, 'r, 'a> CompleteFacts<'b, 'r, 'a> {
97 #[must_use]
99 pub fn source(self) -> &'b Authenticated<'r, 'a> {
100 self.source
101 }
102 #[must_use]
104 pub fn reasons(self) -> Reasons {
105 self.reasons
106 }
107 #[must_use]
109 pub fn freshness(self) -> Freshness {
110 self.freshness
111 }
112 #[must_use]
114 pub fn entry(self) -> EntryLookup<'r, 'a> {
115 self.entry
116 }
117 #[must_use]
120 pub fn absence(self) -> Option<AbsenceEvidence<'r, 'a>> {
121 if !current(self.freshness, self.origin) || !matches!(self.entry, EntryLookup::Absent) {
122 return None;
123 }
124 Some(AbsenceEvidence {
125 target: self.target,
126 now: self.now,
127 reasons: self.reasons,
128 source: EvidenceSource::Complete(*self.source),
129 })
130 }
131}
132
133#[derive(Debug, Clone, Copy)]
135pub enum CompleteEvidence<'b, 'r, 'a> {
136 DeltaNeedsBase,
138 NotApplicable(ScopeFailure),
140 Facts(CompleteFacts<'b, 'r, 'a>),
142}
143
144impl<'r, 'a> Authenticated<'r, 'a> {
145 pub fn evaluate<'b, E: Work>(
149 &'b self,
150 target: &'r Certificate<'a>,
151 time: &Evaluation<'_, E>,
152 ) -> Result<CompleteEvidence<'b, 'r, 'a>> {
153 if self
154 .crl()
155 .tbs()
156 .extensions()
157 .get(oid::DELTA_CRL_INDICATOR, time.execution)?
158 .is_some()
159 {
160 return Ok(CompleteEvidence::DeltaNeedsBase);
161 }
162 let reasons = match self.scope(target, time.execution)? {
163 ScopeOutcome::Applicable(reasons) => reasons,
164 ScopeOutcome::NotApplicable(reason) => {
165 return Ok(CompleteEvidence::NotApplicable(reason));
166 }
167 };
168 let entry = self.entry(target, time.execution)?;
169 let freshness = self.freshness(time.now, time.utc, time.execution)?;
170 Ok(CompleteEvidence::Facts(CompleteFacts {
171 target,
172 now: time.now,
173 source: self,
174 reasons,
175 entry,
176 freshness,
177 origin: time.origin,
178 }))
179 }
180}
181
182#[derive(Debug)]
184pub struct DeltaFacts<'b, 'r, 'a> {
185 target: &'r Certificate<'a>,
186 now: Reading,
187 pair: BoundDelta<'b, 'r, 'a>,
188 reasons: Reasons,
189 base_freshness: Freshness,
190 delta_freshness: Freshness,
191 origin: SourceOrigin,
192 entry: CombinedEntry<'r, 'a>,
193}
194impl<'b, 'r, 'a> DeltaFacts<'b, 'r, 'a> {
195 #[must_use]
197 pub fn pair(&self) -> BoundDelta<'b, 'r, 'a> {
198 self.pair
199 }
200 #[must_use]
202 pub fn base_freshness(&self) -> Freshness {
203 self.base_freshness
204 }
205 #[must_use]
207 pub fn delta_freshness(&self) -> Freshness {
208 self.delta_freshness
209 }
210 #[must_use]
212 pub fn entry(&self) -> &CombinedEntry<'r, 'a> {
213 &self.entry
214 }
215 #[must_use]
217 pub fn absence(&self) -> Option<AbsenceEvidence<'r, 'a>> {
218 if !current(self.delta_freshness, self.origin)
219 || matches!(
220 self.base_freshness,
221 Freshness::NotYetCurrent | Freshness::InvalidUpdateOrder | Freshness::Uncertain(_)
222 )
223 || !matches!(
224 self.entry,
225 CombinedEntry::Absent | CombinedEntry::Removed { .. }
226 )
227 {
228 return None;
229 }
230 Some(AbsenceEvidence {
231 target: self.target,
232 now: self.now,
233 reasons: self.reasons,
234 source: EvidenceSource::Delta {
235 base: *self.pair.base(),
236 delta: *self.pair.delta(),
237 },
238 })
239 }
240}
241
242#[derive(Debug)]
244pub enum DeltaEvidence<'b, 'r, 'a> {
245 NotApplicable(ScopeFailure),
247 Facts(DeltaFacts<'b, 'r, 'a>),
249}
250impl<'b, 'r, 'a> BoundDelta<'b, 'r, 'a> {
251 pub fn evaluate<E: Work>(
254 self,
255 target: &'r Certificate<'a>,
256 time: &Evaluation<'_, E>,
257 ) -> Result<DeltaEvidence<'b, 'r, 'a>> {
258 let reasons = match self.base().scope(target, time.execution)? {
259 ScopeOutcome::Applicable(reasons) => reasons,
260 ScopeOutcome::NotApplicable(reason) => return Ok(DeltaEvidence::NotApplicable(reason)),
261 };
262 let entry = self.entry(target, time.execution)?;
263 let base_freshness = self.base().freshness(time.now, time.utc, time.execution)?;
264 let delta_freshness = self.delta().freshness(time.now, time.utc, time.execution)?;
265 Ok(DeltaEvidence::Facts(DeltaFacts {
266 target,
267 now: time.now,
268 pair: self,
269 reasons,
270 entry,
271 base_freshness,
272 delta_freshness,
273 origin: time.origin,
274 }))
275 }
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280pub enum CoverageAdmission {
281 Added,
283 Redundant,
285 DifferentTarget,
287 DifferentTime,
289 DifferentExecution,
292}
293
294#[derive(Debug)]
298pub struct Coverage<'r, 'a> {
299 reasons: Reasons,
300 sources: Option<OwnedVec<AbsenceEvidence<'r, 'a>>>,
303}
304impl<'r, 'a> Coverage<'r, 'a> {
305 pub fn new<W: Work>(first: AbsenceEvidence<'r, 'a>, execution: &W) -> Result<Self> {
307 let mut sources = execution.vector();
308 sources
309 .try_push(first)
310 .map_err(|reason| first.target.tbs().subject().resource(reason))?;
311 Ok(Self {
312 reasons: first.reasons,
313 sources: Some(sources.into_owned()),
314 })
315 }
316 #[must_use]
318 pub fn reasons(&self) -> Reasons {
319 self.reasons
320 }
321 #[must_use]
326 pub fn target(&self) -> &'r Certificate<'a> {
327 self.sources()[0].target()
328 }
329 #[must_use]
334 pub fn evaluated_at(&self) -> Reading {
335 self.sources()[0].evaluated_at()
336 }
337 #[must_use]
343 pub fn sources(&self) -> &[AbsenceEvidence<'r, 'a>] {
344 self.sources
345 .as_deref()
346 .expect("coverage growth restores its buffer")
347 }
348 pub fn add<W: Work>(
357 &mut self,
358 evidence: AbsenceEvidence<'r, 'a>,
359 execution: &W,
360 ) -> Result<CoverageAdmission> {
361 if !self
362 .sources
363 .as_ref()
364 .expect("coverage owns its buffer")
365 .memory()
366 .same_account(execution.memory())
367 {
368 return Ok(CoverageAdmission::DifferentExecution);
369 }
370 let first = &self.sources()[0];
371 execution
372 .charge_work(1)
373 .map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
374 if first.now != evidence.now {
375 return Ok(CoverageAdmission::DifferentTime);
376 }
377 let left = first.target.as_bytes();
378 let right = evidence.target.as_bytes();
379 let work = u64::try_from(left.len().max(right.len()))
380 .ok()
381 .and_then(|n| n.checked_mul(2))
382 .and_then(|n| n.checked_add(1))
383 .ok_or_else(|| {
384 Error::resource(
385 MemoryError::WorkExceeded,
386 "certificateList.coverage",
387 evidence.target.span().start,
388 )
389 })?;
390 execution
391 .charge_work(work)
392 .map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
393 if left != right {
394 return Ok(CoverageAdmission::DifferentTarget);
395 }
396 let union = self.reasons.union(evidence.reasons);
397 if union == self.reasons {
398 return Ok(CoverageAdmission::Redundant);
399 }
400 let sources = self.sources.take().expect("coverage owns its buffer");
401 let Ok(mut scoped) = sources.with_execution(execution) else {
402 unreachable!("the account was checked before any coverage work")
403 };
404 let admitted = scoped.try_push(evidence);
405 self.sources = Some(scoped.into_owned());
406 admitted.map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
407 self.reasons = union;
408 Ok(CoverageAdmission::Added)
409 }
410}