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<'m, 'd, 'p> ArchiveView<'_, 'm, 'd, 'p> {
137 pub(crate) fn timestamp(&self) -> &Checked<'m, 'd, 'p> {
138 self.timestamp
139 }
140 pub(crate) fn coverage(&self) -> &Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades> {
141 self.coverage
142 }
143}
144impl<'m, 'd, 'p> ArchiveChecked<'m, 'd, 'p> {
145 #[must_use]
147 pub fn timestamp(&self) -> &Checked<'m, 'd, 'p> {
148 &self.timestamp
149 }
150 #[must_use]
152 pub fn index(&self) -> &IndexCoverage {
153 &self.index
154 }
155 #[must_use]
158 pub fn coverage(&self) -> &Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades> {
159 &self.coverage
160 }
161
162 pub fn requirements(
167 &self,
168 prepared: &Preparation<'m, 'd>,
169 operation: &Reader<'d>,
170 ) -> Result<
171 structured_ltv::timestamp::TimestampRequirements<Node, DocumentId<'m, 'd>, (), Cades>,
172 ScopeError,
173 > {
174 super::requirements::derive(&self.timestamp, &self.coverage, prepared, operation)
175 }
176}
177
178#[derive(Debug)]
180pub enum ScopeError {
181 Processing(Error),
183 Unproven,
185 Foreign,
187 Coverage(structured_ltv::material::CoverageError<Node>),
189}
190
191struct Input<'s, 'm, 'd> {
192 prepared: &'s Preparation<'m, 'd>,
193 index: &'s HashIndex<'d>,
194 hash: &'s Cell<Option<OwnedVec<u8>>>,
195}
196impl Source for Input<'_, '_, '_> {
197 type Error = ArchiveStreamError;
198 fn stream<W: Work>(
199 &mut self,
200 receiver: &mut impl Receiver,
201 work: &W,
202 ) -> Result<(), Self::Error> {
203 let hash = self.hash.take().ok_or(ArchiveStreamError::MissingDigest)?;
204 let ranges = archive::imprint_input(
205 self.prepared.document().message(),
206 self.prepared.signer(),
207 self.index,
208 &hash,
209 )
210 .map_err(ArchiveStreamError::Source)?;
211 for range in ranges {
212 work.charge_work(1).map_err(ArchiveStreamError::Resource)?;
213 receiver
214 .write(range)
215 .map_err(ArchiveStreamError::Receiver)?;
216 }
217 Ok(())
218 }
219}
220
221#[derive(Debug)]
224pub enum ArchiveCheckError<SE, PE, RE> {
225 Before(ArchiveError<SE, PE>),
227 Timestamp(CheckError<ArchiveStreamError, PE, RE, ArchiveError<SE, PE>>),
229}
230
231impl<'m, 'd> Context<'_, 'm, 'd> {
232 #[expect(
237 clippy::result_large_err,
238 reason = "index refusal and the original mapped token cause are returned without allocating on an already exhausted account"
239 )]
240 pub fn check_archive_carried<'a, 'p, P, PE, RE>(
241 &'a self,
242 provider: &'a P,
243 path: &'p CheckedPath<'d>,
244 certificate_parent: &Reader<'d>,
245 time: &CheckTime<'_, 'd>,
246 recognizes: impl FnMut(&tsp::TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
247 ) -> Result<
248 ArchiveChecked<'m, 'd, 'p>,
249 ArchiveCheckError<InputError<core::convert::Infallible>, PE, RE>,
250 >
251 where
252 P: for<'scope> SignatureProvider<'scope, Error = PE>,
253 {
254 let source = self
255 .prepared
256 .document()
257 .message()
258 .content()
259 .content()
260 .ok_or_else(|| {
261 ArchiveCheckError::Before(ArchiveError::Processing(Error::Foreign {
262 at: self.entry.property().span().start,
263 }))
264 })?;
265 self.archive(
266 provider,
267 path,
268 certificate_parent,
269 time,
270 &mut Content::<NoDetached>::Carried(source),
271 recognizes,
272 )
273 }
274
275 #[expect(
279 clippy::result_large_err,
280 reason = "detached acquisition and nested archive token failures keep their distinct original provenance without boxing"
281 )]
282 pub fn check_archive_detached<'a, 'p, P, S: Source, PE, RE>(
283 &'a self,
284 provider: &'a P,
285 path: &'p CheckedPath<'d>,
286 certificate_parent: &Reader<'d>,
287 time: &CheckTime<'_, 'd>,
288 detached: &mut S,
289 recognizes: impl FnMut(&tsp::TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
290 ) -> Result<ArchiveChecked<'m, 'd, 'p>, ArchiveCheckError<InputError<S::Error>, PE, RE>>
291 where
292 P: for<'scope> SignatureProvider<'scope, Error = PE>,
293 {
294 if !self.prepared.document().message().content().is_detached() {
295 return Err(ArchiveCheckError::Before(ArchiveError::Processing(
296 Error::Foreign {
297 at: self.entry.property().span().start,
298 },
299 )));
300 }
301 self.archive(
302 provider,
303 path,
304 certificate_parent,
305 time,
306 &mut Content::Detached(detached),
307 recognizes,
308 )
309 }
310
311 #[expect(
312 clippy::result_large_err,
313 reason = "one archive transaction propagates owned source maps and opaque source/provider causes without a refusal-time allocation"
314 )]
315 fn archive<'a, 'p, P, S: Source, PE, RE>(
316 &'a self,
317 provider: &'a P,
318 path: &'p CheckedPath<'d>,
319 certificate_parent: &Reader<'d>,
320 time: &CheckTime<'_, 'd>,
321 original: &mut S,
322 recognizes: impl FnMut(&tsp::TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
323 ) -> Result<ArchiveChecked<'m, 'd, 'p>, ArchiveCheckError<S::Error, PE, RE>>
324 where
325 P: for<'scope> SignatureProvider<'scope, Error = PE>,
326 {
327 let before = ArchiveCheckError::Before;
328 let processing = |cause| before(ArchiveError::Processing(cause));
329 self.require_archive().map_err(processing)?;
330 let index = HashIndex::of_token(self.token, self.reader)
331 .map_err(|cause| processing(Error::Cms(cause)))?;
332 let coverage = self.matched_index(&index, provider).map_err(before)?;
333 let hash = Cell::new(None);
334 let mut covered = Input {
335 prepared: self.prepared,
336 index: &index,
337 hash: &hash,
338 };
339 let checked = self
340 .check_profile(
341 provider,
342 path,
343 certificate_parent,
344 time,
345 &mut covered,
346 (recognizes, |input| {
347 let failure = |cause| ProfileFailure {
348 source: ProfileSource::Information,
349 cause,
350 };
351 let tsp::HashAlgorithmReading::Identifier(algorithm) =
352 input.info.imprint().algorithm()
353 else {
354 return Err(failure(ArchiveError::MalformedAlgorithm));
355 };
356 match archive::same_algorithm(&index, algorithm, &input.reader.execution())
357 .map_err(|cause| failure(ArchiveError::Processing(Error::Cms(cause))))?
358 {
359 Resolution::Supported(true) => {}
360 Resolution::Supported(false) => {
361 return Err(failure(ArchiveError::AlgorithmMismatch));
362 }
363 Resolution::Unsupported => return Err(failure(ArchiveError::Unsupported)),
364 Resolution::Malformed => {
365 return Err(failure(ArchiveError::MalformedAlgorithm));
366 }
367 }
368 let work = input.reader.execution();
369 let identity = match structured_pkix::algorithm::digest::resolve(
370 provider, algorithm, &work,
371 )
372 .map_err(|cause| failure(ArchiveError::Processing(Error::Pkix(cause))))?
373 {
374 Resolution::Supported(value) => value,
375 Resolution::Unsupported => return Err(failure(ArchiveError::Unsupported)),
376 Resolution::Malformed => {
377 return Err(failure(ArchiveError::MalformedAlgorithm));
378 }
379 };
380 let mut identities = work.vector();
381 identities.try_push(identity).map_err(|cause| {
382 failure(ArchiveError::Processing(Error::resource(
383 cause,
384 "cades.archive",
385 input.info.imprint().span().start,
386 )))
387 })?;
388 let batch = DigestBatch::prepare(
389 provider,
390 identities.into_owned(),
391 &work,
392 "cades.archive.content",
393 self.entry.property().span().start,
394 )
395 .map_err(|cause| failure(ArchiveError::Digest(cause)))?;
396 let memo = batch
397 .run(original, &work)
398 .map_err(|cause| failure(ArchiveError::Content(cause)))?;
399 let digest = memo
400 .for_request(0, &work)
401 .map_err(|cause| failure(ArchiveError::Digest(cause)))?
402 .ok_or_else(|| failure(ArchiveError::MissingDigest))?;
403 let mut retained = work.vector();
407 retained
408 .try_extend_from_slice(digest.as_bytes())
409 .map_err(|cause| {
410 failure(ArchiveError::Processing(Error::resource(
411 cause,
412 "cades.archive.content",
413 self.entry.property().span().start,
414 )))
415 })?;
416 hash.set(Some(retained.into_owned()));
417 Ok(())
418 }),
419 )
420 .map_err(ArchiveCheckError::Timestamp)?;
421 let selected = self.coverage(&coverage).map_err(processing)?;
422 Ok(ArchiveChecked {
423 timestamp: checked,
424 index: coverage,
425 coverage: selected,
426 })
427 }
428
429 fn matched_index<'a, P, SE, PE>(
430 &'a self,
431 index: &HashIndex<'d>,
432 provider: &'a P,
433 ) -> Result<IndexCoverage, ArchiveError<SE, PE>>
434 where
435 P: for<'scope> SignatureProvider<'scope, Error = PE>,
436 {
437 let processing = ArchiveError::Processing;
438 self.reader
439 .charge(1 + index.algorithm().algorithm().as_bytes().len().min(8) as u64)
440 .map_err(|cause| processing(Error::encoding(cause, "cades.archive.index")))?;
441 if index.algorithm().algorithm()
442 == structured_asn1::Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 2, 5])
443 {
444 return Err(ArchiveError::ForbiddenDigest {
445 at: index.algorithm().offset(),
446 });
447 }
448 let coverage = match archive::check_index(
449 index,
450 self.prepared.document().message(),
451 self.prepared.signer(),
452 provider,
453 self.reader,
454 )
455 .map_err(ArchiveError::Digest)?
456 {
457 Resolution::Supported(value) => value,
458 Resolution::Unsupported => return Err(ArchiveError::Unsupported),
459 Resolution::Malformed => return Err(ArchiveError::MalformedAlgorithm),
460 };
461 if !coverage.is_valid() {
462 return Err(ArchiveError::UnmatchedIndex);
463 }
464 Ok(coverage)
465 }
466 fn coverage(
467 &self,
468 index: &IndexCoverage,
469 ) -> crate::Result<Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades>> {
470 let work = self.reader.execution();
471 let resource = |cause| {
472 Error::resource(
473 cause,
474 "cades.archive.coverage",
475 self.entry.property().span().start,
476 )
477 };
478 let mut items = work.map();
479 let message = self.prepared.document().message();
480 for (choice, covered) in message
481 .certificate_choices()
482 .iter()
483 .zip(index.certificates())
484 {
485 work.charge_work(1).map_err(resource)?;
486 let span = match choice {
487 structured_cms::signed::CertificateChoice::X509(ordinal) => {
488 message.certificates()[*ordinal].span()
489 }
490 structured_cms::signed::CertificateChoice::Extended(value)
491 | structured_cms::signed::CertificateChoice::AttributeV1(value)
492 | structured_cms::signed::CertificateChoice::AttributeV2(value)
493 | structured_cms::signed::CertificateChoice::Other(value) => value.span(),
494 };
495 items
496 .try_insert(Node::of(span), *covered)
497 .map_err(resource)?;
498 }
499 for (value, covered) in message.revocations().iter().zip(index.revocations()) {
500 work.charge_work(1).map_err(resource)?;
501 items
502 .try_insert(Node::of(value.span()), *covered)
503 .map_err(resource)?;
504 }
505 let mut ranges = work.vector();
506 if let Some(attributes) = self.prepared.signer().signed_attributes() {
507 ranges
512 .try_push(attributes.element().span())
513 .map_err(resource)?;
514 for attribute in attributes.items() {
515 work.charge_work(1).map_err(resource)?;
516 items
517 .try_insert(Node::of(attribute.span()), true)
518 .map_err(resource)?;
519 }
520 }
521
522 if let Some(attributes) = self.prepared.signer().unsigned_attributes() {
523 for attribute in attributes.items() {
524 work.charge_work(1).map_err(resource)?;
525 let all = !attribute.values().is_empty();
529 items
530 .try_insert(Node::of(attribute.span()), all)
531 .map_err(resource)?;
532 }
533 for (position, covered) in index.attribute_values() {
534 work.charge_work(1).map_err(resource)?;
535 let attribute = &attributes.items()[position.attribute];
536 let value = &attribute.values()[position.value];
537 items
538 .try_insert(Node::of(value.span()), *covered)
539 .map_err(resource)?;
540 if *covered {
541 ranges.try_push(value.span()).map_err(resource)?;
542 } else if let Some(all) = items
543 .get_mut(&Node::of(attribute.span()), &work)
544 .map_err(resource)?
545 {
546 *all = false;
547 }
548 }
549 }
550 self.include_nested(&mut items, &mut ranges, &work)?;
551 let source = self.prepared.document();
552 let query = Scope::new(source, source.snapshot(), &work);
553 query
554 .admit(self.entry.property(), 1)
555 .map_err(|cause| match cause {
556 structured_ltv::source::ScopeError::Changed { node } => Error::Foreign {
557 at: node.span().start,
558 },
559 structured_ltv::source::ScopeError::Resource { cause, .. } => resource(cause),
560 })?;
561 Ok(Coverage::for_token(
562 ISSUER,
563 source.snapshot(),
564 Node::of(self.prepared.signer().span()),
565 self.entry.property(),
566 Node::of(
567 self.entry
568 .value()
569 .expect("archive requires original token")
570 .span(),
571 ),
572 Purpose::Archive,
573 items.into_owned(),
574 ))
575 }
576 fn include_nested<W: Work>(
577 &self,
578 items: &mut structured_asn1::memory::OwnedMap<Node, bool, W>,
579 ranges: &mut OwnedVec<structured_asn1::Span, W>,
580 work: &W,
581 ) -> crate::Result<()> {
582 let resource = |cause| {
583 Error::resource(
584 cause,
585 "cades.archive.coverage",
586 self.entry.property().span().start,
587 )
588 };
589 ranges
590 .try_sort_unstable_by(|left, right| {
591 work.charge_work(1)?;
592 Ok::<_, structured_asn1::memory::MemoryError>(left.start.cmp(&right.start))
593 })
594 .map_err(|cause| match cause {
595 structured_asn1::memory::SortError::Resource(cause)
596 | structured_asn1::memory::SortError::Comparison(cause) => resource(cause),
597 })?;
598 let mut maximum = 0;
599 for span in &mut *ranges {
600 work.charge_work(1).map_err(resource)?;
601 maximum = maximum.max(span.start + span.length);
602 span.length = maximum - span.start;
603 }
604 for counter in self.prepared.counters() {
605 let covered = contains(ranges, counter.node(), work).map_err(resource)?;
606 items
607 .try_insert(counter.node(), covered)
608 .map_err(resource)?;
609 }
610 for token in self.prepared.timestamps() {
611 let covered = contains(ranges, token.property(), work).map_err(resource)?;
612 if token.owner() != self.entry.owner() {
613 items
614 .try_insert(token.property(), covered)
615 .map_err(resource)?;
616 }
617 if let Some(value) = token.value() {
618 items
619 .try_insert(
620 Node::of(value.span()),
621 contains(ranges, Node::of(value.span()), work).map_err(resource)?,
622 )
623 .map_err(resource)?;
624 }
625 }
626 Ok(())
627 }
628
629 fn require_archive(&self) -> crate::Result<()> {
630 if self.entry.purpose() != Purpose::Archive
635 || self.entry.preservation_kind().is_some()
636 || self.entry.owner() != Node::of(self.prepared.signer().span())
637 {
638 return Err(Error::Foreign {
639 at: self.entry.property().span().start,
640 });
641 }
642 Ok(())
643 }
644}
645
646fn contains(
647 ranges: &[structured_asn1::Span],
648 node: Node,
649 work: &impl Work,
650) -> Result<bool, structured_asn1::memory::MemoryError> {
651 let mut from = 0;
652 let mut to = ranges.len();
653 let span = node.span();
654 while from < to {
655 work.charge_work(1)?;
656 let middle = from + (to - from) / 2;
657 if ranges[middle].start <= span.start {
658 from = middle + 1;
659 } else {
660 to = middle;
661 }
662 }
663 if from == 0 {
664 return Ok(false);
665 }
666 let range = ranges[from - 1];
667 Ok(range.start + range.length >= span.start + span.length)
668}