1use super::{Index, Placement};
3use crate::Xades;
4use crate::xml::{Access, Snapshot, Xml};
5use structured_asn1::memory::{HashWork, MemoryError, OwnedVec, Work};
6use structured_ltv::material::status::{ArtifactVisitError, member_source, visit_artifacts};
7use structured_ltv::material::{ArtifactKey, ArtifactRef, IndexError};
8use structured_ltv::pki::{
9 AuthorityVisitError, Participant, ParticipantAuthority, ParticipantClosure, UsesError,
10};
11use structured_ltv::source::ScopeError;
12use structured_ltv::timestamp::TimestampScope;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum TimestampPlacementFailure {
17 Missing,
19 OutsideTimestampContainers,
21}
22
23pub struct TimestampPlacementFinding<'certificate, 'document, N> {
25 pub property: N,
27 pub token: N,
29 pub artifact: ArtifactRef<'certificate, 'document>,
31 pub cause: TimestampPlacementFailure,
33}
34
35pub struct TimestampPlacement<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G> {
39 inputs:
40 &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, Xades>,
41 findings: OwnedVec<TimestampPlacementFinding<'certificate, 'document, N>>,
42 snapshot: Snapshot<D, G>,
43}
44impl<'closure, 'requirements, 'view, 'certificate, 'document, N, D: Copy, G: Copy>
45 TimestampPlacement<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G>
46{
47 #[must_use]
49 pub fn inputs(
50 &self,
51 ) -> &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, Xades>
52 {
53 self.inputs
54 }
55 #[must_use]
57 pub fn findings(&self) -> &[TimestampPlacementFinding<'certificate, 'document, N>] {
58 &self.findings
59 }
60 #[must_use]
62 pub fn snapshot(&self) -> Snapshot<D, G> {
63 self.snapshot
64 }
65}
66
67#[derive(Debug)]
69pub enum TimestampPlacementError<N> {
70 Scope,
72 Index(IndexError<N>),
74 Uses(UsesError<N>),
76}
77
78pub type TimestampPlacementResult<'closure, 'requirements, 'view, 'certificate, 'document, X> =
80 Result<
81 TimestampPlacement<
82 'closure,
83 'requirements,
84 'view,
85 'certificate,
86 'document,
87 <X as Xml>::Id,
88 <X as Xml>::Document,
89 <X as Xml>::Generation,
90 >,
91 TimestampPlacementError<<X as Xml>::Id>,
92 >;
93
94#[derive(Hash, PartialEq, Eq)]
95struct Seen<'document> {
96 root: usize,
97 artifact: ArtifactKey<'document>,
98}
99impl HashWork for Seen<'_> {
100 fn hash_work(&self) -> Result<u64, MemoryError> {
101 self.artifact
102 .hash_work()?
103 .checked_add(1)
104 .ok_or(MemoryError::WorkExceeded)
105 }
106}
107
108fn allowed<N: Copy, E>(
109 placement: Placement<N>,
110 property: N,
111 token: N,
112 equal: &mut impl FnMut(N, N) -> Result<bool, E>,
113) -> Result<bool, E> {
114 match placement {
115 Placement::General(_) | Placement::Timestamp(_) => Ok(true),
117 Placement::TimestampToken {
118 property: actual_property,
119 token: actual_token,
120 ..
121 } => Ok(equal(actual_property, property)? && equal(actual_token, token)?),
122 _ => Ok(false),
123 }
124}
125
126fn availability<X: Xml, W: Work, Q: Work>(
127 index: &Index<'_, X::Id, X::Document, X::Generation, W>,
128 artifact: ArtifactRef<'_, '_>,
129 signature: X::Id,
130 property: X::Id,
131 token: X::Id,
132 access: &Access<'_, X, Q>,
133) -> Result<Option<TimestampPlacementFailure>, IndexError<X::Id>> {
134 let mut present = false;
135 for occurrence in index.artifact(artifact, access)? {
136 let occurrence = occurrence?;
137 present = true;
138 let eligible = allowed(
139 occurrence.placement(),
140 property,
141 token,
142 &mut |left, right| {
143 access
144 .equal_id(left, right)
145 .map_err(|cause| IndexError::Resource { signature, cause })
146 },
147 )?;
148 if eligible {
149 return Ok(None);
150 }
151 }
152 Ok(Some(if present {
153 TimestampPlacementFailure::OutsideTimestampContainers
154 } else {
155 TimestampPlacementFailure::Missing
156 }))
157}
158
159pub fn timestamp_placement<
169 'closure,
170 'requirements,
171 'view,
172 'certificate,
173 'document,
174 X: Xml,
175 W: Work,
176 Q: Work,
177>(
178 inputs: &'closure ParticipantClosure<
179 'requirements,
180 'view,
181 'certificate,
182 'document,
183 X::Id,
184 X::Document,
185 X::Generation,
186 Xades,
187 >,
188 index: &Index<'_, X::Id, X::Document, X::Generation, W>,
189 access: &Access<'_, X, Q>,
190) -> TimestampPlacementResult<'closure, 'requirements, 'view, 'certificate, 'document, X> {
191 let required = inputs.requirements();
192 if !matches!(required.scope(), TimestampScope::All(_)) {
193 return Err(TimestampPlacementError::Scope);
194 }
195 let signature = required.signature();
196 if required.scope().location().snapshot != access.snapshot() {
197 return Err(TimestampPlacementError::Index(IndexError::Foreign {
198 signature,
199 }));
200 }
201 index
202 .require_signature(signature, access)
203 .map_err(TimestampPlacementError::Index)?;
204 let work = access.execution();
205 let resource =
206 |cause| TimestampPlacementError::Index(IndexError::Resource { signature, cause });
207 let mut actors = work.vector();
208 for entry in required.entries() {
209 work.charge_work(1).map_err(resource)?;
210 if matches!(entry.authority(), ParticipantAuthority::Path(_)) {
211 actors
212 .try_push(match entry.participant() {
213 Participant::Timestamp { property, token } => Some((property, token)),
214 _ => None,
215 })
216 .map_err(resource)?;
217 }
218 }
219 let mut seen = work.map();
220 let mut findings = work.vector();
221 inputs
222 .authorities()
223 .visit_roots(work, |root, node| {
224 let Some((property, token)) = actors[root] else {
225 return Ok(());
226 };
227 let mut record = |artifact: ArtifactRef<'certificate, 'document>| {
228 let key = Seen {
229 root,
230 artifact: artifact.identity(),
231 };
232 if seen.try_insert(key, ()).map_err(resource)?.is_some() {
233 return Ok(());
234 }
235 if let Some(cause) =
236 availability(index, artifact, signature, property, token, access)
237 .map_err(TimestampPlacementError::Index)?
238 {
239 findings
240 .try_push(TimestampPlacementFinding {
241 property,
242 token,
243 artifact,
244 cause,
245 })
246 .map_err(resource)?;
247 }
248 Ok(())
249 };
250 let path = node.statuses.path();
251 if let Some(certificate) = path.anchor().certificate() {
252 record(ArtifactRef::Certificate(certificate))?;
253 }
254 for member in path.members() {
255 work.charge_work(1).map_err(resource)?;
256 record(ArtifactRef::Certificate(member.certificate()))?;
257 }
258 for input in node.statuses.entries() {
259 work.charge_work(1).map_err(resource)?;
260 if let Some(source) = member_source(input) {
261 visit_artifacts(source, signature, access, |artifact, _, _| record(artifact))
262 .map_err(|cause| match cause {
263 ArtifactVisitError::Scope(ScopeError::Resource { cause, .. }) => {
264 resource(cause)
265 }
266 ArtifactVisitError::Scope(cause) => {
267 TimestampPlacementError::Uses(UsesError::Scope(cause))
268 }
269 ArtifactVisitError::Visitor(cause) => cause,
270 })?;
271 }
272 }
273 Ok(())
274 })
275 .map_err(|cause| match cause {
276 AuthorityVisitError::Processing(cause) => {
277 TimestampPlacementError::Uses(UsesError::Closure(cause))
278 }
279 AuthorityVisitError::Visitor(cause) => cause,
280 })?;
281 Ok(TimestampPlacement {
282 inputs,
283 findings: findings.into_owned(),
284 snapshot: access.snapshot(),
285 })
286}
287
288#[cfg(test)]
289mod tests;