Skip to main content

structured_xades/baseline/
level.rs

1//! One requested baseline level over its complete linked stage reports.
2
3use super::Basic;
4use crate::{
5    Xades, certificate::CheckedPathCertificate, material, prepare::Preparation,
6    unsigned::Requirements,
7};
8use structured_ltv::baseline::{ArchiveMaterial, Archives, LongTerm, Timed};
9
10type BasicOf<'a, N, D, G> = Basic<'a, 'a, 'a, 'a, N, D, G>;
11type TimedOf<'a, N, D, G> = Timed<
12    'a,
13    'a,
14    'a,
15    BasicOf<'a, N, D, G>,
16    CheckedPathCertificate<'a, 'a, N, D, G>,
17    N,
18    D,
19    G,
20    Xades,
21>;
22type CredentialsOf<'a, A, N, D, G> = material::credentials::Report<'a, 'a, 'a, A, N, D, G>;
23type LongTermOf<'a, A, N, D, G> = LongTerm<
24    'a,
25    TimedOf<'a, N, D, G>,
26    material::Checks<'a, 'a, 'a, 'a, 'a, N, D, G>,
27    CredentialsOf<'a, A, N, D, G>,
28>;
29type ArchivesOf<'a, A, N, D, G> = Archives<
30    'a,
31    Preparation<N, D, G>,
32    ArchiveMaterial<
33        'a,
34        structured_ltv::material::ParticipantMaterial<
35            'a,
36            'a,
37            'a,
38            'a,
39            'a,
40            N,
41            D,
42            G,
43            material::Placement<N>,
44            Xades,
45        >,
46        CredentialsOf<'a, A, N, D, G>,
47    >,
48>;
49
50/// Stage reports for exactly one requested level. Each later stage already
51/// retains its earlier ones, so a level never needs a separately supplied copy.
52pub enum Evidence<'a, A, N, D, G> {
53    /// B-B root field/header/key/digest facts.
54    Basic(&'a BasicOf<'a, N, D, G>),
55    /// B-T whole token census and root signature-time association.
56    Timestamped(&'a TimedOf<'a, N, D, G>),
57    /// B-LT whole-signature validation-material facts.
58    LongTerm(&'a LongTermOf<'a, A, N, D, G>),
59    /// B-LTA: whole material plus the census of every original root archive.
60    Archival {
61        /// Whole-signature material, including its time and basic stages.
62        long_term: &'a LongTermOf<'a, A, N, D, G>,
63        /// Material covered by each original archive, in source order.
64        archives: &'a ArchivesOf<'a, A, N, D, G>,
65    },
66}
67
68// Basic and timestamped stages carry no credential census, so their
69// constructors fix `A` and callers need not name it (as `HashMap::new` does).
70impl<'a, N, D, G> Evidence<'a, (), N, D, G> {
71    /// B-B evidence.
72    #[must_use]
73    pub fn basic(report: &'a BasicOf<'a, N, D, G>) -> Self {
74        Self::Basic(report)
75    }
76    /// B-T evidence.
77    #[must_use]
78    pub fn timestamped(report: &'a TimedOf<'a, N, D, G>) -> Self {
79        Self::Timestamped(report)
80    }
81}
82
83impl<A, N: Copy, D: Copy, G: Copy> Evidence<'_, A, N, D, G> {
84    /// Structural profile these reports were produced for.
85    #[must_use]
86    pub fn level(&self) -> Requirements {
87        match self {
88            Self::Basic(_) => Requirements::Basic,
89            Self::Timestamped(_) => Requirements::Timestamped,
90            Self::LongTerm(_) => Requirements::LongTerm,
91            Self::Archival { .. } => Requirements::Archival,
92        }
93    }
94
95    pub(crate) fn prepared(&self) -> &Preparation<N, D, G> {
96        match self {
97            Self::Basic(basic) => basic.inputs().fields.prepared(),
98            Self::Timestamped(timed) => timed.basic().inputs().fields.prepared(),
99            Self::LongTerm(long_term) | Self::Archival { long_term, .. } => {
100                long_term.timed().basic().inputs().fields.prepared()
101            }
102        }
103    }
104}
105
106/// Requested level joined with its linked stage reports. This states only
107/// that every stage predicate of that level holds; status usability,
108/// chronology, algorithm longevity and caller trust stay with the caller.
109pub struct Assessment<'a, A, N, D, G> {
110    evidence: Evidence<'a, A, N, D, G>,
111    met: bool,
112}
113
114impl<'a, A, N: Copy, D: Copy, G: Copy> Assessment<'a, A, N, D, G> {
115    /// Original requested profile, equal to the evidence level.
116    #[must_use]
117    pub fn requested(&self) -> Requirements {
118        self.evidence.level()
119    }
120    /// Same stage reports, retaining every detailed negative finding.
121    #[must_use]
122    pub fn evidence(&self) -> &Evidence<'a, A, N, D, G> {
123        &self.evidence
124    }
125    /// Every stage predicate of the requested level holds.
126    #[must_use]
127    pub fn all_requirements_met(&self) -> bool {
128        self.met
129    }
130}
131
132/// Reports that cannot describe the requested level of one preparation.
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum LevelError {
135    /// The preparation requested a different structural profile.
136    Mismatch {
137        /// Profile enforced by the original preparation.
138        requested: Requirements,
139        /// Level of the supplied stage reports.
140        supplied: Requirements,
141    },
142    /// The archive census uses another preparation, or one archive's material
143    /// uses another authenticated credential census than the whole signature.
144    Foreign,
145}
146
147/// Join the stage reports of the level that the original preparation
148/// requested. A deeper or shallower stage set is refused: additional
149/// containers never promote the requested profile, and a missing stage
150/// cannot be assumed. No source query, cryptography or allocation occurs.
151///
152/// EN319132-1 V1.3.1 ยง6.3 and table 2 define each level as a superset of the
153/// previous one, so B-LTA also needs every B-LT, B-T and B-B condition.
154/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
155///
156/// # Errors
157///
158/// [`LevelError::Mismatch`] when the evidence level differs from the
159/// requested profile, [`LevelError::Foreign`] when the archive census does
160/// not belong to the same preparation and credential census.
161pub fn assess<A, N: Copy, D: Copy, G: Copy>(
162    evidence: Evidence<'_, A, N, D, G>,
163) -> Result<Assessment<'_, A, N, D, G>, LevelError> {
164    let requested = evidence.prepared().requirements();
165    let supplied = evidence.level();
166    if requested != supplied {
167        return Err(LevelError::Mismatch {
168            requested,
169            supplied,
170        });
171    }
172    let met = match &evidence {
173        Evidence::Basic(basic) => basic.all_root_requirements_met(),
174        Evidence::Timestamped(timed) => timed_met(timed),
175        Evidence::LongTerm(long_term) => long_term_met(long_term),
176        Evidence::Archival {
177            long_term,
178            archives,
179        } => {
180            let census = long_term.credentials().credentials();
181            if !core::ptr::eq(archives.census(), evidence.prepared())
182                || archives
183                    .reports()
184                    .iter()
185                    .any(|report| !core::ptr::eq(report.credentials().credentials(), census))
186            {
187                return Err(LevelError::Foreign);
188            }
189            long_term_met(long_term) && archives.all_archive_material_covered()
190        }
191    };
192    Ok(Assessment { evidence, met })
193}
194
195fn timed_met<N: Copy, D: Copy, G: Copy>(timed: &TimedOf<'_, N, D, G>) -> bool {
196    timed.basic().all_root_requirements_met() && timed.all_static_time_requirements_met()
197}
198
199fn long_term_met<A, N: Copy, D: Copy, G: Copy>(long_term: &LongTermOf<'_, A, N, D, G>) -> bool {
200    timed_met(long_term.timed()) && long_term.all_material_requirements_met()
201}