use super::{InputError, Role};
use crate::binary::Location;
use crate::prepare::Prepared;
use crate::xml::{Access, Xml};
use crate::{ISSUER, Xades};
use structured_asn1::memory::{MemoryError, Work};
use structured_ltv::timestamp::{RequiredToken, TimestampRequirements, TimestampScope};
#[derive(Debug, Clone, Copy)]
pub enum TimestampSelection {
All,
BeforeArchive(usize),
}
impl TimestampSelection {
fn includes(self, ordinal: usize, role: Role) -> bool {
match self {
Self::All => true,
Self::BeforeArchive(archive) => {
ordinal < archive
|| matches!(role, Role::AllDataObjects | Role::IndividualDataObjects)
}
}
}
}
#[derive(Debug)]
pub enum RequirementError<E, N> {
Input(InputError<E, N>),
Coverage(super::CoverageError<E, N>),
Selection(structured_ltv::material::CoverageError<N>),
Index {
signature: N,
index: usize,
},
Role {
property: N,
},
}
impl<E, N> From<InputError<E, N>> for RequirementError<E, N> {
fn from(cause: InputError<E, N>) -> Self {
Self::Input(cause)
}
}
pub type RequirementResult<X> = Result<
TimestampRequirements<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation, Xades>,
RequirementError<<X as Xml>::Error, <X as Xml>::Id>,
>;
type Entries<X, W> = structured_asn1::memory::OwnedVec<RequiredToken<<X as Xml>::Id>, W>;
pub fn requirements<X: Xml, W: Work>(
access: &Access<'_, X, W>,
prepared: &Prepared<X>,
selection: TimestampSelection,
) -> RequirementResult<X> {
let (scope, entries) = root_entries(access, prepared, selection)?;
let signature = prepared.bound().containers().signature();
Ok(TimestampRequirements::new(
ISSUER,
signature,
scope,
entries.into_owned(),
))
}
type RootEntries<X, W> = Result<
(
TimestampScope<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
Entries<X, W>,
),
RequirementError<<X as Xml>::Error, <X as Xml>::Id>,
>;
fn root_entries<X: Xml, W: Work>(
access: &Access<'_, X, W>,
prepared: &Prepared<X>,
selection: TimestampSelection,
) -> RootEntries<X, W> {
let layout = prepared.bound().containers();
let signature = layout.signature();
if layout.snapshot() != access.snapshot() {
return Err(InputError::Foreign { node: signature }.into());
}
let pay = || {
access
.query(signature, |_, _, work| Ok(work.charge_work(1)))
.map_err(InputError::Access)?
.map_err(|cause| InputError::Resource {
node: signature,
cause,
})
};
pay()?;
let scope = match selection {
TimestampSelection::All => TimestampScope::All(Location {
snapshot: access.snapshot(),
node: signature,
}),
TimestampSelection::BeforeArchive(index) => {
let Some(selected) = prepared.timestamps().get(index) else {
return Err(RequirementError::Index { signature, index });
};
if selected.role() != Role::Archive {
return Err(RequirementError::Role {
property: selected.node(),
});
}
TimestampScope::BeforeArchive(Location {
snapshot: access.snapshot(),
node: selected.node(),
})
}
};
let resource = |cause| InputError::Resource {
node: scope.location().node,
cause,
};
let mut count = 0usize;
for (ordinal, stamp) in prepared.timestamps().iter().enumerate() {
pay()?;
if selection.includes(ordinal, stamp.role()) {
count = count
.checked_add(stamp.tokens().len())
.ok_or_else(|| resource(MemoryError::WorkExceeded))?;
}
}
let mut entries = access.execution().vector();
entries.try_reserve_exact(count).map_err(resource)?;
for (ordinal, stamp) in prepared.timestamps().iter().enumerate() {
pay()?;
if !selection.includes(ordinal, stamp.role()) {
continue;
}
for token in stamp.tokens() {
pay()?;
entries
.try_push(RequiredToken {
signature,
property: stamp.node(),
token: *token,
purpose: stamp.role().purpose(),
})
.map_err(resource)?;
}
}
access
.query(signature, |_, _, _| Ok(()))
.map_err(InputError::Access)?;
Ok((scope, entries))
}
pub fn requirements_with_counters<X: super::TimestampXml, W: Work>(
access: &Access<'_, X, W>,
prepared: &Prepared<X>,
counters: &super::CounterTimestamps<X::Id, X::Document, X::Generation>,
selection: TimestampSelection,
) -> RequirementResult<X> {
let signature = prepared.bound().containers().signature();
let resource = |cause| InputError::Resource {
node: signature,
cause,
};
if counters.snapshot != access.snapshot()
|| !access
.equal_id(counters.signature, signature)
.map_err(resource)?
{
return Err(InputError::Foreign { node: signature }.into());
}
let (scope, mut entries) = root_entries(access, prepared, selection)?;
let mut signatures = access.execution().vector();
signatures
.try_extend_from_slice(&counters.signatures)
.map_err(resource)?;
let coverage = match selection {
TimestampSelection::All => None,
TimestampSelection::BeforeArchive(index) => {
let input = super::Input::prepare(
access.owner(),
prepared.bound(),
&prepared.timestamps()[index],
access.execution(),
)?;
Some(
input
.element_coverage(access.execution())
.map_err(RequirementError::Coverage)?,
)
}
};
let covered = if let Some(coverage) = &coverage {
let mut mask = access.execution().vector();
let mut index = 0;
while index < signatures.len() {
access
.query(signature, |_, _, work| Ok(work.charge_work(1)))
.map_err(InputError::Access)?
.map_err(resource)?;
let included = coverage
.selects(signatures[index], access)
.map_err(RequirementError::Selection)?;
mask.try_push(included).map_err(resource)?;
index += 1;
}
Some(mask.into_owned())
} else {
None
};
for child in counters.entries() {
let stamp = child.stamp();
access
.query(stamp.node(), |_, _, work| Ok(work.charge_work(1)))
.map_err(InputError::Access)?
.map_err(resource)?;
if let Some(coverage) = &coverage
&& !coverage
.selects(stamp.node(), access)
.map_err(RequirementError::Selection)?
{
continue;
}
for token in stamp.tokens() {
access
.query(*token, |_, _, work| Ok(work.charge_work(1)))
.map_err(InputError::Access)?
.map_err(resource)?;
entries
.try_push(RequiredToken {
signature: stamp.signature,
property: stamp.node(),
token: *token,
purpose: stamp.role().purpose(),
})
.map_err(resource)?;
}
}
access
.query(signature, |_, _, _| Ok(()))
.map_err(InputError::Access)?;
Ok(TimestampRequirements::with_counters(
ISSUER,
signature,
scope,
entries.into_owned(),
signatures.into_owned(),
covered,
))
}