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};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum SourceOrigin {
14 Cached,
16 Replacement,
18}
19
20pub struct Evaluation<'p> {
22 pub now: Reading,
24 pub utc: &'p LeapTable<'p>,
26 pub execution: &'p Memory,
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]
59 pub fn source(self) -> EvidenceSource<'r, 'a> {
60 self.source
61 }
62 #[must_use]
64 pub fn reasons(self) -> Reasons {
65 self.reasons
66 }
67}
68
69fn current(freshness: Freshness, origin: SourceOrigin) -> bool {
70 freshness == Freshness::Current
71 || (freshness == Freshness::AtNextUpdate && origin == SourceOrigin::Cached)
72}
73
74#[derive(Debug, Clone, Copy)]
76pub struct CompleteFacts<'b, 'r, 'a> {
77 target: &'r Certificate<'a>,
78 now: Reading,
79 source: &'b Authenticated<'r, 'a>,
80 reasons: Reasons,
81 freshness: Freshness,
82 origin: SourceOrigin,
83 entry: EntryLookup<'r, 'a>,
84}
85impl<'b, 'r, 'a> CompleteFacts<'b, 'r, 'a> {
86 #[must_use]
88 pub fn source(self) -> &'b Authenticated<'r, 'a> {
89 self.source
90 }
91 #[must_use]
93 pub fn reasons(self) -> Reasons {
94 self.reasons
95 }
96 #[must_use]
98 pub fn freshness(self) -> Freshness {
99 self.freshness
100 }
101 #[must_use]
103 pub fn entry(self) -> EntryLookup<'r, 'a> {
104 self.entry
105 }
106 #[must_use]
109 pub fn absence(self) -> Option<AbsenceEvidence<'r, 'a>> {
110 if !current(self.freshness, self.origin) || !matches!(self.entry, EntryLookup::Absent) {
111 return None;
112 }
113 Some(AbsenceEvidence {
114 target: self.target,
115 now: self.now,
116 reasons: self.reasons,
117 source: EvidenceSource::Complete(*self.source),
118 })
119 }
120}
121
122#[derive(Debug, Clone, Copy)]
124pub enum CompleteEvidence<'b, 'r, 'a> {
125 DeltaNeedsBase,
127 NotApplicable(ScopeFailure),
129 Facts(CompleteFacts<'b, 'r, 'a>),
131}
132
133impl<'r, 'a> Authenticated<'r, 'a> {
134 pub fn evaluate<'b>(
138 &'b self,
139 target: &'r Certificate<'a>,
140 time: &Evaluation<'_>,
141 ) -> Result<CompleteEvidence<'b, 'r, 'a>> {
142 if self
143 .crl()
144 .tbs()
145 .extensions()
146 .get(oid::DELTA_CRL_INDICATOR, time.execution)?
147 .is_some()
148 {
149 return Ok(CompleteEvidence::DeltaNeedsBase);
150 }
151 let reasons = match self.scope(target, time.execution)? {
152 ScopeOutcome::Applicable(reasons) => reasons,
153 ScopeOutcome::NotApplicable(reason) => {
154 return Ok(CompleteEvidence::NotApplicable(reason));
155 }
156 };
157 let entry = self.entry(target, time.execution)?;
158 let freshness = self.freshness(time.now, time.utc, time.execution)?;
159 Ok(CompleteEvidence::Facts(CompleteFacts {
160 target,
161 now: time.now,
162 source: self,
163 reasons,
164 entry,
165 freshness,
166 origin: time.origin,
167 }))
168 }
169}
170
171#[derive(Debug)]
173pub struct DeltaFacts<'b, 'r, 'a> {
174 target: &'r Certificate<'a>,
175 now: Reading,
176 pair: BoundDelta<'b, 'r, 'a>,
177 reasons: Reasons,
178 base_freshness: Freshness,
179 delta_freshness: Freshness,
180 origin: SourceOrigin,
181 entry: CombinedEntry<'r, 'a>,
182}
183impl<'b, 'r, 'a> DeltaFacts<'b, 'r, 'a> {
184 #[must_use]
186 pub fn pair(&self) -> BoundDelta<'b, 'r, 'a> {
187 self.pair
188 }
189 #[must_use]
191 pub fn base_freshness(&self) -> Freshness {
192 self.base_freshness
193 }
194 #[must_use]
196 pub fn delta_freshness(&self) -> Freshness {
197 self.delta_freshness
198 }
199 #[must_use]
201 pub fn entry(&self) -> &CombinedEntry<'r, 'a> {
202 &self.entry
203 }
204 #[must_use]
206 pub fn absence(&self) -> Option<AbsenceEvidence<'r, 'a>> {
207 if !current(self.delta_freshness, self.origin)
208 || matches!(
209 self.base_freshness,
210 Freshness::NotYetCurrent | Freshness::InvalidUpdateOrder | Freshness::Uncertain(_)
211 )
212 || !matches!(
213 self.entry,
214 CombinedEntry::Absent | CombinedEntry::Removed { .. }
215 )
216 {
217 return None;
218 }
219 Some(AbsenceEvidence {
220 target: self.target,
221 now: self.now,
222 reasons: self.reasons,
223 source: EvidenceSource::Delta {
224 base: *self.pair.base(),
225 delta: *self.pair.delta(),
226 },
227 })
228 }
229}
230
231#[derive(Debug)]
233pub enum DeltaEvidence<'b, 'r, 'a> {
234 NotApplicable(ScopeFailure),
236 Facts(DeltaFacts<'b, 'r, 'a>),
238}
239impl<'b, 'r, 'a> BoundDelta<'b, 'r, 'a> {
240 pub fn evaluate(
243 self,
244 target: &'r Certificate<'a>,
245 time: &Evaluation<'_>,
246 ) -> Result<DeltaEvidence<'b, 'r, 'a>> {
247 let reasons = match self.base().scope(target, time.execution)? {
248 ScopeOutcome::Applicable(reasons) => reasons,
249 ScopeOutcome::NotApplicable(reason) => return Ok(DeltaEvidence::NotApplicable(reason)),
250 };
251 let entry = self.entry(target, time.execution)?;
252 let base_freshness = self.base().freshness(time.now, time.utc, time.execution)?;
253 let delta_freshness = self.delta().freshness(time.now, time.utc, time.execution)?;
254 Ok(DeltaEvidence::Facts(DeltaFacts {
255 target,
256 now: time.now,
257 pair: self,
258 reasons,
259 entry,
260 base_freshness,
261 delta_freshness,
262 origin: time.origin,
263 }))
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum CoverageAdmission {
270 Added,
272 Redundant,
274 DifferentTarget,
276 DifferentTime,
278 DifferentExecution,
281}
282
283#[derive(Debug)]
287pub struct Coverage<'r, 'a> {
288 reasons: Reasons,
289 sources: OwnedVec<AbsenceEvidence<'r, 'a>>,
290}
291impl<'r, 'a> Coverage<'r, 'a> {
292 pub fn new(first: AbsenceEvidence<'r, 'a>, execution: &Memory) -> Result<Self> {
294 let mut sources = execution.vector();
295 sources
296 .try_push(first)
297 .map_err(|reason| first.target.tbs().subject().resource(reason))?;
298 Ok(Self {
299 reasons: first.reasons,
300 sources,
301 })
302 }
303 #[must_use]
305 pub fn reasons(&self) -> Reasons {
306 self.reasons
307 }
308 #[must_use]
310 pub fn sources(&self) -> &[AbsenceEvidence<'r, 'a>] {
311 &self.sources
312 }
313 pub fn add(
318 &mut self,
319 evidence: AbsenceEvidence<'r, 'a>,
320 execution: &Memory,
321 ) -> Result<CoverageAdmission> {
322 if !self.sources.memory().same_account(execution) {
323 return Ok(CoverageAdmission::DifferentExecution);
324 }
325 let first = &self.sources[0];
326 execution
327 .charge_work(1)
328 .map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
329 if first.now != evidence.now {
330 return Ok(CoverageAdmission::DifferentTime);
331 }
332 let left = first.target.as_bytes();
333 let right = evidence.target.as_bytes();
334 let work = u64::try_from(left.len().max(right.len()))
335 .ok()
336 .and_then(|n| n.checked_mul(2))
337 .and_then(|n| n.checked_add(1))
338 .ok_or_else(|| {
339 Error::resource(
340 MemoryError::WorkExceeded,
341 "certificateList.coverage",
342 evidence.target.span().start,
343 )
344 })?;
345 execution
346 .charge_work(work)
347 .map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
348 if left != right {
349 return Ok(CoverageAdmission::DifferentTarget);
350 }
351 let union = self.reasons.union(evidence.reasons);
352 if union == self.reasons {
353 return Ok(CoverageAdmission::Redundant);
354 }
355 self.sources
356 .try_push(evidence)
357 .map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
358 self.reasons = union;
359 Ok(CoverageAdmission::Added)
360 }
361}