use crate::binary::{self, BinaryXml, Location};
use crate::binding::{Bound, Coverage, Status};
use crate::container::{DS, XADES};
use crate::reference::{Boolean, LexicalSource, ReferenceXml, ReplayOutcome, Resolution};
use crate::xml::{Access, AccessError, ChildError, Name, Snapshot, Xml};
use crate::{ISSUER, Xades};
use structured_asn1::{
Reader,
memory::{Execution, MemoryError, OwnedVec, Work},
};
use structured_cms::stream::{Receiver, Refused, Source};
use structured_cms::{
provider::SignatureProvider,
timestamp::{CheckContext, CheckedTimestamp, TokenExtension},
};
use structured_ltv::timestamp::{CheckedActor, Purpose, Verified};
pub const XADES_141: &str = "http://uri.etsi.org/01903/v1.4.1#";
mod counters;
mod coverage;
mod material;
pub use counters::{
CounterStamp, CounterTimestampError, CounterTimestampResult, CounterTimestamps,
counter_timestamps,
};
pub use coverage::{CoverageError, CoverageFor};
mod profile;
mod selection;
mod tokens;
pub use material::{Carried, CarriedCause, CarriedError, CarriedMaterial, OtherCarried};
pub use profile::{DigestError, DigestUse};
pub use selection::{
RequirementError, RequirementResult, TimestampSelection, requirements,
requirements_with_counters,
};
pub use tokens::{
EncodingConformance, TokenEntry, TokenPipelineError, TokenPipelineResult, TokenProfile,
TokenReading, TokenSource, TokenView, with_counter_tokens, with_tokens,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
Signature,
AllDataObjects,
IndividualDataObjects,
SignatureAndReferences,
ReferencesOnly,
Archive,
}
impl Role {
#[must_use]
pub fn purpose(self) -> Purpose {
match self {
Self::AllDataObjects | Self::IndividualDataObjects => Purpose::Content,
Self::Signature => Purpose::Signature,
Self::SignatureAndReferences | Self::ReferencesOnly => Purpose::ValidationData,
Self::Archive => Purpose::Archive,
}
}
}
#[derive(Debug)]
pub enum InputError<E, N> {
Receiver(Refused),
Access(AccessError<E, N>),
Unavailable {
node: N,
cause: E,
},
Child(ChildError<E, N>),
Resource {
node: N,
cause: MemoryError,
},
Missing {
node: N,
local: &'static str,
},
Unexpected {
node: N,
},
Duplicate {
node: N,
},
Order {
node: N,
},
Foreign {
node: N,
},
ReferencedData {
node: N,
value: Boolean,
},
Reference {
node: N,
resolution: Resolution<()>,
},
}
pub trait TimestampXml: ReferenceXml {
fn canonicalize<W: Work>(
&self,
snapshot: Snapshot<Self::Document, Self::Generation>,
node: Self::Id,
method: Self::Id,
receiver: &mut impl Receiver,
execution: &W,
) -> Result<(), Self::Error>;
}
#[derive(Debug)]
pub struct Stamp<N, D, G> {
snapshot: Snapshot<D, G>,
signature: N,
node: N,
parent: N,
method: N,
role: Role,
tokens: OwnedVec<N>,
includes: OwnedVec<N>,
}
impl<N: Copy, D: Copy, G: Copy> Stamp<N, D, G> {
#[must_use]
pub fn signature(&self) -> N {
self.signature
}
#[must_use]
pub fn snapshot(&self) -> Snapshot<D, G> {
self.snapshot
}
#[must_use]
pub fn node(&self) -> N {
self.node
}
#[must_use]
pub fn method(&self) -> N {
self.method
}
#[must_use]
pub fn role(&self) -> Role {
self.role
}
#[must_use]
pub fn tokens(&self) -> &[N] {
&self.tokens
}
}
pub type Timestamp<X> = Stamp<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
pub type VerifiedTimestamp<X> =
Verified<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation, Xades>;
pub type TimestampActor<'certificate, 'document, X> = CheckedActor<
'certificate,
'document,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
Xades,
>;
pub type InputResult<T, X> = Result<T, InputError<<X as Xml>::Error, <X as Xml>::Id>>;
pub struct Check<'xml, 'document, 'certificate, 'context, X, P> {
pub xml: &'xml X,
pub provider: &'context P,
pub context: &'context CheckContext<'document, 'certificate>,
}
#[derive(Debug)]
pub enum CheckError<E, N, D, G, PE, RE> {
Input(InputError<E, N>),
TokenIndex {
node: N,
index: usize,
},
Token(TokenCheckError<E, N, D, G, PE, RE>),
}
pub type TokenCheckError<E, N, D, G, PE, RE> = binary::Error<
E,
binary::TokenError<
structured_cms::timestamp::ProfileCheckError<InputError<E, N>, PE, RE, DigestError>,
>,
N,
D,
G,
>;
pub type CheckResult<R, X, PE, RE> = Result<
R,
CheckError<
<X as Xml>::Error,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
PE,
RE,
>,
>;
impl<'document, 'certificate, X: TimestampXml + BinaryXml, P>
Check<'_, 'document, 'certificate, '_, X, P>
{
#[expect(
clippy::result_large_err,
reason = "retain original opaque causes and source maps without allocating on refusal"
)]
pub fn retain_actor<PE, RE>(
&self,
bound: &Bound<X::Id, X::Document, X::Generation>,
stamp: &Timestamp<X>,
index: usize,
operation: &Reader<'_>,
recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
) -> CheckResult<Option<TimestampActor<'certificate, 'document, X>>, X, PE, RE>
where
P: for<'scope> SignatureProvider<'scope, Error = PE>,
{
Ok(self
.retain(bound, stamp, index, operation, recognizes)?
.map(|verified| {
CheckedActor::new(ISSUER, verified, self.context.certificate, self.context.key)
}))
}
#[expect(
clippy::result_large_err,
reason = "retain original opaque causes and source maps without allocating on refusal"
)]
pub fn retain<PE, RE>(
&self,
bound: &Bound<X::Id, X::Document, X::Generation>,
stamp: &Timestamp<X>,
index: usize,
operation: &Reader<'_>,
recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
) -> CheckResult<Option<VerifiedTimestamp<X>>, X, PE, RE>
where
P: for<'scope> SignatureProvider<'scope, Error = PE>,
{
fn evidence(checked: CheckedTimestamp<'_>) -> Option<structured_cms::timestamp::Evidence> {
checked.into_evidence()
}
let located = self.run(bound, stamp, index, operation, recognizes, evidence)?;
Ok(located.value.map(|evidence| {
Verified::new(
ISSUER,
located.location,
stamp.signature,
stamp.node,
stamp.role.purpose(),
evidence,
)
}))
}
#[expect(
clippy::result_large_err,
reason = "preserve opaque original failures and admitted source maps without allocating while handling a refusal"
)]
pub fn run<R, PE, RE>(
&self,
bound: &Bound<X::Id, X::Document, X::Generation>,
stamp: &Timestamp<X>,
index: usize,
operation: &Reader<'_>,
recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
visitor: impl for<'checked> FnOnce(CheckedTimestamp<'checked>) -> R,
) -> CheckResult<binary::LocatedValue<R, X>, X, PE, RE>
where
P: for<'scope> SignatureProvider<'scope, Error = PE>,
{
if index >= stamp.tokens().len() {
return Err(CheckError::TokenIndex {
node: stamp.node(),
index,
});
}
let execution = operation.execution();
let input =
Input::prepare(self.xml, bound, stamp, &execution).map_err(CheckError::Input)?;
self.run_prepared(stamp, index, operation, input, recognizes, visitor)
}
#[expect(
clippy::result_large_err,
reason = "retain original source maps and opaque callback causes"
)]
pub fn retain_counter_actor<PE, RE>(
&self,
counter: &CounterStamp<X::Id, X::Document, X::Generation>,
index: usize,
operation: &Reader<'_>,
recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
) -> CheckResult<Option<TimestampActor<'certificate, 'document, X>>, X, PE, RE>
where
P: for<'scope> SignatureProvider<'scope, Error = PE>,
{
if index >= counter.stamp().tokens().len() {
return Err(CheckError::TokenIndex {
node: counter.stamp().node(),
index,
});
}
let execution = operation.execution();
let input = counter
.input(self.xml, &execution)
.map_err(CheckError::Input)?;
let stamp = counter.stamp();
let located = self.run_prepared(stamp, index, operation, input, recognizes, |checked| {
checked.into_evidence()
})?;
Ok(located.value.map(|evidence| {
CheckedActor::new(
ISSUER,
Verified::new(
ISSUER,
located.location,
stamp.signature,
stamp.node,
stamp.role.purpose(),
evidence,
),
self.context.certificate,
self.context.key,
)
}))
}
#[expect(
clippy::result_large_err,
reason = "retain original source maps and opaque callback causes"
)]
pub fn retain_counter_from<PE, RE>(
&self,
counter: &CounterStamp<X::Id, X::Document, X::Generation>,
entry: TokenEntry<'_, '_, X::Id, X::Document, X::Generation>,
operation: &Reader<'_>,
recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
) -> CheckResult<Option<TimestampActor<'certificate, 'document, X>>, X, PE, RE>
where
P: for<'scope> SignatureProvider<'scope, Error = PE>,
{
let stamp = counter.stamp();
let execution = operation.execution();
let access = Access::new(self.xml, stamp.snapshot, &execution);
access
.query(stamp.node, |_, _, work| Ok(work.charge_work(1)))
.map_err(|cause| CheckError::Input(InputError::Access(cause)))?
.map_err(|cause| {
CheckError::Input(InputError::Resource {
node: stamp.node,
cause,
})
})?;
let source = entry.source;
let equal = |left, right| {
access.equal_id(left, right).map_err(|cause| {
CheckError::Input(InputError::Resource {
node: stamp.node,
cause,
})
})
};
let expected = stamp.tokens.get(source.token_index());
if source.location().snapshot != stamp.snapshot
|| !equal(source.signature(), stamp.signature)?
|| !equal(source.property(), stamp.node)?
|| source.role() != stamp.role
|| match expected {
Some(expected) => !equal(source.location().node, *expected)?,
None => true,
}
{
return Err(CheckError::Input(InputError::Foreign {
node: source.location().node,
}));
}
let mut input = counter
.input(self.xml, &execution)
.map_err(CheckError::Input)?;
let location = source.location();
let reader = entry.reader_in(operation).map_err(|cause| {
CheckError::Token(binary::Error::ReaderCreation { location, cause })
})?;
let checked = entry
.reading
.token()
.check_with_profile(
self.provider,
&reader,
self.context,
&mut input,
recognizes,
|fields| profile::check(&fields),
)
.map_err(|cause| {
CheckError::Token(binary::Error::Callback {
location,
cause: binary::TokenError::Callback(cause),
})
})?;
let evidence = checked.into_evidence();
access
.query(stamp.node, |_, _, _| Ok(()))
.map_err(|cause| CheckError::Input(InputError::Access(cause)))?;
Ok(evidence.map(|evidence| {
CheckedActor::new(
ISSUER,
Verified::new(
ISSUER,
location,
stamp.signature,
stamp.node,
stamp.role.purpose(),
evidence,
),
self.context.certificate,
self.context.key,
)
}))
}
#[expect(
clippy::result_large_err,
reason = "retain original source maps and opaque callback causes"
)]
fn run_prepared<R, PE, RE>(
&self,
stamp: &Timestamp<X>,
index: usize,
operation: &Reader<'_>,
mut input: Input<'_, X>,
recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
visitor: impl for<'checked> FnOnce(CheckedTimestamp<'checked>) -> R,
) -> CheckResult<binary::LocatedValue<R, X>, X, PE, RE>
where
P: for<'scope> SignatureProvider<'scope, Error = PE>,
{
let Some(&node) = stamp.tokens.get(index) else {
return Err(CheckError::TokenIndex {
node: stamp.node,
index,
});
};
let location = Location {
snapshot: stamp.snapshot,
node,
};
binary::with_token(self.xml, location, operation, |token, reader| {
let checked = token.check_with_profile(
self.provider,
reader,
self.context,
&mut input,
recognizes,
|fields| profile::check(&fields),
)?;
Ok(binary::Located {
location,
value: visitor(checked),
})
})
.map_err(CheckError::Token)
}
}
pub fn read<X: Xml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
node: X::Id,
) -> InputResult<Timestamp<X>, X> {
read_in(access, bound.containers(), node)
}
fn read_in<X: Xml, W: Work>(
access: &Access<'_, X, W>,
layout: &crate::container::Layout<X>,
node: X::Id,
) -> InputResult<Timestamp<X>, X> {
let parent = unsigned_parent_in(access, layout, node)?;
let mut contained = false;
for child in access.children(parent) {
let child = child.map_err(InputError::Child)?;
if access
.equal_id(child, node)
.map_err(|cause| InputError::Resource { node: child, cause })?
{
contained = true;
}
}
if !contained {
return Err(InputError::Foreign { node });
}
let role = if named(access, node, XADES, "SignatureTimeStamp")? {
Role::Signature
} else if named(access, node, XADES_141, "ArchiveTimeStamp")? {
Role::Archive
} else if named(access, node, XADES_141, "SigAndRefsTimeStampV2")? {
Role::SignatureAndReferences
} else if named(access, node, XADES_141, "RefsOnlyTimeStampV2")? {
Role::ReferencesOnly
} else {
return Err(InputError::Unexpected { node });
};
read_fields(access, layout.signature(), parent, node, role)
}
pub(crate) fn unsigned_parent<X: Xml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
node: X::Id,
) -> InputResult<X::Id, X> {
unsigned_parent_in(access, bound.containers(), node)
}
fn unsigned_parent_in<X: Xml, W: Work>(
access: &Access<'_, X, W>,
layout: &crate::container::Layout<X>,
node: X::Id,
) -> InputResult<X::Id, X> {
if layout.snapshot() != access.snapshot() {
return Err(InputError::Access(AccessError::Changed { node }));
}
let unsigned = layout.unsigned().ok_or(InputError::Foreign { node })?;
singleton(access, unsigned, XADES, "UnsignedSignatureProperties")
}
pub fn read_all_data_objects<X: Xml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
signed: &crate::properties::Reading<X>,
node: X::Id,
) -> InputResult<Timestamp<X>, X> {
read_data_objects(access, bound, signed, node, Role::AllDataObjects)
}
pub fn read_individual_data_objects<X: Xml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
signed: &crate::properties::Reading<X>,
node: X::Id,
) -> InputResult<Timestamp<X>, X> {
read_data_objects(access, bound, signed, node, Role::IndividualDataObjects)
}
fn read_data_objects<X: Xml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
signed: &crate::properties::Reading<X>,
node: X::Id,
role: Role,
) -> InputResult<Timestamp<X>, X> {
let layout = bound.containers();
if signed.snapshot() != layout.snapshot() {
return Err(InputError::Foreign { node });
}
if !access
.equal_id(signed.signature(), layout.signature())
.map_err(|cause| InputError::Resource { node, cause })?
{
return Err(InputError::Foreign { node });
}
if access.snapshot() != signed.snapshot() {
return Err(InputError::Access(AccessError::Changed { node }));
}
let objects = signed.data_objects().ok_or(InputError::Foreign { node })?;
read_object_fields(access, layout, objects, node, role)
}
fn read_object_fields<X: Xml, W: Work>(
access: &Access<'_, X, W>,
layout: &crate::container::Layout<X>,
objects: &crate::properties::DataObjects<X::Id>,
node: X::Id,
role: Role,
) -> InputResult<Timestamp<X>, X> {
let mut contained = false;
for index in 0..objects.entries().len() {
access
.query(node, |_, _, work| Ok(work.charge_work(1)))
.map_err(InputError::Access)?
.map_err(|cause| InputError::Resource { node, cause })?;
let entry = objects.entries()[index];
if access
.equal_id(entry.node, node)
.map_err(|cause| InputError::Resource { node, cause })?
{
let expected = if role == Role::IndividualDataObjects {
crate::properties::DataObjectRole::IndividualTimestamp
} else {
crate::properties::DataObjectRole::AllTimestamp
};
if entry.role != expected {
return Err(InputError::Unexpected { node });
}
contained = true;
break;
}
}
if !contained {
return Err(InputError::Foreign { node });
}
read_fields(access, layout.signature(), objects.parent(), node, role)
}
fn read_fields<X: Xml, W: Work>(
access: &Access<'_, X, W>,
signature: X::Id,
parent: X::Id,
node: X::Id,
role: Role,
) -> InputResult<Timestamp<X>, X> {
let mut method = None;
let mut tokens = access
.query(node, |_, _, work| Ok(work.vector()))
.map_err(InputError::Access)?;
let mut includes = access
.query(node, |_, _, work| Ok(work.vector()))
.map_err(InputError::Access)?;
for child in access.children(node) {
let child = child.map_err(InputError::Child)?;
if named(access, child, XADES, "Include")? {
if role != Role::IndividualDataObjects {
return Err(InputError::Unexpected { node: child });
}
if method.is_some() || !tokens.is_empty() {
return Err(InputError::Order { node: child });
}
includes
.try_push(child)
.map_err(|cause| InputError::Resource { node, cause })?;
} else if named(access, child, DS, "CanonicalizationMethod")? {
if method.is_some() {
return Err(InputError::Duplicate { node: child });
}
if !tokens.is_empty() {
return Err(InputError::Order { node: child });
}
method = Some(child);
} else if named(access, child, XADES, "EncapsulatedTimeStamp")? {
if role == Role::Signature && !tokens.is_empty() {
return Err(InputError::Duplicate { node: child });
}
tokens
.try_push(child)
.map_err(|cause| InputError::Resource { node, cause })?;
} else {
return Err(InputError::Unexpected { node: child });
}
}
let method = method.ok_or(InputError::Missing {
node,
local: "CanonicalizationMethod",
})?;
if tokens.is_empty() {
return Err(InputError::Missing {
node,
local: "EncapsulatedTimeStamp",
});
}
if role == Role::IndividualDataObjects && includes.is_empty() {
return Err(InputError::Missing {
node,
local: "Include",
});
}
Ok(Stamp {
snapshot: access.snapshot(),
signature,
node,
parent,
method,
role,
tokens: tokens.into_owned(),
includes: includes.into_owned(),
})
}
#[derive(Debug, Clone, Copy)]
enum Part<N> {
Element(N),
Reference { node: N, covered: Option<N> },
}
pub struct Input<'a, X: TimestampXml> {
xml: &'a X,
snapshot: Snapshot<X::Document, X::Generation>,
signature: X::Id,
timestamp: X::Id,
method: X::Id,
role: Role,
parts: OwnedVec<Part<X::Id>>,
}
impl<'a, X: TimestampXml> Input<'a, X> {
pub fn prepare<W: Work>(
xml: &'a X,
bound: &Bound<X::Id, X::Document, X::Generation>,
stamp: &Timestamp<X>,
execution: &W,
) -> InputResult<Self, X> {
Self::prepare_in(xml, bound.containers(), stamp, execution)
}
fn prepare_in<W: Work>(
xml: &'a X,
layout: &crate::container::Layout<X>,
stamp: &Timestamp<X>,
execution: &W,
) -> InputResult<Self, X> {
if stamp.snapshot != layout.snapshot() {
return Err(InputError::Foreign { node: stamp.node });
}
let access = Access::new(xml, stamp.snapshot, execution);
let refusal = |cause| InputError::Resource {
node: stamp.node,
cause,
};
if !access
.equal_id(stamp.signature, layout.signature())
.map_err(refusal)?
{
return Err(InputError::Foreign { node: stamp.node });
}
let mut parts = execution.vector();
match stamp.role {
Role::Signature => {
let value = singleton(&access, stamp.signature, DS, "SignatureValue")?;
parts.try_push(Part::Element(value)).map_err(refusal)?;
}
Role::AllDataObjects => {
append_references(&access, stamp, Some(layout.signed()), &mut parts)?;
}
Role::IndividualDataObjects => {
append_includes(&access, stamp, layout.signed(), &mut parts)?;
}
Role::SignatureAndReferences | Role::ReferencesOnly => {
append_validation_references(&access, stamp, &mut parts)?;
}
Role::Archive => {
append_references(&access, stamp, None, &mut parts)?;
let value = singleton(&access, stamp.signature, DS, "SignatureValue")?;
let signed_info = singleton(&access, stamp.signature, DS, "SignedInfo")?;
parts
.try_push(Part::Element(signed_info))
.map_err(refusal)?;
parts.try_push(Part::Element(value)).map_err(refusal)?;
if let Some(key) = optional(&access, stamp.signature, DS, "KeyInfo")? {
parts.try_push(Part::Element(key)).map_err(refusal)?;
}
let mut reached = false;
for child in access.children(stamp.parent) {
let node = child.map_err(InputError::Child)?;
if access.equal_id(node, stamp.node).map_err(refusal)? {
reached = true;
break;
}
parts.try_push(Part::Element(node)).map_err(refusal)?;
}
if !reached {
return Err(InputError::Foreign { node: stamp.node });
}
for child in access.children(stamp.signature) {
let node = child.map_err(InputError::Child)?;
if !access.equal_id(node, layout.object()).map_err(refusal)?
&& named(&access, node, DS, "Object")?
{
parts.try_push(Part::Element(node)).map_err(refusal)?;
}
}
}
}
Ok(Self {
xml,
snapshot: stamp.snapshot,
signature: stamp.signature,
timestamp: stamp.node,
method: stamp.method,
role: stamp.role,
parts: parts.into_owned(),
})
}
#[must_use]
pub fn location(&self) -> (Snapshot<X::Document, X::Generation>, X::Id) {
(self.snapshot, self.timestamp)
}
}
fn append_validation_references<X: TimestampXml, W: Work>(
access: &Access<'_, X, W>,
stamp: &Timestamp<X>,
parts: &mut OwnedVec<Part<X::Id>, W>,
) -> InputResult<(), X> {
let refusal = |cause| InputError::Resource {
node: stamp.node,
cause,
};
let with_signature = stamp.role == Role::SignatureAndReferences;
if with_signature {
let value = singleton(access, stamp.signature, DS, "SignatureValue")?;
parts.try_push(Part::Element(value)).map_err(refusal)?;
}
let mut found = [false; 4];
let roles = [
(XADES_141, "CompleteCertificateRefsV2"),
(XADES, "CompleteRevocationRefs"),
(XADES_141, "AttributeCertificateRefsV2"),
(XADES, "AttributeRevocationRefs"),
];
let mut reached = false;
for child in access.children(stamp.parent) {
let node = child.map_err(InputError::Child)?;
if access.equal_id(node, stamp.node).map_err(refusal)? {
reached = true;
break;
}
let mut selected = with_signature && named(access, node, XADES, "SignatureTimeStamp")?;
for (index, (namespace, local)) in roles.iter().enumerate() {
if named(access, node, namespace, local)? {
if found[index] {
return Err(InputError::Duplicate { node });
}
found[index] = true;
selected = true;
break;
}
}
if selected {
parts.try_push(Part::Element(node)).map_err(refusal)?;
}
}
if !reached {
return Err(InputError::Foreign { node: stamp.node });
}
for index in 0..2 {
if !found[index] {
return Err(InputError::Missing {
node: stamp.node,
local: roles[index].1,
});
}
}
Ok(())
}
fn append_includes<X: TimestampXml, W: Work>(
access: &Access<'_, X, W>,
stamp: &Timestamp<X>,
signed_properties: X::Id,
parts: &mut OwnedVec<Part<X::Id>, W>,
) -> InputResult<(), X> {
for index in 0..stamp.includes.len() {
access
.query(stamp.node, |_, _, work| Ok(work.charge_work(1)))
.map_err(InputError::Access)?
.map_err(|cause| InputError::Resource {
node: stamp.node,
cause,
})?;
let include = stamp.includes[index];
let value = access
.query(include, |xml, snapshot, work| {
xml.boolean_attribute(
snapshot,
include,
Name {
namespace: "",
local: "referencedData",
},
work,
)
})
.map_err(InputError::Access)?;
if value != Boolean::Value(true) {
return Err(InputError::ReferencedData {
node: include,
value,
});
}
let resolved = access
.query(include, |xml, snapshot, work| {
xml.resolve_reference(
snapshot,
stamp.signature,
include,
LexicalSource::Attribute(Name {
namespace: "",
local: "URI",
}),
work,
|descriptor| {
let r = descriptor.reference;
(r.node, r.target, r.status, r.coverage)
},
)
})
.map_err(InputError::Access)?;
let reference = match resolved {
Resolution::Resolved((node, target, Status::Valid, coverage)) => {
if let Some(target) = target
&& access
.equal_id(target, signed_properties)
.map_err(|cause| InputError::Resource {
node: include,
cause,
})?
{
return Err(InputError::Reference {
node: include,
resolution: Resolution::Resolved(()),
});
}
Part::Reference {
node,
covered: if coverage == Coverage::ElementContent {
target
} else {
None
},
}
}
other => {
return Err(InputError::Reference {
node: include,
resolution: other.map(|_| ()),
});
}
};
parts
.try_push(reference)
.map_err(|cause| InputError::Resource {
node: include,
cause,
})?;
}
Ok(())
}
fn append_references<X: TimestampXml, W: Work>(
access: &Access<'_, X, W>,
stamp: &Timestamp<X>,
excluded: Option<X::Id>,
parts: &mut OwnedVec<Part<X::Id>, W>,
) -> InputResult<(), X> {
let count = access
.query(stamp.signature, |xml, snapshot, work| {
xml.reference_count(snapshot, stamp.signature, work)
})
.map_err(InputError::Access)?;
for ordinal in 0..count {
let (part, target) = access
.query(stamp.signature, |xml, snapshot, work| {
xml.reference(snapshot, stamp.signature, ordinal, work, |r| {
(
Part::Reference {
node: r.node,
covered: if r.status == Status::Valid
&& r.coverage == Coverage::ElementContent
{
r.target
} else {
None
},
},
r.target,
)
})
})
.map_err(InputError::Access)?;
if let (Some(target), Some(excluded)) = (target, excluded)
&& access
.equal_id(target, excluded)
.map_err(|cause| InputError::Resource {
node: stamp.node,
cause,
})?
{
continue;
}
parts.try_push(part).map_err(|cause| InputError::Resource {
node: stamp.node,
cause,
})?;
}
Ok(())
}
impl<X: TimestampXml> Source for Input<'_, X> {
type Error = InputError<X::Error, X::Id>;
fn stream<W: Work>(
&mut self,
receiver: &mut impl Receiver,
execution: &W,
) -> Result<(), Self::Error> {
let access = Access::new(self.xml, self.snapshot, execution);
let mut receiver = Stop {
receiver,
refused: false,
};
for index in 0..self.parts.len() {
execution
.charge_work(1)
.map_err(|cause| InputError::Resource {
node: self.timestamp,
cause,
})?;
match self.parts[index] {
Part::Element(node) => access
.query(node, |xml, snapshot, work| {
xml.canonicalize(snapshot, node, self.method, &mut receiver, work)
})
.map_err(InputError::Access)?,
Part::Reference { node, .. } => {
let outcome = access
.query(node, |xml, snapshot, work| {
xml.replay_reference(
snapshot,
self.signature,
node,
crate::reference::Replay::Timestamp(self.method),
&mut receiver,
work,
)
})
.map_err(InputError::Access)?;
if let ReplayOutcome::Unavailable(cause) = outcome {
return Err(InputError::Unavailable { node, cause });
}
}
}
if receiver.refused {
return Err(InputError::Receiver(Refused));
}
}
Ok(())
}
}
struct Stop<'a, R: Receiver> {
receiver: &'a mut R,
refused: bool,
}
impl<R: Receiver> Receiver for Stop<'_, R> {
fn write(&mut self, bytes: &[u8]) -> Result<(), Refused> {
if self.refused {
return Err(Refused);
}
if self.receiver.write(bytes).is_err() {
self.refused = true;
return Err(Refused);
}
Ok(())
}
}
fn named<X: Xml, W: Work>(
access: &Access<'_, X, W>,
node: X::Id,
namespace: &str,
local: &str,
) -> InputResult<bool, X> {
access
.has_name(node, Name { namespace, local })
.map_err(InputError::Access)
}
pub(crate) fn singleton<X: Xml, W: Work>(
access: &Access<'_, X, W>,
parent: X::Id,
namespace: &str,
local: &'static str,
) -> InputResult<X::Id, X> {
optional(access, parent, namespace, local)?.ok_or(InputError::Missing {
node: parent,
local,
})
}
fn optional<X: Xml, W: Work>(
access: &Access<'_, X, W>,
parent: X::Id,
namespace: &str,
local: &str,
) -> InputResult<Option<X::Id>, X> {
let mut found = None;
for child in access.children(parent) {
let node = child.map_err(InputError::Child)?;
if named(access, node, namespace, local)? {
if found.is_some() {
return Err(InputError::Duplicate { node });
}
found = Some(node);
}
}
Ok(found)
}