use super::{
Authenticated, BoundDelta, CombinedEntry, EntryLookup, Freshness, Reasons, ScopeFailure,
ScopeOutcome,
};
use crate::temporal::{LeapTable, Reading};
use crate::{Certificate, Error, Result, oid};
use structured_asn1::memory::{Memory, MemoryError, OwnedVec, Work};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceOrigin {
Cached,
Replacement,
}
pub struct Evaluation<'p, E: Work = Memory> {
pub now: Reading,
pub utc: &'p LeapTable<'p>,
pub execution: &'p E,
pub origin: SourceOrigin,
}
#[derive(Debug, Clone, Copy)]
pub enum EvidenceSource<'r, 'a> {
Complete(Authenticated<'r, 'a>),
Delta {
base: Authenticated<'r, 'a>,
delta: Authenticated<'r, 'a>,
},
}
#[derive(Debug, Clone, Copy)]
pub struct AbsenceEvidence<'r, 'a> {
target: &'r Certificate<'a>,
now: Reading,
reasons: Reasons,
source: EvidenceSource<'r, 'a>,
}
impl<'r, 'a> AbsenceEvidence<'r, 'a> {
#[must_use]
pub fn target(self) -> &'r Certificate<'a> {
self.target
}
#[must_use]
pub fn evaluated_at(self) -> Reading {
self.now
}
#[must_use]
pub fn source(self) -> EvidenceSource<'r, 'a> {
self.source
}
#[must_use]
pub fn reasons(self) -> Reasons {
self.reasons
}
}
fn current(freshness: Freshness, origin: SourceOrigin) -> bool {
freshness == Freshness::Current
|| (freshness == Freshness::AtNextUpdate && origin == SourceOrigin::Cached)
}
#[derive(Debug, Clone, Copy)]
pub struct CompleteFacts<'b, 'r, 'a> {
target: &'r Certificate<'a>,
now: Reading,
source: &'b Authenticated<'r, 'a>,
reasons: Reasons,
freshness: Freshness,
origin: SourceOrigin,
entry: EntryLookup<'r, 'a>,
}
impl<'b, 'r, 'a> CompleteFacts<'b, 'r, 'a> {
#[must_use]
pub fn target(self) -> &'r Certificate<'a> {
self.target
}
#[must_use]
pub fn evaluated_at(self) -> Reading {
self.now
}
#[must_use]
pub fn source(self) -> &'b Authenticated<'r, 'a> {
self.source
}
#[must_use]
pub fn reasons(self) -> Reasons {
self.reasons
}
#[must_use]
pub fn freshness(self) -> Freshness {
self.freshness
}
#[must_use]
pub fn entry(self) -> EntryLookup<'r, 'a> {
self.entry
}
#[must_use]
pub fn absence(self) -> Option<AbsenceEvidence<'r, 'a>> {
if !current(self.freshness, self.origin) || !matches!(self.entry, EntryLookup::Absent) {
return None;
}
Some(AbsenceEvidence {
target: self.target,
now: self.now,
reasons: self.reasons,
source: EvidenceSource::Complete(*self.source),
})
}
}
#[derive(Debug, Clone, Copy)]
pub enum CompleteEvidence<'b, 'r, 'a> {
DeltaNeedsBase,
NotApplicable(ScopeFailure),
Facts(CompleteFacts<'b, 'r, 'a>),
}
impl<'r, 'a> Authenticated<'r, 'a> {
pub fn evaluate<'b, E: Work>(
&'b self,
target: &'r Certificate<'a>,
time: &Evaluation<'_, E>,
) -> Result<CompleteEvidence<'b, 'r, 'a>> {
if self
.crl()
.tbs()
.extensions()
.get(oid::DELTA_CRL_INDICATOR, time.execution)?
.is_some()
{
return Ok(CompleteEvidence::DeltaNeedsBase);
}
let reasons = match self.scope(target, time.execution)? {
ScopeOutcome::Applicable(reasons) => reasons,
ScopeOutcome::NotApplicable(reason) => {
return Ok(CompleteEvidence::NotApplicable(reason));
}
};
let entry = self.entry(target, time.execution)?;
let freshness = self.freshness(time.now, time.utc, time.execution)?;
Ok(CompleteEvidence::Facts(CompleteFacts {
target,
now: time.now,
source: self,
reasons,
entry,
freshness,
origin: time.origin,
}))
}
}
#[derive(Debug)]
pub struct DeltaFacts<'b, 'r, 'a> {
target: &'r Certificate<'a>,
now: Reading,
pair: BoundDelta<'b, 'r, 'a>,
reasons: Reasons,
base_freshness: Freshness,
delta_freshness: Freshness,
origin: SourceOrigin,
entry: CombinedEntry<'r, 'a>,
}
impl<'b, 'r, 'a> DeltaFacts<'b, 'r, 'a> {
#[must_use]
pub fn target(&self) -> &'r Certificate<'a> {
self.target
}
#[must_use]
pub fn evaluated_at(&self) -> Reading {
self.now
}
#[must_use]
pub fn pair(&self) -> BoundDelta<'b, 'r, 'a> {
self.pair
}
#[must_use]
pub fn base_freshness(&self) -> Freshness {
self.base_freshness
}
#[must_use]
pub fn delta_freshness(&self) -> Freshness {
self.delta_freshness
}
#[must_use]
pub fn entry(&self) -> &CombinedEntry<'r, 'a> {
&self.entry
}
#[must_use]
pub fn absence(&self) -> Option<AbsenceEvidence<'r, 'a>> {
if !current(self.delta_freshness, self.origin)
|| matches!(
self.base_freshness,
Freshness::NotYetCurrent | Freshness::InvalidUpdateOrder | Freshness::Uncertain(_)
)
|| !matches!(
self.entry,
CombinedEntry::Absent | CombinedEntry::Removed { .. }
)
{
return None;
}
Some(AbsenceEvidence {
target: self.target,
now: self.now,
reasons: self.reasons,
source: EvidenceSource::Delta {
base: *self.pair.base(),
delta: *self.pair.delta(),
},
})
}
}
#[derive(Debug)]
pub enum DeltaEvidence<'b, 'r, 'a> {
NotApplicable(ScopeFailure),
Facts(DeltaFacts<'b, 'r, 'a>),
}
impl<'b, 'r, 'a> BoundDelta<'b, 'r, 'a> {
pub fn evaluate<E: Work>(
self,
target: &'r Certificate<'a>,
time: &Evaluation<'_, E>,
) -> Result<DeltaEvidence<'b, 'r, 'a>> {
let reasons = match self.base().scope(target, time.execution)? {
ScopeOutcome::Applicable(reasons) => reasons,
ScopeOutcome::NotApplicable(reason) => return Ok(DeltaEvidence::NotApplicable(reason)),
};
let entry = self.entry(target, time.execution)?;
let base_freshness = self.base().freshness(time.now, time.utc, time.execution)?;
let delta_freshness = self.delta().freshness(time.now, time.utc, time.execution)?;
Ok(DeltaEvidence::Facts(DeltaFacts {
target,
now: time.now,
pair: self,
reasons,
entry,
base_freshness,
delta_freshness,
origin: time.origin,
}))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoverageAdmission {
Added,
Redundant,
DifferentTarget,
DifferentTime,
DifferentExecution,
}
#[derive(Debug)]
pub struct Coverage<'r, 'a> {
reasons: Reasons,
sources: Option<OwnedVec<AbsenceEvidence<'r, 'a>>>,
}
impl<'r, 'a> Coverage<'r, 'a> {
pub fn new<W: Work>(first: AbsenceEvidence<'r, 'a>, execution: &W) -> Result<Self> {
let mut sources = execution.vector();
sources
.try_push(first)
.map_err(|reason| first.target.tbs().subject().resource(reason))?;
Ok(Self {
reasons: first.reasons,
sources: Some(sources.into_owned()),
})
}
#[must_use]
pub fn reasons(&self) -> Reasons {
self.reasons
}
#[must_use]
pub fn target(&self) -> &'r Certificate<'a> {
self.sources()[0].target()
}
#[must_use]
pub fn evaluated_at(&self) -> Reading {
self.sources()[0].evaluated_at()
}
#[must_use]
pub fn sources(&self) -> &[AbsenceEvidence<'r, 'a>] {
self.sources
.as_deref()
.expect("coverage growth restores its buffer")
}
pub fn add<W: Work>(
&mut self,
evidence: AbsenceEvidence<'r, 'a>,
execution: &W,
) -> Result<CoverageAdmission> {
if !self
.sources
.as_ref()
.expect("coverage owns its buffer")
.memory()
.same_account(execution.memory())
{
return Ok(CoverageAdmission::DifferentExecution);
}
let first = &self.sources()[0];
execution
.charge_work(1)
.map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
if first.now != evidence.now {
return Ok(CoverageAdmission::DifferentTime);
}
let left = first.target.as_bytes();
let right = evidence.target.as_bytes();
let work = u64::try_from(left.len().max(right.len()))
.ok()
.and_then(|n| n.checked_mul(2))
.and_then(|n| n.checked_add(1))
.ok_or_else(|| {
Error::resource(
MemoryError::WorkExceeded,
"certificateList.coverage",
evidence.target.span().start,
)
})?;
execution
.charge_work(work)
.map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
if left != right {
return Ok(CoverageAdmission::DifferentTarget);
}
let union = self.reasons.union(evidence.reasons);
if union == self.reasons {
return Ok(CoverageAdmission::Redundant);
}
let sources = self.sources.take().expect("coverage owns its buffer");
let Ok(mut scoped) = sources.with_execution(execution) else {
unreachable!("the account was checked before any coverage work")
};
let admitted = scoped.try_push(evidence);
self.sources = Some(scoped.into_owned());
admitted.map_err(|reason| evidence.target.tbs().subject().resource(reason))?;
self.reasons = union;
Ok(CoverageAdmission::Added)
}
}