Skip to main content

structured_cades/timestamp/
historical.rs

1//! Historical archive inputs, distinct from `ATSv3` and modern baseline status.
2//!
3//! RFC3126 §4.4.1, TS101733 V1.5.1 §6.4.1, and RFC5126 §6.4.1 select
4//! DIFFERENT octets. The OID shared by the first two does not select an
5//! edition. TS101733 V2.2.1 Annex K note 3 requires handling both unsigned
6//! attribute framing interpretations for archiveTimestampV2.
7//! <https://www.rfc-editor.org/rfc/rfc3126#section-4.4.1>
8//! <https://www.rfc-editor.org/rfc/rfc5126#section-6.4.1>
9//! <https://www.etsi.org/deliver/etsi_ts/101700_101799/101733/02.02.01_60/ts_101733v020201p.pdf>
10
11use super::{ArchiveChecked, Checked};
12use crate::{
13    Cades, Error, ISSUER,
14    prepare::{Preparation, encoding::SignerView},
15    preservation::Kind,
16    source::{DocumentId, Node},
17};
18use core::cell::Cell;
19use structured_asn1::{
20    Oid, Reader,
21    length::DerLength,
22    memory::{Memory, OwnedMap, OwnedVec, Work},
23};
24use structured_cms::{
25    Octets,
26    stream::Source,
27    timestamp::{
28        self as tsp, ComputedInput, ImprintAgreement, ImprintContext, ImprintProof, InputFailure,
29    },
30};
31use structured_ltv::{
32    material::Coverage,
33    source::{Scope, Source as _},
34    timestamp::{Purpose, Verified},
35};
36use structured_pkix::algorithm::digest::AsnDigest;
37
38const FIELD: &str = "cades.historical";
39
40mod selection;
41mod snapshot;
42
43/// Caller-established historical edition. Content never chooses a permissive
44/// edition; both `ArchiveV1` editions deliberately have the same attribute OID.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Edition {
47    /// Logical content, signed-attribute contents, signature contents and the
48    /// listed unsigned attribute contents, in RFC3126 §4.4.1 order.
49    Rfc3126,
50    /// Whole `EncapsulatedContentInfo`, material fields, original signer fields
51    /// and unsigned attributes. No detached-byte addition is specified.
52    Ts101733V151,
53    /// RFC5126 input, including actual detached bytes when eContent is absent,
54    /// with BOTH Annex K unsigned-attribute framing alternatives.
55    Rfc5126,
56}
57impl Edition {
58    fn kind(self) -> Kind {
59        match self {
60            Self::Rfc3126 | Self::Ts101733V151 => Kind::ArchiveV1,
61            Self::Rfc5126 => Kind::ArchiveV2,
62        }
63    }
64}
65
66/// Honest description of the unsigned field used by this input. A canonical
67/// projection cannot recover a lost historical BER length/EOC spelling.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum Framing {
70    /// Exact previous field bytes, including any indefinite wrapper/EOC.
71    OriginalSnapshot,
72    /// Newly generated minimal definite framing around original members.
73    CanonicalDefiniteProjection,
74}
75
76/// Refusal before provider/source side effects; no implicit missing-input or
77/// successful missing-preservation fallback is provided.
78#[derive(Debug)]
79pub enum SelectionError {
80    /// Failure in the current source/operation.
81    Processing(Error),
82    /// Failure while consulting the explicitly supplied earlier source.
83    PriorProcessing(Error),
84    /// Exact common coverage/source refusal while admitting a later witness.
85    Coverage(structured_ltv::material::CoverageError<Node>),
86    /// Attribute OID and explicitly selected historical edition disagree.
87    WrongEdition,
88    /// Wrong source/owner/placement, repeated selection, or unreadable value.
89    WrongOccurrence,
90    /// RFC5126 requires actual detached input, but no source was supplied.
91    MissingDetached,
92    /// Original signed fields/material/previous unsigned attributes changed.
93    PriorNotPreserved,
94    /// The proposed previous input contains its own target token.
95    SelfIncluded,
96    /// Exclusion lacks a validated later proof covering this earlier token.
97    UnprovenLater {
98        /// Original excluded archive attribute, without invented coordinates.
99        property: Node,
100    },
101    /// A separate preservation mechanism has no usable qualified processor.
102    Unavailable {
103        /// Original unsupported preservation property.
104        property: Node,
105    },
106    /// Exact range/framing lengths cannot be represented.
107    LengthOverflow,
108}
109impl From<Error> for SelectionError {
110    fn from(cause: Error) -> Self {
111        Self::Processing(cause)
112    }
113}
114
115/// A validated later proof which actually covers the earlier token. A claimed
116/// genTime or source order cannot manufacture this witness. The constructor
117/// borrows the sealed common actor and its exact format-produced coverage.
118pub struct Later<'view, 'model, 'document> {
119    evidence: &'view Verified<Node, DocumentId<'model, 'document>, (), Cades>,
120    coverage: &'view Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
121}
122impl<'v, 'm, 'd> Later<'v, 'm, 'd> {
123    /// Borrows an actual successful `ATSv3` receipt and its input coverage.
124    pub fn modern(checked: &'v ArchiveChecked<'m, 'd, '_>) -> Option<Self> {
125        Some(Self {
126            evidence: checked.timestamp().actor()?.verified(),
127            coverage: checked.coverage(),
128        })
129    }
130    /// Borrows an actual successful historical archive receipt and coverage.
131    pub fn historical(checked: &'v HistoricalChecked<'m, 'd, '_>) -> Option<Self> {
132        Some(Self {
133            evidence: checked.timestamp.actor()?.verified(),
134            coverage: &checked.coverage,
135        })
136    }
137    fn permits(
138        &self,
139        prepared: &Preparation<'m, 'd>,
140        later: &crate::prepare::Timestamp<'d>,
141        earlier: &crate::prepare::Timestamp<'d>,
142        operation: &Reader<'d>,
143    ) -> Result<bool, SelectionError> {
144        operation
145            .charge(1)
146            .map_err(|cause| Error::encoding(cause, FIELD))?;
147        let Some(value) = later.value() else {
148            return Ok(false);
149        };
150        if self.evidence.token().node != Node::of(value.span()) {
151            return Ok(false);
152        }
153        let source = prepared.document();
154        let work = operation.execution();
155        let query = Scope::new(source, source.snapshot(), &work);
156        let node = earlier
157            .value()
158            .map_or(earlier.property(), |value| Node::of(value.span()));
159        let selected = self
160            .coverage
161            .covers(self.evidence, node, &query)
162            .map_err(SelectionError::Coverage)?;
163        if selected {
164            return Ok(true);
165        }
166        self.coverage
167            .covers(self.evidence, earlier.property(), &query)
168            .map_err(SelectionError::Coverage)
169    }
170}
171
172/// Exact earlier source context, retained by borrowing the actual preparation
173/// and producing reader. Capturing performs no reparse, re-encoding or payload
174/// copy. Binding this to a later message is checked before starting a digest.
175pub struct OriginalSnapshot<'view, 'model, 'document> {
176    prepared: &'view Preparation<'model, 'document>,
177    owner: Node,
178    reader: &'view Reader<'document>,
179    signer: SignerView<'view, 'document>,
180}
181impl<'v, 'm, 'd> OriginalSnapshot<'v, 'm, 'd> {
182    /// Selects an actual root/counter in its own original parse session.
183    pub fn capture(
184        prepared: &'v Preparation<'m, 'd>,
185        owner: Node,
186        reader: &'v Reader<'d>,
187    ) -> Result<Self, SelectionError> {
188        let signer = prepared.signer_encoding(owner, reader)?;
189        Ok(Self {
190            prepared,
191            owner,
192            reader,
193            signer,
194        })
195    }
196}
197
198#[derive(Clone, Copy)]
199struct Header {
200    bytes: [u8; 10],
201    length: u8,
202}
203impl Header {
204    fn new(tag: u8, length: u64) -> Self {
205        let length = DerLength::new(length);
206        let mut bytes = [0; 10];
207        bytes[0] = tag;
208        bytes[1..=length.as_bytes().len()].copy_from_slice(length.as_bytes());
209        Self {
210            bytes,
211            length: 1 + u8::try_from(length.as_bytes().len())
212                .expect("DER length uses at most nine octets"),
213        }
214    }
215    fn bytes(&self) -> &[u8] {
216        &self.bytes[..usize::from(self.length)]
217    }
218}
219enum Part<'a> {
220    Current(&'a [u8]),
221    Prior(&'a [u8]),
222    Framing(Header),
223}
224impl Part<'_> {
225    fn length(&self) -> usize {
226        match self {
227            Self::Current(bytes) | Self::Prior(bytes) => bytes.len(),
228            Self::Framing(header) => header.bytes().len(),
229        }
230    }
231}
232enum Payload<'s, 'd, S> {
233    Absent,
234    Carried(&'s Octets<'d>),
235    Detached(&'s mut S),
236}
237
238struct Carried<'s, 'd, E> {
239    source: &'s Octets<'d>,
240    error: core::marker::PhantomData<E>,
241}
242impl<E> Source for Carried<'_, '_, E> {
243    type Error = StreamError<E>;
244    fn stream<W: Work>(
245        &mut self,
246        receiver: &mut impl structured_cms::stream::Receiver,
247        work: &W,
248    ) -> Result<(), Self::Error> {
249        self.source
250            .stream(receiver, work)
251            .map_err(StreamError::Carried)
252    }
253}
254struct Lift<'a, S>(&'a mut S);
255impl<S: Source> Source for Lift<'_, S> {
256    type Error = StreamError<S::Error>;
257    fn stream<W: Work>(
258        &mut self,
259        receiver: &mut impl structured_cms::stream::Receiver,
260        work: &W,
261    ) -> Result<(), Self::Error> {
262        self.0.stream(receiver, work).map_err(StreamError::Source)
263    }
264}
265
266#[derive(PartialEq, Eq, Hash)]
267struct ArchiveValue<'a> {
268    generation: u8,
269    bytes: &'a [u8],
270}
271impl structured_asn1::memory::HashWork for ArchiveValue<'_> {
272    fn hash_work(&self) -> Result<u64, structured_asn1::memory::MemoryError> {
273        (self.bytes.len() as u64)
274            .checked_add(2)
275            .ok_or(structured_asn1::memory::MemoryError::WorkExceeded)
276    }
277}
278
279/// Prepared range recipe. Only descriptors/framing are owned; document bytes
280/// remain original borrowed ranges. Detached acquisition happens once, after
281/// all variant states have been admitted, with no complete-document buffer.
282pub struct HistoricalInput<'input, 'model, 'document, S> {
283    prepared: &'input Preparation<'model, 'document>,
284    operation: &'input Reader<'document>,
285    prior: Option<&'input Reader<'input>>,
286    ordinal: usize,
287    edition: Edition,
288    framing: Framing,
289    parts: OwnedVec<Part<'input>>,
290    payload_at: usize,
291    unsigned_at: usize,
292    unsigned_header: Option<Header>,
293    original_unsigned: Option<&'input [u8]>,
294    payload: Payload<'input, 'document, S>,
295    covered: Option<OwnedMap<Node, bool>>,
296    matched: Cell<Option<Variant>>,
297}
298
299/// Actual matching interpretation, never guessed from the token's date/OID.
300#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301pub enum Variant {
302    /// RFC3126's prescribed sequence of logical field/attribute contents.
303    ListedContents,
304    /// Complete original/projected unsigned field, including its framing.
305    UnsignedField,
306    /// Unsigned-field contents with its outer wrapper/EOC excluded.
307    UnsignedContents,
308}
309
310/// Acquisition/format refusal during a computed imprint. Processing retains
311/// its honest original cause; it is not a semantic digest disagreement.
312#[derive(Debug)]
313pub enum StreamError<E> {
314    /// Opaque failure acquiring the actual detached bytes.
315    Source(E),
316    /// Original carried segment traversal or retained receiver refusal.
317    Carried(structured_cms::octets::SourceError),
318    /// Original range/metadata processing failure.
319    Processing(Error),
320}
321
322impl<'s, 'm, 'd, S> HistoricalInput<'s, 'm, 'd, S> {
323    /// Uses a genuine earlier unsigned field, including its original BER
324    /// wrapper and EOC. Signed fields, global material fields and the complete
325    /// previous attribute multiset are bound to this current owner FIRST.
326    /// Neither a guessed date nor a synthetic previous encoding substitutes
327    /// for the supplied source. All older archive values must still be present.
328    pub fn from_snapshot<'old_model, 'old_document: 's>(
329        prepared: &'s Preparation<'m, 'd>,
330        ordinal: usize,
331        edition: Edition,
332        snapshot: &'s OriginalSnapshot<'_, 'old_model, 'old_document>,
333        later: &[Later<'_, 'm, 'd>],
334        operation: &'s Reader<'d>,
335        detached: Option<&'s mut S>,
336    ) -> Result<Self, SelectionError> {
337        prepared
338            .document()
339            .message()
340            .require_session(operation, FIELD)
341            .map_err(Error::Cms)?;
342        snapshot
343            .prepared
344            .document()
345            .message()
346            .require_session(snapshot.reader, FIELD)
347            .map_err(|cause| SelectionError::PriorProcessing(Error::Cms(cause)))?;
348        let target = prepared
349            .timestamps()
350            .get(ordinal)
351            .ok_or(SelectionError::WrongOccurrence)?;
352        let view = prepared.signer_encoding(target.owner(), operation)?;
353        snapshot.boundaries(prepared, view, operation)?;
354        snapshot.parent(prepared, target.owner(), operation)?;
355        let mut attributes = snapshot.attributes(view, operation)?;
356        let previous = snapshot.previous(prepared, target.owner(), ordinal, operation)?;
357        Self::project(
358            prepared, ordinal, edition, &previous, later, operation, detached,
359        )?
360        .restore(snapshot, view, &mut attributes)
361    }
362    /// Explicit final-message projection. `previous` contains exact prepared
363    /// archive occurrence ordinals, not claimed times. Every excluded later
364    /// archive must supply a validated coverage witness over this target.
365    /// This spells NEW definite headers and never claims to recover lost BER.
366    pub fn project(
367        prepared: &'s Preparation<'m, 'd>,
368        ordinal: usize,
369        edition: Edition,
370        previous: &[usize],
371        later: &[Later<'_, 'm, 'd>],
372        operation: &'s Reader<'d>,
373        detached: Option<&'s mut S>,
374    ) -> Result<Self, SelectionError> {
375        prepared
376            .document()
377            .message()
378            .require_session(operation, FIELD)
379            .map_err(Error::Cms)?;
380        operation
381            .charge(1)
382            .map_err(|cause| Error::encoding(cause, FIELD))?;
383        let target = prepared
384            .timestamps()
385            .get(ordinal)
386            .ok_or(SelectionError::WrongOccurrence)?;
387        if target.preservation_kind() != Some(edition.kind()) {
388            return Err(SelectionError::WrongEdition);
389        }
390        if target.value().is_none() {
391            return Err(SelectionError::WrongOccurrence);
392        }
393        let view = prepared.signer_encoding(target.owner(), operation)?;
394        let work = operation.execution();
395        let resource = |cause| {
396            SelectionError::Processing(Error::resource(
397                cause,
398                FIELD,
399                target.property().span().start,
400            ))
401        };
402        let mut placement = false;
403        for index in 0..view.attributes() {
404            work.charge_work(1).map_err(resource)?;
405            if Node::of(
406                view.attribute(index)
407                    .ok_or(SelectionError::WrongOccurrence)?
408                    .span,
409            ) == target.property()
410            {
411                placement = true;
412                break;
413            }
414        }
415        if !placement {
416            return Err(SelectionError::WrongOccurrence);
417        }
418        let chosen = selection::choose(prepared, ordinal, edition, previous, later, operation)?;
419        let content = prepared.document().message().content().content();
420        let payload = match (edition, content, detached) {
421            (Edition::Rfc3126, Some(source), _) => Payload::Carried(source),
422            (Edition::Rfc5126, None, Some(source)) => Payload::Detached(source),
423            (Edition::Rfc5126, None, None) => return Err(SelectionError::MissingDetached),
424            _ => Payload::Absent,
425        };
426        let mut parts = work.vector();
427        let mut covered = work.map();
428        let payload_at = selection::Output::new(&mut parts, &mut covered, &work, target.property())
429            .prefix(prepared, view, target.owner(), edition, operation)?;
430        let unsigned_at = parts.len();
431        {
432            let mut output =
433                selection::Output::new(&mut parts, &mut covered, &work, target.property());
434            output.unsigned(prepared, view, edition, &chosen)?;
435            output.context(prepared, view, target.owner(), edition)?;
436        }
437        let mut unsigned_length = 0u64;
438        for part in &parts[unsigned_at..] {
439            work.charge_work(1).map_err(resource)?;
440            unsigned_length = unsigned_length
441                .checked_add(part.length() as u64)
442                .ok_or(SelectionError::LengthOverflow)?;
443        }
444        Ok(Self {
445            prepared,
446            operation,
447            prior: None,
448            ordinal,
449            edition,
450            framing: Framing::CanonicalDefiniteProjection,
451            parts: parts.into_owned(),
452            payload_at,
453            unsigned_at,
454            unsigned_header: (unsigned_length != 0).then(|| Header::new(0xa1, unsigned_length)),
455            original_unsigned: None,
456            payload,
457            covered: Some(covered.into_owned()),
458            matched: Cell::new(None),
459        })
460    }
461    /// Whether this input retained original or generated unsigned framing.
462    pub fn framing(&self) -> Framing {
463        self.framing
464    }
465    /// Last fully completed matching variant, or no matching computation.
466    pub fn matched_variant(&self) -> Option<Variant> {
467        self.matched.get()
468    }
469}
470
471fn archive_kind(kind: Oid<'_>) -> Option<Kind> {
472    match Kind::from_oid(kind) {
473        Some(value) if value.is_archive() => Some(value),
474        _ => None,
475    }
476}
477fn category(kind: Oid<'_>) -> Option<usize> {
478    for (index, last) in [14, 21, 22, 23, 24, 25, 26, 27].into_iter().enumerate() {
479        if kind == Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 2, last]) {
480            return Some(index);
481        }
482    }
483    None
484}
485
486fn covered_container<W: Work>(
487    view: SignerView<'_, '_>,
488    node: Node,
489    selected: &OwnedMap<Node, bool, W>,
490    work: &impl Work,
491) -> Result<bool, structured_asn1::memory::MemoryError> {
492    // Parsed top-level attributes are disjoint and in original physical order.
493    // Binary search avoids a counter-by-attribute cross-product. Selection is
494    // consulted ONLY after whole-range containment, never by a prefix overlap.
495    let span = node.span();
496    let mut from = 0;
497    let mut to = view.attributes();
498    while from < to {
499        work.charge_work(1)?;
500        let middle = from + (to - from) / 2;
501        let attribute = view.attribute(middle).expect("bounded original catalogue");
502        if attribute.span.start <= span.start {
503            from = middle + 1;
504        } else {
505            to = middle;
506        }
507    }
508    if from == 0 {
509        return Ok(false);
510    }
511    work.charge_work(1)?;
512    let attribute = view
513        .attribute(from - 1)
514        .expect("preceding original attribute");
515    if span.end() > attribute.span.end() {
516        return Ok(false);
517    }
518    Ok(selected.get(&Node::of(attribute.span), work)? == Some(&true))
519}
520
521impl<P, PE, S: Source> ComputedInput<P, PE> for HistoricalInput<'_, '_, '_, S> {
522    type Error = StreamError<S::Error>;
523    fn compute<'context, 'reader, 'document>(
524        &mut self,
525        context: &'context ImprintContext<'reader, 'document, P, Self::Error>,
526    ) -> Result<ImprintProof<'context>, InputFailure<Self::Error>>
527    where
528        P: AsnDigest<'document, Error = PE>,
529    {
530        self.matched.set(None);
531        let mut first = context.start()?;
532        let mut second = if self.edition == Edition::Rfc5126 {
533            Some(context.start()?)
534        } else {
535            None
536        };
537        for part in &self.parts[..self.payload_at] {
538            emit(part, &mut first, self.operation, self.prior)?;
539            if let Some(second) = &mut second {
540                emit(part, second, self.operation, self.prior)?;
541            }
542        }
543        match &mut self.payload {
544            Payload::Absent => {}
545            Payload::Carried(source) => {
546                let mut source = Carried::<S::Error> {
547                    source,
548                    error: core::marker::PhantomData,
549                };
550                if let Some(second) = &mut second {
551                    context.stream_from(&mut source, self.operation, &mut [&mut first, second])?;
552                } else {
553                    context.stream_from(&mut source, self.operation, &mut [&mut first])?;
554                }
555            }
556            Payload::Detached(source) => {
557                if let Some(second) = &mut second {
558                    context.stream_from(
559                        &mut Lift(*source),
560                        self.operation,
561                        &mut [&mut first, second],
562                    )?;
563                } else {
564                    context.stream_from(&mut Lift(*source), self.operation, &mut [&mut first])?;
565                }
566            }
567        }
568        for part in &self.parts[self.payload_at..self.unsigned_at] {
569            emit(part, &mut first, self.operation, self.prior)?;
570            if let Some(second) = &mut second {
571                emit(part, second, self.operation, self.prior)?;
572            }
573        }
574        if self.edition != Edition::Rfc3126 {
575            if let Some(bytes) = self.original_unsigned {
576                first.write_from(
577                    bytes,
578                    self.prior
579                        .expect("original unsigned belongs to captured source"),
580                )?;
581            } else if let Some(header) = &self.unsigned_header {
582                first.write(header.bytes())?;
583            }
584        }
585        for part in &self.parts[self.unsigned_at..] {
586            if self.original_unsigned.is_some() && self.edition != Edition::Rfc3126 {
587                if let Some(second) = &mut second {
588                    emit(part, second, self.operation, self.prior)?;
589                }
590            } else {
591                emit(part, &mut first, self.operation, self.prior)?;
592                if let Some(second) = &mut second {
593                    emit(part, second, self.operation, self.prior)?;
594                }
595            }
596        }
597        let first = first.finish()?;
598        let second = match second {
599            Some(state) => Some(state.finish()?),
600            None => None,
601        };
602        let a = context.compare(&first)?;
603        let b = match &second {
604            Some(digest) => Some(context.compare(digest)?),
605            None => None,
606        };
607        if a.agreement() == ImprintAgreement::Matches {
608            self.matched.set(Some(if self.edition == Edition::Rfc3126 {
609                Variant::ListedContents
610            } else {
611                Variant::UnsignedField
612            }));
613            Ok(a)
614        } else if let Some(b) = b {
615            if b.agreement() == ImprintAgreement::Matches {
616                self.matched.set(Some(Variant::UnsignedContents));
617            }
618            Ok(b)
619        } else {
620            Ok(a)
621        }
622    }
623}
624fn emit<'c, 'r, 'd, P: AsnDigest<'d>, E>(
625    part: &Part<'_>,
626    state: &mut tsp::ImprintState<'c, 'r, 'd, P, E>,
627    operation: &Reader<'_>,
628    prior: Option<&Reader<'_>>,
629) -> Result<(), structured_cms::stream::Refused> {
630    match part {
631        Part::Current(bytes) => state.write_from(bytes, operation),
632        Part::Prior(bytes) => {
633            state.write_from(bytes, prior.expect("prior descriptor has its source"))
634        }
635        Part::Framing(header) => state.write(header.bytes()),
636    }
637}
638
639/// Complete common TSA result plus exact original-item coverage. This remains
640/// separate from modern baseline-format conformance and application trust.
641pub struct HistoricalChecked<'model, 'document, 'path> {
642    timestamp: Checked<'model, 'document, 'path>,
643    coverage: Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
644    framing: Framing,
645    variant: Option<Variant>,
646}
647impl<'m, 'd, 'p> HistoricalChecked<'m, 'd, 'p> {
648    /// Completed common signature/ESS/authority/time facts, including negatives.
649    pub fn timestamp(&self) -> &Checked<'m, 'd, 'p> {
650        &self.timestamp
651    }
652    /// Exact mathematical selection; use its own successful actor to bind it.
653    pub fn coverage(&self) -> &Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades> {
654        &self.coverage
655    }
656    /// Original versus explicitly projected unsigned-field framing.
657    pub fn framing(&self) -> Framing {
658        self.framing
659    }
660    /// Completed matching variant; absent on semantic disagreement.
661    pub fn matched_variant(&self) -> Option<Variant> {
662        self.variant
663    }
664    /// Derives every actually covered earlier TSA/counter from this archive's
665    /// own successful actor. Its own TSA is excluded by exact token identity,
666    /// not by an attribute identifier which might contain other older values.
667    pub fn requirements(
668        &self,
669        prepared: &Preparation<'m, 'd>,
670        operation: &Reader<'d>,
671    ) -> Result<
672        structured_ltv::timestamp::TimestampRequirements<Node, DocumentId<'m, 'd>, (), Cades>,
673        super::ArchiveScopeError,
674    > {
675        super::requirements::derive(&self.timestamp, &self.coverage, prepared, operation)
676    }
677}
678
679impl<'m, 'd> super::Context<'_, 'm, 'd> {
680    /// Performs the SAME complete common CMS/ESS/TSA/effective-key/path/time
681    /// pipeline as an ordinary token, using this sealed historical recipe.
682    /// Historical mathematical validity does not waive modern baseline rules.
683    #[expect(
684        clippy::result_large_err,
685        reason = "original profile causes stay unboxed on refusal"
686    )]
687    pub fn check_historical<'a, 'p, P, S: Source, PE, RE>(
688        &'a self,
689        provider: &'a P,
690        path: &'p structured_ltv::pki::CheckedPath<'d>,
691        certificate_parent: &Reader<'d>,
692        time: &super::CheckTime<'_, 'd>,
693        input: &mut HistoricalInput<'_, 'm, 'd, S>,
694        recognizes: impl FnMut(
695            &tsp::TokenExtension<'_>,
696            &structured_asn1::memory::Execution<'_>,
697        ) -> Result<bool, RE>,
698    ) -> Result<HistoricalChecked<'m, 'd, 'p>, super::CheckError<StreamError<S::Error>, PE, RE>>
699    where
700        P: for<'scope> structured_cms::provider::SignatureProvider<'scope, Error = PE>,
701    {
702        if self.ordinal != input.ordinal
703            || !core::ptr::eq(self.prepared, input.prepared)
704            || input.covered.is_none()
705        {
706            return Err(super::CheckError::Selection(Error::Foreign {
707                at: self.entry.property().span().start,
708            }));
709        }
710        let value = self
711            .entry
712            .value()
713            .ok_or(super::CheckError::Selection(Error::Foreign {
714                at: self.entry.property().span().start,
715            }))?;
716        let timestamp = self.check_computed_profile(
717            provider,
718            path,
719            certificate_parent,
720            time,
721            input,
722            (recognizes, |_| {
723                Ok::<(), tsp::ProfileFailure<core::convert::Infallible>>(())
724            }),
725        )?;
726        let coverage = Coverage::for_token(
727            ISSUER,
728            self.prepared.document().snapshot(),
729            self.entry.owner(),
730            self.entry.property(),
731            Node::of(value.span()),
732            Purpose::Archive,
733            input
734                .covered
735                .take()
736                .ok_or(super::CheckError::Selection(Error::Foreign {
737                    at: self.entry.property().span().start,
738                }))?,
739        );
740        Ok(HistoricalChecked {
741            timestamp,
742            coverage,
743            framing: input.framing,
744            variant: input.matched.get(),
745        })
746    }
747}