use super::Purpose;
use crate::pki::{CounterReceipts, TimestampPath};
use crate::source::{Location, Query, ScopeError};
use core::marker::PhantomData;
use structured_asn1::memory::OwnedVec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimestampScope<N, D, G> {
All(Location<N, D, G>),
BeforeArchive(Location<N, D, G>),
}
impl<N: Copy, D: Copy, G: Copy> TimestampScope<N, D, G> {
#[must_use]
pub fn location(self) -> Location<N, D, G> {
match self {
Self::All(location) | Self::BeforeArchive(location) => location,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct RequiredToken<N> {
pub signature: N,
pub property: N,
pub token: N,
pub purpose: Purpose,
}
pub struct TimestampRequirements<N, D, G, I> {
signature: N,
scope: TimestampScope<N, D, G>,
entries: OwnedVec<RequiredToken<N>>,
counters: Option<Counters<N>>,
issuer: PhantomData<I>,
}
struct Counters<N> {
signatures: OwnedVec<N>,
covered: Option<OwnedVec<bool>>,
}
impl<N: Copy, D: Copy, G: Copy, I> TimestampRequirements<N, D, G, I> {
#[must_use]
pub fn new(
_issuer: I,
signature: N,
scope: TimestampScope<N, D, G>,
entries: OwnedVec<RequiredToken<N>>,
) -> Self {
Self {
signature,
scope,
entries,
counters: None,
issuer: PhantomData,
}
}
#[must_use]
pub fn with_counters(
_issuer: I,
signature: N,
scope: TimestampScope<N, D, G>,
entries: OwnedVec<RequiredToken<N>>,
signatures: OwnedVec<N>,
covered: Option<OwnedVec<bool>>,
) -> Self {
Self {
signature,
scope,
entries,
counters: Some(Counters {
signatures,
covered,
}),
issuer: PhantomData,
}
}
#[must_use]
pub fn signature(&self) -> N {
self.signature
}
#[must_use]
pub fn scope(&self) -> TimestampScope<N, D, G> {
self.scope
}
#[must_use]
pub fn entries(&self) -> &[RequiredToken<N>] {
&self.entries
}
}
pub struct TimestampPaths<'view, 'certificate, 'document, N, D, G, I> {
required: &'view TimestampRequirements<N, D, G, I>,
paths: &'view [&'view TimestampPath<'view, 'certificate, 'document, N, D, G, I>],
}
impl<'view, 'certificate, 'document, N, D, G, I>
TimestampPaths<'view, 'certificate, 'document, N, D, G, I>
{
#[must_use]
pub fn required(&self) -> &'view TimestampRequirements<N, D, G, I> {
self.required
}
#[must_use]
pub fn paths(
&self,
) -> &'view [&'view TimestampPath<'view, 'certificate, 'document, N, D, G, I>] {
self.paths
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimestampPathFailure {
Count,
Source {
index: usize,
},
}
pub enum TimestampPathBinding<'view, 'certificate, 'document, N, D, G, I> {
Bound(TimestampPaths<'view, 'certificate, 'document, N, D, G, I>),
Unproven(TimestampPathFailure),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ObligationError<N> {
Scope(ScopeError<N>),
Foreign {
node: N,
},
}
impl<N> From<ScopeError<N>> for ObligationError<N> {
fn from(cause: ScopeError<N>) -> Self {
Self::Scope(cause)
}
}
pub type PathBindingResult<'view, 'certificate, 'document, N, D, G, I> =
Result<TimestampPathBinding<'view, 'certificate, 'document, N, D, G, I>, ObligationError<N>>;
impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, I> TimestampRequirements<N, D, G, I> {
pub fn counter_in_scope<Q: Query<Id = N, Document = D, Generation = G>>(
&self,
ordinal: usize,
query: &Q,
) -> Result<bool, ObligationError<N>> {
let node = self.scope.location().node;
if self.scope.location().snapshot != query.snapshot() {
return Err(ObligationError::Foreign { node });
}
query.admit(node, 2)?;
match self.scope {
TimestampScope::All(_) => Ok(true),
TimestampScope::BeforeArchive(_) => {
let Some(covered) = self.counters.as_ref().and_then(|c| c.covered.as_ref()) else {
return Err(ObligationError::Foreign { node });
};
covered
.get(ordinal)
.copied()
.ok_or(ObligationError::Foreign { node })
}
}
}
pub fn includes_counters<
'p,
'd,
C: CounterReceipts<'p, 'd, Id = N>,
Q: Query<Id = N, Document = D, Generation = G>,
>(
&self,
counters: &C,
query: &Q,
) -> Result<bool, ScopeError<N>>
where
'd: 'p,
{
let Some(expected) = &self.counters else {
return Ok(counters.len() == 0);
};
if expected.signatures.len() != counters.len() {
return Ok(false);
}
for (index, expected) in expected.signatures.iter().enumerate() {
let node = counters.counter(index).signature;
if !query
.equal(*expected, node)
.map_err(|cause| ScopeError::Resource { node, cause })?
{
return Ok(false);
}
}
Ok(true)
}
pub fn bind_paths<'view, 'certificate, 'document, Q>(
&'view self,
paths: &'view [&'view TimestampPath<'view, 'certificate, 'document, N, D, G, I>],
query: &Q,
) -> PathBindingResult<'view, 'certificate, 'document, N, D, G, I>
where
Q: Query<Id = N, Document = D, Generation = G>,
{
let location = self.scope.location();
if location.snapshot != query.snapshot() {
return Err(ObligationError::Foreign {
node: location.node,
});
}
query.admit(self.signature, 2)?;
if paths.len() != self.entries.len() {
return Ok(TimestampPathBinding::Unproven(TimestampPathFailure::Count));
}
let resource = |cause| ScopeError::Resource {
node: location.node,
cause,
};
for (index, path) in paths.iter().enumerate() {
query.admit(self.signature, 2)?;
let expected = &self.entries[index];
let actual = path.actor().verified();
if !query
.equal(actual.signature(), expected.signature)
.map_err(resource)?
|| !query
.equal(actual.property(), expected.property)
.map_err(resource)?
|| actual.token().snapshot != location.snapshot
|| !query
.equal(actual.token().node, expected.token)
.map_err(resource)?
|| actual.purpose() != expected.purpose
{
return Ok(TimestampPathBinding::Unproven(
TimestampPathFailure::Source { index },
));
}
}
Ok(TimestampPathBinding::Bound(TimestampPaths {
required: self,
paths,
}))
}
}