1use super::{
4 ArchiveTimeStamp, EvidenceRecord, FIELD, Membership, ObjectInput, PriorSequence, TimeStamp,
5 input::{CachedInput, InputError, LazyMemos},
6};
7use crate::{
8 Error, ErrorCode,
9 content_source::ContentError,
10 digest::PassError,
11 provider::SignatureProvider,
12 stream::{RangesError, Source},
13 timestamp::{
14 self, CheckContext, CheckedTimestamp, Evidence, InfoError, ProfileCheckError,
15 ProfileFailure, TokenExtension,
16 },
17};
18use core::convert::Infallible;
19use structured_asn1::{
20 Reader,
21 digest::Resolution,
22 memory::{Execution, OwnedVec, Work},
23};
24use structured_pkix::{
25 Certificate,
26 algorithm::digest::{self, Algorithm},
27 path::WorkingKey,
28 temporal::{Instant, LeapTable},
29};
30
31pub struct Selection<'a> {
33 pub certificate: &'a Certificate<'a>,
35 pub certificate_parent: &'a Reader<'a>,
37 pub key: WorkingKey<'a>,
39}
40impl<'a> Selection<'a> {
41 fn historical_context<'s>(&'s self, utc: &'s LeapTable<'a>) -> CheckContext<'a, 's> {
42 CheckContext {
43 certificate: self.certificate,
44 certificate_parent: self.certificate_parent,
45 key: self.key,
46 requested: None,
47 utc,
48 reference: None,
49 window: None,
50 }
51 }
52}
53pub trait Authorities<'document> {
55 type Error;
57 fn select<'a, W: Work>(
59 &'a self,
60 index: usize,
61 stamp: &'a ArchiveTimeStamp<'document>,
62 work: &W,
63 ) -> Result<Selection<'a>, Self::Error>
64 where
65 'document: 'a;
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum Decision {
71 Valid,
73 Invalid,
75 Unknown,
77}
78pub struct Historical<'a, 'token> {
80 pub index: usize,
82 pub current: &'a CheckedTimestamp<'token>,
84 pub selection: &'a Selection<'token>,
86 pub following: Option<&'a Evidence>,
88 pub following_chain: Option<&'a Evidence>,
91 pub verification_time: Instant,
93}
94
95#[derive(Debug)]
97pub enum PlanError {
98 Processing(Error),
100 Information(ContentError<InfoError<AlgorithmError>>),
102 Nonconforming {
104 index: usize,
106 span: structured_asn1::Span,
108 },
109 UnsupportedTimestamp {
111 index: usize,
113 },
114 UnsupportedAlgorithm {
116 at: u64,
118 },
119}
120#[derive(Debug)]
123pub enum AlgorithmError {
124 Processing(Error),
126 Unsupported {
128 at: u64,
130 },
131}
132
133#[derive(Debug)]
135pub enum VerificationError<SE, PE, RE, AE, HE, FE = Infallible> {
136 Membership {
138 index: usize,
140 },
141 Plan(PlanError),
143 Processing(Error),
145 Acquisition(PassError<SE, PE>),
147 Selection {
149 index: usize,
151 cause: AE,
153 },
154 Timestamp {
156 index: usize,
158 cause: ProfileCheckError<InputError<SE, PE>, PE, RE, FE>,
160 },
161 Renewal {
163 index: usize,
165 cause: ProfileCheckError<InputError<RangesError, PE>, PE, RE, FE>,
167 },
168 Historical {
170 index: usize,
172 cause: HE,
174 },
175}
176
177pub type VerificationResult<'a, 'd, SE, PE, RE, AE, HE, FE = Infallible> =
180 Result<Report<'a, 'd>, VerificationError<SE, PE, RE, AE, HE, FE>>;
181
182pub struct Checked<'a> {
184 index: usize,
185 common: CheckedTimestamp<'a>,
186 selection: Selection<'a>,
187 history: Decision,
188}
189impl<'a> Checked<'a> {
190 #[must_use]
192 pub fn index(&self) -> usize {
193 self.index
194 }
195 #[must_use]
197 pub fn timestamp(&self) -> &CheckedTimestamp<'_> {
198 &self.common
199 }
200 #[must_use]
202 pub fn selection(&self) -> &Selection<'_> {
203 &self.selection
204 }
205 #[must_use]
207 pub fn historical(&self) -> Decision {
208 self.history
209 }
210 pub fn into_parts(self) -> (usize, CheckedTimestamp<'a>, Selection<'a>, Decision) {
215 (self.index, self.common, self.selection, self.history)
216 }
217}
218
219pub struct Report<'a, 'document> {
221 record: &'a EvidenceRecord<'document>,
222 checks: OwnedVec<Checked<'a>>,
223 ordering: OwnedVec<OrderFinding>,
224 complete: bool,
225}
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub struct OrderFinding {
229 pub earlier: usize,
231 pub later: usize,
233 pub cause: OrderCause,
235}
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub enum OrderCause {
239 Reversed,
241 Overlap,
243 Utc(structured_pkix::temporal::Uncertainty),
245 UnprovenTimestamp,
247 Bounds {
249 earlier: Option<timestamp::BoundUncertainty>,
251 later: Option<timestamp::BoundUncertainty>,
253 },
254}
255pub struct Verified<'report, 'record, 'document>(&'report Report<'record, 'document>);
258impl<'a, 'd> Report<'a, 'd> {
259 pub fn into_parts(
264 self,
265 ) -> (
266 &'a EvidenceRecord<'d>,
267 OwnedVec<Checked<'a>>,
268 OwnedVec<OrderFinding>,
269 bool,
270 ) {
271 (self.record, self.checks, self.ordering, self.complete)
272 }
273 pub fn checks(&self) -> impl DoubleEndedIterator<Item = &Checked<'a>> + ExactSizeIterator {
275 self.checks.iter().rev()
276 }
277 #[must_use]
279 pub fn ordering(&self) -> &[OrderFinding] {
280 &self.ordering
281 }
282 #[must_use]
284 pub fn verified(&self) -> Option<Verified<'_, 'a, 'd>> {
285 if self.complete {
286 Some(Verified(self))
287 } else {
288 None
289 }
290 }
291}
292impl<'a, 'd> Verified<'_, 'a, 'd> {
293 #[must_use]
295 pub fn record(&self) -> &'a EvidenceRecord<'d> {
296 self.0.record
297 }
298 pub fn checks(&self) -> impl DoubleEndedIterator<Item = &Checked<'a>> + ExactSizeIterator {
300 self.0.checks()
301 }
302}
303
304pub struct InputPlan<'record, 'document> {
308 record: &'record EvidenceRecord<'document>,
309 chains: OwnedVec<Algorithm>,
310}
311impl<'r, 'd> InputPlan<'r, 'd> {
312 #[expect(
313 clippy::result_large_err,
314 reason = "a carried algorithm failure retains its original source map and cause inline, including when no diagnostic allocation can succeed"
315 )]
316 fn read(record: &'r EvidenceRecord<'d>, operation: &Reader<'_>) -> Result<Self, PlanError> {
317 let origin = record
318 .original
319 .reader_in(operation)
320 .map_err(PlanError::Processing)?;
321 let work = origin.execution_query(operation);
322 let mut declared = work.vector();
323 for identifier in &record.algorithms {
324 let value = known(identifier, &work).map_err(original_algorithm_error)?;
325 insert_algorithm(&mut declared, value, &work, identifier.offset())
326 .map_err(PlanError::Processing)?;
327 }
328 let mut chains = work.vector();
329 let mut chain_index = 0;
330 for (index, stamp) in record.timestamps.iter().enumerate() {
331 work.charge_work(1).map_err(|cause| {
332 PlanError::Processing(Error::resource(
333 cause,
334 FIELD,
335 stamp.original.element.span().start,
336 ))
337 })?;
338 let TimeStamp::Rfc3161(token) = &stamp.token else {
339 return Err(PlanError::UnsupportedTimestamp { index });
340 };
341 if !token.faults().is_empty()
342 || !token.message().faults().is_empty()
343 || !token.signer().faults().is_empty()
344 {
345 return Err(PlanError::Nonconforming {
346 index,
347 span: token.message().span(),
348 });
349 }
350 let parent = stamp
351 .timestamp
352 .reader_in(operation)
353 .map_err(PlanError::Processing)?;
354 let timestamp_algorithm =
355 token_algorithm(token, &parent).map_err(PlanError::Information)?;
356 let value = if let Some(identifier) = stamp.algorithm {
357 known(&identifier, &work).map_err(original_algorithm_error)?
358 } else {
359 timestamp_algorithm
360 };
361 require_width(stamp, value.width(), &work).map_err(PlanError::Processing)?;
362 let chain = &record.chains[chain_index];
363 if index == chain.first {
364 chains.try_push(value).map_err(|cause| {
365 PlanError::Processing(Error::resource(
366 cause,
367 FIELD,
368 chain.original.element.span().start,
369 ))
370 })?;
371 } else if chains[chain_index] != value {
372 return Err(PlanError::Processing(Error::schema(
373 ErrorCode::AlgorithmProtection,
374 FIELD,
375 stamp.original.element.span().start,
376 )));
377 }
378 if index + 1 == chain.end {
379 chain_index += 1;
380 }
381 }
382 let mut used = work.vector();
383 for algorithm in &chains {
384 insert_algorithm(
385 &mut used,
386 *algorithm,
387 &work,
388 record.original.element.span().start,
389 )
390 .map_err(PlanError::Processing)?;
391 }
392 require_union(
393 &declared,
394 &used,
395 &work,
396 record.original.element.span().start,
397 )
398 .map_err(PlanError::Processing)?;
399 Ok(Self {
400 record,
401 chains: chains.into_owned(),
402 })
403 }
404 #[must_use]
407 pub fn algorithms(&self) -> &[Algorithm] {
408 &self.chains
409 }
410 #[must_use]
412 pub fn record(&self) -> &'r EvidenceRecord<'d> {
413 self.record
414 }
415
416 fn token(&self, index: usize) -> (&'r ArchiveTimeStamp<'d>, &'r timestamp::Token<'d>) {
417 let record = self.record;
418 let stamp = &record.timestamps[index];
419 let TimeStamp::Rfc3161(token) = &stamp.token else {
420 unreachable!("the immutable input plan admitted every timestamp format")
421 };
422 (stamp, token)
423 }
424
425 #[expect(
428 clippy::too_many_arguments,
429 reason = "the immutable plan keeps source, provider, authority and UTC contexts independent of the three differently scoped callbacks"
430 )]
431 #[expect(
432 clippy::result_large_err,
433 reason = "the literal-source entry preserves mapped timestamp and opaque callback failures without a diagnostic allocation"
434 )]
435 pub fn verify_with_profile<'a, P, PE, A, S, RE, HE, FE>(
436 &self,
437 provider: &'a P,
438 operation: &Reader<'a>,
439 authorities: &'a A,
440 utc: &LeapTable<'a>,
441 reference: Instant,
442 objects: &mut [S],
443 membership: Membership,
444 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
445 historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
446 profile: impl for<'view, 'scope> FnMut(
447 usize,
448 timestamp::ProfileInput<'view, 'd, 'a, 'scope>,
449 ) -> Result<(), ProfileFailure<FE>>,
450 ) -> VerificationResult<'a, 'd, S::Error, PE, RE, A::Error, HE, FE>
451 where
452 'r: 'a,
453 'd: 'a,
454 P: for<'scope> SignatureProvider<'scope, Error = PE>,
455 A: Authorities<'d>,
456 S: Source,
457 {
458 self.record.verify_plan_sources(
459 self,
460 provider,
461 operation,
462 authorities,
463 utc,
464 reference,
465 super::objects::Streams(objects),
466 membership,
467 recognizes,
468 historical,
469 profile,
470 )
471 }
472
473 #[expect(
476 clippy::too_many_arguments,
477 reason = "algorithm-dependent source execution has separate producing-reader, provider, authority and callback lifetimes"
478 )]
479 #[expect(
480 clippy::result_large_err,
481 reason = "request-source refusal keeps the actual mapped timestamp and caller causes inline on exhaustion"
482 )]
483 pub fn verify_requests_with_profile<'a, P, PE, A, S, RE, HE, FE>(
484 &self,
485 provider: &'a P,
486 operation: &Reader<'a>,
487 authorities: &'a A,
488 utc: &LeapTable<'a>,
489 reference: Instant,
490 objects: &mut [S],
491 membership: Membership,
492 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
493 historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
494 profile: impl for<'view, 'scope> FnMut(
495 usize,
496 timestamp::ProfileInput<'view, 'd, 'a, 'scope>,
497 ) -> Result<(), ProfileFailure<FE>>,
498 ) -> VerificationResult<'a, 'd, S::Error, PE, RE, A::Error, HE, FE>
499 where
500 'r: 'a,
501 'd: 'a,
502 P: for<'scope> SignatureProvider<'scope, Error = PE>,
503 A: Authorities<'d>,
504 S: crate::digest::RequestSource<'a, P>,
505 {
506 self.record.verify_plan_sources(
507 self,
508 provider,
509 operation,
510 authorities,
511 utc,
512 reference,
513 super::objects::Requests(objects),
514 membership,
515 recognizes,
516 historical,
517 profile,
518 )
519 }
520}
521#[expect(
525 clippy::result_large_err,
526 reason = "the actual carried information/source-map failure must survive before provider or covered-source side effects"
527)]
528pub fn token_algorithm<'d>(
529 token: ×tamp::Token<'d>,
530 parent: &Reader<'d>,
531) -> Result<Algorithm, ContentError<InfoError<AlgorithmError>>> {
532 token.with_info(parent, |info, reader, _| {
533 if info.has_faults() {
534 return Err(AlgorithmError::Processing(Error::schema(
535 ErrorCode::MalformedValue,
536 FIELD,
537 info.span().start,
538 )));
539 }
540 let timestamp::HashAlgorithmReading::Identifier(identifier) = info.imprint().algorithm()
541 else {
542 return Err(AlgorithmError::Processing(Error::schema(
543 ErrorCode::AlgorithmSyntax,
544 FIELD,
545 info.imprint().span().start,
546 )));
547 };
548 known(identifier, &reader.execution())
549 })
550}
551fn known<W: Work>(
552 identifier: &structured_pkix::AlgorithmIdentifier<'_>,
553 work: &W,
554) -> Result<Algorithm, AlgorithmError> {
555 match digest::algorithm(identifier, work)
556 .map_err(|cause| AlgorithmError::Processing(Error::pkix(cause)))?
557 {
558 Resolution::Supported(value) => Ok(value),
559 Resolution::Unsupported => Err(AlgorithmError::Unsupported {
560 at: identifier.offset(),
561 }),
562 Resolution::Malformed => Err(AlgorithmError::Processing(Error::schema(
563 ErrorCode::AlgorithmSyntax,
564 FIELD,
565 identifier.offset(),
566 ))),
567 }
568}
569fn original_algorithm_error(cause: AlgorithmError) -> PlanError {
570 match cause {
571 AlgorithmError::Processing(cause) => PlanError::Processing(cause),
572 AlgorithmError::Unsupported { at } => PlanError::UnsupportedAlgorithm { at },
573 }
574}
575fn insert_algorithm<W: Work>(
576 set: &mut OwnedVec<Algorithm, W>,
577 value: Algorithm,
578 work: &W,
579 at: u64,
580) -> crate::Result<()> {
581 for member in &*set {
585 work.charge_work(1)
586 .map_err(|cause| Error::resource(cause, FIELD, at))?;
587 if *member == value {
588 return Ok(());
589 }
590 }
591 set.try_push(value)
592 .map_err(|cause| Error::resource(cause, FIELD, at))
593}
594
595fn require_width<W: Work>(
596 stamp: &ArchiveTimeStamp<'_>,
597 width: usize,
598 work: &W,
599) -> crate::Result<()> {
600 if let Some(tree) = &stamp.tree {
601 for level in tree {
602 for hash in &level.hashes {
603 work.charge_work(1)
604 .map_err(|cause| Error::resource(cause, FIELD, hash.span().start))?;
605 if hash.len() != width {
606 return Err(Error::schema(
607 ErrorCode::DigestWidth,
608 FIELD,
609 hash.span().start,
610 ));
611 }
612 }
613 }
614 }
615 Ok(())
616}
617
618fn require_union<W: Work>(
619 declared: &[Algorithm],
620 used: &[Algorithm],
621 work: &W,
622 at: u64,
623) -> crate::Result<()> {
624 for (left, right) in [(declared, used), (used, declared)] {
627 for requested in left {
628 let mut found = false;
629 for actual in right {
630 work.charge_work(1)
631 .map_err(|cause| Error::resource(cause, FIELD, at))?;
632 if requested == actual {
633 found = true;
634 break;
635 }
636 }
637 if !found {
638 return Err(Error::schema(ErrorCode::AlgorithmProtection, FIELD, at));
639 }
640 }
641 }
642 Ok(())
643}
644
645impl<'document> EvidenceRecord<'document> {
646 #[expect(
649 clippy::result_large_err,
650 reason = "preflight returns the carried-source diagnostic even when no diagnostic allocation can succeed"
651 )]
652 pub fn input_plan(
653 &self,
654 operation: &Reader<'_>,
655 ) -> Result<InputPlan<'_, 'document>, PlanError> {
656 InputPlan::read(self, operation)
657 }
658 #[expect(
668 clippy::too_many_arguments,
669 reason = "standalone verification receives explicit source/group, provider, authority, UTC and historical/recognition boundaries"
670 )]
671 #[expect(
672 clippy::result_large_err,
673 reason = "standalone verification retains original failure categories without allocating error storage on resource refusal"
674 )]
675 pub fn verify<'a, P, PE, A, S, RE, HE>(
676 &'a self,
677 provider: &'a P,
678 operation: &Reader<'a>,
679 authorities: &'a A,
680 utc: &LeapTable<'a>,
681 reference: Instant,
682 objects: &mut [S],
683 membership: Membership,
684 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
685 historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
686 ) -> VerificationResult<'a, 'document, S::Error, PE, RE, A::Error, HE>
687 where
688 'document: 'a,
689 P: for<'scope> SignatureProvider<'scope, Error = PE>,
690 A: Authorities<'document>,
691 S: Source,
692 {
693 self.verify_with_profile(
694 provider,
695 operation,
696 authorities,
697 utc,
698 reference,
699 objects,
700 membership,
701 recognizes,
702 historical,
703 |_, _| Ok::<_, ProfileFailure<Infallible>>(()),
704 )
705 }
706
707 #[expect(
711 clippy::too_many_arguments,
712 reason = "profiled standalone verification keeps original provider/key/UTC inputs and three independently scoped callbacks explicit"
713 )]
714 #[expect(
715 clippy::result_large_err,
716 reason = "the complete carried mapping and profile/caller causes survive on the nonallocating failure path"
717 )]
718 pub fn verify_with_profile<'a, P, PE, A, S, RE, HE, FE>(
719 &'a self,
720 provider: &'a P,
721 operation: &Reader<'a>,
722 authorities: &'a A,
723 utc: &LeapTable<'a>,
724 reference: Instant,
725 objects: &mut [S],
726 membership: Membership,
727 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
728 historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
729 profile: impl for<'view, 'scope> FnMut(
730 usize,
731 timestamp::ProfileInput<'view, 'document, 'a, 'scope>,
732 ) -> Result<(), ProfileFailure<FE>>,
733 ) -> VerificationResult<'a, 'document, S::Error, PE, RE, A::Error, HE, FE>
734 where
735 'document: 'a,
736 P: for<'scope> SignatureProvider<'scope, Error = PE>,
737 A: Authorities<'document>,
738 S: Source,
739 {
740 self.verify_sources(
741 provider,
742 operation,
743 authorities,
744 utc,
745 reference,
746 super::objects::Streams(objects),
747 membership,
748 recognizes,
749 historical,
750 profile,
751 )
752 }
753
754 #[expect(
759 clippy::too_many_arguments,
760 reason = "schema-derived request sources retain their original authority, execution and callback boundaries without a second dispatcher"
761 )]
762 #[expect(
763 clippy::result_large_err,
764 reason = "format-source errors and mapped token/profile refusals remain distinct without allocating error storage"
765 )]
766 pub fn verify_requests_with_profile<'a, P, PE, A, S, RE, HE, FE>(
767 &'a self,
768 provider: &'a P,
769 operation: &Reader<'a>,
770 authorities: &'a A,
771 utc: &LeapTable<'a>,
772 reference: Instant,
773 objects: &mut [S],
774 membership: Membership,
775 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
776 historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
777 profile: impl for<'view, 'scope> FnMut(
778 usize,
779 timestamp::ProfileInput<'view, 'document, 'a, 'scope>,
780 ) -> Result<(), ProfileFailure<FE>>,
781 ) -> VerificationResult<'a, 'document, S::Error, PE, RE, A::Error, HE, FE>
782 where
783 'document: 'a,
784 P: for<'scope> SignatureProvider<'scope, Error = PE>,
785 A: Authorities<'document>,
786 S: crate::digest::RequestSource<'a, P>,
787 {
788 self.verify_sources(
789 provider,
790 operation,
791 authorities,
792 utc,
793 reference,
794 super::objects::Requests(objects),
795 membership,
796 recognizes,
797 historical,
798 profile,
799 )
800 }
801
802 #[expect(
803 clippy::too_many_arguments,
804 reason = "one dispatch forwards the same literal/request-source verification contexts and separately scoped callbacks"
805 )]
806 #[expect(
807 clippy::result_large_err,
808 reason = "forwarding cannot replace preflight, source-map or opaque caller errors with an allocating wrapper"
809 )]
810 fn verify_sources<'a, P, PE, A, S, RE, HE, FE>(
811 &'a self,
812 provider: &'a P,
813 operation: &Reader<'a>,
814 authorities: &'a A,
815 utc: &LeapTable<'a>,
816 reference: Instant,
817 objects: S,
818 membership: Membership,
819 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
820 historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
821 profile: impl for<'view, 'scope> FnMut(
822 usize,
823 timestamp::ProfileInput<'view, 'document, 'a, 'scope>,
824 ) -> Result<(), ProfileFailure<FE>>,
825 ) -> VerificationResult<'a, 'document, S::Error, PE, RE, A::Error, HE, FE>
826 where
827 'document: 'a,
828 P: for<'scope> SignatureProvider<'scope, Error = PE>,
829 A: Authorities<'document>,
830 S: super::objects::Sources<'a, P>,
831 {
832 let plan = InputPlan::read(self, operation).map_err(VerificationError::Plan)?;
833 self.verify_plan_sources(
834 &plan,
835 provider,
836 operation,
837 authorities,
838 utc,
839 reference,
840 objects,
841 membership,
842 recognizes,
843 historical,
844 profile,
845 )
846 }
847
848 #[expect(
849 clippy::too_many_arguments,
850 reason = "the single execution kernel binds the original plan and all already-admitted source/provider/callback contexts"
851 )]
852 #[expect(
853 clippy::result_large_err,
854 reason = "token, renewal, selector and source-map refusals are returned unchanged when allocation is unavailable"
855 )]
856 fn verify_plan_sources<'a, P, PE, A, S, RE, HE, FE>(
857 &'a self,
858 plan: &InputPlan<'a, 'document>,
859 provider: &'a P,
860 operation: &Reader<'a>,
861 authorities: &'a A,
862 utc: &LeapTable<'a>,
863 reference: Instant,
864 objects: S,
865 membership: Membership,
866 mut recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
867 mut historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
868 mut profile: impl for<'view, 'scope> FnMut(
869 usize,
870 timestamp::ProfileInput<'view, 'document, 'a, 'scope>,
871 ) -> Result<(), ProfileFailure<FE>>,
872 ) -> VerificationResult<'a, 'document, S::Error, PE, RE, A::Error, HE, FE>
873 where
874 'document: 'a,
875 P: for<'scope> SignatureProvider<'scope, Error = PE>,
876 A: Authorities<'document>,
877 S: super::objects::Sources<'a, P>,
878 {
879 let origin = self
880 .original
881 .reader_in(operation)
882 .map_err(VerificationError::Processing)?;
883 let work = origin.execution_query(operation);
884 let at = self.original.element.span().start;
885 let resource = |cause| VerificationError::Processing(Error::resource(cause, FIELD, at));
886 admit_membership(self, objects.len(), membership, &work).map_err(|cause| match cause {
887 GroupError::Processing(cause) => VerificationError::Processing(cause),
888 GroupError::Membership(index) => VerificationError::Membership { index },
889 })?;
890 let projected =
893 PriorSequence::all(self, operation).map_err(VerificationError::Processing)?;
894 let prefixes = prior_views(self, &projected, operation, &work)
895 .map_err(VerificationError::Processing)?;
896 let memo_set = LazyMemos::new(provider, &plan.chains, objects);
897 let mut checks: OwnedVec<Checked<'a>, _> = work.vector();
898 checks
899 .try_reserve_exact(self.timestamps.len())
900 .map_err(resource)?;
901 let mut complete = true;
902 for chain_index in (0..self.chains.len()).rev() {
903 let chain = &self.chains[chain_index];
904 let prior = prefixes[chain_index].as_ref();
905 for index in (chain.first..chain.end).rev() {
906 work.charge_work(1).map_err(resource)?;
907 let (stamp, token) = plan.token(index);
908 let reader = stamp
909 .timestamp
910 .reader_in(operation)
911 .map_err(VerificationError::Processing)?;
912 let selection = authorities
913 .select(index, stamp, &reader.execution())
914 .map_err(|cause| VerificationError::Selection { index, cause })?;
915 let context = selection.historical_context(utc);
916 let common = if index == chain.first {
917 let mut input = CachedInput {
918 stamp,
919 memos: &memo_set,
920 ordinal: chain_index,
921 membership,
922 previous: prior,
923 };
924 token
925 .check_historical_input_with_profile(
926 provider,
927 &reader,
928 &context,
929 &mut input,
930 |extension, execution| recognizes(index, extension, execution),
931 |input| profile(index, input),
932 )
933 .map_err(|cause| VerificationError::Timestamp { index, cause })?
934 } else {
935 let mut source = [[self.timestamps[index - 1].timestamp.element.as_bytes()]];
936 let mut input = ObjectInput::new(stamp, &mut source, Membership::Object);
937 token
938 .check_historical_input_with_profile(
939 provider,
940 &reader,
941 &context,
942 &mut input,
943 |extension, execution| recognizes(index, extension, execution),
944 |input| profile(index, input),
945 )
946 .map_err(|cause| VerificationError::Renewal { index, cause })?
947 };
948 let history = historical(
949 history_input(
950 self,
951 chain_index,
952 index,
953 &common,
954 &selection,
955 &checks,
956 reference,
957 ),
958 &reader.execution(),
959 )
960 .map_err(|cause| VerificationError::Historical { index, cause })?;
961 complete &= common.evidence().is_some() && history == Decision::Valid;
962 checks
963 .try_push(Checked {
964 index,
965 common,
966 selection,
967 history,
968 })
969 .map_err(resource)?;
970 }
971 }
972 let ordering = order_findings(&checks, utc, operation, &work, at)
973 .map_err(VerificationError::Processing)?;
974 complete &= ordering.is_empty();
975 Ok(Report {
976 record: self,
977 checks: checks.into_owned(),
978 ordering,
979 complete,
980 })
981 }
982}
983
984enum GroupError {
985 Processing(Error),
986 Membership(usize),
987}
988fn admit_membership<W: Work>(
989 record: &EvidenceRecord<'_>,
990 objects: usize,
991 membership: Membership,
992 work: &W,
993) -> Result<(), GroupError> {
994 let at = record.original.element.span().start;
995 if objects == 0 || (membership == Membership::Object && objects != 1) {
996 return Err(GroupError::Processing(Error::schema(
997 ErrorCode::MissingField,
998 FIELD,
999 at,
1000 )));
1001 }
1002 for chain in &record.chains {
1003 work.charge_work(1)
1004 .map_err(|cause| GroupError::Processing(Error::resource(cause, FIELD, at)))?;
1005 if !super::input::membership_shape(&record.timestamps[chain.first], objects, membership) {
1006 return Err(GroupError::Membership(chain.first));
1007 }
1008 }
1009 Ok(())
1010}
1011type Prefixes<'r, 'd> = OwnedVec<Option<PriorSequence<'r, 'd>>>;
1012fn prior_views<'r, 'd, W: Work>(
1013 record: &'r EvidenceRecord<'d>,
1014 projected: &'r [crate::encoding::Projection<'d>],
1015 operation: &Reader<'_>,
1016 work: &W,
1017) -> crate::Result<Prefixes<'r, 'd>> {
1018 let resource = |cause| Error::resource(cause, FIELD, record.original.element.span().start);
1019 let mut prefixes = work.vector();
1020 prefixes
1021 .try_reserve_exact(record.chains.len())
1022 .map_err(resource)?;
1023 prefixes.try_push(None).map_err(resource)?;
1024 for index in 1..record.chains.len() {
1025 prefixes
1026 .try_push(Some(PriorSequence::borrowed(
1027 record, index, projected, operation,
1028 )?))
1029 .map_err(resource)?;
1030 }
1031 Ok(prefixes.into_owned())
1032}
1033fn order_findings<W: Work>(
1034 checks: &[Checked<'_>],
1035 utc: &LeapTable<'_>,
1036 operation: &Reader<'_>,
1037 work: &W,
1038 at: u64,
1039) -> crate::Result<OwnedVec<OrderFinding>> {
1040 let resource = |cause| Error::resource(cause, FIELD, at);
1041 let mut findings = work.vector();
1042 for pair in checks.windows(2) {
1043 work.charge_work(1).map_err(resource)?;
1044 if let Some(cause) = order(&pair[1], &pair[0], utc, operation)? {
1045 findings
1046 .try_push(OrderFinding {
1047 earlier: pair[1].index,
1048 later: pair[0].index,
1049 cause,
1050 })
1051 .map_err(resource)?;
1052 }
1053 }
1054 Ok(findings.into_owned())
1055}
1056
1057fn history_input<'v, 'a>(
1058 record: &EvidenceRecord<'_>,
1059 chain: usize,
1060 index: usize,
1061 current: &'v CheckedTimestamp<'a>,
1062 selection: &'v Selection<'a>,
1063 checks: &'v [Checked<'a>],
1064 reference: Instant,
1065) -> Historical<'v, 'a> {
1066 let following = checks.last().and_then(|next| next.common.evidence());
1067 let following_chain = record
1068 .chains
1069 .get(chain + 1)
1070 .and_then(|next| checks.get(record.timestamps.len() - 1 - next.first))
1071 .and_then(|next| next.common.evidence());
1072 Historical {
1073 index,
1074 current,
1075 selection,
1076 following,
1077 following_chain,
1078 verification_time: reference,
1079 }
1080}
1081
1082fn order(
1083 earlier: &Checked<'_>,
1084 later: &Checked<'_>,
1085 utc: &LeapTable<'_>,
1086 operation: &Reader<'_>,
1087) -> crate::Result<Option<OrderCause>> {
1088 let work = operation.execution();
1089 let (Some(a), Some(b)) = (earlier.common.evidence(), later.common.evidence()) else {
1090 return Ok(Some(OrderCause::UnprovenTimestamp));
1091 };
1092 order_evidence(
1093 a,
1094 earlier.selection.certificate,
1095 b,
1096 later.selection.certificate,
1097 utc,
1098 &work,
1099 )
1100}
1101
1102pub fn order_evidence<W: Work>(
1107 a: &Evidence,
1108 left: &Certificate<'_>,
1109 b: &Evidence,
1110 right: &Certificate<'_>,
1111 utc: &LeapTable<'_>,
1112 work: &W,
1113) -> crate::Result<Option<OrderCause>> {
1114 let resource = |cause| Error::resource(cause, FIELD, a.source().carrier().start);
1115 if a.facts().ordering() && b.facts().ordering() {
1116 let first = left.execution_in(work);
1117 let second = right.execution_in(work);
1118 first.charge_work(1).map_err(resource)?;
1119 second.charge_work(1).map_err(resource)?;
1120 let same = if core::ptr::eq(left, right) {
1121 true
1122 } else if left.as_bytes().len() != right.as_bytes().len() {
1123 false
1124 } else {
1125 first
1126 .charge_work(left.as_bytes().len() as u64)
1127 .map_err(resource)?;
1128 second
1129 .charge_work(right.as_bytes().len() as u64)
1130 .map_err(resource)?;
1131 left.as_bytes() == right.as_bytes()
1132 };
1133 if same {
1134 return match a.facts().compare_generation(b.facts(), utc, work) {
1139 Ok(core::cmp::Ordering::Greater) => Ok(Some(OrderCause::Reversed)),
1140 Ok(_) => Ok(None),
1141 Err(timestamp::GenerationOrderError::Resource(cause)) => Err(resource(cause)),
1142 Err(timestamp::GenerationOrderError::Uncertain(cause)) => {
1143 Ok(Some(OrderCause::Utc(cause)))
1144 }
1145 };
1146 }
1147 }
1148 let a = match timestamp::interval_in(a.facts().gen_time(), a.facts().accuracy(), utc, work) {
1149 Ok(value) => Ok(value),
1150 Err(timestamp::IntervalError::Resource(cause)) => return Err(resource(cause)),
1151 Err(timestamp::IntervalError::Uncertain(cause)) => Err(cause),
1152 };
1153 let b = match timestamp::interval_in(b.facts().gen_time(), b.facts().accuracy(), utc, work) {
1154 Ok(value) => Ok(value),
1155 Err(timestamp::IntervalError::Resource(cause)) => return Err(resource(cause)),
1156 Err(timestamp::IntervalError::Uncertain(cause)) => Err(cause),
1157 };
1158 match (a, b) {
1159 (Ok(a), Ok(b)) if a.latest <= b.earliest => Ok(None),
1160 (Ok(a), Ok(b)) if a.earliest > b.latest => Ok(Some(OrderCause::Reversed)),
1161 (Ok(_), Ok(_)) => Ok(Some(OrderCause::Overlap)),
1162 (a, b) => Ok(Some(OrderCause::Bounds {
1163 earlier: a.err(),
1164 later: b.err(),
1165 })),
1166 }
1167}