use crate::binary::{self, BinaryXml, Location};
use crate::certificate::{self, Agreement, DigestProvider};
use crate::xml::{Access, Snapshot, Xml};
use core::cmp::Ordering;
use structured_asn1::memory::{MemoryError, Work};
use structured_cms::{
provider::Resolution,
stream::{self, Sink, Source, StreamError},
};
pub trait StreamingDigest<X: Xml>: DigestProvider<X> {
type State;
type Output: AsRef<[u8]>;
fn compare<W: Work>(
&self,
left: &Self::Identity,
right: &Self::Identity,
execution: &W,
) -> Result<Ordering, Self::Error>;
fn start<W: Work>(
&self,
identity: &Self::Identity,
execution: &W,
) -> Result<Resolution<Self::State>, Self::Error>;
fn update<W: Work>(
&self,
state: &mut Self::State,
bytes: &[u8],
execution: &W,
) -> Result<(), Self::Error>;
fn finish<W: Work>(
&self,
state: Self::State,
execution: &W,
) -> Result<Self::Output, Self::Error>;
fn matches_output<W: Work>(
&self,
identity: &Self::Identity,
output: &Self::Output,
expected: &[u8],
execution: &W,
) -> Result<bool, Self::Error>;
}
#[derive(Debug, Clone, Copy)]
pub struct Reference<N, D, G> {
pub snapshot: Snapshot<D, G>,
pub method: N,
pub value: N,
}
#[derive(Debug)]
pub enum Error<SE, PE> {
Source(SE),
Provider(PE),
Resource(MemoryError),
Stream(structured_cms::Error),
OutputWidth {
expected: usize,
actual: usize,
},
}
struct Updating<'a, X: Xml, P: StreamingDigest<X>> {
provider: &'a P,
state: &'a mut P::State,
}
impl<X: Xml, P: StreamingDigest<X>> Sink for Updating<'_, X, P> {
type Error = P::Error;
fn write<W: Work>(&mut self, bytes: &[u8], execution: &W) -> Result<(), Self::Error> {
self.provider.update(self.state, bytes, execution)
}
}
pub fn check<X: BinaryXml, P: StreamingDigest<X>, S: Source, W: Work>(
xml: &X,
reference: Reference<X::Id, X::Document, X::Generation>,
source: &mut S,
provider: &P,
execution: &W,
) -> binary::BinaryResult<Agreement, X, Error<S::Error, P::Error>> {
check_with(
xml,
reference,
provider,
execution,
|identity, expected, work| {
run_resolved::<X, P, S, W>(&identity, expected, source, provider, work)
},
)
.map(|reading| match reading {
Checked::Answer(answer) | Checked::Value(answer) => answer,
})
}
pub(crate) enum Checked<T> {
Answer(Agreement),
Value(T),
}
pub(crate) fn check_with<X: BinaryXml, P: StreamingDigest<X>, W: Work, SE, R>(
xml: &X,
reference: Reference<X::Id, X::Document, X::Generation>,
provider: &P,
execution: &W,
visit: impl FnOnce(
certificate::FormatDigest<P::Identity>,
&[u8],
&W,
) -> Result<R, Error<SE, P::Error>>,
) -> binary::BinaryResult<Checked<R>, X, Error<SE, P::Error>> {
let access = Access::new(xml, reference.snapshot, execution);
let method = Location {
snapshot: reference.snapshot,
node: reference.method,
};
if let Some(answer) =
certificate::preflight_method(&access, reference.method).map_err(|cause| {
binary::Error::Access {
location: method,
cause,
}
})?
{
return Ok(Checked::Answer(answer));
}
binary::with_octets(
xml,
Location {
snapshot: reference.snapshot,
node: reference.value,
},
execution,
|expected| {
Ok::<_, core::convert::Infallible>(check_expected(
&access, reference, expected, provider, visit,
))
},
)
.map_err(|error| error.map_callback(|never| match never {}))?
}
fn check_expected<X: BinaryXml, P: StreamingDigest<X>, W: Work, SE, R>(
access: &Access<'_, X, W>,
reference: Reference<X::Id, X::Document, X::Generation>,
expected: &[u8],
provider: &P,
visit: impl FnOnce(
certificate::FormatDigest<P::Identity>,
&[u8],
&W,
) -> Result<R, Error<SE, P::Error>>,
) -> binary::BinaryResult<Checked<R>, X, Error<SE, P::Error>> {
let execution = access.execution();
let method = Location {
snapshot: reference.snapshot,
node: reference.method,
};
let resolved = access
.query(reference.method, |_, _, _| {
Ok(provider.resolve(access, reference.method))
})
.map_err(|cause| binary::Error::Access {
location: method,
cause,
})?
.map_err(|cause| binary::Error::Callback {
location: method,
cause: Error::Provider(cause),
})?;
let identity = match resolved {
Resolution::Supported(identity) => identity,
Resolution::Unsupported => return Ok(Checked::Answer(Agreement::Unsupported)),
Resolution::Malformed => return Ok(Checked::Answer(Agreement::MalformedMethod)),
};
let identity = match certificate::baseline_digest::<X, P>(identity, provider) {
certificate::Expected::Ready(identity) => identity,
certificate::Expected::Answer(answer) => return Ok(Checked::Answer(answer)),
};
if let Some(width) = provider.width(identity.identity())
&& expected.len() != width
{
return Ok(Checked::Answer(Agreement::MalformedValue));
}
visit(identity, expected, execution)
.map(Checked::Value)
.map_err(|cause| binary::Error::Callback {
location: Location {
snapshot: reference.snapshot,
node: reference.value,
},
cause,
})
}
pub(crate) fn run_resolved<X: Xml, P: StreamingDigest<X>, S: Source, W: Work>(
identity: &certificate::FormatDigest<P::Identity>,
expected: &[u8],
source: &mut S,
provider: &P,
execution: &W,
) -> Result<Agreement, Error<S::Error, P::Error>> {
let width = provider.width(identity.identity());
if let Some(width) = width
&& expected.len() != width
{
return Ok(Agreement::MalformedValue);
}
let output = match compute_resolved::<X, P, S, W>(identity, source, provider, execution)? {
Resolution::Supported(output) => output,
Resolution::Unsupported => return Ok(Agreement::Unsupported),
Resolution::Malformed => return Ok(Agreement::MalformedMethod),
};
execution.charge_work(1).map_err(Error::Resource)?;
let agrees = provider
.matches_output(identity.identity(), &output, expected, execution)
.map_err(Error::Provider)?;
Ok(if agrees {
Agreement::Matches
} else {
Agreement::Differs
})
}
type Computation<O, SE, PE> = Result<Resolution<O>, Error<SE, PE>>;
pub(crate) fn compute_resolved<X: Xml, P: StreamingDigest<X>, S: Source, W: Work>(
identity: &certificate::FormatDigest<P::Identity>,
source: &mut S,
provider: &P,
execution: &W,
) -> Computation<P::Output, S::Error, P::Error> {
let width = provider.width(identity.identity());
execution.charge_work(1).map_err(Error::Resource)?;
let mut state = match provider
.start(identity.identity(), execution)
.map_err(Error::Provider)?
{
Resolution::Supported(state) => state,
Resolution::Unsupported => return Ok(Resolution::Unsupported),
Resolution::Malformed => return Ok(Resolution::Malformed),
};
let mut updating = Updating::<X, P> {
provider,
state: &mut state,
};
match stream::run(source, &mut updating, execution, "xades.digestInput", 0) {
Ok(()) => {}
Err(StreamError::Source(cause)) => return Err(Error::Source(cause)),
Err(StreamError::Callback(cause)) => return Err(Error::Provider(cause)),
Err(StreamError::Processing(cause)) => return Err(Error::Stream(cause)),
}
execution.charge_work(1).map_err(Error::Resource)?;
let output = provider.finish(state, execution).map_err(Error::Provider)?;
if let Some(expected) = width
&& output.as_ref().len() != expected
{
return Err(Error::OutputWidth {
expected,
actual: output.as_ref().len(),
});
}
Ok(Resolution::Supported(output))
}