Skip to main content

structured_ltv/material/
uses.rs

1//! Exact artifact uses across all reached actors, independent of path receipts.
2
3use super::ArtifactRef;
4use super::index::ArtifactKey;
5use super::status::{ArtifactVisitError, member_source, visit_artifacts};
6use crate::pki::{
7    AuthorityClosureError, AuthorityUses, ParticipantAuthority, ParticipantClosure, Uses, UsesError,
8};
9use crate::source::{Query, ScopeError};
10use structured_asn1::memory::{MemoryError, OwnedMap, OwnedVec, Work};
11
12/// An original certificate/status value with ALL its actual actor purposes.
13/// Exact kind and original DER identify an artifact; two checked paths or
14/// separately parsed copies do not make the same certificate exclusive to one.
15pub struct ArtifactUse<'certificate, 'document> {
16    artifact: ArtifactRef<'certificate, 'document>,
17    uses: Uses,
18    targets: Option<usize>,
19}
20
21impl<'certificate, 'document> ArtifactUse<'certificate, 'document> {
22    /// First original value carrying this exact kind/DER identity.
23    #[must_use]
24    pub fn artifact(&self) -> ArtifactRef<'certificate, 'document> {
25        self.artifact
26    }
27    /// Union of every reached actor's uses, including status-authority uses.
28    #[must_use]
29    pub fn uses(&self) -> Uses {
30        self.uses
31    }
32}
33
34/// Complete X.509/CRL/OCSP artifact census for the same participant requirements.
35/// Credential-format-specific data remain separate. This does not assert CA
36/// status, allowed placement, freshness or baseline grade.
37pub struct ArtifactUses<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G, I> {
38    inputs: &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>,
39    entries: OwnedVec<ArtifactUse<'certificate, 'document>>,
40    targets: OwnedVec<StatusTarget>,
41}
42
43// A status artifact applies to THIS certificate, not its authentication signer.
44// Both ordinals refer to the same immutable artifact catalogue; no source bytes
45// or parsed certificates are copied to preserve this relation.
46struct StatusTarget {
47    certificate: usize,
48    next: Option<usize>,
49}
50
51impl<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G, I>
52    ArtifactUses<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G, I>
53{
54    /// Same source-derived actor/status input closure, without another query.
55    #[must_use]
56    pub fn inputs(
57        &self,
58    ) -> &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>
59    {
60        self.inputs
61    }
62    /// Every distinct artifact once in first-encounter order.
63    #[must_use]
64    pub fn entries(&self) -> &[ArtifactUse<'certificate, 'document>] {
65        &self.entries
66    }
67    /// The certificates whose status the CRL/OCSP entry at `ordinal` reports,
68    /// as ordinals into [`Self::entries`]; empty for a certificate entry.
69    /// Each step is constant time; a caller paying per target admits it.
70    ///
71    /// # Panics
72    /// When `ordinal` is not an ordinal of [`Self::entries`].
73    #[must_use]
74    pub fn targets(&self, ordinal: usize) -> Targets<'_> {
75        Targets {
76            targets: &self.targets,
77            next: self.entries[ordinal].targets,
78        }
79    }
80}
81
82/// Status targets of one artifact, most recently recorded first.
83pub struct Targets<'a> {
84    targets: &'a [StatusTarget],
85    next: Option<usize>,
86}
87
88impl Iterator for Targets<'_> {
89    type Item = usize;
90    fn next(&mut self) -> Option<usize> {
91        let target = &self.targets[self.next?];
92        self.next = target.next;
93        Some(target.certificate)
94    }
95}
96
97impl core::iter::FusedIterator for Targets<'_> {}
98
99/// Original source/role association and resource failures remain terminal.
100pub type ArtifactUsesResult<'closure, 'requirements, 'view, 'certificate, 'document, Q, I> = Result<
101    ArtifactUses<
102        'closure,
103        'requirements,
104        'view,
105        'certificate,
106        'document,
107        <Q as Query>::Id,
108        <Q as Query>::Document,
109        <Q as Query>::Generation,
110        I,
111    >,
112    UsesError<<Q as Query>::Id>,
113>;
114
115struct Census<'certificate, 'document, W: Work> {
116    index: OwnedMap<ArtifactKey<'document>, usize, W>,
117    entries: OwnedVec<ArtifactUse<'certificate, 'document>, W>,
118    targets: OwnedVec<StatusTarget, W>,
119}
120
121impl<'certificate, 'document, W: Work> Census<'certificate, 'document, W> {
122    fn record(
123        &mut self,
124        artifact: ArtifactRef<'certificate, 'document>,
125        uses: Uses,
126        work: &W,
127    ) -> Result<usize, MemoryError> {
128        work.charge_work(1)?;
129        let key = artifact.identity();
130        if let Some(&index) = self.index.get(&key, work)? {
131            self.entries[index].uses.merge(uses);
132            return Ok(index);
133        }
134        let index = self.entries.len();
135        self.index.try_insert(key, index)?;
136        self.entries.try_push(ArtifactUse {
137            artifact,
138            uses,
139            targets: None,
140        })?;
141        Ok(index)
142    }
143}
144
145/// Merge purposes by exact original artifact identity AFTER role propagation.
146/// EN319132-1 ยงยง5.4.2(5)/5.4.3(4) concern exclusive certificate/status uses,
147/// which cannot be inferred from path identity or the first occurrence alone.
148/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
149///
150/// Reuses the archive index's key/hash admission and status source visitor.
151/// Complete/delta contributors, actual OCSP/CRL signers and existing direct
152/// anchors participate. No parse, provider, signature or normalization repeats.
153/// Current Work pays even when the original role census used another account;
154/// partial metadata and scratch index are released on every refusal.
155pub fn artifact_uses<'closure, 'requirements, 'view, 'certificate, 'document, Q: Query, I>(
156    authorities: &AuthorityUses<
157        'closure,
158        'requirements,
159        'view,
160        'certificate,
161        'document,
162        Q::Id,
163        Q::Document,
164        Q::Generation,
165        I,
166    >,
167    query: &Q,
168) -> ArtifactUsesResult<'closure, 'requirements, 'view, 'certificate, 'document, Q, I> {
169    let inputs = authorities.inputs();
170    let requirements = inputs.requirements();
171    if requirements.scope().location().snapshot != query.snapshot() {
172        return Err(UsesError::Foreign);
173    }
174    query
175        .admit(requirements.signature(), 1)
176        .map_err(UsesError::Scope)?;
177    let work = query.execution();
178    let resource = |cause| UsesError::Closure(AuthorityClosureError::Resource(cause));
179    let mut census = Census {
180        index: work.map(),
181        entries: work.vector(),
182        targets: work.vector(),
183    };
184    for authority in authorities.entries() {
185        work.charge_work(1).map_err(resource)?;
186        let path = authority.node().statuses.path();
187        let uses = authority.uses();
188        if let Some(certificate) = path.anchor().certificate() {
189            census
190                .record(ArtifactRef::Certificate(certificate), uses, work)
191                .map_err(resource)?;
192        }
193        for member in path.members() {
194            census
195                .record(ArtifactRef::Certificate(member.certificate()), uses, work)
196                .map_err(resource)?;
197        }
198        for input in authority.node().statuses.entries() {
199            work.charge_work(1).map_err(resource)?;
200            let Some(source) = member_source(input) else {
201                continue;
202            };
203            let certificate = census
204                .record(ArtifactRef::Certificate(source.target()), uses, work)
205                .map_err(resource)?;
206            visit_artifacts(source, requirements.signature(), query, |artifact, _, _| {
207                let index = census.record(artifact, uses, work)?;
208                if !matches!(artifact, ArtifactRef::Certificate(_)) {
209                    let next = census.entries[index].targets;
210                    let target = census.targets.len();
211                    census
212                        .targets
213                        .try_push(StatusTarget { certificate, next })?;
214                    census.entries[index].targets = Some(target);
215                }
216                Ok::<_, MemoryError>(())
217            })
218            .map_err(|cause| match cause {
219                ArtifactVisitError::Scope(ScopeError::Resource { cause, .. })
220                | ArtifactVisitError::Visitor(cause) => resource(cause),
221                ArtifactVisitError::Scope(cause) => UsesError::Scope(cause),
222            })?;
223        }
224    }
225    for entry in requirements.entries() {
226        work.charge_work(1).map_err(resource)?;
227        let ParticipantAuthority::Anchor(anchor) = entry.authority() else {
228            continue;
229        };
230        if let Some(certificate) = anchor.anchor().certificate() {
231            census
232                .record(
233                    ArtifactRef::Certificate(certificate),
234                    Uses::participant(entry.participant()),
235                    work,
236                )
237                .map_err(resource)?;
238        }
239    }
240    Ok(ArtifactUses {
241        inputs,
242        entries: census.entries.into_owned(),
243        targets: census.targets.into_owned(),
244    })
245}
246
247#[cfg(test)]
248mod tests;