use super::{ReferenceDigests, ReferenceSearch};
use crate::{
Xades,
certificate::Agreement,
pki::CounterPaths,
unsigned,
xml::{Access, AccessError, Xml},
};
use structured_asn1::memory::{MemoryError, OwnedVec, Work};
use structured_ltv::timestamp::{TimestampRequirements, TimestampScope};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReferencePresenceFailure {
Missing,
IdentifierOnly,
OtherFormat,
Unavailable(Agreement),
}
pub struct ReferencePresenceRow {
required: bool,
failure: Option<ReferencePresenceFailure>,
}
impl ReferencePresenceRow {
#[must_use]
pub fn required(&self) -> bool {
self.required
}
#[must_use]
pub fn failure(&self) -> Option<ReferencePresenceFailure> {
self.failure
}
}
pub struct ReferencePresence<'report, 'references, 'source, N, D, G> {
digests: &'report ReferenceDigests<'references, 'source, N, D, G>,
rows: OwnedVec<ReferencePresenceRow>,
all_present: bool,
}
impl<'report, 'references, 'source, N, D, G>
ReferencePresence<'report, 'references, 'source, N, D, G>
{
#[must_use]
pub fn digests(&self) -> &'report ReferenceDigests<'references, 'source, N, D, G> {
self.digests
}
#[must_use]
pub fn rows(&self) -> &[ReferencePresenceRow] {
&self.rows
}
#[must_use]
pub fn all_required_present(&self) -> bool {
self.all_present
}
}
#[derive(Debug)]
pub enum ReferencePresenceError<E, N> {
Access(AccessError<E, N>),
Scope,
Resource(MemoryError),
}
pub type ReferencePresenceResult<'report, 'references, 'source, X> = Result<
ReferencePresence<
'report,
'references,
'source,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
>,
ReferencePresenceError<<X as Xml>::Error, <X as Xml>::Id>,
>;
pub fn reference_presence<'report, 'references, 'source, X: Xml, W: Work>(
digests: &'report ReferenceDigests<'references, 'source, X::Id, X::Document, X::Generation>,
required: &TimestampRequirements<X::Id, X::Document, X::Generation, Xades>,
counters: &CounterPaths<'_, '_, X::Id, X::Document, X::Generation>,
access: &Access<'_, X, W>,
) -> ReferencePresenceResult<'report, 'references, 'source, X> {
require_sources(digests, required, counters, access)?;
let prepared = digests.prepared();
let mut certificates = false;
let mut revocations = false;
for entry in prepared.unsigned().entries() {
access
.execution()
.charge_work(1)
.map_err(ReferencePresenceError::Resource)?;
match entry.role {
unsigned::Role::Certificates | unsigned::Role::AttributeCertificates => {
certificates = true;
}
unsigned::Role::Revocation | unsigned::Role::AttributeRevocation => revocations = true,
unsigned::Role::Archive => {
certificates = true;
revocations = true;
}
_ => (),
}
}
for data in prepared.validation() {
access
.execution()
.charge_work(1)
.map_err(ReferencePresenceError::Resource)?;
if data.role() == super::Role::Any {
certificates |= data
.certificates()
.is_some_and(|values| !values.entries().is_empty());
revocations |= data
.revocations()
.is_some_and(|values| !values.entries().is_empty());
}
}
let mut rows = access.execution().vector();
rows.try_reserve_exact(digests.rows().len())
.map_err(ReferencePresenceError::Resource)?;
let mut all_present = true;
for (ordinal, row) in digests.rows().iter().enumerate() {
access
.execution()
.charge_work(1)
.map_err(ReferencePresenceError::Resource)?;
let property = &prepared.validation_references()[row.property()];
let required = match property.role() {
unsigned::Role::CertificateReferences
| unsigned::Role::AttributeCertificateReferences => certificates,
unsigned::Role::RevocationReferences
| unsigned::Role::AttributeRevocationReferences => revocations,
_ => unreachable!("privately constructed reference property role"),
};
let failure = if required {
presence_failure(
row.search(),
digests
.comparisons(ordinal)
.expect("original comparison row"),
access.execution(),
)
.map_err(ReferencePresenceError::Resource)?
} else {
None
};
if failure.is_some() {
all_present = false;
}
rows.try_push(ReferencePresenceRow { required, failure })
.map_err(ReferencePresenceError::Resource)?;
}
access
.query(required.signature(), |_, _, _| Ok(()))
.map_err(ReferencePresenceError::Access)?;
Ok(ReferencePresence {
digests,
rows: rows.into_owned(),
all_present,
})
}
fn presence_failure<W: Work>(
search: ReferenceSearch,
values: &[super::MaterialDigest],
work: &W,
) -> Result<Option<ReferencePresenceFailure>, MemoryError> {
let mut unavailable = None;
match search {
ReferenceSearch::IdentifierOnly => {
return Ok(Some(ReferencePresenceFailure::IdentifierOnly));
}
ReferenceSearch::OtherFormat => return Ok(Some(ReferencePresenceFailure::OtherFormat)),
ReferenceSearch::Unavailable(cause) => {
return Ok(Some(ReferencePresenceFailure::Unavailable(cause)));
}
ReferenceSearch::Compared => (),
}
for value in values {
work.charge_work(1)?;
match value.agreement() {
Agreement::Matches => return Ok(None),
Agreement::Differs => (),
cause => {
if unavailable.is_none() {
unavailable = Some(cause);
}
}
}
}
Ok(Some(unavailable.map_or(
ReferencePresenceFailure::Missing,
ReferencePresenceFailure::Unavailable,
)))
}
fn require_sources<X: Xml, W: Work>(
digests: &ReferenceDigests<'_, '_, X::Id, X::Document, X::Generation>,
required: &TimestampRequirements<X::Id, X::Document, X::Generation, Xades>,
counters: &CounterPaths<'_, '_, X::Id, X::Document, X::Generation>,
access: &Access<'_, X, W>,
) -> Result<(), ReferencePresenceError<X::Error, X::Id>> {
let original = digests.prepared().bound().containers();
let TimestampScope::All(scope) = required.scope() else {
return Err(ReferencePresenceError::Scope);
};
if original.snapshot() != access.snapshot()
|| scope.snapshot != original.snapshot()
|| counters.snapshot() != original.snapshot()
{
return Err(ReferencePresenceError::Scope);
}
access
.query(original.signature(), |_, _, _| Ok(()))
.map_err(ReferencePresenceError::Access)?;
if !access
.equal_id(scope.node, original.signature())
.map_err(ReferencePresenceError::Resource)?
|| !access
.equal_id(required.signature(), original.signature())
.map_err(ReferencePresenceError::Resource)?
|| !access
.equal_id(counters.signature(), original.signature())
.map_err(ReferencePresenceError::Resource)?
|| !required
.includes_counters(counters, access)
.map_err(|cause| ReferencePresenceError::Access(cause.into()))?
{
return Err(ReferencePresenceError::Scope);
}
let Some(sources) = digests.tokens() else {
if required.entries().is_empty() {
return Ok(());
}
return Err(ReferencePresenceError::Scope);
};
if sources.len() != required.entries().len() {
return Err(ReferencePresenceError::Scope);
}
for (source, entry) in sources.iter().zip(required.entries()) {
access
.execution()
.charge_work(1)
.map_err(ReferencePresenceError::Resource)?;
if source.location().snapshot != original.snapshot()
|| source.role().purpose() != entry.purpose
|| !access
.equal_id(source.signature(), entry.signature)
.map_err(ReferencePresenceError::Resource)?
|| !access
.equal_id(source.property(), entry.property)
.map_err(ReferencePresenceError::Resource)?
|| !access
.equal_id(source.location().node, entry.token)
.map_err(ReferencePresenceError::Resource)?
{
return Err(ReferencePresenceError::Scope);
}
}
Ok(())
}