1use super::{
4 CheckError, CheckTime, Checked, Context, InputError, ProfileFailure, ProfileSource, tsp,
5};
6use crate::{
7 Cades, Error, ISSUER,
8 prepare::Preparation,
9 source::{DocumentId, Node},
10};
11use core::cell::Cell;
12use structured_asn1::digest::Resolution;
13use structured_asn1::memory::Memory;
14use structured_asn1::{
15 Reader,
16 memory::{Execution, OwnedVec, Work},
17};
18use structured_cms::{
19 Octets,
20 archive::{self, HashIndex, IndexCoverage},
21 digest::{DigestBatch, DigestError, PassError},
22};
23use structured_cms::{
24 provider::SignatureProvider,
25 stream::{Receiver, Refused, Source},
26};
27use structured_ltv::{
28 material::Coverage,
29 source::{Query, Scope, Source as _},
30};
31use structured_ltv::{pki::CheckedPath, timestamp::Purpose};
32
33#[derive(Debug)]
36pub enum NoDetached {}
37impl Source for NoDetached {
38 type Error = core::convert::Infallible;
39 fn stream<W: Work>(&mut self, _: &mut impl Receiver, _: &W) -> Result<(), Self::Error> {
40 match *self {}
41 }
42}
43
44#[derive(Debug)]
46pub enum ArchiveError<SE, PE> {
47 Processing(Error),
49 Digest(DigestError<PE>),
51 Unsupported,
53 MalformedAlgorithm,
55 ForbiddenDigest {
57 at: u64,
59 },
60 UnmatchedIndex,
62 AlgorithmMismatch,
64 Content(PassError<SE, PE>),
66 MissingDigest,
68}
69
70#[derive(Debug)]
72pub enum ArchiveStreamError {
73 Resource(structured_asn1::memory::MemoryError),
75 Receiver(Refused),
77 MissingDigest,
79 Source(structured_cms::Error),
81}
82
83enum Content<'s, 'd, S> {
84 Carried(&'s Octets<'d>),
85 Detached(&'s mut S),
86}
87impl<S: Source> Source for Content<'_, '_, S> {
88 type Error = InputError<S::Error>;
89 fn stream<W: Work>(
90 &mut self,
91 receiver: &mut impl Receiver,
92 work: &W,
93 ) -> Result<(), Self::Error> {
94 match self {
95 Self::Carried(source) => source.stream(receiver, work).map_err(InputError::Carried),
96 Self::Detached(source) => source.stream(receiver, work).map_err(InputError::Detached),
97 }
98 }
99}
100
101pub struct ArchiveChecked<'model, 'document, 'path> {
105 timestamp: Checked<'model, 'document, 'path>,
106 index: IndexCoverage,
107 coverage: Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
108}
109
110#[derive(Clone, Copy)]
114pub struct ArchiveView<'view, 'model, 'document, 'path> {
115 timestamp: &'view Checked<'model, 'document, 'path>,
116 coverage: &'view Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
117}
118impl<'v, 'm, 'd, 'p> From<&'v ArchiveChecked<'m, 'd, 'p>> for ArchiveView<'v, 'm, 'd, 'p> {
119 fn from(value: &'v ArchiveChecked<'m, 'd, 'p>) -> Self {
120 Self {
121 timestamp: value.timestamp(),
122 coverage: value.coverage(),
123 }
124 }
125}
126impl<'v, 'm, 'd, 'p> From<&'v super::historical::HistoricalChecked<'m, 'd, 'p>>
127 for ArchiveView<'v, 'm, 'd, 'p>
128{
129 fn from(value: &'v super::historical::HistoricalChecked<'m, 'd, 'p>) -> Self {
130 Self {
131 timestamp: value.timestamp(),
132 coverage: value.coverage(),
133 }
134 }
135}
136impl<'v, 'm, 'd, 'p> From<&'v crate::evidence::RecordArchive<'_, '_, 'm, 'd, 'p>>
137 for ArchiveView<'v, 'm, 'd, 'p>
138{
139 fn from(value: &'v crate::evidence::RecordArchive<'_, '_, 'm, 'd, 'p>) -> Self {
140 Self {
141 timestamp: value.timestamp(),
142 coverage: value.coverage(),
143 }
144 }
145}
146impl<'m, 'd, 'p> ArchiveView<'_, 'm, 'd, 'p> {
147 pub(crate) fn timestamp(&self) -> &Checked<'m, 'd, 'p> {
148 self.timestamp
149 }
150 pub(crate) fn coverage(&self) -> &Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades> {
151 self.coverage
152 }
153}
154impl<'m, 'd, 'p> ArchiveChecked<'m, 'd, 'p> {
155 #[must_use]
157 pub fn timestamp(&self) -> &Checked<'m, 'd, 'p> {
158 &self.timestamp
159 }
160 #[must_use]
162 pub fn index(&self) -> &IndexCoverage {
163 &self.index
164 }
165 #[must_use]
168 pub fn coverage(&self) -> &Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades> {
169 &self.coverage
170 }
171
172 pub fn requirements(
177 &self,
178 prepared: &Preparation<'m, 'd>,
179 operation: &Reader<'d>,
180 ) -> Result<
181 structured_ltv::timestamp::TimestampRequirements<Node, DocumentId<'m, 'd>, (), Cades>,
182 ScopeError,
183 > {
184 super::requirements::derive(&self.timestamp, &self.coverage, prepared, operation)
185 }
186}
187
188#[derive(Debug)]
190pub enum ScopeError {
191 Processing(Error),
193 Unproven,
195 Foreign,
197 Coverage(structured_ltv::material::CoverageError<Node>),
199}
200
201struct Input<'s, 'm, 'd> {
202 prepared: &'s Preparation<'m, 'd>,
203 index: &'s HashIndex<'d>,
204 hash: &'s Cell<Option<OwnedVec<u8>>>,
205}
206impl Source for Input<'_, '_, '_> {
207 type Error = ArchiveStreamError;
208 fn stream<W: Work>(
209 &mut self,
210 receiver: &mut impl Receiver,
211 work: &W,
212 ) -> Result<(), Self::Error> {
213 let hash = self.hash.take().ok_or(ArchiveStreamError::MissingDigest)?;
214 let ranges = archive::imprint_input(
215 self.prepared.document().message(),
216 self.prepared.signer(),
217 self.index,
218 &hash,
219 )
220 .map_err(ArchiveStreamError::Source)?;
221 for range in ranges {
222 work.charge_work(1).map_err(ArchiveStreamError::Resource)?;
223 receiver
224 .write(range)
225 .map_err(ArchiveStreamError::Receiver)?;
226 }
227 Ok(())
228 }
229}
230
231#[derive(Debug)]
234pub enum ArchiveCheckError<SE, PE, RE> {
235 Before(ArchiveError<SE, PE>),
237 Timestamp(CheckError<ArchiveStreamError, PE, RE, ArchiveError<SE, PE>>),
239}
240
241impl<'m, 'd> Context<'_, 'm, 'd> {
242 #[expect(
247 clippy::result_large_err,
248 reason = "index refusal and the original mapped token cause are returned without allocating on an already exhausted account"
249 )]
250 pub fn check_archive_carried<'a, 'p, P, PE, RE>(
251 &'a self,
252 provider: &'a P,
253 path: &'p CheckedPath<'d>,
254 certificate_parent: &Reader<'d>,
255 time: &CheckTime<'_, 'd>,
256 recognizes: impl FnMut(&tsp::TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
257 ) -> Result<
258 ArchiveChecked<'m, 'd, 'p>,
259 ArchiveCheckError<InputError<core::convert::Infallible>, PE, RE>,
260 >
261 where
262 P: for<'scope> SignatureProvider<'scope, Error = PE>,
263 {
264 let source = self
265 .prepared
266 .document()
267 .message()
268 .content()
269 .content()
270 .ok_or_else(|| {
271 ArchiveCheckError::Before(ArchiveError::Processing(Error::Foreign {
272 at: self.entry.property().span().start,
273 }))
274 })?;
275 self.archive(
276 provider,
277 path,
278 certificate_parent,
279 time,
280 &mut Content::<NoDetached>::Carried(source),
281 recognizes,
282 )
283 }
284
285 #[expect(
289 clippy::result_large_err,
290 reason = "detached acquisition and nested archive token failures keep their distinct original provenance without boxing"
291 )]
292 pub fn check_archive_detached<'a, 'p, P, S: Source, PE, RE>(
293 &'a self,
294 provider: &'a P,
295 path: &'p CheckedPath<'d>,
296 certificate_parent: &Reader<'d>,
297 time: &CheckTime<'_, 'd>,
298 detached: &mut S,
299 recognizes: impl FnMut(&tsp::TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
300 ) -> Result<ArchiveChecked<'m, 'd, 'p>, ArchiveCheckError<InputError<S::Error>, PE, RE>>
301 where
302 P: for<'scope> SignatureProvider<'scope, Error = PE>,
303 {
304 if !self.prepared.document().message().content().is_detached() {
305 return Err(ArchiveCheckError::Before(ArchiveError::Processing(
306 Error::Foreign {
307 at: self.entry.property().span().start,
308 },
309 )));
310 }
311 self.archive(
312 provider,
313 path,
314 certificate_parent,
315 time,
316 &mut Content::Detached(detached),
317 recognizes,
318 )
319 }
320
321 #[expect(
322 clippy::result_large_err,
323 reason = "one archive transaction propagates owned source maps and opaque source/provider causes without a refusal-time allocation"
324 )]
325 fn archive<'a, 'p, P, S: Source, PE, RE>(
326 &'a self,
327 provider: &'a P,
328 path: &'p CheckedPath<'d>,
329 certificate_parent: &Reader<'d>,
330 time: &CheckTime<'_, 'd>,
331 original: &mut S,
332 recognizes: impl FnMut(&tsp::TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
333 ) -> Result<ArchiveChecked<'m, 'd, 'p>, ArchiveCheckError<S::Error, PE, RE>>
334 where
335 P: for<'scope> SignatureProvider<'scope, Error = PE>,
336 {
337 let before = ArchiveCheckError::Before;
338 let processing = |cause| before(ArchiveError::Processing(cause));
339 self.require_archive().map_err(processing)?;
340 let index = HashIndex::of_token(self.token, self.reader)
341 .map_err(|cause| processing(Error::Cms(cause)))?;
342 let coverage = self.matched_index(&index, provider).map_err(before)?;
343 let hash = Cell::new(None);
344 let mut covered = Input {
345 prepared: self.prepared,
346 index: &index,
347 hash: &hash,
348 };
349 let checked = self
350 .check_profile(
351 provider,
352 path,
353 certificate_parent,
354 time,
355 &mut covered,
356 (recognizes, |input| {
357 let failure = |cause| ProfileFailure {
358 source: ProfileSource::Information,
359 cause,
360 };
361 let tsp::HashAlgorithmReading::Identifier(algorithm) =
362 input.info.imprint().algorithm()
363 else {
364 return Err(failure(ArchiveError::MalformedAlgorithm));
365 };
366 match archive::same_algorithm(&index, algorithm, &input.reader.execution())
367 .map_err(|cause| failure(ArchiveError::Processing(Error::Cms(cause))))?
368 {
369 Resolution::Supported(true) => {}
370 Resolution::Supported(false) => {
371 return Err(failure(ArchiveError::AlgorithmMismatch));
372 }
373 Resolution::Unsupported => return Err(failure(ArchiveError::Unsupported)),
374 Resolution::Malformed => {
375 return Err(failure(ArchiveError::MalformedAlgorithm));
376 }
377 }
378 let work = input.reader.execution();
379 let identity = match structured_pkix::algorithm::digest::resolve(
380 provider, algorithm, &work,
381 )
382 .map_err(|cause| failure(ArchiveError::Processing(Error::Pkix(cause))))?
383 {
384 Resolution::Supported(value) => value,
385 Resolution::Unsupported => return Err(failure(ArchiveError::Unsupported)),
386 Resolution::Malformed => {
387 return Err(failure(ArchiveError::MalformedAlgorithm));
388 }
389 };
390 let mut identities = work.vector();
391 identities.try_push(identity).map_err(|cause| {
392 failure(ArchiveError::Processing(Error::resource(
393 cause,
394 "cades.archive",
395 input.info.imprint().span().start,
396 )))
397 })?;
398 let batch = DigestBatch::prepare(
399 provider,
400 identities.into_owned(),
401 &work,
402 "cades.archive.content",
403 self.entry.property().span().start,
404 )
405 .map_err(|cause| failure(ArchiveError::Digest(cause)))?;
406 let memo = batch
407 .run(original, &work)
408 .map_err(|cause| failure(ArchiveError::Content(cause)))?;
409 let digest = memo
410 .for_request(0, &work)
411 .map_err(|cause| failure(ArchiveError::Digest(cause)))?
412 .ok_or_else(|| failure(ArchiveError::MissingDigest))?;
413 let mut retained = work.vector();
417 retained
418 .try_extend_from_slice(digest.as_bytes())
419 .map_err(|cause| {
420 failure(ArchiveError::Processing(Error::resource(
421 cause,
422 "cades.archive.content",
423 self.entry.property().span().start,
424 )))
425 })?;
426 hash.set(Some(retained.into_owned()));
427 Ok(())
428 }),
429 )
430 .map_err(ArchiveCheckError::Timestamp)?;
431 let selected = self.coverage(&coverage).map_err(processing)?;
432 Ok(ArchiveChecked {
433 timestamp: checked,
434 index: coverage,
435 coverage: selected,
436 })
437 }
438
439 fn matched_index<'a, P, SE, PE>(
440 &'a self,
441 index: &HashIndex<'d>,
442 provider: &'a P,
443 ) -> Result<IndexCoverage, ArchiveError<SE, PE>>
444 where
445 P: for<'scope> SignatureProvider<'scope, Error = PE>,
446 {
447 let processing = ArchiveError::Processing;
448 self.reader
449 .charge(1 + index.algorithm().algorithm().as_bytes().len().min(8) as u64)
450 .map_err(|cause| processing(Error::encoding(cause, "cades.archive.index")))?;
451 if index.algorithm().algorithm()
452 == structured_asn1::Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 2, 5])
453 {
454 return Err(ArchiveError::ForbiddenDigest {
455 at: index.algorithm().offset(),
456 });
457 }
458 let coverage = match archive::check_index(
459 index,
460 self.prepared.document().message(),
461 self.prepared.signer(),
462 provider,
463 self.reader,
464 )
465 .map_err(ArchiveError::Digest)?
466 {
467 Resolution::Supported(value) => value,
468 Resolution::Unsupported => return Err(ArchiveError::Unsupported),
469 Resolution::Malformed => return Err(ArchiveError::MalformedAlgorithm),
470 };
471 if !coverage.is_valid() {
472 return Err(ArchiveError::UnmatchedIndex);
473 }
474 Ok(coverage)
475 }
476 fn coverage(
477 &self,
478 index: &IndexCoverage,
479 ) -> crate::Result<Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades>> {
480 let work = self.reader.execution();
481 let resource = |cause| {
482 Error::resource(
483 cause,
484 "cades.archive.coverage",
485 self.entry.property().span().start,
486 )
487 };
488 let mut items = work.map();
489 let message = self.prepared.document().message();
490 for (choice, covered) in message
491 .certificate_choices()
492 .iter()
493 .zip(index.certificates())
494 {
495 work.charge_work(1).map_err(resource)?;
496 let span = match choice {
497 structured_cms::signed::CertificateChoice::X509(ordinal) => {
498 message.certificates()[*ordinal].span()
499 }
500 structured_cms::signed::CertificateChoice::Extended(value)
501 | structured_cms::signed::CertificateChoice::AttributeV1(value)
502 | structured_cms::signed::CertificateChoice::AttributeV2(value)
503 | structured_cms::signed::CertificateChoice::Other(value) => value.span(),
504 };
505 items
506 .try_insert(Node::of(span), *covered)
507 .map_err(resource)?;
508 }
509 for (value, covered) in message.revocations().iter().zip(index.revocations()) {
510 work.charge_work(1).map_err(resource)?;
511 items
512 .try_insert(Node::of(value.span()), *covered)
513 .map_err(resource)?;
514 }
515 let mut ranges = work.vector();
516 if let Some(attributes) = self.prepared.signer().signed_attributes() {
517 ranges
522 .try_push(attributes.element().span())
523 .map_err(resource)?;
524 for attribute in attributes.items() {
525 work.charge_work(1).map_err(resource)?;
526 items
527 .try_insert(Node::of(attribute.span()), true)
528 .map_err(resource)?;
529 }
530 }
531
532 if let Some(attributes) = self.prepared.signer().unsigned_attributes() {
533 for attribute in attributes.items() {
534 work.charge_work(1).map_err(resource)?;
535 let all = !attribute.values().is_empty();
539 items
540 .try_insert(Node::of(attribute.span()), all)
541 .map_err(resource)?;
542 }
543 for (position, covered) in index.attribute_values() {
544 work.charge_work(1).map_err(resource)?;
545 let attribute = &attributes.items()[position.attribute];
546 let value = &attribute.values()[position.value];
547 items
548 .try_insert(Node::of(value.span()), *covered)
549 .map_err(resource)?;
550 if *covered {
551 ranges.try_push(value.span()).map_err(resource)?;
552 } else if let Some(all) = items
553 .get_mut(&Node::of(attribute.span()), &work)
554 .map_err(resource)?
555 {
556 *all = false;
557 }
558 }
559 }
560 self.include_nested(&mut items, &mut ranges, &work)?;
561 let source = self.prepared.document();
562 let query = Scope::new(source, source.snapshot(), &work);
563 query
564 .admit(self.entry.property(), 1)
565 .map_err(|cause| match cause {
566 structured_ltv::source::ScopeError::Changed { node } => Error::Foreign {
567 at: node.span().start,
568 },
569 structured_ltv::source::ScopeError::Resource { cause, .. } => resource(cause),
570 })?;
571 Ok(Coverage::for_token(
572 ISSUER,
573 source.snapshot(),
574 Node::of(self.prepared.signer().span()),
575 self.entry.property(),
576 Node::of(
577 self.entry
578 .value()
579 .expect("archive requires original token")
580 .span(),
581 ),
582 Purpose::Archive,
583 items.into_owned(),
584 ))
585 }
586 fn include_nested<W: Work>(
587 &self,
588 items: &mut structured_asn1::memory::OwnedMap<Node, bool, W>,
589 ranges: &mut OwnedVec<structured_asn1::Span, W>,
590 work: &W,
591 ) -> crate::Result<()> {
592 let resource = |cause| {
593 Error::resource(
594 cause,
595 "cades.archive.coverage",
596 self.entry.property().span().start,
597 )
598 };
599 ranges
600 .try_sort_unstable_by(|left, right| {
601 work.charge_work(1)?;
602 Ok::<_, structured_asn1::memory::MemoryError>(left.start.cmp(&right.start))
603 })
604 .map_err(|cause| match cause {
605 structured_asn1::memory::SortError::Resource(cause)
606 | structured_asn1::memory::SortError::Comparison(cause) => resource(cause),
607 })?;
608 let mut maximum = 0;
609 for span in &mut *ranges {
610 work.charge_work(1).map_err(resource)?;
611 maximum = maximum.max(span.start + span.length);
612 span.length = maximum - span.start;
613 }
614 for counter in self.prepared.counters() {
615 let covered = contains(ranges, counter.node(), work).map_err(resource)?;
616 items
617 .try_insert(counter.node(), covered)
618 .map_err(resource)?;
619 }
620 for token in self.prepared.timestamps() {
621 let covered = contains(ranges, token.property(), work).map_err(resource)?;
622 if token.owner() != self.entry.owner() {
623 items
624 .try_insert(token.property(), covered)
625 .map_err(resource)?;
626 }
627 if let Some(value) = token.value() {
628 items
629 .try_insert(
630 Node::of(value.span()),
631 contains(ranges, Node::of(value.span()), work).map_err(resource)?,
632 )
633 .map_err(resource)?;
634 }
635 }
636 Ok(())
637 }
638
639 fn require_archive(&self) -> crate::Result<()> {
640 if self.entry.purpose() != Purpose::Archive
645 || self.entry.preservation_kind().is_some()
646 || self.entry.owner() != Node::of(self.prepared.signer().span())
647 {
648 return Err(Error::Foreign {
649 at: self.entry.property().span().start,
650 });
651 }
652 Ok(())
653 }
654}
655
656fn contains(
657 ranges: &[structured_asn1::Span],
658 node: Node,
659 work: &impl Work,
660) -> Result<bool, structured_asn1::memory::MemoryError> {
661 let mut from = 0;
662 let mut to = ranges.len();
663 let span = node.span();
664 while from < to {
665 work.charge_work(1)?;
666 let middle = from + (to - from) / 2;
667 if ranges[middle].start <= span.start {
668 from = middle + 1;
669 } else {
670 to = middle;
671 }
672 }
673 if from == 0 {
674 return Ok(false);
675 }
676 let range = ranges[from - 1];
677 Ok(range.start + range.length >= span.start + span.length)
678}