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