1use crate::properties::legacy::LEGACY;
4use crate::source::{Document, DocumentId, Node, Original};
5use crate::{Cades, Error, ISSUER, Level, Result};
6use structured_asn1::memory::{OwnedMap, OwnedVec, Work};
7use structured_asn1::{Element, Reader, Span};
8use structured_cms::attribute::{Attribute, Attributes};
9use structured_cms::cades::{Profile, Role};
10use structured_cms::counter::{self, Visit, WalkError};
11use structured_cms::signer::SignerInfo;
12use structured_cms::{ErrorCode, oid};
13use structured_ltv::baseline::ArchiveCensus;
14use structured_ltv::timestamp::{Purpose, RequiredToken, TimestampRequirements, TimestampScope};
15
16const FIELD: &str = "cades";
17pub(crate) mod encoding;
18pub(crate) mod tokens;
19pub use tokens::ReadError;
20
21#[derive(Debug)]
23pub struct Finding<'model, 'document> {
24 pub location: Original<'model, 'document>,
26 pub severity: Severity,
28 pub rule: Rule,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Severity {
35 Required,
37 Advisory,
39}
40
41#[derive(Debug)]
43pub enum Rule {
44 Value {
46 attribute: structured_asn1::Oid<'static>,
48 field: &'static str,
50 },
51 Schema {
53 code: ErrorCode,
55 field: &'static str,
57 },
58 Missing(structured_asn1::Oid<'static>),
60 Forbidden(structured_asn1::Oid<'static>),
63 SigningCertificate,
65 CredentialReferences(structured_asn1::Oid<'static>),
68 SignatureTimestamp,
70 ArchiveTimestamp,
72 Certificate,
74 ContentType,
76 DataTypeDescription,
78 PolicyStore,
80 Md5,
82 ArchiveAlgorithm,
85 SigningCertificateVersion,
87 IssuerHint,
89}
90
91#[derive(Debug, Clone, Copy)]
94pub struct Timestamp<'a> {
95 owner: Node,
96 property: Node,
97 value: Option<crate::source::Value<'a>>,
98 purpose: Purpose,
99 preservation_kind: Option<crate::preservation::Kind>,
100}
101
102impl<'a> Timestamp<'a> {
103 #[must_use]
105 pub fn owner(&self) -> Node {
106 self.owner
107 }
108 #[must_use]
110 pub fn property(&self) -> Node {
111 self.property
112 }
113 #[must_use]
115 pub fn value(&self) -> Option<Element<'a>> {
116 self.value.map(|value| value.element())
117 }
118 #[must_use]
120 pub fn source(&self) -> Option<crate::source::Value<'a>> {
121 self.value
122 }
123 #[must_use]
125 pub fn purpose(&self) -> Purpose {
126 self.purpose
127 }
128 #[must_use]
131 pub fn preservation_kind(&self) -> Option<crate::preservation::Kind> {
132 self.preservation_kind
133 }
134}
135
136pub struct Counter<'a> {
139 parent: Node,
140 node: Node,
141 malformed: Option<structured_cms::Error>,
142 signature: core::ops::Range<usize>,
143 properties: Option<crate::properties::Properties<'a>>,
144 pub(crate) encoding: Option<encoding::SignerEncoding<'a>>,
145}
146
147impl<'a> Counter<'a> {
148 #[must_use]
150 pub fn parent(&self) -> Node {
151 self.parent
152 }
153 #[must_use]
155 pub fn node(&self) -> Node {
156 self.node
157 }
158 #[must_use]
160 pub fn malformed(&self) -> Option<&structured_cms::Error> {
161 self.malformed.as_ref()
162 }
163 #[must_use]
165 pub fn properties(&self) -> Option<&crate::properties::Properties<'a>> {
166 self.properties.as_ref()
167 }
168}
169
170pub struct Preparation<'model, 'document> {
173 document: &'model Document<'model, 'document>,
174 signer: &'model SignerInfo<'document>,
175 requested: Level,
176 profile: Profile<'document>,
177 properties: crate::properties::Properties<'document>,
178 findings: OwnedVec<Finding<'model, 'document>>,
179 counters: OwnedVec<Counter<'document>>,
180 parallel: Option<Parallel<'document>>,
181 counter_index: OwnedMap<Node, usize>,
182 signatures: OwnedVec<structured_cms::octets::Piece<'document>>,
183 timestamps: OwnedVec<Timestamp<'document>>,
184 pub(crate) timestamp_ranges: Option<OwnedMap<Node, core::ops::Range<usize>>>,
185 required: TimestampRequirements<Node, DocumentId<'model, 'document>, (), Cades>,
186 conforming: bool,
187 basic_conforming: bool,
188 preservation: OwnedVec<crate::preservation::Required<'document>>,
189 pub(crate) parsed: tokens::Cache<'model, 'document>,
190}
191
192struct Parallel<'d> {
193 counters: Option<OwnedVec<Counter<'d>>>,
194 index: Option<OwnedMap<Node, usize>>,
195 timestamps: Option<OwnedVec<Timestamp<'d>>>,
196}
197
198impl<'m, 'd> Preparation<'m, 'd> {
199 #[expect(
207 clippy::result_large_err,
208 reason = "keep original timestamp syntax failures and their independent source maps without refusal-time allocation"
209 )]
210 pub fn read(
211 document: &'m Document<'m, 'd>,
212 signer: &'m SignerInfo<'d>,
213 requested: Level,
214 parent: &Reader<'d>,
215 ) -> core::result::Result<Self, ReadError> {
216 let selected = document.signer(signer)?;
217 document.message().require_session(parent, FIELD)?;
218 let execution = parent.execution();
219 let profile = structured_cms::cades::inspect_profile(signer, Role::Signature, parent)?;
220 let mut findings = execution.vector();
221 let properties =
222 root_properties(document, signer, requested, &profile, parent, &mut findings)?;
223 let mut timestamps = execution.vector();
224 collect_timestamps(signer, parent, &mut timestamps)?;
225 let mut signatures = execution.vector();
226 let mut preservation = crate::preservation::roots(document.message(), parent, &execution)?;
227 let counters = read_counters(
228 document,
229 signer,
230 parent,
231 &mut findings,
232 &mut timestamps,
233 &mut signatures,
234 &mut preservation,
235 )?;
236 let mut parallel =
237 read_parallel(document, signer, parent, &mut signatures, &mut preservation)?;
238 let timestamp_ranges = history_timestamps(
239 &preservation,
240 &mut timestamps,
241 &mut parallel,
242 parent,
243 Node::of(signer.span()),
244 )?;
245 let tokens = token_obligations(×tamps, &execution, selected.node.span().start)?;
246 let CounterCatalogue {
247 children,
248 index: counter_index,
249 mut conforming,
250 } = counter_catalogue(&counters, parent, selected.node)?;
251 let mut basic_conforming = conforming;
252 let mut parsed = tokens::read(
253 document,
254 ×tamps,
255 selected.node,
256 &counter_index,
257 parent,
258 tokens::Findings {
259 rows: &mut findings,
260 work: &execution,
261 },
262 )?;
263 parallel_findings(document, parallel.as_ref(), &mut parsed, parent)?;
264 apply_findings(
265 &findings,
266 &execution,
267 &mut conforming,
268 &mut basic_conforming,
269 )?;
270 let required = TimestampRequirements::with_counters(
271 ISSUER,
272 selected.node,
273 TimestampScope::All(selected),
274 tokens.into_owned(),
275 children.into_owned(),
276 None,
277 );
278 Ok(Self {
279 document,
280 signer,
281 requested,
282 profile,
283 properties,
284 findings: findings.into_owned(),
285 counters,
286 parallel,
287 counter_index: counter_index.into_owned(),
288 signatures: signatures.into_owned(),
289 timestamps: timestamps.into_owned(),
290 timestamp_ranges,
291 required,
292 conforming,
293 basic_conforming,
294 preservation: preservation.into_owned(),
295 parsed,
296 })
297 }
298
299 #[must_use]
301 pub fn requested(&self) -> Level {
302 self.requested
303 }
304 #[must_use]
308 pub fn preservation(&self) -> &[crate::preservation::Required<'d>] {
309 &self.preservation
310 }
311 pub fn preservation_findings(&self) -> &[Finding<'m, 'd>] {
315 self.parsed.related.as_deref().unwrap_or(&[])
316 }
317 #[must_use]
319 pub fn signer(&self) -> &'m SignerInfo<'d> {
320 self.signer
321 }
322 #[must_use]
324 pub fn document(&self) -> &'m Document<'m, 'd> {
325 self.document
326 }
327 #[must_use]
329 pub fn profile(&self) -> &Profile<'d> {
330 &self.profile
331 }
332 #[must_use]
334 pub fn properties(&self) -> &crate::properties::Properties<'d> {
335 &self.properties
336 }
337 #[must_use]
339 pub fn counters(&self) -> &[Counter<'d>] {
340 &self.counters
341 }
342 pub fn parallel_counters(&self) -> &[Counter<'d>] {
346 self.parallel
347 .as_ref()
348 .and_then(|parallel| parallel.counters.as_deref())
349 .unwrap_or(&[])
350 }
351 pub(crate) fn source_timestamps(&self) -> impl Iterator<Item = &Timestamp<'d>> {
352 self.timestamps.iter().chain(
353 self.parallel
354 .as_ref()
355 .and_then(|parallel| parallel.timestamps.as_deref())
356 .unwrap_or(&[])
357 .iter(),
358 )
359 }
360 pub fn signature_input(
363 &self,
364 timestamp: usize,
365 parent: &Reader<'_>,
366 ) -> Result<Option<&[structured_cms::octets::Piece<'d>]>> {
367 self.document.message().require_session(parent, FIELD)?;
368 parent
369 .charge(1)
370 .map_err(|cause| Error::encoding(cause, FIELD))?;
371 let Some(token) = self.timestamps.get(timestamp) else {
372 return Ok(None);
373 };
374 self.signature_of(token.owner, parent).map(Some)
375 }
376 pub fn counter_content_input(
380 &self,
381 timestamp: usize,
382 parent: &Reader<'_>,
383 ) -> Result<Option<&[structured_cms::octets::Piece<'d>]>> {
384 self.document.message().require_session(parent, FIELD)?;
385 parent
386 .charge(1)
387 .map_err(|cause| Error::encoding(cause, FIELD))?;
388 let Some(token) = self.timestamps.get(timestamp) else {
389 return Ok(None);
390 };
391 match self.counter_parent(token.owner, parent)? {
392 None => Ok(None),
393 Some(owner) => self.signature_of(owner, parent).map(Some),
394 }
395 }
396 pub(crate) fn signature_of(
397 &self,
398 owner: Node,
399 parent: &Reader<'_>,
400 ) -> Result<&[structured_cms::octets::Piece<'d>]> {
401 self.signature_of_in(owner, parent, parent)
402 }
403 pub(crate) fn signature_of_in(
404 &self,
405 owner: Node,
406 origin: &Reader<'_>,
407 operation: &Reader<'_>,
408 ) -> Result<&[structured_cms::octets::Piece<'d>]> {
409 self.document.message().require_session(origin, FIELD)?;
410 let work = origin.execution_query(operation);
411 work.charge_work(1)
412 .map_err(|cause| Error::resource(cause, FIELD, owner.span().start))?;
413 if owner == Node::of(self.signer.span()) {
414 return Ok(self.signer.signature().pieces());
415 }
416 for signer in self.document.message().signers() {
417 work.charge_work(1)
418 .map_err(|cause| Error::resource(cause, FIELD, owner.span().start))?;
419 if owner == Node::of(signer.span()) {
420 return Ok(signer.signature().pieces());
421 }
422 }
423 Ok(&self.signatures[self.counter_record(owner, &work)?.signature.clone()])
424 }
425 pub(crate) fn signer_encoding(
426 &self,
427 owner: Node,
428 parent: &Reader<'_>,
429 ) -> Result<encoding::SignerView<'_, 'd>> {
430 self.document.message().require_session(parent, FIELD)?;
431 for signer in self.document.message().signers() {
432 parent
433 .charge(1)
434 .map_err(|cause| Error::encoding(cause, FIELD))?;
435 if owner == Node::of(signer.span()) {
436 return Ok(encoding::SignerView::Root(signer));
437 }
438 }
439 let work = parent.execution();
440 self.counter_record(owner, &work)?
441 .encoding
442 .as_ref()
443 .map(encoding::SignerView::Counter)
444 .ok_or(Error::Foreign {
445 at: owner.span().start,
446 })
447 }
448 pub(crate) fn counter_parent(&self, owner: Node, parent: &Reader<'_>) -> Result<Option<Node>> {
449 self.counter_parent_in(owner, parent, parent)
450 }
451 pub(crate) fn counter_parent_in(
452 &self,
453 owner: Node,
454 origin: &Reader<'_>,
455 operation: &Reader<'_>,
456 ) -> Result<Option<Node>> {
457 self.document.message().require_session(origin, FIELD)?;
458 let work = origin.execution_query(operation);
459 for signer in self.document.message().signers() {
460 work.charge_work(1)
461 .map_err(|cause| Error::resource(cause, FIELD, owner.span().start))?;
462 if owner == Node::of(signer.span()) {
463 return Ok(None);
464 }
465 }
466 Ok(Some(self.counter_record(owner, &work)?.parent))
467 }
468 fn counter_record(&self, owner: Node, work: &impl Work) -> Result<&Counter<'d>> {
469 let resource = |cause| Error::resource(cause, FIELD, owner.span().start);
470 if let Some(index) = self.counter_index.get(&owner, work).map_err(resource)? {
471 return Ok(&self.counters[*index]);
472 }
473 if let Some(parallel) = &self.parallel
474 && let Some(index) = ¶llel.index
475 && let Some(ordinal) = index.get(&owner, work).map_err(resource)?
476 {
477 return Ok(¶llel
478 .counters
479 .as_ref()
480 .expect("index belongs to its counter catalogue")[*ordinal]);
481 }
482 Err(Error::Foreign {
483 at: owner.span().start,
484 })
485 }
486 #[must_use]
488 pub fn timestamps(&self) -> &[Timestamp<'d>] {
489 &self.timestamps
490 }
491 #[must_use]
493 pub fn timestamp_requirements(
494 &self,
495 ) -> &TimestampRequirements<Node, DocumentId<'m, 'd>, (), Cades> {
496 &self.required
497 }
498 #[must_use]
500 pub fn findings(&self) -> &[Finding<'m, 'd>] {
501 &self.findings
502 }
503 #[must_use]
505 pub fn all_format_requirements_met(&self) -> bool {
506 self.conforming
507 }
508 #[must_use]
511 pub fn basic_format_requirements_met(&self) -> bool {
512 self.basic_conforming
513 }
514}
515
516impl<'m, 'd> ArchiveCensus for Preparation<'m, 'd> {
517 type Id = Node;
518 type Document = DocumentId<'m, 'd>;
519 type Generation = ();
520 fn signature(&self) -> Original<'m, 'd> {
521 self.document.locate(self.signer.span())
522 }
523 fn timestamps(&self) -> usize {
524 self.timestamps.len()
525 }
526 fn archive(&self, ordinal: usize) -> Option<Node> {
527 let token = &self.timestamps[ordinal];
528 if token.owner == Node::of(self.signer.span()) && token.purpose == Purpose::Archive {
529 Some(match token.value {
530 Some(value) => Node::of(value.element().span()),
531 None => token.property,
532 })
533 } else {
534 None
535 }
536 }
537}
538
539fn root_properties<'m, 'd, W: Work>(
540 document: &Document<'m, 'd>,
541 signer: &SignerInfo<'d>,
542 requested: Level,
543 profile: &structured_cms::cades::Profile<'d>,
544 parent: &Reader<'d>,
545 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
546) -> Result<crate::properties::Properties<'d>> {
547 root_rules(document, signer, requested, profile, parent, findings)?;
548 let properties = crate::properties::Properties::read(signer, parent)?;
549 property_findings(document, &properties, parent, findings)?;
550 mechanism_findings(document, signer, profile, &properties, parent, findings)?;
551 legacy_rules(
552 document,
553 signer,
554 &properties,
555 Some(requested),
556 parent,
557 findings,
558 )?;
559 Ok(properties)
560}
561
562fn apply_findings<W: Work>(
563 findings: &[Finding<'_, '_>],
564 work: &W,
565 conforming: &mut bool,
566 basic_conforming: &mut bool,
567) -> Result<()> {
568 for finding in findings {
569 work.charge_work(1)
570 .map_err(|cause| Error::resource(cause, FIELD, finding.location.node.span().start))?;
571 if finding.severity == Severity::Required {
572 *conforming = false;
573 if !matches!(
574 finding.rule,
575 Rule::SignatureTimestamp | Rule::ArchiveTimestamp | Rule::Forbidden(_)
576 ) {
577 *basic_conforming = false;
578 }
579 }
580 }
581 Ok(())
582}
583
584fn schema_findings<'m, 'd, W: Work>(
585 document: &Document<'m, 'd>,
586 signer: &SignerInfo<'_>,
587 profile: &Profile<'_>,
588 parent: &Reader<'_>,
589 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
590) -> Result<()> {
591 if let Some(signed) = signer.signed_attributes() {
592 for attribute in signed.items() {
593 parent
594 .charge(1 + attribute.kind().as_bytes().len().min(11) as u64)
595 .map_err(|cause| Error::encoding(cause, FIELD))?;
596 if crate::preservation::Kind::from_oid(attribute.kind())
597 .is_some_and(crate::preservation::Kind::is_archive)
598 {
599 finding(
604 document,
605 attribute.span(),
606 Severity::Required,
607 Rule::Schema {
608 code: ErrorCode::AttributePlacement,
609 field: "cades.historical",
610 },
611 findings,
612 )?;
613 }
614 }
615 }
616 for error in signer
617 .faults()
618 .iter()
619 .chain(profile.faults())
620 .chain(profile.signing().faults())
621 {
622 parent.charge(1).map_err(|e| Error::encoding(e, FIELD))?;
623 finding(
624 document,
625 Span {
626 start: error.offset(),
627 length: 0,
628 },
629 Severity::Required,
630 Rule::Schema {
631 code: error.code(),
632 field: error.field(),
633 },
634 findings,
635 )?;
636 }
637 for certificate in [profile.signing().v1(), profile.signing().v2()]
638 .into_iter()
639 .flatten()
640 {
641 for error in certificate.faults() {
642 parent.charge(1).map_err(|e| Error::encoding(e, FIELD))?;
643 finding(
644 document,
645 Span {
646 start: error.offset(),
647 length: 0,
648 },
649 Severity::Required,
650 Rule::Schema {
651 code: error.code(),
652 field: error.field(),
653 },
654 findings,
655 )?;
656 }
657 }
658 Ok(())
659}
660
661fn property_findings<'m, 'd, W: Work>(
662 document: &Document<'m, 'd>,
663 properties: &crate::properties::Properties<'_>,
664 parent: &Reader<'_>,
665 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
666) -> Result<()> {
667 for hint in properties.issuer_hints() {
668 parent
669 .charge(1)
670 .map_err(|cause| Error::encoding(cause, FIELD))?;
671 finding(
672 document,
673 *hint,
674 Severity::Advisory,
675 Rule::IssuerHint,
676 findings,
677 )?;
678 }
679 for fault in properties.faults() {
680 parent
681 .charge(1)
682 .map_err(|cause| Error::encoding(cause, FIELD))?;
683 finding(
684 document,
685 Span {
686 start: fault.cause.offset(),
687 length: 0,
688 },
689 Severity::Required,
690 match &fault.cause {
691 Error::Syntax {
692 code: ErrorCode::AttributeCardinality,
693 field,
694 ..
695 } => Rule::Schema {
696 code: ErrorCode::AttributeCardinality,
697 field,
698 },
699 _ => Rule::Value {
700 attribute: fault.attribute,
701 field: fault.field,
702 },
703 },
704 findings,
705 )?;
706 }
707 Ok(())
708}
709
710fn mechanism_findings<'m, 'd, W: Work>(
711 document: &Document<'m, 'd>,
712 signer: &SignerInfo<'_>,
713 profile: &Profile<'_>,
714 properties: &crate::properties::Properties<'_>,
715 parent: &Reader<'_>,
716 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
717) -> Result<()> {
718 let md5 = structured_asn1::Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 2, 5]);
719 parent
720 .charge(2 + signer.digest_algorithm().algorithm().as_bytes().len() as u64)
721 .map_err(|cause| Error::encoding(cause, FIELD))?;
722 if signer.digest_algorithm().algorithm() == md5 {
725 finding(
726 document,
727 signer.span(),
728 Severity::Required,
729 Rule::Md5,
730 findings,
731 )?;
732 }
733 for attribute in [profile.signing().v1(), profile.signing().v2()]
734 .into_iter()
735 .flatten()
736 {
737 for certificate in attribute.identifiers() {
738 parent
739 .charge(1)
740 .map_err(|cause| Error::encoding(cause, FIELD))?;
741 if let structured_cms::provider::DigestAlgorithm::Written(identifier) =
742 certificate.digest_algorithm()
743 {
744 parent
745 .charge(2 * (1 + identifier.algorithm().as_bytes().len() as u64))
746 .map_err(|cause| Error::encoding(cause, FIELD))?;
747 if identifier.algorithm() == md5 {
748 finding(
749 document,
750 certificate.element().span(),
751 Severity::Required,
752 Rule::Md5,
753 findings,
754 )?;
755 }
756 if attribute.version() == structured_cms::ess::Version::V2
760 && identifier.algorithm() == oid::SHA1
761 {
762 finding(
763 document,
764 certificate.element().span(),
765 Severity::Required,
766 Rule::SigningCertificateVersion,
767 findings,
768 )?;
769 }
770 }
771 if !matches!(
772 certificate.issuer_serial(),
773 structured_cms::ess::IssuerReading::Absent
774 ) {
775 finding(
776 document,
777 certificate.element().span(),
778 Severity::Advisory,
779 Rule::IssuerHint,
780 findings,
781 )?;
782 }
783 }
784 }
785 if let Some(policy) = properties.policy() {
786 parent
787 .charge(1 + policy.algorithm.algorithm().as_bytes().len() as u64)
788 .map_err(|cause| Error::encoding(cause, FIELD))?;
789 if policy.algorithm.algorithm() == md5 {
790 finding(
791 document,
792 signer.span(),
793 Severity::Required,
794 Rule::Md5,
795 findings,
796 )?;
797 }
798 }
799 if properties.store().is_some() && properties.policy().is_none_or(|policy| policy.hash_unknown)
802 {
803 finding(
804 document,
805 signer.span(),
806 Severity::Required,
807 Rule::PolicyStore,
808 findings,
809 )?;
810 }
811 Ok(())
812}
813
814fn legacy_rules<'m, 'd, W: Work>(
815 document: &Document<'m, 'd>,
816 signer: &SignerInfo<'_>,
817 properties: &crate::properties::Properties<'_>,
818 level: Option<Level>,
819 parent: &Reader<'_>,
820 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
821) -> Result<()> {
822 let mut credentials = None;
823 let mut seen = 0u8;
824 for (protected, set) in [
825 (true, signer.signed_attributes()),
826 (false, signer.unsigned_attributes()),
827 ] {
828 let Some(set) = set else {
829 continue;
830 };
831 for attribute in set.items() {
832 let Some((ordinal, kind)) = legacy_kind(attribute.kind(), parent)? else {
833 continue;
834 };
835 legacy_cardinality(
836 document,
837 attribute,
838 ordinal,
839 &mut seen,
840 level.is_some() && !protected,
841 parent,
842 findings,
843 )?;
844 if protected {
848 finding(
849 document,
850 attribute.span(),
851 Severity::Required,
852 Rule::Schema {
853 code: ErrorCode::AttributePlacement,
854 field: "cades.legacy.unsignedAttrs",
855 },
856 findings,
857 )?;
858 }
859 if let Some(level) = level {
862 finding(
863 document,
864 attribute.span(),
865 if level >= Level::LongTerm {
866 Severity::Required
867 } else {
868 Severity::Advisory
869 },
870 Rule::Forbidden(kind),
871 findings,
872 )?;
873 }
874 if kind != LEGACY[6] && kind != LEGACY[7] {
875 continue;
876 }
877 let present = if let Some(present) = credentials {
878 present
879 } else {
880 let present = protected_credential(signer, properties, parent)?;
881 credentials = Some(present);
882 present
883 };
884 if !present {
887 finding(
888 document,
889 attribute.span(),
890 Severity::Required,
891 Rule::CredentialReferences(kind),
892 findings,
893 )?;
894 }
895 }
896 }
897 Ok(())
898}
899
900fn legacy_cardinality<'m, 'd, W: Work>(
901 document: &Document<'m, 'd>,
902 attribute: &Attribute<'_>,
903 ordinal: u8,
904 seen: &mut u8,
905 root: bool,
906 parent: &Reader<'_>,
907 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
908) -> Result<()> {
909 parent
910 .charge(2)
911 .map_err(|cause| Error::encoding(cause, FIELD))?;
912 if root && ordinal != 4 && ordinal != 5 {
917 debug_assert!(ordinal < 8);
918 let bit = 1u8 << ordinal;
919 if *seen & bit != 0 {
920 finding(
921 document,
922 attribute.span(),
923 Severity::Required,
924 Rule::Schema {
925 code: ErrorCode::DuplicateAttribute,
926 field: "cades.legacy",
927 },
928 findings,
929 )?;
930 }
931 *seen |= bit;
932 }
933 Ok(())
934}
935
936fn legacy_kind(
937 kind: structured_asn1::Oid<'_>,
938 parent: &Reader<'_>,
939) -> Result<Option<(u8, structured_asn1::Oid<'static>)>> {
940 for (ordinal, expected) in (0u8..).zip(LEGACY) {
941 parent
942 .charge(1 + kind.as_bytes().len().min(expected.as_bytes().len()) as u64)
943 .map_err(|cause| Error::encoding(cause, FIELD))?;
944 if kind == expected {
945 return Ok(Some((ordinal, expected)));
946 }
947 }
948 Ok(None)
949}
950
951fn protected_credential(
952 signer: &SignerInfo<'_>,
953 properties: &crate::properties::Properties<'_>,
954 parent: &Reader<'_>,
955) -> Result<bool> {
956 let Some(protected_set) = signer.signed_attributes() else {
957 return Ok(false);
958 };
959 let protected = protected_set.element().span();
960 for value in properties.open_values() {
961 parent
962 .charge(1)
963 .map_err(|cause| Error::encoding(cause, FIELD))?;
964 let span = value.enclosure.span();
965 if span.start >= protected.start
966 && span.end() <= protected.end()
967 && matches!(
968 value.role,
969 crate::properties::OpenRole::AttributeCertificate
970 | crate::properties::OpenRole::OtherAttributeCertificate
971 | crate::properties::OpenRole::SignedAssertion
972 )
973 {
974 return Ok(true);
975 }
976 }
977 Ok(false)
978}
979
980fn root_rules<'m, 'd, W: Work>(
981 document: &Document<'m, 'd>,
982 signer: &SignerInfo<'_>,
983 requested: Level,
984 profile: &Profile<'_>,
985 parent: &Reader<'_>,
986 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
987) -> Result<()> {
988 schema_findings(document, signer, profile, parent, findings)?;
989 for error in document.message().faults() {
990 parent.charge(1).map_err(|e| Error::encoding(e, FIELD))?;
991 finding(
992 document,
993 Span {
994 start: error.offset(),
995 length: 0,
996 },
997 Severity::Required,
998 Rule::Schema {
999 code: error.code(),
1000 field: error.field(),
1001 },
1002 findings,
1003 )?;
1004 }
1005 parent
1006 .charge(1 + document.message().content().kind().as_bytes().len() as u64)
1007 .map_err(|e| Error::encoding(e, FIELD))?;
1008 if document.message().content().kind() != oid::DATA {
1009 finding(
1010 document,
1011 document.message().span(),
1012 Severity::Required,
1013 Rule::ContentType,
1014 findings,
1015 )?;
1016 }
1017 binding_findings(
1018 document,
1019 signer,
1020 structured_cms::binding::Scope::Content(document.message().content().kind()),
1021 parent,
1022 findings,
1023 )?;
1024 protected_rules(document, signer, profile, parent, findings)?;
1025 unsigned_rules(document, signer, requested, parent, findings)
1026}
1027
1028fn binding_findings<'m, 'd, 'a, W: Work>(
1029 document: &Document<'m, 'd>,
1030 signer: &SignerInfo<'a>,
1031 scope: structured_cms::binding::Scope<'a>,
1032 parent: &Reader<'a>,
1033 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
1034) -> Result<()> {
1035 match structured_cms::binding::inspect(signer, parent, scope) {
1036 Ok(_) => {}
1037 Err(error) if error.is_terminal() => return Err(Error::Cms(error)),
1038 Err(error) => finding(
1039 document,
1040 Span {
1041 start: error.offset(),
1042 length: 0,
1043 },
1044 Severity::Required,
1045 Rule::Schema {
1046 code: error.code(),
1047 field: error.field(),
1048 },
1049 findings,
1050 )?,
1051 }
1052 Ok(())
1053}
1054
1055fn protected_rules<'m, 'd, W: Work>(
1056 document: &Document<'m, 'd>,
1057 signer: &SignerInfo<'_>,
1058 profile: &Profile<'_>,
1059 parent: &Reader<'_>,
1060 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
1061) -> Result<()> {
1062 let protected = signer.signed_attributes();
1063 for kind in [oid::CONTENT_TYPE, oid::MESSAGE_DIGEST, oid::SIGNING_TIME] {
1064 if !present(document, protected, kind, parent, findings)? {
1065 finding(
1066 document,
1067 signer.span(),
1068 Severity::Required,
1069 Rule::Missing(kind),
1070 findings,
1071 )?;
1072 }
1073 }
1074 if profile.signing().v1().is_none() && profile.signing().v2().is_none() {
1075 finding(
1076 document,
1077 signer.span(),
1078 Severity::Required,
1079 Rule::SigningCertificate,
1080 findings,
1081 )?;
1082 }
1083 if document.message().certificates().is_empty() {
1084 finding(
1085 document,
1086 document.message().span(),
1087 Severity::Required,
1088 Rule::Certificate,
1089 findings,
1090 )?;
1091 }
1092 let hints = present(document, protected, oid::CONTENT_HINTS, parent, findings)?;
1093 let mime = present(document, protected, oid::MIME_TYPE, parent, findings)?;
1094 if !hints && !mime {
1095 finding(
1096 document,
1097 signer.span(),
1098 Severity::Advisory,
1099 Rule::DataTypeDescription,
1100 findings,
1101 )?;
1102 }
1103 for kind in [
1105 oid::COMMITMENT_TYPE_INDICATION,
1106 oid::SIGNER_LOCATION,
1107 oid::SIGNER_ATTRIBUTES_V2,
1108 oid::SIGNATURE_POLICY_IDENTIFIER,
1109 oid::CONTENT_REFERENCE,
1110 oid::CONTENT_IDENTIFIER,
1111 oid::SIGNATURE_POLICY_STORE,
1112 ] {
1113 let set = if kind == oid::SIGNATURE_POLICY_STORE {
1114 signer.unsigned_attributes()
1115 } else {
1116 protected
1117 };
1118 present(document, set, kind, parent, findings)?;
1119 }
1120 Ok(())
1121}
1122
1123fn unsigned_rules<'m, 'd, W: Work>(
1124 document: &Document<'m, 'd>,
1125 signer: &SignerInfo<'_>,
1126 requested: Level,
1127 parent: &Reader<'_>,
1128 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
1129) -> Result<()> {
1130 let mut signature_times = 0;
1131 let mut archives = 0;
1132 if let Some(unsigned) = signer.unsigned_attributes() {
1133 for attribute in unsigned.items() {
1134 parent
1135 .charge(1 + attribute.kind().as_bytes().len() as u64)
1136 .map_err(|e| Error::encoding(e, FIELD))?;
1137 if attribute.kind() == oid::SIGNATURE_TIMESTAMP {
1138 signature_times += 1;
1139 }
1140 if attribute.kind() == oid::ARCHIVE_TIMESTAMP_V3 {
1141 archives += 1;
1142 }
1143 if (attribute.kind() == oid::SIGNATURE_TIMESTAMP && requested == Level::Basic)
1146 || (attribute.kind() == oid::ARCHIVE_TIMESTAMP_V3 && requested < Level::Archival)
1147 {
1148 finding(
1149 document,
1150 attribute.span(),
1151 Severity::Advisory,
1152 Rule::Forbidden(if attribute.kind() == oid::SIGNATURE_TIMESTAMP {
1153 oid::SIGNATURE_TIMESTAMP
1154 } else {
1155 oid::ARCHIVE_TIMESTAMP_V3
1156 }),
1157 findings,
1158 )?;
1159 }
1160 }
1161 }
1162 if requested >= Level::Timestamped && signature_times == 0 {
1163 finding(
1164 document,
1165 signer.span(),
1166 Severity::Required,
1167 Rule::SignatureTimestamp,
1168 findings,
1169 )?;
1170 }
1171 if requested == Level::Archival && archives == 0 {
1172 finding(
1173 document,
1174 signer.span(),
1175 Severity::Required,
1176 Rule::ArchiveTimestamp,
1177 findings,
1178 )?;
1179 }
1180 Ok(())
1181}
1182
1183fn present<'m, 'd, W: Work>(
1184 document: &Document<'m, 'd>,
1185 set: Option<&Attributes<'_>>,
1186 kind: structured_asn1::Oid<'static>,
1187 parent: &Reader<'_>,
1188 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
1189) -> Result<bool> {
1190 let Some(set) = set else {
1191 return Ok(false);
1192 };
1193 match set.unique_in(kind, parent) {
1194 Ok(value) => Ok(value.is_some()),
1195 Err(cause) if cause.is_terminal() => Err(Error::Cms(cause)),
1196 Err(cause) => {
1197 finding(
1201 document,
1202 Span {
1203 start: cause.offset(),
1204 length: 0,
1205 },
1206 Severity::Required,
1207 Rule::Schema {
1208 code: cause.code(),
1209 field: cause.field(),
1210 },
1211 findings,
1212 )?;
1213 Ok(true)
1214 }
1215 }
1216}
1217
1218fn finding<'m, 'd, W: Work>(
1219 document: &Document<'m, 'd>,
1220 span: Span,
1221 severity: Severity,
1222 rule: Rule,
1223 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
1224) -> Result<()> {
1225 findings
1226 .try_push(Finding {
1227 location: document.locate(span),
1228 severity,
1229 rule,
1230 })
1231 .map_err(|cause| Error::resource(cause, FIELD, span.start))
1232}
1233
1234fn collect_timestamps<'a, W: Work>(
1235 signer: &SignerInfo<'a>,
1236 parent: &Reader<'a>,
1237 out: &mut OwnedVec<Timestamp<'a>, W>,
1238) -> Result<()> {
1239 for set in [signer.signed_attributes(), signer.unsigned_attributes()]
1240 .into_iter()
1241 .flatten()
1242 {
1243 for attribute in set.items() {
1244 parent
1245 .charge(3 * (1 + attribute.kind().as_bytes().len().min(12) as u64))
1246 .map_err(|e| Error::encoding(e, FIELD))?;
1247 let purpose = if attribute.kind() == oid::CONTENT_TIMESTAMP {
1248 Purpose::Content
1249 } else if attribute.kind() == oid::SIGNATURE_TIMESTAMP {
1250 Purpose::Signature
1251 } else if attribute.kind() == oid::ARCHIVE_TIMESTAMP_V3 {
1252 Purpose::Archive
1253 } else {
1254 continue;
1255 };
1256 if attribute.values().is_empty() {
1257 out.try_push(Timestamp {
1258 owner: Node::of(signer.span()),
1259 property: Node::of(attribute.span()),
1260 value: None,
1261 purpose,
1262 preservation_kind: None,
1263 })
1264 .map_err(|cause| Error::resource(cause, FIELD, attribute.span().start))?;
1265 }
1266 for (index, value) in attribute.values().iter().enumerate() {
1267 let source =
1268 attribute
1269 .value_reader_in(index, parent, FIELD)?
1270 .ok_or(Error::Foreign {
1271 at: value.span().start,
1272 })?;
1273 out.try_push(Timestamp {
1274 owner: Node::of(signer.span()),
1275 property: Node::of(attribute.span()),
1276 value: Some(crate::source::Value::of(&source, *value, FIELD)),
1277 purpose,
1278 preservation_kind: None,
1279 })
1280 .map_err(|cause| Error::resource(cause, FIELD, value.span().start))?;
1281 }
1282 }
1283 }
1284 Ok(())
1285}
1286
1287fn collect_historical_timestamps<'d, W: Work>(
1288 required: &[crate::preservation::Required<'d>],
1289 parent: &Reader<'d>,
1290 timestamps: &mut OwnedVec<Timestamp<'d>, W>,
1291) -> Result<()> {
1292 for entry in required {
1293 parent
1294 .charge(1)
1295 .map_err(|cause| Error::encoding(cause, FIELD))?;
1296 if !entry.kind.is_archive() && !entry.kind.is_reference_timestamp() {
1297 continue;
1298 }
1299 timestamps
1300 .try_push(Timestamp {
1301 owner: entry.signature,
1302 property: entry.property,
1303 value: entry.value,
1304 purpose: if entry.kind.is_archive() {
1305 Purpose::Archive
1306 } else {
1307 Purpose::ValidationData
1308 },
1309 preservation_kind: Some(entry.kind),
1310 })
1311 .map_err(|cause| Error::resource(cause, FIELD, entry.property.span().start))?;
1312 }
1313 Ok(())
1314}
1315
1316struct CounterCatalogue {
1317 children: OwnedVec<Node>,
1318 index: OwnedMap<Node, usize>,
1319 conforming: bool,
1320}
1321fn counter_catalogue(
1322 counters: &[Counter<'_>],
1323 parent: &Reader<'_>,
1324 signature: Node,
1325) -> Result<CounterCatalogue> {
1326 let work = parent.execution();
1327 let mut children = work.vector();
1328 children
1329 .try_reserve_exact(counters.len())
1330 .map_err(|cause| Error::resource(cause, FIELD, signature.span().start))?;
1331 let mut index = work.map();
1332 let mut conforming = true;
1333 for (ordinal, counter) in counters.iter().enumerate() {
1334 work.charge_work(1)
1335 .map_err(|cause| Error::resource(cause, FIELD, counter.node.span().start))?;
1336 if counter.malformed.is_some() {
1337 conforming = false;
1338 }
1339 children
1340 .try_push(counter.node)
1341 .map_err(|cause| Error::resource(cause, FIELD, counter.node.span().start))?;
1342 if index
1343 .try_insert(counter.node, ordinal)
1344 .map_err(|cause| Error::resource(cause, FIELD, counter.node.span().start))?
1345 .is_some()
1346 {
1347 return Err(Error::Foreign {
1348 at: counter.node.span().start,
1349 });
1350 }
1351 }
1352 Ok(CounterCatalogue {
1353 children: children.into_owned(),
1354 index: index.into_owned(),
1355 conforming,
1356 })
1357}
1358
1359fn timestamp_ranges(
1360 timestamps: &[Timestamp<'_>],
1361 parent: &Reader<'_>,
1362) -> Result<OwnedMap<Node, core::ops::Range<usize>>> {
1363 let work = parent.execution();
1364 let mut ranges = work.map();
1365 for (ordinal, entry) in timestamps.iter().enumerate() {
1366 let resource = |cause| Error::resource(cause, FIELD, entry.property.span().start);
1367 work.charge_work(1).map_err(resource)?;
1368 if let Some(previous) = ranges.get_mut(&entry.property, &work).map_err(resource)? {
1369 let previous: &mut core::ops::Range<usize> = previous;
1370 if previous.end != ordinal {
1373 return Err(Error::Foreign {
1374 at: entry.property.span().start,
1375 });
1376 }
1377 previous.end += 1;
1378 } else {
1379 ranges
1380 .try_insert(entry.property, ordinal..ordinal + 1)
1381 .map_err(resource)?;
1382 }
1383 }
1384 Ok(ranges.into_owned())
1385}
1386
1387fn history_timestamps<'d, W: Work>(
1388 preservation: &[crate::preservation::Required<'d>],
1389 timestamps: &mut OwnedVec<Timestamp<'d>, W>,
1390 parallel: &mut Option<Parallel<'d>>,
1391 parent: &Reader<'d>,
1392 signer: Node,
1393) -> Result<Option<OwnedMap<Node, core::ops::Range<usize>>>> {
1394 let execution = parent.execution();
1395 let mut historical = false;
1396 for required in preservation {
1397 execution
1398 .charge_work(1)
1399 .map_err(|cause| Error::resource(cause, FIELD, required.property.span().start))?;
1400 if required.kind.is_archive() || required.kind.is_reference_timestamp() {
1401 historical = true;
1402 break;
1403 }
1404 }
1405 if historical
1406 && let Some(parallel) = parallel
1407 && let Some(extra) = parallel.timestamps.take()
1408 {
1409 execution
1410 .charge_work(extra.len() as u64)
1411 .map_err(|cause| Error::resource(cause, FIELD, signer.span().start))?;
1412 for entry in extra {
1413 timestamps
1414 .try_push(entry)
1415 .map_err(|cause| Error::resource(cause, FIELD, signer.span().start))?;
1416 }
1417 }
1418 collect_historical_timestamps(preservation, parent, timestamps)?;
1419 if historical {
1420 Ok(Some(timestamp_ranges(timestamps, parent)?))
1421 } else {
1422 Ok(None)
1423 }
1424}
1425
1426fn parallel_lookup(
1427 counters: Option<&[Counter<'_>]>,
1428 parent: &Reader<'_>,
1429) -> Result<Option<OwnedMap<Node, usize>>> {
1430 let Some(counters) = counters else {
1431 return Ok(None);
1432 };
1433 let work = parent.execution();
1434 let mut lookup = work.map();
1435 for (ordinal, counter) in counters.iter().enumerate() {
1436 if lookup
1437 .try_insert(counter.node, ordinal)
1438 .map_err(|cause| Error::resource(cause, FIELD, counter.node.span().start))?
1439 .is_some()
1440 {
1441 return Err(Error::Foreign {
1442 at: counter.node.span().start,
1443 });
1444 }
1445 }
1446 Ok(Some(lookup.into_owned()))
1447}
1448
1449fn read_parallel<'d, W: Work>(
1450 document: &Document<'_, 'd>,
1451 selected: &SignerInfo<'d>,
1452 parent: &Reader<'d>,
1453 signatures: &mut OwnedVec<structured_cms::octets::Piece<'d>, W>,
1454 preservation: &mut OwnedVec<crate::preservation::Required<'d>, W>,
1455) -> Result<Option<Parallel<'d>>> {
1456 let work = parent.execution();
1457 let mut counters = None;
1458 let mut timestamps = None;
1459 for root in document.message().signers() {
1460 work.charge_work(1)
1461 .map_err(|cause| Error::resource(cause, FIELD, root.span().start))?;
1462 if core::ptr::eq(root, selected) {
1463 continue;
1464 }
1465 collect_timestamps(
1466 root,
1467 parent,
1468 timestamps.get_or_insert_with(|| work.vector()),
1469 )?;
1470 let visited = counter::walk(root, parent, |visit, reader| -> Result<()> {
1471 let entry = match visit {
1472 Visit::Signer {
1473 signer,
1474 parent: owner,
1475 ..
1476 } => {
1477 crate::preservation::collect(signer, reader, preservation)?;
1478 collect_timestamps(
1479 signer,
1480 reader,
1481 timestamps.get_or_insert_with(|| work.vector()),
1482 )?;
1483 let start = signatures.len();
1484 signatures
1485 .try_extend_from_slice(signer.signature().pieces())
1486 .map_err(|cause| Error::resource(cause, FIELD, signer.span().start))?;
1487 let properties =
1488 if crate::properties::Properties::has_legacy_values(signer, reader)? {
1489 Some(crate::properties::Properties::read(signer, reader)?)
1490 } else {
1491 None
1492 };
1493 Counter {
1494 parent: Node::of(owner),
1495 node: Node::of(signer.span()),
1496 malformed: None,
1497 signature: start..signatures.len(),
1498 properties,
1499 encoding: Some(encoding::SignerEncoding::read(signer, reader)?),
1500 }
1501 }
1502 Visit::Malformed {
1503 source,
1504 parent: owner,
1505 } => Counter {
1506 parent: Node::of(owner),
1507 node: Node::of(Span {
1508 start: source.offset(),
1509 length: 0,
1510 }),
1511 malformed: Some(source),
1512 signature: signatures.len()..signatures.len(),
1513 properties: None,
1514 encoding: None,
1515 },
1516 };
1517 let at = entry.node.span().start;
1518 counters
1519 .get_or_insert_with(|| work.vector())
1520 .try_push(entry)
1521 .map_err(|cause| Error::resource(cause, FIELD, at))
1522 })
1523 .map_err(|cause| match cause {
1524 WalkError::Processing(cause) => Error::Cms(cause),
1525 WalkError::Callback { source, .. } => source,
1526 })?;
1527 drop(visited);
1528 }
1529 let timestamps = timestamps
1530 .filter(|rows| !rows.is_empty())
1531 .map(OwnedVec::into_owned);
1532 let index = parallel_lookup(counters.as_deref(), parent)?;
1533 let counters = counters.map(OwnedVec::into_owned);
1534 if counters.is_none() && timestamps.is_none() {
1535 return Ok(None);
1536 }
1537 Ok(Some(Parallel {
1538 counters,
1539 index,
1540 timestamps,
1541 }))
1542}
1543
1544fn parallel_findings<'m, 'd>(
1545 document: &Document<'m, 'd>,
1546 parallel: Option<&Parallel<'d>>,
1547 parsed: &mut tokens::Cache<'m, 'd>,
1548 parent: &Reader<'d>,
1549) -> Result<()> {
1550 let Some(counters) = parallel.and_then(|parallel| parallel.counters.as_deref()) else {
1551 return Ok(());
1552 };
1553 let work = parent.execution();
1554 let mut related = None;
1555 for counter in counters {
1556 work.charge_work(1)
1557 .map_err(|cause| Error::resource(cause, FIELD, counter.node.span().start))?;
1558 let Some(cause) = counter.malformed() else {
1559 continue;
1560 };
1561 if related.is_none() {
1562 related = Some(match parsed.related.take() {
1563 Some(saved) => saved.with_execution(&work).map_err(|_| Error::Foreign {
1564 at: counter.node.span().start,
1565 })?,
1566 None => work.vector(),
1567 });
1568 }
1569 finding(
1573 document,
1574 counter.node.span(),
1575 Severity::Required,
1576 Rule::Schema {
1577 code: cause.code(),
1578 field: cause.field(),
1579 },
1580 related.as_mut().expect("created for this original finding"),
1581 )?;
1582 }
1583 if let Some(related) = related {
1584 parsed.related = Some(related.into_owned());
1585 }
1586 Ok(())
1587}
1588
1589fn read_counters<'m, 'd, W: Work>(
1590 document: &Document<'m, 'd>,
1591 signer: &SignerInfo<'d>,
1592 parent: &Reader<'d>,
1593 findings: &mut OwnedVec<Finding<'m, 'd>, W>,
1594 timestamps: &mut OwnedVec<Timestamp<'d>, W>,
1595 signatures: &mut OwnedVec<structured_cms::octets::Piece<'d>, W>,
1596 preservation: &mut OwnedVec<crate::preservation::Required<'d>, W>,
1597) -> Result<OwnedVec<Counter<'d>>> {
1598 counter::walk(signer, parent, |visit, reader| -> Result<Counter<'d>> {
1599 match visit {
1600 Visit::Signer {
1601 signer: child,
1602 parent: owner,
1603 ..
1604 } => {
1605 crate::preservation::collect(child, reader, preservation)?;
1606 let profile =
1607 structured_cms::cades::inspect_profile(child, Role::Countersignature, reader)?;
1608 schema_findings(document, child, &profile, reader, findings)?;
1609 binding_findings(
1614 document,
1615 child,
1616 structured_cms::binding::Scope::CounterSignature,
1617 reader,
1618 findings,
1619 )?;
1620 let properties = crate::properties::Properties::read(child, reader)?;
1621 property_findings(document, &properties, reader, findings)?;
1622 mechanism_findings(document, child, &profile, &properties, reader, findings)?;
1623 legacy_rules(document, child, &properties, None, reader, findings)?;
1624 collect_timestamps(child, reader, timestamps)?;
1625 let start = signatures.len();
1626 signatures
1627 .try_extend_from_slice(child.signature().pieces())
1628 .map_err(|cause| Error::resource(cause, FIELD, child.span().start))?;
1629 let end = signatures.len();
1630 Ok(Counter {
1631 parent: Node::of(owner),
1632 node: Node::of(child.span()),
1633 malformed: None,
1634 signature: start..end,
1635 properties: Some(properties),
1636 encoding: Some(encoding::SignerEncoding::read(child, reader)?),
1637 })
1638 }
1639 Visit::Malformed {
1640 source,
1641 parent: owner,
1642 } => {
1643 let at = source.offset();
1644 Ok(Counter {
1645 parent: Node::of(owner),
1646 node: Node::of(Span {
1647 start: at,
1648 length: 0,
1649 }),
1650 malformed: Some(source),
1651 signature: signatures.len()..signatures.len(),
1652 properties: None,
1653 encoding: None,
1654 })
1655 }
1656 }
1657 })
1658 .map_err(|error| match error {
1659 WalkError::Processing(error) => Error::Cms(error),
1660 WalkError::Callback { source: error, .. } => error,
1661 })
1662}
1663
1664fn token_obligations<W: Work>(
1665 timestamps: &[Timestamp<'_>],
1666 execution: &W,
1667 at: u64,
1668) -> Result<OwnedVec<RequiredToken<Node>, W>> {
1669 let mut tokens = execution.vector();
1670 tokens
1671 .try_reserve_exact(timestamps.len())
1672 .map_err(|cause| Error::resource(cause, FIELD, at))?;
1673 for entry in timestamps {
1674 tokens
1675 .try_push(RequiredToken {
1676 signature: entry.owner,
1677 property: entry.property,
1678 token: entry
1679 .value
1680 .map_or(entry.property, |value| Node::of(value.element().span())),
1681 purpose: entry.purpose,
1682 })
1683 .map_err(|cause| Error::resource(cause, FIELD, entry.property.span().start))?;
1684 }
1685 Ok(tokens)
1686}