use super::Role;
use crate::binary::{
self, EncodingDeclaration, Location, OwnedBinaryXml, OwnedDecoded, PkiEncoding,
};
use crate::prepare::Prepared;
use crate::xml::{Access, AccessError, Snapshot, Xml};
use structured_asn1::{
Encoding, Reader,
memory::{MemoryError, Work},
};
use structured_cms::timestamp::Token;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenProfile {
Cms,
Etsi,
}
#[derive(Debug)]
pub enum EncodingConformance {
Matches,
Mismatch(structured_cms::Error),
Unchecked(PkiEncoding),
}
pub struct TokenSource<N, D, G> {
location: Location<N, D, G>,
signature: N,
property: N,
role: Role,
stamp: usize,
ordinal: usize,
encoding: Encoding,
declaration: EncodingDeclaration,
conformance: EncodingConformance,
source: OwnedDecoded,
}
impl<N: Copy, D: Copy, G: Copy> TokenSource<N, D, G> {
#[must_use]
pub fn declaration(&self) -> EncodingDeclaration {
self.declaration
}
#[must_use]
pub fn encoding_conformance(&self) -> &EncodingConformance {
&self.conformance
}
#[must_use]
pub fn signature(&self) -> N {
self.signature
}
#[must_use]
pub fn location(&self) -> Location<N, D, G> {
self.location
}
#[must_use]
pub fn property(&self) -> N {
self.property
}
#[must_use]
pub fn role(&self) -> Role {
self.role
}
#[must_use]
pub fn stamp_index(&self) -> usize {
self.stamp
}
#[must_use]
pub fn token_index(&self) -> usize {
self.ordinal
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.source.bytes
}
}
pub struct TokenReading<'source> {
reader: Reader<'source>,
token: Token<'source>,
}
#[derive(Clone, Copy)]
pub struct TokenEntry<'view, 'source, N, D, G> {
pub(super) source: &'view TokenSource<N, D, G>,
pub(super) reading: &'view TokenReading<'source>,
}
impl<'source> TokenReading<'source> {
#[must_use]
pub fn reader(&self) -> &Reader<'source> {
&self.reader
}
#[must_use]
pub fn token(&self) -> &Token<'source> {
&self.token
}
}
pub struct TokenView<'source, N, D, G> {
sources: &'source [TokenSource<N, D, G>],
readings: &'source [TokenReading<'source>],
snapshot: Snapshot<D, G>,
signature: N,
}
impl<'source, N, D, G> TokenView<'source, N, D, G> {
#[must_use]
pub fn entry(&self, index: usize) -> Option<TokenEntry<'_, 'source, N, D, G>> {
Some(TokenEntry {
source: self.sources.get(index)?,
reading: self.readings.get(index)?,
})
}
#[must_use]
pub fn sources(&self) -> &'source [TokenSource<N, D, G>] {
self.sources
}
#[must_use]
pub fn readings(&self) -> &'source [TokenReading<'source>] {
self.readings
}
}
impl<'source, N: Copy, D: Copy, G: Copy> TokenView<'source, N, D, G> {
#[must_use]
pub fn snapshot(&self) -> Snapshot<D, G> {
self.snapshot
}
#[must_use]
pub fn signature(&self) -> N {
self.signature
}
pub(super) fn reborrow<'query>(&self) -> TokenView<'query, N, D, G>
where
'source: 'query,
{
TokenView {
sources: self.sources,
readings: self.readings,
snapshot: self.snapshot,
signature: self.signature,
}
}
}
#[derive(Debug)]
pub enum TokenPipelineError<E, VE, N, D, G> {
Foreign {
node: N,
},
Access(AccessError<E, N>),
Encoding(binary::Error<E, core::convert::Infallible, N, D, G>),
Reader {
location: Location<N, D, G>,
cause: structured_asn1::Error,
},
Token {
location: Location<N, D, G>,
cause: structured_cms::Error,
},
Resource {
node: N,
cause: MemoryError,
},
Visitor(VE),
}
pub type TokenPipelineResult<T, X, VE> = Result<
T,
TokenPipelineError<
<X as Xml>::Error,
VE,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
>,
>;
pub fn with_tokens<X: OwnedBinaryXml, R, VE>(
xml: &X,
prepared: &Prepared<X>,
operation: &Reader<'_>,
profile: TokenProfile,
visitor: impl for<'source> FnOnce(
&Reader<'source>,
TokenView<'source, X::Id, X::Document, X::Generation>,
) -> Result<R, VE>,
) -> TokenPipelineResult<R, X, VE> {
with_inputs(xml, prepared, None, operation, profile, visitor)
}
pub fn with_counter_tokens<X: OwnedBinaryXml, R, VE>(
xml: &X,
prepared: &Prepared<X>,
counters: &super::CounterTimestamps<X::Id, X::Document, X::Generation>,
operation: &Reader<'_>,
profile: TokenProfile,
visitor: impl for<'source> FnOnce(
&Reader<'source>,
TokenView<'source, X::Id, X::Document, X::Generation>,
) -> Result<R, VE>,
) -> TokenPipelineResult<R, X, VE> {
with_inputs(xml, prepared, Some(counters), operation, profile, visitor)
}
impl<N: Copy, D: Copy, G: Copy> TokenEntry<'_, '_, N, D, G> {
pub(super) fn reader_in<'query>(
&'query self,
operation: &Reader<'query>,
) -> structured_asn1::Result<Reader<'query>> {
self.source
.source
.reader_in(operation, self.source.encoding)
}
}
fn with_inputs<X: OwnedBinaryXml, R, VE>(
xml: &X,
prepared: &Prepared<X>,
counters: Option<&super::CounterTimestamps<X::Id, X::Document, X::Generation>>,
operation: &Reader<'_>,
profile: TokenProfile,
visitor: impl for<'source> FnOnce(
&Reader<'source>,
TokenView<'source, X::Id, X::Document, X::Generation>,
) -> Result<R, VE>,
) -> TokenPipelineResult<R, X, VE> {
let layout = prepared.bound().containers();
let execution = operation.execution();
let access = Access::new(xml, layout.snapshot(), &execution);
admit(&access, layout.signature())?;
if let Some(counters) = counters
&& (counters.snapshot != layout.snapshot()
|| !access
.equal_id(counters.signature, layout.signature())
.map_err(|cause| TokenPipelineError::Resource {
node: layout.signature(),
cause,
})?)
{
return Err(TokenPipelineError::Foreign {
node: layout.signature(),
});
}
let mut sources = execution.vector();
let children = counters
.into_iter()
.flat_map(|counters| counters.entries.iter().map(super::CounterStamp::stamp));
let mut timestamps = prepared.timestamps().iter().chain(children).enumerate();
loop {
admit(&access, layout.signature())?;
let Some((stamp, timestamp)) = timestamps.next() else {
break;
};
for ordinal in 0..timestamp.tokens().len() {
admit(&access, timestamp.node())?;
let node = timestamp.tokens()[ordinal];
let location = Location {
snapshot: layout.snapshot(),
node,
};
let (declaration, encoding) = select_encoding(profile, &access, location)?;
let source = binary::owned_source(xml, location, operation)
.map_err(TokenPipelineError::Access)?
.value;
let conformance = declaration_conformance::<X, VE>(
profile,
declaration.encoding(),
&source,
operation,
location,
)?;
sources
.try_push(TokenSource {
location,
signature: timestamp.signature,
property: timestamp.node(),
role: timestamp.role(),
stamp,
ordinal,
encoding,
declaration,
conformance,
source,
})
.map_err(|cause| TokenPipelineError::Resource { node, cause })?;
}
}
let mut readings = execution.vector();
for index in 0..sources.len() {
admit(&access, layout.signature())?;
let source = &sources[index];
let mut reader = source
.source
.reader_in(operation, source.encoding)
.map_err(|cause| TokenPipelineError::Reader {
location: source.location,
cause,
})?;
let token = Token::read_from(&mut reader).map_err(|cause| TokenPipelineError::Token {
location: source.location,
cause,
})?;
readings
.try_push(TokenReading { reader, token })
.map_err(|cause| TokenPipelineError::Resource {
node: source.location.node,
cause,
})?;
}
admit(&access, layout.signature())?;
let result = visitor(
operation,
TokenView {
sources: &sources,
readings: &readings,
snapshot: layout.snapshot(),
signature: layout.signature(),
},
)
.map_err(TokenPipelineError::Visitor)?;
admit(&access, layout.signature())?;
Ok(result)
}
fn select_encoding<X: Xml, W: Work, VE>(
profile: TokenProfile,
access: &Access<'_, X, W>,
location: Location<X::Id, X::Document, X::Generation>,
) -> TokenPipelineResult<(EncodingDeclaration, Encoding), X, VE> {
let declaration = binary::pki_encoding(access, location.node)
.map_err(|cause| TokenPipelineError::Encoding(binary::Error::Access { location, cause }))?
.ok_or(TokenPipelineError::Encoding(
binary::Error::UnknownEncoding { location },
))?;
let encoding = match (profile, declaration.encoding()) {
(TokenProfile::Cms, PkiEncoding::Der | PkiEncoding::Ber | PkiEncoding::Cer)
| (TokenProfile::Etsi, PkiEncoding::Ber) => Encoding::Ber,
(TokenProfile::Etsi, PkiEncoding::Der) => Encoding::Der,
(_, encoding) => {
return Err(TokenPipelineError::Encoding(
binary::Error::UnsupportedEncoding { location, encoding },
));
}
};
Ok((declaration, encoding))
}
fn declaration_conformance<X: Xml, VE>(
profile: TokenProfile,
declared: PkiEncoding,
source: &OwnedDecoded,
operation: &Reader<'_>,
location: Location<X::Id, X::Document, X::Generation>,
) -> TokenPipelineResult<EncodingConformance, X, VE> {
if profile == TokenProfile::Cms && declared == PkiEncoding::Der {
let mut strict = source
.reader_in(operation, Encoding::Der)
.map_err(|cause| TokenPipelineError::Reader { location, cause })?;
match Token::read_from(&mut strict) {
Ok(_) => Ok(EncodingConformance::Matches),
Err(cause) if cause.is_terminal() => Err(TokenPipelineError::Token { location, cause }),
Err(cause) => Ok(EncodingConformance::Mismatch(cause)),
}
} else if declared == PkiEncoding::Cer {
Ok(EncodingConformance::Unchecked(declared))
} else {
Ok(EncodingConformance::Matches)
}
}
fn admit<X: Xml, W: Work, VE>(
access: &Access<'_, X, W>,
node: X::Id,
) -> TokenPipelineResult<(), X, VE> {
access
.query(node, |_, _, work| Ok(work.charge_work(1)))
.map_err(TokenPipelineError::Access)?
.map_err(|cause| TokenPipelineError::Resource { node, cause })
}