1use super::record::Checked;
3use crate::{
4 Error,
5 prepare::Preparation,
6 preservation::{Kind, Required},
7 source::Node,
8};
9use core::cmp::Ordering;
10use structured_asn1::{
11 Reader,
12 memory::{Execution, OwnedMap, OwnedVec, SortError, Work},
13};
14use structured_cms::{
15 evidence::verify::{self as ers, Decision, OrderCause},
16 timestamp::Evidence,
17};
18use structured_pkix::temporal::{Instant, LeapTable};
19const FIELD: &str = "cades.preservation.close";
20
21pub struct History<'view, 'prepared, 'm, 'd, 'path> {
23 pub record: &'view Checked<'prepared, 'm, 'd, 'path>,
25 pub following: Option<&'view Evidence>,
28 pub verification_time: Instant,
30}
31#[derive(Debug)]
33pub enum ClosureError<E> {
34 Processing(Error),
36 Unproven {
38 property: Node,
40 },
41 Foreign {
43 property: Node,
45 },
46 Date(structured_pkix::temporal::CompareError),
48 Dependency {
50 first: Node,
52 second: Node,
54 },
55 Chronology {
57 earlier: Node,
59 later: Node,
61 cause: OrderCause,
63 },
64 History {
66 property: Node,
68 decision: Decision,
70 },
71 Callback(E),
73}
74
75pub struct Closure<'checks, 'prepared, 'm, 'd, 'path> {
79 prepared: &'prepared Preparation<'m, 'd>,
80 records: OwnedVec<&'checks Checked<'prepared, 'm, 'd, 'path>>,
81 index: OwnedMap<Node, usize>,
82}
83impl<'checks, 'prepared, 'm, 'd, 'path> Closure<'checks, 'prepared, 'm, 'd, 'path> {
84 #[must_use]
86 pub fn prepared(&self) -> &'prepared Preparation<'m, 'd> {
87 self.prepared
88 }
89 #[must_use]
91 pub fn records(&self) -> &[&'checks Checked<'prepared, 'm, 'd, 'path>] {
92 &self.records
93 }
94 pub fn contains<W: Work>(
97 &self,
98 required: &Required<'_>,
99 work: &W,
100 ) -> Result<bool, structured_asn1::memory::MemoryError> {
101 let Some(index) = self.index.get(&required.property, work)? else {
102 return Ok(false);
103 };
104 work.charge_work(3)?;
105 let actual = self.records[*index].required;
106 Ok(actual.signature == required.signature
107 && actual.kind == required.kind
108 && actual.placement == required.placement)
109 }
110}
111
112pub fn close_preservation<'checks, 'prepared, 'm, 'd, 'path, E>(
117 prepared: &'prepared Preparation<'m, 'd>,
118 records: &[&'checks Checked<'prepared, 'm, 'd, 'path>],
119 operation: &Reader<'d>,
120 utc: &LeapTable<'_>,
121 reference: Instant,
122 mut historical: impl FnMut(
123 History<'_, 'prepared, 'm, 'd, 'path>,
124 &Execution<'_>,
125 ) -> Result<Decision, E>,
126) -> Result<Closure<'checks, 'prepared, 'm, 'd, 'path>, ClosureError<E>> {
127 prepared
128 .document()
129 .message()
130 .require_session(operation, FIELD)
131 .map_err(|cause| ClosureError::Processing(Error::Cms(cause)))?;
132 let work = operation.execution();
133 let resource = |cause| {
134 ClosureError::Processing(Error::resource(
135 cause,
136 FIELD,
137 prepared.signer().span().start,
138 ))
139 };
140 let mut selected = work.vector();
141 let mut index = work.map();
142 selected
143 .try_reserve_exact(records.len())
144 .map_err(resource)?;
145 for record in records {
146 work.charge_work(1).map_err(resource)?;
147 let property = record.required.property;
148 if !core::ptr::eq(record.prepared, prepared)
149 || !record.required.kind.is_evidence()
150 || index
151 .try_insert(property, selected.len())
152 .map_err(resource)?
153 .is_some()
154 {
155 return Err(ClosureError::Foreign { property });
156 }
157 if record.verified().is_none() {
158 return Err(ClosureError::Unproven { property });
159 }
160 selected.try_push(*record).map_err(resource)?;
161 }
162 for required in prepared.preservation() {
163 work.charge_work(1).map_err(resource)?;
164 if !required.kind.is_evidence() {
165 continue;
166 }
167 if index
168 .get(&required.property, &work)
169 .map_err(resource)?
170 .is_none()
171 {
172 return Err(ClosureError::Unproven {
173 property: required.property,
174 });
175 }
176 if required.placement != structured_cms::attribute::Placement::Unsigned {
177 return Err(ClosureError::Unproven {
178 property: required.property,
179 });
180 }
181 }
182 if !prepared.evidence_faults().is_empty() {
185 return Err(ClosureError::Unproven {
186 property: prepared.evidence_faults()[0].0,
187 });
188 }
189 order_selected(prepared, &mut selected, &index, utc, &work)?;
190 check_chronology(&selected, utc, &work)?;
191 check_history(&selected, reference, &mut historical, &work)?;
192 let mut sorted = work.map();
194 for (ordinal, record) in selected.iter().enumerate() {
195 sorted
196 .try_insert(record.required.property, ordinal)
197 .map_err(resource)?;
198 }
199 Ok(Closure {
200 prepared,
201 records: selected.into_owned(),
202 index: sorted.into_owned(),
203 })
204}
205
206fn order_selected<E, W: Work>(
207 prepared: &Preparation<'_, '_>,
208 selected: &mut OwnedVec<&Checked<'_, '_, '_, '_>, W>,
209 index: &OwnedMap<Node, usize, W>,
210 utc: &LeapTable<'_>,
211 work: &W,
212) -> Result<(), ClosureError<E>> {
213 let resource = |cause| {
214 ClosureError::Processing(Error::resource(
215 cause,
216 FIELD,
217 prepared.signer().span().start,
218 ))
219 };
220 let mut ranks = work.vector();
225 ranks.try_reserve_exact(selected.len()).map_err(resource)?;
226 for _ in selected.iter() {
227 ranks.try_push(0usize).map_err(resource)?;
228 }
229 for left in 0..selected.len() {
230 for right in left + 1..selected.len() {
231 work.charge_work(3).map_err(resource)?;
232 if selected[left].required.signature != selected[right].required.signature {
233 continue;
234 }
235 let order = precedes(prepared, selected[left], selected[right], utc, work)?;
236 let later = if order == Ordering::Less { right } else { left };
237 debug_assert!(ranks[later] < selected.len());
238 ranks[later] += 1;
239 }
240 }
241 selected
242 .try_sort_unstable_by(|a, b| {
243 work.charge_work(3).map_err(resource)?;
244 let owner = a
245 .required
246 .signature
247 .span()
248 .start
249 .cmp(&b.required.signature.span().start);
250 if owner != Ordering::Equal {
251 return Ok(owner);
252 }
253 let left = *index
254 .get(&a.required.property, work)
255 .map_err(resource)?
256 .ok_or(ClosureError::Foreign {
257 property: a.required.property,
258 })?;
259 let right = *index
260 .get(&b.required.property, work)
261 .map_err(resource)?
262 .ok_or(ClosureError::Foreign {
263 property: b.required.property,
264 })?;
265 Ok::<_, ClosureError<E>>(ranks[left].cmp(&ranks[right]).then_with(|| {
266 a.required
267 .property
268 .span()
269 .start
270 .cmp(&b.required.property.span().start)
271 }))
272 })
273 .map_err(|cause| match cause {
274 SortError::Resource(cause) => resource(cause),
275 SortError::Comparison(cause) => cause,
276 })?;
277 for left in 0..selected.len() {
278 for right in left + 1..selected.len() {
279 work.charge_work(3).map_err(resource)?;
280 if selected[left].required.signature != selected[right].required.signature {
281 continue;
282 }
283 if precedes(prepared, selected[left], selected[right], utc, work)? != Ordering::Less {
284 return Err(ClosureError::Dependency {
285 first: selected[left].required.property,
286 second: selected[right].required.property,
287 });
288 }
289 }
290 }
291 Ok(())
292}
293
294fn check_chronology<E, W: Work>(
295 selected: &[&Checked<'_, '_, '_, '_>],
296 utc: &LeapTable<'_>,
297 work: &W,
298) -> Result<(), ClosureError<E>> {
299 for pair in selected.windows(2) {
300 let resource = |cause| {
301 ClosureError::Processing(Error::resource(
302 cause,
303 FIELD,
304 pair[0].required.property.span().start,
305 ))
306 };
307 work.charge_work(1).map_err(resource)?;
308 if pair[0].required.signature != pair[1].required.signature {
309 continue;
310 }
311 let a = pair[0]
312 .checks()
313 .last()
314 .and_then(|check| check.actor())
315 .ok_or(ClosureError::Unproven {
316 property: pair[0].required.property,
317 })?;
318 let b = pair[1]
319 .checks()
320 .next()
321 .and_then(|check| check.actor())
322 .ok_or(ClosureError::Unproven {
323 property: pair[1].required.property,
324 })?;
325 if let Some(cause) = ers::order_evidence(
326 a.verified().evidence(),
327 a.certificate(),
328 b.verified().evidence(),
329 b.certificate(),
330 utc,
331 work,
332 )
333 .map_err(|cause| ClosureError::Processing(Error::Cms(cause)))?
334 {
335 return Err(ClosureError::Chronology {
336 earlier: pair[0].required.property,
337 later: pair[1].required.property,
338 cause,
339 });
340 }
341 }
342 Ok(())
343}
344
345fn check_history<'checks, 'prepared, 'm, 'd, 'path, E, W: Work>(
346 selected: &[&'checks Checked<'prepared, 'm, 'd, 'path>],
347 reference: Instant,
348 historical: &mut impl FnMut(History<'_, 'prepared, 'm, 'd, 'path>, &W) -> Result<Decision, E>,
349 work: &W,
350) -> Result<(), ClosureError<E>> {
351 for ordinal in (0..selected.len()).rev() {
352 let resource = |cause| {
353 ClosureError::Processing(Error::resource(
354 cause,
355 FIELD,
356 selected[ordinal].required.property.span().start,
357 ))
358 };
359 work.charge_work(1).map_err(resource)?;
360 let record = selected[ordinal];
361 let following = if let Some(next) = selected.get(ordinal + 1)
362 && next.required.signature == record.required.signature
363 {
364 Some(
365 next.checks()
366 .next()
367 .and_then(|check| check.actor())
368 .ok_or(ClosureError::Unproven {
369 property: next.required.property,
370 })?
371 .verified()
372 .evidence(),
373 )
374 } else {
375 None
376 };
377 let decision = historical(
378 History {
379 record,
380 following,
381 verification_time: reference,
382 },
383 work,
384 )
385 .map_err(ClosureError::Callback)?;
386 if decision != Decision::Valid {
387 return Err(ClosureError::History {
388 property: record.required.property,
389 decision,
390 });
391 }
392 }
393 Ok(())
394}
395
396fn precedes<E, W: Work>(
397 prepared: &Preparation<'_, '_>,
398 a: &Checked<'_, '_, '_, '_>,
399 b: &Checked<'_, '_, '_, '_>,
400 utc: &LeapTable<'_>,
401 work: &W,
402) -> Result<Ordering, ClosureError<E>> {
403 let resource = |cause| {
404 ClosureError::Processing(Error::resource(
405 cause,
406 FIELD,
407 a.required.property.span().start,
408 ))
409 };
410 let ac = a.covered.as_ref().ok_or(ClosureError::Unproven {
411 property: a.required.property,
412 })?;
413 let bc = b.covered.as_ref().ok_or(ClosureError::Unproven {
414 property: b.required.property,
415 })?;
416 let ab = super::coverage::selects(ac, b.required.property.span(), work).map_err(resource)?;
417 let ba = super::coverage::selects(bc, a.required.property.span(), work).map_err(resource)?;
418 let order = match (ab, ba) {
419 (false, true) => Ordering::Less,
420 (true, false) => Ordering::Greater,
421 _ => {
422 return Err(ClosureError::Dependency {
423 first: a.required.property,
424 second: b.required.property,
425 });
426 }
427 };
428 if a.required.kind == Kind::LongTermValidation && b.required.kind == Kind::LongTermValidation {
429 work.charge_work(2).map_err(resource)?;
430 let ae = prepared
431 .parsed
432 .proofs
433 .entries
434 .get(a.proof)
435 .ok_or(ClosureError::Foreign {
436 property: a.required.property,
437 })?;
438 let be = prepared
439 .parsed
440 .proofs
441 .entries
442 .get(b.proof)
443 .ok_or(ClosureError::Foreign {
444 property: b.required.property,
445 })?;
446 let (crate::prepare::proofs::Body::Poe(a), crate::prepare::proofs::Body::Poe(b)) =
447 (&ae.body, &be.body)
448 else {
449 return Err(ClosureError::Dependency {
450 first: ae.required.property,
451 second: be.required.property,
452 });
453 };
454 if a.compare_date(b, utc, work).map_err(ClosureError::Date)? != order {
455 return Err(ClosureError::Dependency {
456 first: ae.required.property,
457 second: be.required.property,
458 });
459 }
460 }
461 Ok(order)
462}