use super::index::Key;
use super::status::{ArtifactVisitError, member_source, visit_artifacts};
use super::{ArtifactRef, Index, IndexError, Placement};
use crate::pki::{
AuthorityVisitError, Participant, ParticipantAuthority, ParticipantClosure, UsesError,
};
use crate::timestamp::TimestampScope;
use crate::xml::{Access, Snapshot, Xml};
use structured_asn1::memory::{HashWork, MemoryError, OwnedVec, Work};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimestampPlacementFailure {
Missing,
OutsideTimestampContainers,
}
pub struct TimestampPlacementFinding<'certificate, 'document, N> {
pub property: N,
pub token: N,
pub artifact: ArtifactRef<'certificate, 'document>,
pub cause: TimestampPlacementFailure,
}
pub struct TimestampPlacement<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G> {
inputs: &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G>,
findings: OwnedVec<TimestampPlacementFinding<'certificate, 'document, N>>,
snapshot: Snapshot<D, G>,
}
impl<'closure, 'requirements, 'view, 'certificate, 'document, N, D: Copy, G: Copy>
TimestampPlacement<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G>
{
#[must_use]
pub fn inputs(
&self,
) -> &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G> {
self.inputs
}
#[must_use]
pub fn findings(&self) -> &[TimestampPlacementFinding<'certificate, 'document, N>] {
&self.findings
}
#[must_use]
pub fn snapshot(&self) -> Snapshot<D, G> {
self.snapshot
}
}
#[derive(Debug)]
pub enum TimestampPlacementError<E, N> {
Scope,
Index(IndexError<E, N>),
Uses(UsesError<E, N>),
}
pub type TimestampPlacementResult<'closure, 'requirements, 'view, 'certificate, 'document, X> =
Result<
TimestampPlacement<
'closure,
'requirements,
'view,
'certificate,
'document,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
>,
TimestampPlacementError<<X as Xml>::Error, <X as Xml>::Id>,
>;
#[derive(Hash, PartialEq, Eq)]
struct Seen<'document> {
root: usize,
artifact: Key<'document>,
}
impl HashWork for Seen<'_> {
fn hash_work(&self) -> Result<u64, MemoryError> {
self.artifact
.hash_work()?
.checked_add(1)
.ok_or(MemoryError::WorkExceeded)
}
}
fn allowed<N: Copy, E>(
placement: Placement<N>,
property: N,
token: N,
equal: &mut impl FnMut(N, N) -> Result<bool, E>,
) -> Result<bool, E> {
match placement {
Placement::General(_) | Placement::Timestamp(_) => Ok(true),
Placement::TimestampToken {
property: actual_property,
token: actual_token,
..
} => Ok(equal(actual_property, property)? && equal(actual_token, token)?),
_ => Ok(false),
}
}
fn availability<X: Xml, W: Work, Q: Work>(
index: &Index<'_, X::Id, X::Document, X::Generation, W>,
artifact: ArtifactRef<'_, '_>,
signature: X::Id,
property: X::Id,
token: X::Id,
access: &Access<'_, X, Q>,
) -> Result<Option<TimestampPlacementFailure>, IndexError<X::Error, X::Id>> {
let mut present = false;
for occurrence in index.artifact(artifact, access)? {
let occurrence = occurrence?;
present = true;
let eligible = allowed(
occurrence.placement(),
property,
token,
&mut |left, right| {
access
.equal_id(left, right)
.map_err(|cause| IndexError::Resource { signature, cause })
},
)?;
if eligible {
return Ok(None);
}
}
Ok(Some(if present {
TimestampPlacementFailure::OutsideTimestampContainers
} else {
TimestampPlacementFailure::Missing
}))
}
pub fn timestamp_placement<
'closure,
'requirements,
'view,
'certificate,
'document,
X: Xml,
W: Work,
Q: Work,
>(
inputs: &'closure ParticipantClosure<
'requirements,
'view,
'certificate,
'document,
X::Id,
X::Document,
X::Generation,
>,
index: &Index<'_, X::Id, X::Document, X::Generation, W>,
access: &Access<'_, X, Q>,
) -> TimestampPlacementResult<'closure, 'requirements, 'view, 'certificate, 'document, X> {
let required = inputs.requirements();
if !matches!(required.scope(), TimestampScope::All(_)) {
return Err(TimestampPlacementError::Scope);
}
let signature = required.signature();
if required.scope().location().snapshot != access.snapshot() {
return Err(TimestampPlacementError::Index(IndexError::Foreign {
signature,
}));
}
index
.require_signature(signature, access)
.map_err(TimestampPlacementError::Index)?;
let work = access.execution();
let resource =
|cause| TimestampPlacementError::Index(IndexError::Resource { signature, cause });
let mut actors = work.vector();
for entry in required.entries() {
work.charge_work(1).map_err(resource)?;
if matches!(entry.authority(), ParticipantAuthority::Path(_)) {
actors
.try_push(match entry.participant() {
Participant::Timestamp { property, token } => Some((property, token)),
_ => None,
})
.map_err(resource)?;
}
}
let mut seen = work.map();
let mut findings = work.vector();
inputs
.authorities()
.visit_roots(work, |root, node| {
let Some((property, token)) = actors[root] else {
return Ok(());
};
let mut record = |artifact: ArtifactRef<'certificate, 'document>| {
let key = Seen {
root,
artifact: artifact.identity(),
};
if seen.try_insert(key, ()).map_err(resource)?.is_some() {
return Ok(());
}
if let Some(cause) =
availability(index, artifact, signature, property, token, access)
.map_err(TimestampPlacementError::Index)?
{
findings
.try_push(TimestampPlacementFinding {
property,
token,
artifact,
cause,
})
.map_err(resource)?;
}
Ok(())
};
let path = node.statuses.path();
if let Some(certificate) = path.anchor().certificate() {
record(ArtifactRef::Certificate(certificate))?;
}
for member in path.members() {
work.charge_work(1).map_err(resource)?;
record(ArtifactRef::Certificate(member.certificate()))?;
}
for input in node.statuses.entries() {
work.charge_work(1).map_err(resource)?;
if let Some(source) = member_source(input) {
visit_artifacts(source, signature, access, |artifact, _, _| record(artifact))
.map_err(|cause| match cause {
ArtifactVisitError::Access(cause) => {
TimestampPlacementError::Uses(UsesError::Access(cause))
}
ArtifactVisitError::Resource(cause) => resource(cause),
ArtifactVisitError::Visitor(cause) => cause,
})?;
}
}
Ok(())
})
.map_err(|cause| match cause {
AuthorityVisitError::Processing(cause) => {
TimestampPlacementError::Uses(UsesError::Closure(cause))
}
AuthorityVisitError::Visitor(cause) => cause,
})?;
Ok(TimestampPlacement {
inputs,
findings: findings.into_owned(),
snapshot: access.snapshot(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::timestamp::Role;
#[test]
fn another_timestamp_token_cannot_supply_self_carried_validation_data() {
for (property, token, expected, cost) in
[(4, 8, true, 2), (4, 9, false, 2), (5, 8, false, 1)]
{
let mut calls = 0;
let placement = Placement::TimestampToken {
property,
token,
role: Role::Signature,
};
assert_eq!(
allowed(placement, 4, 8, &mut |left, right| {
calls += 1;
Ok::<_, ()>(left == right)
}),
Ok(expected)
);
assert_eq!(calls, cost);
}
for placement in [Placement::General(1), Placement::Timestamp(2)] {
assert_eq!(
allowed(placement, 4, 8, &mut |_, _| -> Result<bool, ()> {
panic!("no fabricated token association")
}),
Ok(true)
);
}
for placement in [
Placement::KeyInfo(1),
Placement::Signature(2),
Placement::Attribute(3),
] {
assert_eq!(
allowed(placement, 4, 8, &mut |_, _| -> Result<bool, ()> {
panic!("placement is already excluded")
}),
Ok(false)
);
}
}
#[test]
fn token_identity_refusal_does_not_become_missing_or_allowed_material() {
let placement = Placement::TimestampToken {
property: 4,
token: 8,
role: Role::Signature,
};
assert_eq!(
allowed(placement, 4, 8, &mut |_, _| Err::<bool, _>(
MemoryError::WorkExceeded
)),
Err(MemoryError::WorkExceeded)
);
}
}