use super::{
Authenticated, BoundDelta, CombinedEntry, EntryLookup, Freshness, Reasons, RevocationTarget,
ScopeFailure, ScopeOutcome,
};
use crate::temporal::{LeapTable, Reading};
use crate::{Certificate, Result, oid};
use structured_asn1::memory::{Memory, 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)]
pub struct AbsenceEvidence<'r, 'a, T = Certificate<'a>> {
target: &'r T,
now: Reading,
reasons: Reasons,
source: EvidenceSource<'r, 'a>,
}
impl<T> Copy for AbsenceEvidence<'_, '_, T> {}
impl<T> Clone for AbsenceEvidence<'_, '_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<'r, 'a, T> AbsenceEvidence<'r, 'a, T> {
#[must_use]
pub fn target(self) -> &'r T {
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)]
pub struct CompleteFacts<'b, 'r, 'a, T = Certificate<'a>> {
target: &'r T,
now: Reading,
source: &'b Authenticated<'r, 'a>,
reasons: Reasons,
freshness: Freshness,
origin: SourceOrigin,
entry: EntryLookup<'r, 'a>,
}
impl<T> Copy for CompleteFacts<'_, '_, '_, T> {}
impl<T> Clone for CompleteFacts<'_, '_, '_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<'b, 'r, 'a, T> CompleteFacts<'b, 'r, 'a, T> {
#[must_use]
pub fn target(self) -> &'r T {
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, T>> {
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)]
pub enum CompleteEvidence<'b, 'r, 'a, T = Certificate<'a>> {
DeltaNeedsBase,
NotApplicable(ScopeFailure),
Facts(CompleteFacts<'b, 'r, 'a, T>),
}
impl<T> Copy for CompleteEvidence<'_, '_, '_, T> {}
impl<T> Clone for CompleteEvidence<'_, '_, '_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<'r, 'a> Authenticated<'r, 'a> {
pub fn evaluate<'b, T: RevocationTarget<'a>, E: Work>(
&'b self,
target: &'r T,
time: &Evaluation<'_, E>,
) -> Result<CompleteEvidence<'b, 'r, 'a, T>> {
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, T = Certificate<'a>> {
target: &'r T,
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, T> DeltaFacts<'b, 'r, 'a, T> {
#[must_use]
pub fn target(&self) -> &'r T {
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, T>> {
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, T = Certificate<'a>> {
NotApplicable(ScopeFailure),
Facts(DeltaFacts<'b, 'r, 'a, T>),
}
impl<'b, 'r, 'a> BoundDelta<'b, 'r, 'a> {
pub fn evaluate<T: RevocationTarget<'a>, E: Work>(
self,
target: &'r T,
time: &Evaluation<'_, E>,
) -> Result<DeltaEvidence<'b, 'r, 'a, T>> {
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, T = Certificate<'a>> {
reasons: Reasons,
sources: Option<OwnedVec<AbsenceEvidence<'r, 'a, T>>>,
}
impl<'r, 'a, T: RevocationTarget<'a>> Coverage<'r, 'a, T> {
pub fn new<W: Work>(first: AbsenceEvidence<'r, 'a, T>, execution: &W) -> Result<Self> {
let mut sources = execution.vector();
sources
.try_push(first)
.map_err(|reason| first.target.issuer().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 T {
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, T>] {
self.sources
.as_deref()
.expect("coverage growth restores its buffer")
}
pub fn add<W: Work>(
&mut self,
evidence: AbsenceEvidence<'r, 'a, T>,
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.issuer().resource(reason))?;
if first.now != evidence.now {
return Ok(CoverageAdmission::DifferentTime);
}
let left = first.target.encoded();
let right = evidence.target.encoded();
for target in [first.target, evidence.target] {
target
.execution_in(execution)
.charge_work(1)
.map_err(|reason| target.issuer().resource(reason))?;
}
if first.target.kind() != evidence.target.kind() || left.len() != right.len() {
return Ok(CoverageAdmission::DifferentTarget);
}
if !core::ptr::eq(first.target, evidence.target) {
for target in [first.target, evidence.target] {
target
.execution_in(execution)
.charge_work(target.encoded().len() as u64)
.map_err(|reason| target.issuer().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.issuer().resource(reason))?;
self.reasons = union;
Ok(CoverageAdmission::Added)
}
}