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