Skip to main content

structured_ltv/material/
participants.rs

1//! Complete participant X.509 material for one original archive input.
2
3use super::status::{AuthorityMaterial, archived_authorities};
4use super::{ArchiveError, Archived, ArtifactRef, Coverage, Index, IndexError};
5use crate::pki::{Participant, ParticipantAuthority, ParticipantClosure};
6use crate::source::Query;
7use crate::timestamp::{Purpose, TimestampScope, Verified};
8use structured_asn1::memory::{HashWork, MemoryError, OwnedVec, Work};
9
10/// Direct credential anchor's distinct certificate-material obligation.
11pub struct AnchorMaterial<N, D, G, P> {
12    participant: Participant<N>,
13    certificate: Option<Archived<N, D, G, P>>,
14}
15
16impl<N: Copy, D, G, P> AnchorMaterial<N, D, G, P> {
17    /// Original authenticated credential using this direct anchor.
18    #[must_use]
19    pub fn participant(&self) -> Participant<N> {
20        self.participant
21    }
22
23    /// Existing anchor certificate's actual archive availability. `None` means
24    /// this explicit anchor has no certificate, not an unsuccessful lookup.
25    #[must_use]
26    pub fn certificate(&self) -> Option<&Archived<N, D, G, P>> {
27        self.certificate.as_ref()
28    }
29}
30
31/// Every participant's reached X.509 artifacts under the SAME original archive.
32/// Source requirements and negative status facts remain available in `inputs`.
33/// This does not decide status usability, trust, exclusive material placement,
34/// credential-format-specific material, chronology or an attained baseline level.
35pub struct ParticipantMaterial<
36    'closure,
37    'requirements,
38    'view,
39    'certificate,
40    'document,
41    N,
42    D,
43    G,
44    P,
45    I,
46> {
47    inputs: &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>,
48    authorities: AuthorityMaterial<'closure, 'view, 'certificate, 'document, N, D, G, P>,
49    anchors: OwnedVec<AnchorMaterial<N, D, G, P>>,
50    all_covered: bool,
51}
52
53impl<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G, P, I>
54    ParticipantMaterial<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G, P, I>
55{
56    /// Complete source-bound actor and status-authority input set.
57    #[must_use]
58    pub fn inputs(
59        &self,
60    ) -> &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>
61    {
62        self.inputs
63    }
64
65    /// Original certificates and every complete/delta/OCSP authority artifact.
66    #[must_use]
67    pub fn authorities(
68        &self,
69    ) -> &AuthorityMaterial<'closure, 'view, 'certificate, 'document, N, D, G, P> {
70        &self.authorities
71    }
72
73    /// Direct anchors, never substituted with non-anchor revocation inputs.
74    #[must_use]
75    pub fn anchors(&self) -> &[AnchorMaterial<N, D, G, P>] {
76        &self.anchors
77    }
78
79    /// Whether every required X.509 artifact has an exact covered copy before
80    /// this archive. This is NOT complete credential-format or level acceptance.
81    #[must_use]
82    pub fn all_x509_artifacts_covered(&self) -> bool {
83        self.all_covered
84    }
85}
86
87/// Foreign archive evidence cannot satisfy another original obligation set.
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum ParticipantArchiveError<N> {
90    /// Different source, selected signature, archive purpose or archive property.
91    Foreign {
92        /// Original required archive property.
93        expected: N,
94        /// Supplied token's actual original property.
95        actual: N,
96    },
97    /// Exact source/index/coverage/material processing cause.
98    Material(ArchiveError<N>),
99    /// Actor traversal, identity comparison or retained capacity refused.
100    Resource {
101        /// Original required archive property.
102        archive: N,
103        /// Current-operation refusal, not missing material.
104        cause: MemoryError,
105    },
106}
107
108/// Match the complete participant closure to its one particular original archive,
109/// then reuse the exact artifact/coverage kernel for every reached authority and
110/// existing direct-anchor certificate. EN319132-1 §5.4.2/§5.4.3/§6.3(aa) distinguishes
111/// existing anchor certificate material from non-anchor status obligations.
112/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
113///
114/// Own/later token material cannot repair preceding input, and a successful token
115/// for a different archive cannot be substituted. No parsing, provider, status or
116/// signature work is repeated. Late refusal drops every partial report.
117pub fn archived_participants<
118    'closure,
119    'requirements,
120    'view,
121    'certificate,
122    'document,
123    N,
124    D,
125    G,
126    P,
127    O,
128    W,
129    Q,
130    C,
131    I,
132>(
133    inputs: &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>,
134    index: &Index<'_, N, D, G, P, O, W, I>,
135    coverage: &Coverage<N, D, G, C, I>,
136    evidence: &Verified<N, D, G, I>,
137    query: &Q,
138) -> Result<
139    ParticipantMaterial<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G, P, I>,
140    ParticipantArchiveError<N>,
141>
142where
143    N: Copy + Eq + HashWork,
144    D: Copy + Eq,
145    G: Copy + Eq,
146    P: Copy,
147    W: Work,
148    C: Work,
149    Q: Query<Id = N, Document = D, Generation = G>,
150{
151    let required = inputs.requirements();
152    let scope = required.scope();
153    let archive = scope.location();
154    let foreign = || ParticipantArchiveError::Foreign {
155        expected: archive.node,
156        actual: coverage.subject(),
157    };
158    if !matches!(scope, TimestampScope::BeforeArchive(_))
159        || archive.snapshot != query.snapshot()
160        || archive.snapshot != evidence.token().snapshot
161    {
162        return Err(foreign());
163    }
164    let resource = |cause| ParticipantArchiveError::Resource {
165        archive: archive.node,
166        cause,
167    };
168    query.admit(archive.node, 1).map_err(|cause| {
169        ParticipantArchiveError::Material(ArchiveError::Index(IndexError::Scope(cause)))
170    })?;
171    let mut anchors = query.execution().vector();
172    let cost = archive
173        .node
174        .hash_work()
175        .and_then(|cost| {
176            cost.checked_add(coverage.subject().hash_work()?)
177                .ok_or(MemoryError::WorkExceeded)
178        })
179        .and_then(|cost| {
180            cost.checked_add(required.signature().hash_work()?)
181                .ok_or(MemoryError::WorkExceeded)
182        })
183        .and_then(|cost| {
184            cost.checked_add(evidence.signature().hash_work()?)
185                .ok_or(MemoryError::WorkExceeded)
186        })
187        .map_err(resource)?;
188    query.execution().charge_work(cost).map_err(resource)?;
189    if archive.node != coverage.subject()
190        || required.signature() != evidence.signature()
191        || evidence.purpose() != Purpose::Archive
192    {
193        return Err(foreign());
194    }
195    coverage
196        .require_evidence(evidence, archive.node, query)
197        .map_err(|cause| ParticipantArchiveError::Material(ArchiveError::Coverage(cause)))?;
198    let authorities = archived_authorities(inputs.authorities(), index, coverage, evidence, query)
199        .map_err(ParticipantArchiveError::Material)?;
200    let mut all_covered = authorities.all_artifacts_covered();
201    for entry in required.entries() {
202        query.execution().charge_work(1).map_err(resource)?;
203        let ParticipantAuthority::Anchor(anchor) = entry.authority() else {
204            continue;
205        };
206        let certificate = match anchor.anchor().certificate() {
207            Some(certificate) => {
208                let value = index
209                    .archived(
210                        ArtifactRef::Certificate(certificate),
211                        coverage,
212                        evidence,
213                        query,
214                    )
215                    .map_err(ParticipantArchiveError::Material)?;
216                if !matches!(value, Archived::Present(_)) {
217                    all_covered = false;
218                }
219                Some(value)
220            }
221            None => None,
222        };
223        anchors
224            .try_push(AnchorMaterial {
225                participant: entry.participant(),
226                certificate,
227            })
228            .map_err(resource)?;
229    }
230    Ok(ParticipantMaterial {
231        inputs,
232        authorities,
233        anchors: anchors.into_owned(),
234        all_covered,
235    })
236}