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