use super::{Batch, CheckError, Checked, PassError};
use crate::prepare::ResolvedSigner;
use crate::provider::SignatureProvider;
use crate::stream::Source;
use crate::{Error, ErrorCode, SignedData};
use structured_asn1::Reader;
use structured_asn1::memory::{Execution, OwnedVec, Work};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContentMode {
Carried,
Detached,
}
pub struct DocumentBatch<'operation, 'a, P: SignatureProvider<'a>> {
message: &'a SignedData<'a>,
batch: Batch<'a, P>,
mode: ContentMode,
execution: Execution<'operation>,
}
impl<'operation, 'a, P: SignatureProvider<'a>> DocumentBatch<'operation, 'a, P> {
pub fn prepare(
provider: &'a P,
message: &'a SignedData<'a>,
requests: OwnedVec<ResolvedSigner<'a, P>>,
mode: ContentMode,
parent: &'operation Reader<'a>,
) -> Result<Self, CheckError<P::Error>> {
const FIELD: &str = "signedData.encapContentInfo";
message
.require_session(parent, FIELD)
.map_err(CheckError::Processing)?;
let execution = parent.execution();
execution.charge_work(1).map_err(|cause| {
CheckError::Processing(Error::resource(
cause,
FIELD,
message.content().span().start,
))
})?;
if (mode == ContentMode::Detached) != message.content().is_detached() {
return Err(CheckError::Processing(Error::schema(
ErrorCode::InvalidLocation,
FIELD,
message.content().span().start,
)));
}
for request in &requests {
execution.charge_work(1).map_err(|cause| {
CheckError::Processing(Error::resource(cause, FIELD, request.signer().span().start))
})?;
message
.require_signer(request.signer(), "signedData.signerInfos")
.map_err(CheckError::Processing)?;
}
let batch = Batch::prepare(provider, requests, &execution, FIELD, message.span().start)?;
Ok(Self {
message,
batch,
mode,
execution,
})
}
pub fn run_carried(
self,
) -> Result<DocumentChecks<'a>, PassError<crate::octets::SourceError, P::Error>> {
let refusal = || {
crate::stream::StreamError::Processing(Error::schema(
ErrorCode::InvalidLocation,
"signedData.encapContentInfo",
self.message.content().span().start,
))
};
if self.mode != ContentMode::Carried {
return Err(refusal());
}
let Some(mut content) = self.message.content().content() else {
return Err(refusal());
};
let checks = self.batch.run(&mut content, &self.execution)?;
Ok(DocumentChecks {
message: self.message,
checks,
})
}
pub fn run_detached<S: Source>(
self,
source: &mut S,
) -> Result<DocumentChecks<'a>, PassError<S::Error, P::Error>> {
if self.mode != ContentMode::Detached || !self.message.content().is_detached() {
return Err(crate::stream::StreamError::Processing(Error::schema(
ErrorCode::InvalidLocation,
"signedData.encapContentInfo",
self.message.content().span().start,
)));
}
let checks = self.batch.run(source, &self.execution)?;
Ok(DocumentChecks {
message: self.message,
checks,
})
}
}
pub struct DocumentChecks<'a> {
message: &'a SignedData<'a>,
checks: OwnedVec<Checked<'a>>,
}
impl<'a> DocumentChecks<'a> {
#[must_use]
pub fn message(&self) -> &'a SignedData<'a> {
self.message
}
#[must_use]
pub fn checks(&self) -> &[Checked<'a>] {
&self.checks
}
pub fn require<W: Work>(
&self,
message: &SignedData<'_>,
checked: &Checked<'_>,
execution: &W,
) -> crate::Result<()> {
let at = checked.signer().span().start;
execution
.charge_work(1)
.map_err(|cause| Error::resource(cause, "signedData.signerInfos", at))?;
let address = core::ptr::from_ref(checked).cast::<Checked<'a>>();
if !core::ptr::eq(message, self.message) || !self.checks.as_ptr_range().contains(&address) {
return Err(Error::schema(
ErrorCode::InvalidLocation,
"signedData.signerInfos",
at,
));
}
Ok(())
}
}