Skip to main content

structured_pades/baseline/
archives.rs

1//! Complete per-occurrence historical protection, without erasing early B-T.
2
3use crate::{
4    material::Placement,
5    query::Query,
6    range::RangeSource,
7    timestamp::{Node, Pades, Requirements},
8};
9use structured_asn1::memory::{MemoryError, OwnedVec, Work};
10use structured_ltv::{
11    baseline::ArchiveMaterial,
12    material::ParticipantMaterial,
13    source::{Location, Query as _, ScopeError},
14    timestamp::TimestampScope,
15};
16
17/// Both original material predicates under one exact document timestamp.
18pub type Seal<'a, 'cl, 'r, 'v, 'c, 'd, 'auth, D, G, C> = ArchiveMaterial<
19    'a,
20    ParticipantMaterial<'cl, 'r, 'v, 'c, 'd, Node, D, G, Placement, Pades>,
21    crate::material::credentials::Report<'auth, C, D, G>,
22>;
23
24/// Original-order optional material receipts for the complete DTS census.
25pub type SealReports<'s, 'a, 'cl, 'r, 'v, 'c, 'd, 'auth, D, G, C> =
26    &'s [Option<&'s Seal<'a, 'cl, 'r, 'v, 'c, 'd, 'auth, D, G, C>>];
27
28/// Concrete source-bound `PAdES` document-timestamp material census.
29pub type SealsOf<'s, 'a, 'cl, 'r, 'v, 'c, 'd, 'auth, D, G, C> =
30    Seals<'s, Seal<'a, 'cl, 'r, 'v, 'c, 'd, 'auth, D, G, C>, D, G>;
31
32/// Every original document timestamp in source order. An absent report is an
33/// explicitly unassessed occurrence, not successful absence of a timestamp.
34/// Early signature-existence timestamps need not already carry LT material;
35/// no aggregate here relabels them as failed LTA renewals or hides their facts.
36pub struct Seals<'s, R, D, G> {
37    pub(super) sources: OwnedVec<Occurrence<D, G>>,
38    pub(super) reports: &'s [Option<&'s R>],
39    pub(super) required: &'s structured_ltv::timestamp::TimestampRequirements<Node, D, G, Pades>,
40    pub(super) session: &'s crate::Session,
41    pub(super) vri_revisions: OwnedVec<usize>,
42}
43pub(super) struct Occurrence<D, G> {
44    pub(super) location: Location<Node, D, G>,
45    pub(super) revision: Option<usize>,
46}
47impl<'s, R, D: Copy, G: Copy> Seals<'s, R, D, G> {
48    /// Complete original count, including unassessed and failed occurrences.
49    #[must_use]
50    pub fn len(&self) -> usize {
51        self.sources.len()
52    }
53    /// Actual absence of document timestamp occurrences.
54    #[must_use]
55    pub fn is_empty(&self) -> bool {
56        self.sources.is_empty()
57    }
58    /// Exact original token location, with an explicit missing-Contents node.
59    #[must_use]
60    pub fn source(&self, ordinal: usize) -> Option<Location<Node, D, G>> {
61        self.sources.get(ordinal).map(|source| source.location)
62    }
63    /// Exact whole-revision coverage, absent for an unreadable/partial source.
64    #[must_use]
65    pub fn revision(&self, ordinal: usize) -> Option<usize> {
66        self.sources.get(ordinal).and_then(|source| source.revision)
67    }
68    /// Original paired material report at this ordinal. Its negative findings
69    /// remain available independently of another occurrence's successful seal.
70    #[must_use]
71    pub fn report(&self, ordinal: usize) -> Option<&'s R> {
72        self.reports.get(ordinal).copied().flatten()
73    }
74}
75
76/// Source/current refusal versus a substituted or incomplete source census.
77#[derive(Debug)]
78pub enum Error {
79    /// Report count or actual source/root/scope was substituted.
80    Foreign {
81        /// Original selecting root or timestamp.
82        node: Node,
83    },
84    /// Original source changed or its current operation refused.
85    Scope(ScopeError<Node>),
86    /// Original report metadata storage refused before copying.
87    Resource {
88        /// Actual original root.
89        node: Node,
90        /// Original capacity/work cause.
91        cause: MemoryError,
92    },
93}
94
95/// Join one slot for EVERY original PDF DTS with its exact common material
96/// receipt. No trust, chronology, algorithm-longevity or successful-suffix
97/// inference occurs. EN319142-1 §§6.3(w,x) and EN319102-1 §5.6.3 keep historic
98/// input obligations distinct from the signature-existence service of B-T.
99pub fn seals<'s, 'a, 'cl, 'r, 'v, 'c, 'd, 'auth, C, S: RangeSource>(
100    required: &'s Requirements<'_, '_, S::Document, S::Generation>,
101    reports: SealReports<'s, 'a, 'cl, 'r, 'v, 'c, 'd, 'auth, S::Document, S::Generation, C>,
102    query: &Query<'_, '_, S>,
103) -> Result<SealsOf<'s, 'a, 'cl, 'r, 'v, 'c, 'd, 'auth, S::Document, S::Generation, C>, Error> {
104    let root = required.all().signature();
105    query.admit(root, 1).map_err(Error::Scope)?;
106    if !query.belongs(required.signature().owner().value.session)
107        || required.revision().is_some()
108        || reports.len() != required.document_timestamps().len()
109    {
110        return Err(Error::Foreign { node: root });
111    }
112    let mut sources = query.execution().vector();
113    sources
114        .try_reserve_exact(reports.len())
115        .map_err(|cause| Error::Resource { node: root, cause })?;
116    for (source, report) in required.document_timestamps().iter().zip(reports) {
117        query.admit(root, 1).map_err(Error::Scope)?;
118        let node = source.token();
119        if let Some(report) = report {
120            let actual = report.x509().inputs().requirements();
121            if actual.signature() != root
122                || !matches!(actual.scope(), TimestampScope::BeforeArchive(location)
123                if location.snapshot == query.snapshot() && location.node == node)
124            {
125                return Err(Error::Foreign { node });
126            }
127        }
128        let revision = source
129            .signature()
130            .and_then(|signature| match signature.coverage() {
131                crate::signature::Coverage::Revision(revision) => Some(revision),
132                crate::signature::Coverage::Partial => None,
133            });
134        sources
135            .try_push(Occurrence {
136                location: Location {
137                    snapshot: query.snapshot(),
138                    node,
139                },
140                revision,
141            })
142            .map_err(|cause| Error::Resource { node, cause })?;
143    }
144    query.admit(root, 1).map_err(Error::Scope)?;
145    let mut vri_revisions = query.execution().vector();
146    for source in required.vri_timestamps() {
147        query.admit(root, 1).map_err(Error::Scope)?;
148        vri_revisions
149            .try_push(source.revision)
150            .map_err(|cause| Error::Resource { node: root, cause })?;
151    }
152    Ok(Seals {
153        sources: sources.into_owned(),
154        reports,
155        required: required.all(),
156        session: required.signature().owner().value.session,
157        vri_revisions: vri_revisions.into_owned(),
158    })
159}