1use super::catalogue::{ContainerKind, container_kind};
4use super::{Index, Placement};
5use crate::Xades;
6use crate::pki::CounterPaths;
7use crate::prepare::Prepared;
8use crate::xml::{Access, Snapshot, Xml};
9use structured_asn1::memory::{HashWork, MemoryError, OwnedMap, OwnedVec, Work};
10use structured_ltv::material::status::{
11 ArtifactVisitError, StatusPart, member_source, visit_artifacts,
12};
13use structured_ltv::material::{ArtifactRef, IndexError};
14use structured_ltv::pki::{
15 Participant, ParticipantAuthority, ParticipantClosure, ParticipantRequirements, PathStatuses,
16};
17use structured_ltv::source::ScopeError;
18use structured_ltv::timestamp::TimestampScope;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum ConditionalFailure {
23 Missing,
25 Elsewhere,
27}
28
29pub struct ConditionalFinding<'certificate, 'document, N> {
31 pub property: N,
33 pub participant: Participant<N>,
35 pub artifact: ArtifactRef<'certificate, 'document>,
37 pub cause: ConditionalFailure,
39}
40
41pub struct ConditionalPlacement<'certificate, 'document, N, D, G> {
46 snapshot: Snapshot<D, G>,
47 signature: N,
48 findings: OwnedVec<ConditionalFinding<'certificate, 'document, N>>,
49}
50
51impl<'certificate, 'document, N: Copy, D: Copy, G: Copy>
52 ConditionalPlacement<'certificate, 'document, N, D, G>
53{
54 #[must_use]
56 pub fn snapshot(&self) -> Snapshot<D, G> {
57 self.snapshot
58 }
59 #[must_use]
61 pub fn signature(&self) -> N {
62 self.signature
63 }
64 #[must_use]
66 pub fn findings(&self) -> &[ConditionalFinding<'certificate, 'document, N>] {
67 &self.findings
68 }
69}
70
71#[derive(Debug)]
73pub enum ConditionalError<E, N> {
74 Scope,
76 Index(IndexError<N>),
78 Property(crate::properties::Error<E, N>),
80}
81
82pub type ConditionalResult<'certificate, 'document, X> = Result<
84 ConditionalPlacement<
85 'certificate,
86 'document,
87 <X as Xml>::Id,
88 <X as Xml>::Document,
89 <X as Xml>::Generation,
90 >,
91 ConditionalError<<X as Xml>::Error, <X as Xml>::Id>,
92>;
93
94#[derive(Clone, Copy)]
97enum CopyRule<N> {
98 Property(N),
99 PropertyOrKeyInfo { property: N, signature: N },
100}
101
102fn eligible<N: Copy, E>(
103 rule: CopyRule<N>,
104 placement: Placement<N>,
105 embedded: bool,
106 mut equal: impl FnMut(N, N) -> Result<bool, E>,
107) -> Result<bool, E> {
108 if embedded {
109 return Ok(false);
110 }
111 match (rule, placement) {
112 (CopyRule::Property(property), Placement::Attribute(actual))
113 | (CopyRule::PropertyOrKeyInfo { property, .. }, Placement::Signature(actual)) => {
114 equal(property, actual)
115 }
116 (CopyRule::PropertyOrKeyInfo { signature, .. }, Placement::KeyInfo(actual)) => {
117 equal(signature, actual)
118 }
119 _ => Ok(false),
120 }
121}
122
123pub fn conditional_placement<'certificate, 'document, X: Xml, W: Work, Q: Work>(
146 prepared: &Prepared<X>,
147 inputs: &Inputs<'_, '_, 'certificate, 'document, X>,
148 counters: &CounterPaths<'_, 'document, X::Id, X::Document, X::Generation>,
149 index: &Index<'_, X::Id, X::Document, X::Generation, W>,
150 access: &Access<'_, X, Q>,
151) -> ConditionalResult<'certificate, 'document, X> {
152 let required = inputs.requirements();
153 if !matches!(required.scope(), TimestampScope::All(_)) {
154 return Err(ConditionalError::Scope);
155 }
156 let signature = prepared.bound().containers().signature();
157 let resource = |cause| ConditionalError::Index(IndexError::Resource { signature, cause });
158 if prepared.bound().containers().snapshot() != access.snapshot()
159 || required.scope().location().snapshot != access.snapshot()
160 || counters.snapshot() != access.snapshot()
161 || !access
162 .equal_id(required.signature(), signature)
163 .map_err(resource)?
164 || !access
165 .equal_id(counters.signature(), signature)
166 .map_err(resource)?
167 {
168 return Err(ConditionalError::Index(IndexError::Foreign { signature }));
169 }
170 index
171 .require_signature(signature, access)
172 .map_err(ConditionalError::Index)?;
173 let owners = Owners::build(required, access.execution()).map_err(resource)?;
174 owners.require_counters(inputs, counters, signature, access)?;
175 let mut findings = access.execution().vector();
176 let mut record = |owner, property, kind| {
177 check_property(
178 inputs,
179 &owners,
180 (owner, property, kind),
181 index,
182 access,
183 &mut findings,
184 )
185 };
186 for entry in prepared.unsigned().entries() {
187 access.execution().charge_work(1).map_err(resource)?;
188 if let Some(kind) = ContainerKind::inventory(entry.role) {
189 record(signature, entry.node, kind)?;
190 }
191 }
192 for actor in counters.actors() {
193 access.execution().charge_work(1).map_err(resource)?;
194 let Some(layout) = actor.containers() else {
195 continue;
196 };
197 let [parent, _] = crate::unsigned::containers(access, layout.unsigned())
198 .map_err(ConditionalError::Property)?;
199 let Some(parent) = parent else {
200 continue;
201 };
202 for child in access.children(parent) {
203 let node = child.map_err(|cause| {
204 ConditionalError::Property(crate::properties::Error::Child(cause))
205 })?;
206 if let Some(kind) = container_kind(access, node).map_err(ConditionalError::Property)? {
207 record(layout.signature(), node, kind)?;
208 }
209 }
210 }
211 index
212 .require_signature(signature, access)
213 .map_err(ConditionalError::Index)?;
214 Ok(ConditionalPlacement {
215 snapshot: access.snapshot(),
216 signature,
217 findings: findings.into_owned(),
218 })
219}
220
221type Inputs<'requirements, 'view, 'certificate, 'document, X> = ParticipantClosure<
222 'requirements,
223 'view,
224 'certificate,
225 'document,
226 <X as Xml>::Id,
227 <X as Xml>::Document,
228 <X as Xml>::Generation,
229 Xades,
230>;
231
232struct ActorLink {
233 requirement: usize,
234 root: Option<usize>,
235 next: Option<usize>,
236}
237
238struct Owners<N: HashWork, W: Work> {
241 heads: OwnedMap<N, (usize, usize), W>,
242 links: OwnedVec<ActorLink, W>,
243 counters: usize,
244}
245
246impl<N: Copy + Eq + HashWork, W: Work> Owners<N, W> {
247 fn build<D: Copy, G: Copy>(
248 required: &ParticipantRequirements<'_, '_, N, D, G, Xades>,
249 work: &W,
250 ) -> Result<Self, MemoryError> {
251 let mut result = Self {
252 heads: work.map(),
253 links: work.vector(),
254 counters: 0,
255 };
256 let mut root = 0;
257 for (requirement, entry) in required.entries().iter().enumerate() {
258 work.charge_work(1)?;
259 let path = if matches!(entry.authority(), ParticipantAuthority::Path(_)) {
260 let index = root;
261 root += 1;
262 Some(index)
263 } else {
264 None
265 };
266 let owner = match entry.participant() {
267 Participant::Signer(owner)
268 | Participant::Credential {
269 signature: owner, ..
270 } => owner,
271 Participant::Counter {
272 signature: owner, ..
273 } => {
274 result.counters += 1;
275 owner
276 }
277 Participant::Timestamp { .. } => continue,
278 };
279 let previous = result.heads.get(&owner, work)?.copied();
280 let ordinal = result.links.len();
281 result.links.try_push(ActorLink {
282 requirement,
283 root: path,
284 next: None,
285 })?;
286 let head = if let Some((head, tail)) = previous {
287 result.links[tail].next = Some(ordinal);
288 head
289 } else {
290 ordinal
291 };
292 result.heads.try_insert(owner, (head, ordinal))?;
293 }
294 Ok(result)
295 }
296
297 fn require_counters<X: Xml<Id = N>, Q: Work>(
298 &self,
299 inputs: &Inputs<'_, '_, '_, '_, X>,
300 counters: &CounterPaths<'_, '_, N, X::Document, X::Generation>,
301 signature: N,
302 access: &Access<'_, X, Q>,
303 ) -> Result<(), ConditionalError<X::Error, N>> {
304 let foreign = || ConditionalError::Index(IndexError::Foreign { signature });
305 let resource = |cause| ConditionalError::Index(IndexError::Resource { signature, cause });
306 if self.counters != counters.actors().len() {
307 return Err(foreign());
308 }
309 for actor in counters.actors() {
310 access.execution().charge_work(1).map_err(resource)?;
311 let original = actor.signer().counter().original();
312 let mut next = self
313 .heads
314 .get(&original.child(), access.execution())
315 .map_err(resource)?
316 .map(|&(head, _)| head);
317 let mut found = false;
318 while let Some(ordinal) = next {
319 access.execution().charge_work(1).map_err(resource)?;
320 let link = &self.links[ordinal];
321 next = link.next;
322 let entry = &inputs.requirements().entries()[link.requirement];
323 if let Participant::Counter { property, .. } = entry.participant() {
324 let ParticipantAuthority::Path(path) = entry.authority() else {
325 return Err(foreign());
326 };
327 if !core::ptr::eq(path, actor.signer().path())
328 || !access
329 .equal_id(property, original.node())
330 .map_err(resource)?
331 {
332 return Err(foreign());
333 }
334 found = true;
335 break;
336 }
337 }
338 if !found {
339 return Err(foreign());
340 }
341 }
342 Ok(())
343 }
344}
345
346fn check_property<'certificate, 'document, X: Xml, W: Work, Q: Work>(
347 inputs: &Inputs<'_, '_, 'certificate, 'document, X>,
348 owners: &Owners<X::Id, Q>,
349 (owner, property, kind): (X::Id, X::Id, ContainerKind),
350 index: &Index<'_, X::Id, X::Document, X::Generation, W>,
351 access: &Access<'_, X, Q>,
352 findings: &mut OwnedVec<ConditionalFinding<'certificate, 'document, X::Id>, Q>,
353) -> Result<(), ConditionalError<X::Error, X::Id>> {
354 let signature = inputs.requirements().signature();
355 let resource = |cause| ConditionalError::Index(IndexError::Resource { signature, cause });
356 let mut record = |participant, artifact, rule| {
357 if let Some(cause) = availability(index, artifact, rule, signature, access)? {
358 findings
359 .try_push(ConditionalFinding {
360 property,
361 participant,
362 artifact,
363 cause,
364 })
365 .map_err(resource)?;
366 }
367 Ok(())
368 };
369 let mut next = owners
370 .heads
371 .get(&owner, access.execution())
372 .map_err(resource)?
373 .map(|&(head, _)| head);
374 while let Some(ordinal) = next {
375 access.execution().charge_work(1).map_err(resource)?;
376 let link = &owners.links[ordinal];
377 next = link.next;
378 let entry = &inputs.requirements().entries()[link.requirement];
379 let participant = entry.participant();
380 let ordinary = matches!(
381 participant,
382 Participant::Signer(_) | Participant::Counter { .. }
383 );
384 if ordinary == kind.attribute() {
385 continue;
386 }
387 match entry.authority() {
388 ParticipantAuthority::Path(_) => {
389 let statuses =
390 inputs.authorities().roots()[link.root.expect("Path actor has a status root")];
391 let properties = if kind.certificates() {
392 [Some(property), None]
393 } else {
394 [None, Some(property)]
395 };
396 path_copies(
397 statuses,
398 participant,
399 properties,
400 owner,
401 access,
402 &mut record,
403 )?;
404 }
405 ParticipantAuthority::Anchor(anchor) => {
406 if kind.certificates()
407 && let Some(certificate) = anchor.anchor().certificate()
408 {
409 record(
410 participant,
411 ArtifactRef::Certificate(certificate),
412 copy_rule(property, owner, ordinary),
413 )?;
414 }
415 }
416 ParticipantAuthority::CertificateFree => {}
417 }
418 }
419 Ok(())
420}
421
422fn availability<X: Xml, W: Work, Q: Work>(
423 index: &Index<'_, X::Id, X::Document, X::Generation, W>,
424 artifact: ArtifactRef<'_, '_>,
425 rule: CopyRule<X::Id>,
426 signature: X::Id,
427 access: &Access<'_, X, Q>,
428) -> Result<Option<ConditionalFailure>, ConditionalError<X::Error, X::Id>> {
429 let resource = |cause| ConditionalError::Index(IndexError::Resource { signature, cause });
430 let mut present = false;
431 for occurrence in index
432 .artifact(artifact, access)
433 .map_err(ConditionalError::Index)?
434 {
435 let occurrence = occurrence.map_err(ConditionalError::Index)?;
436 present = true;
437 if eligible(
438 rule,
439 occurrence.placement(),
440 occurrence.embedded_certificate().is_some(),
441 |left, right| access.equal_id(left, right).map_err(resource),
442 )? {
443 return Ok(None);
444 }
445 }
446 Ok(Some(if present {
447 ConditionalFailure::Elsewhere
448 } else {
449 ConditionalFailure::Missing
450 }))
451}
452
453fn copy_rule<N: Copy>(property: N, signature: N, ordinary: bool) -> CopyRule<N> {
454 if ordinary {
455 CopyRule::PropertyOrKeyInfo {
456 property,
457 signature,
458 }
459 } else {
460 CopyRule::Property(property)
461 }
462}
463
464fn path_copies<'certificate, 'document, X: Xml, W: Work>(
465 statuses: &PathStatuses<'_, 'certificate, 'document>,
466 participant: Participant<X::Id>,
467 [certificate, status]: [Option<X::Id>; 2],
468 signature: X::Id,
469 access: &Access<'_, X, W>,
470 mut record: impl FnMut(
471 Participant<X::Id>,
472 ArtifactRef<'certificate, 'document>,
473 CopyRule<X::Id>,
474 ) -> Result<(), ConditionalError<X::Error, X::Id>>,
475) -> Result<(), ConditionalError<X::Error, X::Id>> {
476 let resource = |cause| ConditionalError::Index(IndexError::Resource { signature, cause });
477 let work = access.execution();
478 let path = statuses.path();
479 let ordinary = matches!(
480 participant,
481 Participant::Signer(_) | Participant::Counter { .. }
482 );
483 let rule = |property| copy_rule(property, signature, ordinary);
484 if let Some(property) = certificate {
485 if ordinary && let Some(anchor) = path.anchor().certificate() {
486 record(
487 participant,
488 ArtifactRef::Certificate(anchor),
489 rule(property),
490 )?;
491 }
492 let members = if ordinary {
493 path.members()
494 } else {
495 path.members()
496 .last_chunk::<1>()
497 .expect("checked path has a target")
498 };
499 for member in members {
500 work.charge_work(1).map_err(resource)?;
501 record(
502 participant,
503 ArtifactRef::Certificate(member.certificate()),
504 rule(property),
505 )?;
506 }
507 }
508 if let Some(property) = status {
509 let entries = if ordinary {
510 statuses.entries()
511 } else {
512 statuses
513 .entries()
514 .last_chunk::<1>()
515 .expect("complete target status input")
516 };
517 for input in entries {
518 work.charge_work(1).map_err(resource)?;
519 let Some(source) = member_source(input) else {
520 continue;
521 };
522 visit_artifacts(source, signature, access, |artifact, _, part| {
523 if part == StatusPart::SigningCertificate {
526 return Ok(());
527 }
528 record(participant, artifact, rule(property))
529 })
530 .map_err(|cause| match cause {
531 ArtifactVisitError::Scope(ScopeError::Resource { cause, .. }) => resource(cause),
532 ArtifactVisitError::Scope(cause) => {
533 ConditionalError::Index(IndexError::Scope(cause))
534 }
535 ArtifactVisitError::Visitor(cause) => cause,
536 })?;
537 }
538 }
539 Ok(())
540}