Skip to main content

structured_pades/baseline/
profile.rs

1//! Explicit current versus original-occurrence validation-data protection.
2
3use super::{LongTermOf, Seal, Seals, SealsError};
4use crate::{query::Query, range::RangeSource};
5use structured_ltv::source::Query as _;
6
7/// A requested source window, never a search for successful timestamp results.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Selection {
10    /// One ordinal in the complete original document-timestamp census.
11    AtSource(usize),
12    /// The last written occurrence, including an unreadable or failed one.
13    Current,
14}
15
16/// Closed format/static-cryptographic and material facts for one original
17/// occurrence. Historical continuity, algorithm longevity, status usability
18/// and application trust remain distinct from these profile requirements.
19pub struct Assessment<'s, L, R, D, G> {
20    long_term: Option<&'s L>,
21    seals: &'s Seals<'s, R, D, G>,
22    selection: Selection,
23    ordinal: Option<usize>,
24    revision: Option<usize>,
25    long_term_complete: bool,
26    material_covered: Option<bool>,
27}
28impl<'s, L, R, D, G> Assessment<'s, L, R, D, G> {
29    /// Actual caller-selected projection; current never means last successful.
30    #[must_use]
31    pub fn selection(&self) -> Selection {
32        self.selection
33    }
34    /// Exact original occurrence, absent only when no DTS exists.
35    #[must_use]
36    pub fn ordinal(&self) -> Option<usize> {
37        self.ordinal
38    }
39    /// Source revision, absent for an unreadable or partial-range occurrence.
40    #[must_use]
41    pub fn revision(&self) -> Option<usize> {
42        self.revision
43    }
44    /// Actual linked LT report, retaining all its negative findings.
45    #[must_use]
46    pub fn long_term(&self) -> Option<&'s L> {
47        self.long_term
48    }
49    /// Complete original census, including earlier unsuccessful occurrences.
50    #[must_use]
51    pub fn seals(&self) -> &'s Seals<'s, R, D, G> {
52        self.seals
53    }
54    /// Basic, all required TSA paths/static time and complete LT material hold.
55    #[must_use]
56    pub fn all_long_term_requirements_met(&self) -> bool {
57        self.long_term_complete
58    }
59    /// Selected original input covers every preceding required material value;
60    /// None is unassessed, false retains a completed negative material result.
61    #[must_use]
62    pub fn material_covered(&self) -> Option<bool> {
63        self.material_covered
64    }
65    /// Closed B-LTA format/static-cryptographic/material requirements only.
66    /// This is not historical `PoE` continuity, longevity or a trust verdict.
67    #[must_use]
68    pub fn all_profile_requirements_met(&self) -> bool {
69        self.revision.is_some() && self.long_term_complete && self.material_covered == Some(true)
70    }
71}
72
73/// A complete source-ordinal assessment with independently retained details.
74pub type AssessmentOf<'s, 'a, 'cl, 'r, 'c, 'd, 'auth, 'v, C, D, G> = Assessment<
75    's,
76    LongTermOf<'s, 'a, 'cl, 'r, 'c, 'd, 'auth, C, D, G>,
77    Seal<'a, 'cl, 'r, 'v, 'c, 'd, 'auth, D, G, C>,
78    D,
79    G,
80>;
81
82/// Joins closed same-source LT and preceding-material reports at an explicit
83/// original DTS. EN319142-1 ยง6.3 Table1 and (w,x) require protection of the
84/// complete preceding validation material, including every earlier TSA. An
85/// early B-T DTS need not itself already provide the later LT service. All
86/// occurrences remain in `seals`; no failed last occurrence is replaced.
87/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31914201/01.02.01_60/en_31914201v010201p.pdf>
88pub fn assess<'s, 'a, 'cl, 'r, 'c, 'd, 'auth, 'v, C, S: RangeSource>(
89    selection: Selection,
90    long_term: Option<
91        &'s LongTermOf<'s, 'a, 'cl, 'r, 'c, 'd, 'auth, C, S::Document, S::Generation>,
92    >,
93    seals: &'s super::archives::SealsOf<
94        's,
95        'a,
96        'cl,
97        'r,
98        'v,
99        'c,
100        'd,
101        'auth,
102        S::Document,
103        S::Generation,
104        C,
105    >,
106    query: &Query<'_, '_, S>,
107) -> Result<
108    AssessmentOf<'s, 'a, 'cl, 'r, 'c, 'd, 'auth, 'v, C, S::Document, S::Generation>,
109    SealsError,
110> {
111    let root = seals.required.signature();
112    query.admit(root, 1).map_err(SealsError::Scope)?;
113    if !query.belongs(seals.session)
114        || seals.required.scope().location().snapshot != query.snapshot()
115    {
116        return Err(SealsError::Foreign { node: root });
117    }
118    let ordinal = match selection {
119        Selection::Current => seals.sources.len().checked_sub(1),
120        Selection::AtSource(ordinal) => {
121            if ordinal >= seals.sources.len() {
122                return Err(SealsError::Foreign { node: root });
123            }
124            Some(ordinal)
125        }
126    };
127    let revision = ordinal.and_then(|ordinal| seals.sources[ordinal].revision);
128    let material_covered = ordinal.and_then(|ordinal| {
129        seals.reports[ordinal].map(structured_ltv::baseline::ArchiveMaterial::all_material_covered)
130    });
131    let mut long_term_complete = false;
132    if let Some(long_term) = long_term {
133        let signature = long_term
134            .timed()
135            .basic()
136            .binding()
137            .prepared()
138            .container
139            .signature();
140        let required = long_term.timed().paths().required();
141        if !query.belongs(signature.owner().value.session)
142            || required.signature() != root
143            || required.scope().location().snapshot != query.snapshot()
144        {
145            return Err(SealsError::Foreign { node: root });
146        }
147        let (Some(revision), Some(ordinal)) = (revision, ordinal) else {
148            return Err(SealsError::Foreign { node: root });
149        };
150        if long_term.x509().revision() != revision {
151            return Err(SealsError::Foreign { node: root });
152        }
153        let prefix =
154            seals.required.entries().len() - seals.sources.len() - seals.vri_revisions.len();
155        let pdf_end = prefix + ordinal + 1;
156        let vri_start = prefix + seals.sources.len();
157        let mut cursor = 0;
158        for (index, expected) in seals.required.entries().iter().enumerate() {
159            query.admit(root, 5).map_err(SealsError::Scope)?;
160            if index >= pdf_end
161                && (index < vri_start || seals.vri_revisions[index - vri_start] > revision)
162            {
163                continue;
164            }
165            let Some(actual) = required.entries().get(cursor) else {
166                return Err(SealsError::Foreign { node: root });
167            };
168            if actual.signature != expected.signature
169                || actual.property != expected.property
170                || actual.token != expected.token
171                || actual.purpose != expected.purpose
172            {
173                return Err(SealsError::Foreign { node: root });
174            }
175            cursor += 1;
176        }
177        if cursor != required.entries().len() {
178            return Err(SealsError::Foreign { node: root });
179        }
180        long_term_complete = long_term.timed().all_static_time_requirements_met()
181            && long_term.all_material_requirements_met();
182    }
183    query.admit(root, 1).map_err(SealsError::Scope)?;
184    Ok(Assessment {
185        long_term,
186        seals,
187        selection,
188        ordinal,
189        revision,
190        long_term_complete,
191        material_covered,
192    })
193}