1use super::{Catalogue, Pair};
4use crate::{
5 binary,
6 certificate::{self, Agreement, DigestProvider},
7 container::DS,
8 digest, properties,
9 reference::{
10 Completeness, ObjectLocation, Parent, ReferenceXml, Replay, ReplayOutcome, VisitError,
11 },
12 timestamp::TimestampXml,
13 xml::{Access, AccessError, Name, Snapshot, Xml},
14};
15use core::cmp::Ordering;
16use structured_asn1::digest::{Digest, Resolution};
17use structured_asn1::memory::{MemoryError, OwnedVec, Work};
18use structured_cms::stream::{Receiver, Source};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum State {
23 Processed,
25 NoDetachedManifest,
27 UnclassifiedManifest,
29 Algorithm(Agreement),
31}
32
33#[derive(Debug)]
35pub enum PairOutcome<E> {
36 Matches,
38 Differs,
40 MissingReference,
42 AmbiguousReference,
44 NotDetached,
46 SameAlgorithm,
48 Algorithm(Agreement),
50 Unavailable(E),
52}
53
54#[derive(Debug)]
56pub struct PairCheck<N, E> {
57 pub pair: Pair<N>,
59 pub reference: Option<N>,
61 pub old_target_status: Option<crate::binding::Status>,
64 pub outcome: PairOutcome<E>,
66}
67
68pub struct Report<N, D, G, E> {
70 snapshot: Snapshot<D, G>,
71 signature: N,
72 property: N,
73 state: State,
74 pairs: OwnedVec<PairCheck<N, E>>,
75 agrees: bool,
76}
77impl<N: Copy, D: Copy, G: Copy, E> Report<N, D, G, E> {
78 #[must_use]
80 pub fn snapshot(&self) -> Snapshot<D, G> {
81 self.snapshot
82 }
83 #[must_use]
85 pub fn signature(&self) -> N {
86 self.signature
87 }
88 #[must_use]
90 pub fn property(&self) -> N {
91 self.property
92 }
93 #[must_use]
95 pub fn state(&self) -> State {
96 self.state
97 }
98 #[must_use]
100 pub fn pairs(&self) -> &[PairCheck<N, E>] {
101 &self.pairs
102 }
103 #[must_use]
106 pub fn agrees(&self) -> bool {
107 self.agrees
108 }
109}
110
111#[derive(Debug)]
113pub enum SourceError<E, N> {
114 Access(AccessError<E, N>),
116 Unavailable {
118 node: N,
120 cause: E,
122 },
123}
124
125#[derive(Debug)]
127pub enum CheckError<E, PE, N, D, G> {
128 Access(AccessError<E, N>),
130 Property(properties::Error<E, N>),
132 Binary(binary::Error<E, MemoryError, N, D, G>),
134 Provider {
136 node: N,
138 cause: PE,
140 },
141 Digest {
143 node: N,
145 cause: digest::Error<SourceError<E, N>, PE>,
147 },
148 Resource {
150 node: N,
152 cause: MemoryError,
154 },
155 Incomplete(Completeness<N>),
157}
158
159pub type CheckResult<T, X, PE> = Result<
161 T,
162 CheckError<<X as Xml>::Error, PE, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
163>;
164pub type Checked<X> =
166 Report<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation, <X as Xml>::Error>;
167
168pub(super) struct Instruction<N> {
169 pub(super) node: N,
170 pub(super) location: ObjectLocation,
171 pub(super) status: crate::binding::Status,
172}
173struct Expected<N> {
174 pair: Pair<N>,
175 reference: OwnedVec<u8>,
176 object: OwnedVec<u8>,
177}
178struct Candidate<N, I, O> {
179 instruction: Instruction<N>,
180 old: certificate::Expected<I>,
181 output: O,
182}
183
184type Candidates<'q, X, P> =
185 OwnedVec<Candidate<<X as Xml>::Id, <P as Digest<'q>>::Identity, <P as Digest<'q>>::Output>>;
186
187fn memoize<'q, X: TimestampXml, P: DigestProvider<'q, X>, W: Work>(
188 xml: &X,
189 access: &Access<'_, X, W>,
190 reading: &Catalogue<X::Id, X::Document, X::Generation>,
191 instructions: &[Instruction<X::Id>],
192 identity: &certificate::FormatDigest<P::Identity>,
193 provider: &'q P,
194) -> CheckResult<Resolution<Candidates<'q, X, P>>, X, P::Error> {
195 let work = access.execution();
196 let mut candidates = work.vector();
197 let mut ordinal = 0;
198 while ordinal < instructions.len() {
199 work.charge_work(1).map_err(|cause| CheckError::Resource {
200 node: reading.node,
201 cause,
202 })?;
203 let instruction = &instructions[ordinal];
204 let method = original_method(access, instruction.node).map_err(CheckError::Property)?;
205 let old = resolve(access, method, provider)?;
206 let mut source = Input {
207 xml,
208 snapshot: reading.snapshot,
209 mode: Mode::Canonical {
210 node: instruction.node,
211 method: reading.canonicalization,
212 },
213 };
214 let output =
215 match digest::compute_resolved::<P, _, _>(identity, &mut source, provider, work)
216 .map_err(|cause| CheckError::Digest {
217 node: instruction.node,
218 cause,
219 })? {
220 Resolution::Supported(output) => output,
221 Resolution::Unsupported => return Ok(Resolution::Unsupported),
222 Resolution::Malformed => return Ok(Resolution::Malformed),
223 };
224 candidates
225 .try_push(Candidate {
226 instruction: Instruction {
227 node: instruction.node,
228 location: instruction.location,
229 status: instruction.status,
230 },
231 old,
232 output,
233 })
234 .map_err(|cause| CheckError::Resource {
235 node: instruction.node,
236 cause,
237 })?;
238 ordinal += 1;
239 }
240 Ok(Resolution::Supported(candidates.into_owned()))
241}
242
243pub(super) enum Mode<N> {
244 Canonical { node: N, method: N },
245 Reference { signature: N, node: N },
246}
247pub(super) struct Input<'a, X: TimestampXml> {
248 pub(super) xml: &'a X,
249 pub(super) snapshot: Snapshot<X::Document, X::Generation>,
250 pub(super) mode: Mode<X::Id>,
251}
252impl<X: TimestampXml> Source for Input<'_, X> {
253 type Error = SourceError<X::Error, X::Id>;
254 fn stream<W: Work>(
255 &mut self,
256 receiver: &mut impl Receiver,
257 work: &W,
258 ) -> Result<(), Self::Error> {
259 let access = Access::new(self.xml, self.snapshot, work);
260 match self.mode {
261 Mode::Canonical { node, method } => access
262 .query(node, |xml, snapshot, work| {
263 xml.canonicalize(snapshot, node, method, receiver, work)
264 })
265 .map_err(SourceError::Access),
266 Mode::Reference { signature, node } => {
267 match access
268 .query(node, |xml, snapshot, work| {
269 xml.replay_reference(
270 snapshot,
271 signature,
272 node,
273 Replay::ReferenceOutput,
274 receiver,
275 work,
276 )
277 })
278 .map_err(SourceError::Access)?
279 {
280 ReplayOutcome::Complete => Ok(()),
281 ReplayOutcome::Unavailable(cause) => {
282 Err(SourceError::Unavailable { node, cause })
283 }
284 }
285 }
286 }
287 }
288}
289
290pub fn check<'q, X: TimestampXml + binary::BinaryXml, P: DigestProvider<'q, X>, W: Work>(
298 xml: &X,
299 reading: &Catalogue<X::Id, X::Document, X::Generation>,
300 provider: &'q P,
301 work: &W,
302) -> CheckResult<Checked<X>, X, P::Error> {
303 let result = check_inner(xml, reading, provider, work)?;
304 Access::new(xml, reading.snapshot, work)
305 .query(reading.node, |_, _, _| Ok(()))
306 .map_err(CheckError::Access)?;
307 Ok(result)
308}
309
310fn check_inner<'q, X: TimestampXml + binary::BinaryXml, P: DigestProvider<'q, X>, W: Work>(
311 xml: &X,
312 reading: &Catalogue<X::Id, X::Document, X::Generation>,
313 provider: &'q P,
314 work: &W,
315) -> CheckResult<Checked<X>, X, P::Error> {
316 let access = Access::new(xml, reading.snapshot, work);
317 access
318 .query(reading.node, |_, _, _| Ok(()))
319 .map_err(CheckError::Access)?;
320 if let Some(answer) =
321 certificate::preflight_method(&access, reading.digest).map_err(CheckError::Access)?
322 {
323 return Ok(report(
324 reading,
325 State::Algorithm(answer),
326 work.vector().into_owned(),
327 false,
328 ));
329 }
330 let expected = read_expected(xml, reading, work)?;
331 let instructions = inventory(&access, reading.signature, reading.node)?;
332 if let Some(state) = manifest_state::<X, W, P::Error>(&instructions, reading.node, work)? {
333 return Ok(report(reading, state, work.vector().into_owned(), false));
334 }
335 let identity = match resolve(&access, reading.digest, provider)? {
336 certificate::Expected::Ready(identity) => identity,
337 certificate::Expected::Answer(answer) => {
338 return Ok(report(
339 reading,
340 State::Algorithm(answer),
341 work.vector().into_owned(),
342 false,
343 ));
344 }
345 };
346 let width = provider.width(identity.identity());
347 let mut usable = false;
348 for ordinal in 0..expected.len() {
351 work.charge_work(1).map_err(|cause| CheckError::Resource {
352 node: reading.node,
353 cause,
354 })?;
355 let pair = &expected[ordinal];
356 if let Some(width) = width
357 && (pair.reference.len() != width || pair.object.len() != width)
358 {
359 continue;
360 }
361 usable = true;
362 }
363 let candidates = if usable {
364 match memoize(xml, &access, reading, &instructions, &identity, provider)? {
365 Resolution::Supported(candidates) => candidates,
366 Resolution::Unsupported => {
367 return Ok(report(
368 reading,
369 State::Algorithm(Agreement::Unsupported),
370 work.vector().into_owned(),
371 false,
372 ));
373 }
374 Resolution::Malformed => {
375 return Ok(report(
376 reading,
377 State::Algorithm(Agreement::MalformedMethod),
378 work.vector().into_owned(),
379 false,
380 ));
381 }
382 }
383 } else {
384 work.vector().into_owned()
385 };
386 finish_pairs(
387 xml,
388 reading,
389 &expected,
390 &candidates,
391 &identity,
392 provider,
393 work,
394 )
395}
396
397fn manifest_state<X: Xml, W: Work, PE>(
398 instructions: &[Instruction<X::Id>],
399 node: X::Id,
400 work: &W,
401) -> CheckResult<Option<State>, X, PE> {
402 let mut unknown = false;
403 let mut ordinal = 0;
404 while ordinal < instructions.len() {
405 work.charge_work(1)
406 .map_err(|cause| CheckError::Resource { node, cause })?;
407 let instruction = &instructions[ordinal];
408 if instruction.location == ObjectLocation::Detached {
409 return Ok(None);
410 }
411 if instruction.location == ObjectLocation::Unknown {
412 unknown = true;
413 }
414 ordinal += 1;
415 }
416 Ok(Some(if unknown {
417 State::UnclassifiedManifest
418 } else {
419 State::NoDetachedManifest
420 }))
421}
422
423fn finish_pairs<'q, X: TimestampXml, P: DigestProvider<'q, X>, W: Work>(
424 xml: &X,
425 reading: &Catalogue<X::Id, X::Document, X::Generation>,
426 expected: &[Expected<X::Id>],
427 candidates: &[Candidate<X::Id, P::Identity, P::Output>],
428 identity: &certificate::FormatDigest<P::Identity>,
429 provider: &'q P,
430 work: &W,
431) -> CheckResult<Checked<X>, X, P::Error> {
432 let mut results = work.vector();
433 let mut agrees = true;
434 let mut ordinal = 0;
435 while ordinal < expected.len() {
436 work.charge_work(1).map_err(|cause| CheckError::Resource {
437 node: reading.node,
438 cause,
439 })?;
440 let pair = &expected[ordinal];
441 let result = process_pair(xml, reading, pair, candidates, identity, provider, work)?;
442 agrees &= matches!(result.outcome, PairOutcome::Matches);
443 results
444 .try_push(result)
445 .map_err(|cause| CheckError::Resource {
446 node: pair.pair.node,
447 cause,
448 })?;
449 ordinal += 1;
450 }
451 Ok(report(
452 reading,
453 State::Processed,
454 results.into_owned(),
455 agrees,
456 ))
457}
458
459fn report<N: Copy, D: Copy, G: Copy, E>(
460 reading: &Catalogue<N, D, G>,
461 state: State,
462 pairs: OwnedVec<PairCheck<N, E>>,
463 agrees: bool,
464) -> Report<N, D, G, E> {
465 Report {
466 snapshot: reading.snapshot,
467 signature: reading.signature,
468 property: reading.node,
469 state,
470 pairs,
471 agrees,
472 }
473}
474
475pub(super) fn resolve<'q, X: Xml, P: DigestProvider<'q, X>, W: Work>(
476 access: &Access<'_, X, W>,
477 node: X::Id,
478 provider: &P,
479) -> CheckResult<certificate::Expected<P::Identity>, X, P::Error> {
480 let resolved = access
481 .query(node, |_, _, _| Ok(provider.resolve(access, node)))
482 .map_err(CheckError::Access)?
483 .map_err(|cause| CheckError::Provider { node, cause })?;
484 Ok(match resolved {
485 Resolution::Supported(identity) => certificate::baseline_digest::<X, P>(identity, provider),
486 Resolution::Unsupported => certificate::Expected::Answer(Agreement::Unsupported),
487 Resolution::Malformed => certificate::Expected::Answer(Agreement::MalformedMethod),
488 })
489}
490
491pub(super) fn original_method<X: Xml, W: Work>(
492 access: &Access<'_, X, W>,
493 node: X::Id,
494) -> properties::PropertyResult<X::Id, X> {
495 let mut method = None;
496 for child in access.children(node) {
497 let child = child.map_err(properties::Error::Child)?;
498 if access
499 .has_name(
500 child,
501 Name {
502 namespace: DS,
503 local: "DigestMethod",
504 },
505 )
506 .map_err(properties::Error::Access)?
507 {
508 if method.is_some() {
509 return Err(properties::Error::Duplicate { node: child });
510 }
511 method = Some(child);
512 }
513 }
514 method.ok_or(properties::Error::Missing {
515 parent: node,
516 local: "DigestMethod",
517 })
518}
519
520pub(super) fn inventory<X: ReferenceXml, W: Work, PE>(
521 access: &Access<'_, X, W>,
522 signature: X::Id,
523 context: X::Id,
524) -> CheckResult<OwnedVec<Instruction<X::Id>>, X, PE> {
525 let work = access.execution();
526 let mut entries = work.vector();
527 let complete = access
528 .query(context, |xml, snapshot, work| {
529 Ok(
530 xml.visit_authenticated_references(snapshot, signature, work, |descriptor| {
531 if matches!(descriptor.parent, Parent::Manifest(_)) {
532 entries
533 .try_push(Instruction {
534 node: descriptor.reference.node,
535 location: descriptor.object_location,
536 status: descriptor.reference.status,
537 })
538 .map_err(|cause| CheckError::Resource {
539 node: descriptor.reference.node,
540 cause,
541 })?;
542 }
543 Ok::<_, CheckError<X::Error, PE, X::Id, X::Document, X::Generation>>(())
544 }),
545 )
546 })
547 .map_err(CheckError::Access)?;
548 let complete = match complete {
549 Ok(complete) => complete,
550 Err(VisitError::Processing(cause)) => {
551 return Err(CheckError::Access(AccessError::Xml(cause)));
552 }
553 Err(VisitError::Visitor(cause)) => return Err(cause),
554 };
555 if complete != Completeness::Complete {
556 return Err(CheckError::Incomplete(complete));
557 }
558 Ok(entries.into_owned())
559}
560
561fn read_expected<X: binary::BinaryXml, W: Work, PE>(
562 xml: &X,
563 reading: &Catalogue<X::Id, X::Document, X::Generation>,
564 work: &W,
565) -> CheckResult<OwnedVec<Expected<X::Id>>, X, PE> {
566 let mut expected = work.vector();
567 for ordinal in 0..reading.pairs.len() {
568 work.charge_work(1).map_err(|cause| CheckError::Resource {
569 node: reading.node,
570 cause,
571 })?;
572 let pair = reading.pairs[ordinal];
573 let copy = |node| {
574 binary::with_octets(
575 xml,
576 binary::Location {
577 snapshot: reading.snapshot,
578 node,
579 },
580 work,
581 |bytes| {
582 let mut owned = work.vector();
583 owned.try_extend_from_slice(bytes)?;
584 Ok(owned.into_owned())
585 },
586 )
587 .map_err(CheckError::Binary)
588 };
589 let reference = copy(pair.reference)?;
590 let object = copy(pair.object)?;
591 expected
592 .try_push(Expected {
593 pair,
594 reference,
595 object,
596 })
597 .map_err(|cause| CheckError::Resource {
598 node: pair.node,
599 cause,
600 })?;
601 }
602 Ok(expected.into_owned())
603}
604
605fn process_pair<'q, X: TimestampXml, P: DigestProvider<'q, X>, W: Work>(
606 xml: &X,
607 reading: &Catalogue<X::Id, X::Document, X::Generation>,
608 pair: &Expected<X::Id>,
609 candidates: &[Candidate<X::Id, P::Identity, P::Output>],
610 identity: &certificate::FormatDigest<P::Identity>,
611 provider: &'q P,
612 work: &W,
613) -> CheckResult<PairCheck<X::Id, X::Error>, X, P::Error> {
614 let width = provider.width(identity.identity());
615 let mut result = PairCheck {
616 pair: pair.pair,
617 reference: None,
618 old_target_status: None,
619 outcome: PairOutcome::MissingReference,
620 };
621 if let Some(width) = width
622 && (pair.reference.len() != width || pair.object.len() != width)
623 {
624 result.outcome = PairOutcome::Algorithm(Agreement::MalformedValue);
625 return Ok(result);
626 }
627 let mut selected = None;
628 let mut ordinal = 0;
629 while ordinal < candidates.len() {
630 work.charge_work(1).map_err(|cause| CheckError::Resource {
631 node: pair.pair.node,
632 cause,
633 })?;
634 let candidate = &candidates[ordinal];
635 ordinal += 1;
636 if provider
637 .matches(
638 identity.identity(),
639 &candidate.output,
640 &pair.reference,
641 work,
642 )
643 .map_err(|cause| CheckError::Provider {
644 node: pair.pair.reference,
645 cause,
646 })?
647 {
648 if selected.is_some() {
649 result.outcome = PairOutcome::AmbiguousReference;
650 return Ok(result);
651 }
652 selected = Some(candidate);
653 }
654 }
655 let Some(candidate) = selected else {
656 return Ok(result);
657 };
658 let node = candidate.instruction.node;
659 result.reference = Some(node);
660 result.old_target_status = Some(candidate.instruction.status);
661 if candidate.instruction.location != ObjectLocation::Detached {
662 result.outcome = PairOutcome::NotDetached;
663 return Ok(result);
664 }
665 let old = match &candidate.old {
672 certificate::Expected::Ready(old) => old,
673 certificate::Expected::Answer(answer) => {
674 result.outcome = PairOutcome::Algorithm(*answer);
675 return Ok(result);
676 }
677 };
678 if provider
679 .compare(identity.identity(), old.identity(), work)
680 .map_err(|cause| CheckError::Provider { node, cause })?
681 == Ordering::Equal
682 {
683 result.outcome = PairOutcome::SameAlgorithm;
684 return Ok(result);
685 }
686 let mut source = Input {
687 xml,
688 snapshot: reading.snapshot,
689 mode: Mode::Reference {
690 signature: reading.signature,
691 node,
692 },
693 };
694 result.outcome = match digest::run_resolved::<P, _, _>(
695 identity,
696 &pair.object,
697 &mut source,
698 provider,
699 work,
700 ) {
701 Ok(Agreement::Matches) => PairOutcome::Matches,
702 Ok(Agreement::Differs) => PairOutcome::Differs,
703 Ok(answer) => PairOutcome::Algorithm(answer),
704 Err(digest::Error::Source(SourceError::Unavailable { cause, .. })) => {
705 PairOutcome::Unavailable(cause)
706 }
707 Err(cause) => {
708 return Err(CheckError::Digest {
709 node: pair.pair.object,
710 cause,
711 });
712 }
713 };
714 Ok(result)
715}