1use super::{CmsObject, ObjectError, View};
3use crate::{
4 Error,
5 basic::PathRestriction,
6 prepare::Preparation,
7 preservation::{Kind, Required},
8 timestamp::{self, ProfileError, VisitError},
9};
10use core::{
11 cell::{Cell, RefCell},
12 convert::Infallible,
13};
14use structured_asn1::{
15 Element, Reader,
16 memory::{Execution, OwnedVec, Work},
17};
18use structured_cms::{
19 evidence::{ArchiveTimeStamp, Membership, verify as ers},
20 provider::SignatureProvider,
21 stream::{Receiver, Source},
22 timestamp::TokenExtension,
23};
24use structured_ltv::pki::CheckedPath;
25use structured_pkix::temporal::{Instant, LeapTable};
26
27const FIELD: &str = "cades.evidence.record";
28
29pub struct Authority<'path, 'd> {
31 pub path: &'path CheckedPath<'d>,
33 pub certificate_parent: &'path Reader<'d>,
35}
36pub trait Authorities<'d> {
38 type Error;
40 fn select<'path, W: Work>(
42 &'path self,
43 index: usize,
44 token: &structured_cms::timestamp::Token<'d>,
45 work: &W,
46 ) -> Result<Authority<'path, 'd>, Self::Error>
47 where
48 'd: 'path;
49}
50
51#[derive(Debug)]
53pub enum SourceError<E> {
54 Cms(ObjectError),
56 Detached(E),
58}
59enum Object<'s, 'a, 'd, S> {
60 Cms(&'s mut CmsObject<'a, 'd>),
61 Detached(&'s mut S),
62}
63impl<S: Source> Source for Object<'_, '_, '_, S> {
64 type Error = SourceError<S::Error>;
65 fn stream<W: Work>(&mut self, out: &mut impl Receiver, work: &W) -> Result<(), Self::Error> {
66 match self {
67 Self::Cms(source) => source.stream(out, work).map_err(SourceError::Cms),
68 Self::Detached(source) => source.stream(out, work).map_err(SourceError::Detached),
69 }
70 }
71}
72#[derive(Debug)]
74pub enum SelectionError<E> {
75 Caller(E),
77 Processing(Error),
79}
80pub(super) struct Selected<'path, 'd, A> {
81 pub(super) source: &'path A,
82 pub(super) start: usize,
83 pub(super) items: RefCell<OwnedVec<Option<Authority<'path, 'd>>>>,
84}
85impl<'path, 'd, A> Selected<'path, 'd, A> {
86 pub(super) fn path<W: Work>(
87 &self,
88 index: usize,
89 work: &W,
90 at: u64,
91 ) -> Result<&'path CheckedPath<'d>, Error> {
92 work.charge_work(1)
93 .map_err(|cause| Error::resource(cause, FIELD, at))?;
94 let items = self.items.try_borrow().map_err(|_| Error::Foreign { at })?;
95 let selected = items
96 .get(index)
97 .and_then(Option::as_ref)
98 .ok_or(Error::Foreign { at })?;
99 Ok(selected.path)
100 }
101}
102impl<'path, 'd, A: Authorities<'d>> ers::Authorities<'d> for Selected<'path, 'd, A>
103where
104 'd: 'path,
105{
106 type Error = SelectionError<A::Error>;
107 fn select<'a, W: Work>(
108 &'a self,
109 index: usize,
110 stamp: &'a ArchiveTimeStamp<'d>,
111 work: &W,
112 ) -> Result<ers::Selection<'a>, Self::Error>
113 where
114 'd: 'a,
115 {
116 let at = stamp.original().element().span().start;
117 work.charge_work(1)
118 .map_err(|cause| SelectionError::Processing(Error::resource(cause, FIELD, at)))?;
119 let source: &'path A = self.source;
122 let structured_cms::evidence::TimeStamp::Rfc3161(token) = stamp.timestamp() else {
123 return Err(SelectionError::Processing(Error::Foreign { at }));
124 };
125 let selected: Authority<'path, 'd> = source
126 .select(self.start + index, token, work)
127 .map_err(SelectionError::Caller)?;
128 let target = selected
129 .path
130 .members()
131 .last()
132 .ok_or(SelectionError::Processing(Error::Foreign { at }))?;
133 let result = ers::Selection {
134 certificate: target.certificate(),
135 certificate_parent: selected.certificate_parent,
136 key: target.key(),
137 };
138 let mut items = self
139 .items
140 .try_borrow_mut()
141 .map_err(|_| SelectionError::Processing(Error::Foreign { at }))?;
142 let slot = items
143 .get_mut(index)
144 .ok_or(SelectionError::Processing(Error::Foreign { at }))?;
145 if slot.is_some() {
146 return Err(SelectionError::Processing(Error::Foreign { at }));
147 }
148 *slot = Some(selected);
149 Ok(result)
150 }
151}
152
153pub struct Checked<'prepared, 'm, 'd, 'path> {
158 pub(super) prepared: &'prepared Preparation<'m, 'd>,
159 pub(super) required: Required<'d>,
160 pub(super) proof: usize,
161 pub(super) covered: Option<OwnedVec<structured_asn1::Span>>,
162 pub(super) timestamps: core::ops::Range<usize>,
163 pub(super) checks: OwnedVec<timestamp::Checked<'m, 'd, 'path>>,
164 pub(super) ordering: OwnedVec<ers::OrderFinding>,
165 pub(super) complete: bool,
166}
167pub struct Verified<'check, 'prepared, 'm, 'd, 'path>(&'check Checked<'prepared, 'm, 'd, 'path>);
169impl<'prepared, 'm, 'd, 'path> Checked<'prepared, 'm, 'd, 'path> {
170 pub(super) fn bind_coverage<W: Work>(
171 &mut self,
172 spans: Option<OwnedVec<structured_asn1::Span>>,
173 work: &W,
174 ) -> Result<(), Error> {
175 self.covered = spans
176 .map(|spans| {
177 super::coverage::normalize_owned(spans, work, self.required.property.span().start)
178 })
179 .transpose()?;
180 Ok(())
181 }
182 pub fn checks(
184 &self,
185 ) -> impl DoubleEndedIterator<Item = ×tamp::Checked<'m, 'd, 'path>> + ExactSizeIterator
186 {
187 self.checks.iter().rev()
188 }
189 #[must_use]
191 pub fn ordering(&self) -> &[ers::OrderFinding] {
192 &self.ordering
193 }
194 #[must_use]
196 pub fn verified(&self) -> Option<Verified<'_, 'prepared, 'm, 'd, 'path>> {
197 if self.complete {
198 Some(Verified(self))
199 } else {
200 None
201 }
202 }
203}
204impl<'prepared, 'm, 'd, 'path> Verified<'_, 'prepared, 'm, 'd, 'path> {
205 pub(super) fn report(&self) -> &Checked<'prepared, 'm, 'd, 'path> {
206 self.0
207 }
208 #[must_use]
210 pub fn required(&self) -> Required<'d> {
211 self.0.required
212 }
213 #[must_use]
215 pub fn prepared(&self) -> &'prepared Preparation<'m, 'd> {
216 self.0.prepared
217 }
218 #[must_use]
220 pub fn timestamps(&self) -> core::ops::Range<usize> {
221 self.0.timestamps.clone()
222 }
223}
224
225#[derive(Debug)]
227pub enum CheckError<SE, PE, RE, AE, HE> {
228 Selection(Error),
230 Snapshot(super::SnapshotError),
232 Plan(ers::PlanError),
234 Authority(AE),
236 Record(
238 ers::VerificationError<
239 SourceError<SE>,
240 PE,
241 RE,
242 SelectionError<AE>,
243 HE,
244 ProfileError<PE, Error>,
245 >,
246 ),
247}
248
249pub type CheckResult<'a, 'm, 'd, 'path, SE, PE, RE, AE, HE> =
251 Result<Checked<'a, 'm, 'd, 'path>, CheckError<SE, PE, RE, AE, HE>>;
252
253impl<'a, 'm, 'd> View<'a, 'm, 'd> {
254 #[expect(
257 clippy::too_many_arguments,
258 reason = "Original CMS, current execution, selected provider/path and independent trust callbacks are separate agreed boundaries."
259 )]
260 #[expect(
261 clippy::result_large_err,
262 reason = "Source maps and opaque provider/history causes remain inline even on allocation refusal."
263 )]
264 pub fn check_internal<'path, P, PE, A, RE, HE>(
265 &self,
266 envelope: Element<'d>,
267 provider: &P,
268 operation: &Reader<'d>,
269 authorities: &'path A,
270 utc: &LeapTable<'d>,
271 reference: Instant,
272 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
273 historical: impl FnMut(ers::Historical<'_, '_>, &Execution<'_>) -> Result<ers::Decision, HE>,
274 ) -> CheckResult<'a, 'm, 'd, 'path, Infallible, PE, RE, A::Error, HE>
275 where
276 P: for<'scope> SignatureProvider<'scope, Error = PE>,
277 A: Authorities<'d>,
278 'd: 'path,
279 {
280 if self.required().kind != Kind::EvidenceRecordInternal {
281 return Err(CheckError::Selection(Error::Foreign {
282 at: self.required().property.span().start,
283 }));
284 }
285 let mut source = self
286 .cms_object(envelope, operation)
287 .map_err(CheckError::Selection)?;
288 let covered = source
289 .coverage(self, operation)
290 .map_err(CheckError::Selection)?;
291 self.check_objects(
292 covered,
293 provider,
294 operation,
295 authorities,
296 utc,
297 reference,
298 &mut [Object::<timestamp::NoDetached>::Cms(&mut source)],
299 Membership::Object,
300 recognizes,
301 historical,
302 )
303 }
304 #[expect(
308 clippy::too_many_arguments,
309 reason = "The actual previous snapshot supplements the independent source, provider/path and history inputs."
310 )]
311 #[expect(
312 clippy::result_large_err,
313 reason = "Snapshot and source-mapped verification failures preserve their causes without error-path allocation."
314 )]
315 pub fn check_internal_snapshot<'path, P, PE, A, RE, HE>(
316 &self,
317 envelope: Element<'d>,
318 snapshot: &super::CmsSnapshot<'_, 'd>,
319 provider: &P,
320 operation: &Reader<'d>,
321 authorities: &'path A,
322 utc: &LeapTable<'d>,
323 reference: Instant,
324 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
325 historical: impl FnMut(ers::Historical<'_, '_>, &Execution<'_>) -> Result<ers::Decision, HE>,
326 ) -> CheckResult<'a, 'm, 'd, 'path, Infallible, PE, RE, A::Error, HE>
327 where
328 P: for<'scope> SignatureProvider<'scope, Error = PE>,
329 A: Authorities<'d>,
330 'd: 'path,
331 {
332 if self.required().kind != Kind::EvidenceRecordInternal {
333 return Err(CheckError::Selection(Error::Foreign {
334 at: self.required().property.span().start,
335 }));
336 }
337 let mut source = self
338 .cms_object_snapshot(envelope, snapshot, operation)
339 .map_err(CheckError::Snapshot)?;
340 let covered = source
341 .coverage(self, operation)
342 .map_err(CheckError::Selection)?;
343 self.check_objects(
344 covered,
345 provider,
346 operation,
347 authorities,
348 utc,
349 reference,
350 &mut [Object::<timestamp::NoDetached>::Cms(&mut source)],
351 Membership::Object,
352 recognizes,
353 historical,
354 )
355 }
356 #[expect(
359 clippy::too_many_arguments,
360 reason = "The caller's detached source cannot be conflated with original CMS provenance or trust/provider callbacks."
361 )]
362 #[expect(
363 clippy::result_large_err,
364 reason = "Detached acquisition and original mapped/provider failures must survive resource refusal without boxing."
365 )]
366 pub fn check_external<'path, P, PE, A, S, RE, HE>(
367 &self,
368 envelope: Element<'d>,
369 detached: &mut S,
370 provider: &P,
371 operation: &Reader<'d>,
372 authorities: &'path A,
373 utc: &LeapTable<'d>,
374 reference: Instant,
375 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
376 historical: impl FnMut(ers::Historical<'_, '_>, &Execution<'_>) -> Result<ers::Decision, HE>,
377 ) -> CheckResult<'a, 'm, 'd, 'path, S::Error, PE, RE, A::Error, HE>
378 where
379 P: for<'scope> SignatureProvider<'scope, Error = PE>,
380 A: Authorities<'d>,
381 S: Source,
382 'd: 'path,
383 {
384 if self.required().kind != Kind::EvidenceRecordExternal {
385 return Err(CheckError::Selection(Error::Foreign {
386 at: self.required().property.span().start,
387 }));
388 }
389 let mut source = self
390 .cms_object(envelope, operation)
391 .map_err(CheckError::Selection)?;
392 let covered = source
393 .coverage(self, operation)
394 .map_err(CheckError::Selection)?;
395 self.check_objects(
396 covered,
397 provider,
398 operation,
399 authorities,
400 utc,
401 reference,
402 &mut [Object::Cms(&mut source), Object::Detached(detached)],
403 Membership::Group,
404 recognizes,
405 historical,
406 )
407 }
408
409 #[expect(
412 clippy::too_many_arguments,
413 reason = "Genuine previous CMS and detached content are distinct original sources alongside current verification inputs."
414 )]
415 #[expect(
416 clippy::result_large_err,
417 reason = "Original two-source and snapshot failures remain inline with the complete common verification cause."
418 )]
419 pub fn check_external_snapshot<'path, P, PE, A, S, RE, HE>(
420 &self,
421 envelope: Element<'d>,
422 snapshot: &super::CmsSnapshot<'_, 'd>,
423 detached: &mut S,
424 provider: &P,
425 operation: &Reader<'d>,
426 authorities: &'path A,
427 utc: &LeapTable<'d>,
428 reference: Instant,
429 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
430 historical: impl FnMut(ers::Historical<'_, '_>, &Execution<'_>) -> Result<ers::Decision, HE>,
431 ) -> CheckResult<'a, 'm, 'd, 'path, S::Error, PE, RE, A::Error, HE>
432 where
433 P: for<'scope> SignatureProvider<'scope, Error = PE>,
434 A: Authorities<'d>,
435 S: Source,
436 'd: 'path,
437 {
438 if self.required().kind != Kind::EvidenceRecordExternal {
439 return Err(CheckError::Selection(Error::Foreign {
440 at: self.required().property.span().start,
441 }));
442 }
443 let mut source = self
444 .cms_object_snapshot(envelope, snapshot, operation)
445 .map_err(CheckError::Snapshot)?;
446 let covered = source
447 .coverage(self, operation)
448 .map_err(CheckError::Selection)?;
449 self.check_objects(
450 covered,
451 provider,
452 operation,
453 authorities,
454 utc,
455 reference,
456 &mut [Object::Cms(&mut source), Object::Detached(detached)],
457 Membership::Group,
458 recognizes,
459 historical,
460 )
461 }
462
463 #[expect(
464 clippy::too_many_arguments,
465 reason = "This single kernel shares actual object/group recipes, provider/path checks and independent history callbacks."
466 )]
467 #[expect(
468 clippy::result_large_err,
469 reason = "Common ERS failures retain source mapping and opaque callback causes without a new allocation."
470 )]
471 fn check_objects<'path, P, PE, A, S, RE, HE>(
472 &self,
473 covered: OwnedVec<structured_asn1::Span>,
474 provider: &P,
475 operation: &Reader<'d>,
476 authorities: &'path A,
477 utc: &LeapTable<'d>,
478 reference: Instant,
479 objects: &mut [Object<'_, 'a, 'd, S>],
480 membership: Membership,
481 mut recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
482 mut historical: impl FnMut(ers::Historical<'_, '_>, &Execution<'_>) -> Result<ers::Decision, HE>,
483 ) -> CheckResult<'a, 'm, 'd, 'path, S::Error, PE, RE, A::Error, HE>
484 where
485 P: for<'scope> SignatureProvider<'scope, Error = PE>,
486 A: Authorities<'d>,
487 S: Source,
488 'd: 'path,
489 {
490 let required = self.required();
491 let record = self.record().ok_or_else(|| {
492 CheckError::Selection(Error::Foreign {
493 at: required.property.span().start,
494 })
495 })?;
496 let plan = record.input_plan(operation).map_err(CheckError::Plan)?;
497 let work = operation.execution();
498 let resource = |cause| {
499 CheckError::Selection(Error::resource(
500 cause,
501 FIELD,
502 required.property.span().start,
503 ))
504 };
505 let mut selected = work.vector();
506 let mut restrictions = work.vector();
507 selected
508 .try_reserve_exact(record.timestamps().len())
509 .map_err(resource)?;
510 restrictions
511 .try_reserve_exact(record.timestamps().len())
512 .map_err(resource)?;
513 for _ in record.timestamps() {
514 work.charge_work(1).map_err(resource)?;
515 selected.try_push(None).map_err(resource)?;
516 restrictions
517 .try_push(Cell::new(PathRestriction::Unproven))
518 .map_err(resource)?;
519 }
520 let selected = Selected {
521 source: authorities,
522 start: self.entry.timestamps.start,
523 items: RefCell::new(selected.into_owned()),
524 };
525 let report = plan
526 .verify_with_profile(
527 provider,
528 operation,
529 &selected,
530 utc,
531 reference,
532 objects,
533 membership,
534 |index, extension, execution| {
535 recognizes(self.entry.timestamps.start + index, extension, execution)
536 },
537 |mut facts, execution| {
538 facts.index += self.entry.timestamps.start;
539 historical(facts, execution)
540 },
541 |index, input| {
542 let path = selected
543 .path(
544 index,
545 &input.parent.execution(),
546 required.property.span().start,
547 )
548 .map_err(|cause| structured_cms::timestamp::ProfileFailure {
549 source: structured_cms::timestamp::ProfileSource::Envelope,
550 cause: ProfileError::Input(cause),
551 })?;
552 let restriction = timestamp::Context::profile_rules(&input, path, provider)?;
553 restrictions[index].set(restriction);
554 Ok(())
555 },
556 )
557 .map_err(CheckError::Record)?;
558 let mut checked = self
559 .finish_record(report, &selected, &restrictions, operation)
560 .map_err(CheckError::Selection)?;
561 checked
562 .bind_coverage(Some(covered), &work)
563 .map_err(CheckError::Selection)?;
564 Ok(checked)
565 }
566 pub(super) fn finish_record<'path, A, W: Work>(
567 &self,
568 report: ers::Report<'_, 'd>,
569 selected: &Selected<'path, 'd, A>,
570 restrictions: &OwnedVec<Cell<PathRestriction>, W>,
571 operation: &Reader<'d>,
572 ) -> Result<Checked<'a, 'm, 'd, 'path>, Error>
573 where
574 'd: 'path,
575 {
576 let required = self.required();
577 let work = operation.execution();
578 let resource = |cause| Error::resource(cause, FIELD, required.property.span().start);
579 let (_, results, ordering, mut complete) = report.into_parts();
580 let mut checks = work.vector();
581 checks.try_reserve_exact(results.len()).map_err(resource)?;
582 for result in results {
583 let (index, common, _, _) = result.into_parts();
584 let path = selected.path(index, &work, required.property.span().start)?;
585 let target = path
586 .members()
587 .last()
588 .expect("nonempty selection admitted above");
589 let global = self.entry.timestamps.start + index;
590 let checked = timestamp::with_token(self.prepared, global, operation, |context| {
591 Ok::<_, Infallible>(context.checked(
592 common,
593 restrictions[index].get(),
594 target.certificate(),
595 target.key(),
596 ))
597 })
598 .map_err(|cause| match cause {
599 VisitError::Processing(cause) => cause,
600 VisitError::Unreadable { property } => Error::Foreign {
601 at: property.span().start,
602 },
603 VisitError::Callback(never) => match never {},
604 })?;
605 if checked.actor().is_none() {
606 complete = false;
607 }
608 checks.try_push(checked).map_err(resource)?;
609 }
610 Ok(Checked {
611 prepared: self.prepared,
612 required,
613 proof: self.ordinal,
614 covered: None,
615 timestamps: self.entry.timestamps.clone(),
616 checks: checks.into_owned(),
617 ordering,
618 complete,
619 })
620 }
621}