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