1use super::{ReferenceDigestError, ReferenceKind};
4use crate::{
5 binary::{self, BinaryXml, Location},
6 certificate::{Agreement, DigestProvider, FormatDigest},
7 digest,
8 material::{self, Retained},
9 prepare::{Preparation, Prepared},
10 timestamp::{CarriedMaterial, TokenSource},
11 xml::{Access, Xml},
12};
13use core::{cmp::Ordering, ops::Range};
14use structured_asn1::digest::{Digest, Resolution};
15use structured_asn1::memory::{HashWork, MemoryError, OwnedMap, OwnedVec, Work};
16use structured_cms::stream::RangesError;
17use structured_ltv::material::ArtifactSource;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ReferenceSearch {
22 Compared,
25 IdentifierOnly,
27 OtherFormat,
29 Unavailable(Agreement),
31}
32
33pub struct ReferenceRow<N> {
35 property: usize,
36 entry: usize,
37 node: N,
38 search: ReferenceSearch,
39 range: Range<usize>,
40}
41
42impl<N: Copy> ReferenceRow<N> {
43 #[must_use]
45 pub fn property(&self) -> usize {
46 self.property
47 }
48 #[must_use]
50 pub fn entry(&self) -> usize {
51 self.entry
52 }
53 #[must_use]
55 pub fn node(&self) -> N {
56 self.node
57 }
58 #[must_use]
60 pub fn search(&self) -> ReferenceSearch {
61 self.search
62 }
63}
64
65#[derive(Debug, Clone, Copy)]
67pub struct MaterialDigest {
68 source: ArtifactSource,
69 embedded: Option<usize>,
70 agreement: Agreement,
71}
72
73impl MaterialDigest {
74 #[must_use]
76 pub fn source(&self) -> Option<usize> {
77 match self.source {
78 ArtifactSource::Material(index) => Some(index),
79 _ => None,
80 }
81 }
82 #[must_use]
85 pub fn origin(&self) -> ArtifactSource {
86 self.source
87 }
88 #[must_use]
91 pub fn embedded_certificate(&self) -> Option<usize> {
92 self.embedded
93 }
94 #[must_use]
96 pub fn agreement(&self) -> Agreement {
97 self.agreement
98 }
99}
100
101pub struct ReferenceDigests<'references, 'source, N, D, G> {
105 prepared: &'references Preparation<N, D, G>,
106 sources: &'source [Retained<N, D, G>],
107 tokens: Option<&'source [TokenSource<N, D, G>]>,
108 rows: OwnedVec<ReferenceRow<N>>,
109 comparisons: OwnedVec<MaterialDigest>,
110}
111
112impl<'references, 'source, N, D, G> ReferenceDigests<'references, 'source, N, D, G> {
113 #[must_use]
115 pub fn prepared(&self) -> &'references Preparation<N, D, G> {
116 self.prepared
117 }
118 #[must_use]
120 pub fn sources(&self) -> &'source [Retained<N, D, G>] {
121 self.sources
122 }
123 #[must_use]
126 pub fn tokens(&self) -> Option<&'source [TokenSource<N, D, G>]> {
127 self.tokens
128 }
129 #[must_use]
131 pub fn rows(&self) -> &[ReferenceRow<N>] {
132 &self.rows
133 }
134 #[must_use]
137 pub fn comparisons(&self, reference: usize) -> Option<&[MaterialDigest]> {
138 let row = self.rows.get(reference)?;
139 self.comparisons.get(row.range.clone())
140 }
141}
142
143pub type ReferenceCatalogueResult<'references, 'source, X, PE> = binary::BinaryResult<
145 ReferenceDigests<
146 'references,
147 'source,
148 <X as Xml>::Id,
149 <X as Xml>::Document,
150 <X as Xml>::Generation,
151 >,
152 X,
153 ReferenceDigestError<PE>,
154>;
155
156#[derive(Clone, Copy, PartialEq, Eq, Hash)]
157struct CacheKey {
158 source: usize,
159 method: usize,
160}
161
162#[derive(Clone, Copy, PartialEq, Eq, Hash)]
163struct SourceKey<'source> {
164 kind: material::Kind,
165 bytes: &'source [u8],
166}
167
168#[derive(Clone, Copy)]
169struct Candidate<'source> {
170 source: ArtifactSource,
171 embedded: Option<usize>,
172 kind: material::Kind,
173 bytes: &'source [u8],
174}
175
176fn candidates<'source, N: Copy, D: Copy, G: Copy, W: Work>(
177 view: &material::View<'source, N, D, G>,
178 tokens: Option<&CarriedMaterial<'source, N, D, G>>,
179 work: &W,
180) -> Result<OwnedVec<Candidate<'source>, W>, MemoryError> {
181 let mut values = work.vector();
182 values.try_reserve_exact(view.sources().len())?;
183 for (source, retained) in view.sources().iter().enumerate() {
184 work.charge_work(1)?;
185 values.try_push(Candidate {
186 source: ArtifactSource::Material(source),
187 embedded: None,
188 kind: retained.source().kind(),
189 bytes: retained.source().bytes(),
190 })?;
191 }
192 for basic in view.basic_responses() {
193 work.charge_work(1)?;
194 for (embedded, certificate) in basic.response().certificates().iter().enumerate() {
195 work.charge_work(1)?;
196 values.try_push(Candidate {
197 source: ArtifactSource::Material(basic.source_index()),
198 embedded: Some(embedded),
199 kind: material::Kind::Certificate,
200 bytes: certificate.as_bytes(),
201 })?;
202 }
203 }
204 if let Some(tokens) = tokens {
205 for (token, reading) in tokens.view.readings().iter().enumerate() {
206 work.charge_work(1)?;
207 for (certificate, value) in reading.token().message().certificates().iter().enumerate()
208 {
209 work.charge_work(1)?;
210 values.try_push(Candidate {
211 source: ArtifactSource::TokenCertificate { token, certificate },
212 embedded: None,
213 kind: material::Kind::Certificate,
214 bytes: value.as_bytes(),
215 })?;
216 }
217 }
218 for crl in tokens.crls() {
219 work.charge_work(1)?;
220 values.try_push(Candidate {
221 source: ArtifactSource::TokenRevocation(crl.origin),
222 embedded: None,
223 kind: material::Kind::Crl,
224 bytes: crl.value.as_bytes(),
225 })?;
226 }
227 for response in tokens.responses() {
228 work.charge_work(1)?;
229 values.try_push(Candidate {
230 source: ArtifactSource::TokenRevocation(response.origin),
231 embedded: None,
232 kind: material::Kind::Ocsp,
233 bytes: response.value.as_bytes(),
234 })?;
235 }
236 for basic in tokens.basic_responses() {
237 work.charge_work(1)?;
238 for (embedded, certificate) in basic.value.certificates().iter().enumerate() {
239 work.charge_work(1)?;
240 values.try_push(Candidate {
241 source: ArtifactSource::TokenRevocation(basic.origin),
242 embedded: Some(embedded),
243 kind: material::Kind::Certificate,
244 bytes: certificate.as_bytes(),
245 })?;
246 }
247 }
248 }
249 Ok(values)
250}
251
252impl HashWork for SourceKey<'_> {
253 fn hash_work(&self) -> Result<u64, MemoryError> {
254 u64::try_from(self.bytes.len())
255 .map_err(|_| MemoryError::WorkExceeded)?
256 .checked_add(2)
257 .ok_or(MemoryError::WorkExceeded)
258 }
259}
260
261fn source_ids<W: Work>(
262 sources: &[Candidate<'_>],
263 work: &W,
264) -> Result<OwnedVec<usize, W>, MemoryError> {
265 let mut index = work.map();
266 let mut ids = work.vector();
267 ids.try_reserve_exact(sources.len())?;
268 for (ordinal, source) in sources.iter().enumerate() {
269 work.charge_work(1)?;
270 let key = SourceKey {
271 kind: source.kind,
272 bytes: source.bytes,
273 };
274 let canonical = if let Some(&existing) = index.get(&key, work)? {
275 existing
276 } else {
277 index.try_insert(key, ordinal)?;
278 ordinal
279 };
280 ids.try_push(canonical)?;
281 }
282 Ok(ids)
283}
284
285impl HashWork for CacheKey {
286 fn hash_work(&self) -> Result<u64, MemoryError> {
287 Ok(2)
288 }
289}
290
291pub(super) struct Memo<'provider, 'source, P: Digest<'provider>, W: Work> {
292 provider: &'provider P,
293 identities: OwnedVec<FormatDigest<P::Identity>, W>,
294 entries: OwnedMap<CacheKey, Option<Resolution<P::Output>>, W>,
295 sources: Option<OwnedVec<usize, W>>,
296 candidates: Option<OwnedVec<Candidate<'source>, W>>,
297}
298
299#[derive(Clone, Copy)]
300pub(super) struct CandidatePools<'view, 'source, N, D, G> {
301 pub(super) xml: &'view material::View<'source, N, D, G>,
302 pub(super) tokens: Option<&'view CarriedMaterial<'source, N, D, G>>,
303}
304
305impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq> CandidatePools<'_, '_, N, D, G> {
306 pub(super) fn same_source<X: Xml<Id = N, Document = D, Generation = G>, W: Work>(
307 self,
308 location: Location<N, D, G>,
309 access: &Access<'_, X, W>,
310 ) -> Result<bool, MemoryError> {
311 if self.xml.snapshot() != location.snapshot {
312 return Ok(false);
313 }
314 if let Some(tokens) = self.tokens {
315 if tokens.view.snapshot() != location.snapshot {
316 return Ok(false);
317 }
318 if !access.equal_id(tokens.view.signature(), location.node)? {
319 return Ok(false);
320 }
321 }
322 access.equal_id(self.xml.signature(), location.node)
323 }
324}
325
326impl<'provider, 'source, P: Digest<'provider>, W: Work> Memo<'provider, 'source, P, W> {
327 pub(super) fn new(provider: &'provider P, work: &W) -> Self {
328 Self {
329 provider,
330 identities: work.vector(),
331 entries: work.map(),
332 sources: None,
333 candidates: None,
334 }
335 }
336 fn row<N: Copy, D: Copy, G: Copy>(
337 &mut self,
338 identity: FormatDigest<P::Identity>,
339 expected: &[u8],
340 kind: material::Kind,
341 pools: CandidatePools<'_, 'source, N, D, G>,
342 comparisons: &mut OwnedVec<MaterialDigest, W>,
343 work: &W,
344 ) -> Result<(), digest::Error<RangesError, P::Error>> {
345 let method = self.method(identity, work)?;
346 if self.sources.is_none() {
347 let values =
348 candidates(pools.xml, pools.tokens, work).map_err(digest::Error::Resource)?;
349 self.sources = Some(source_ids(&values, work).map_err(digest::Error::Resource)?);
350 self.candidates = Some(values);
351 }
352 let count = self
353 .candidates
354 .as_ref()
355 .expect("prepared original candidates")
356 .len();
357 for ordinal in 0..count {
358 work.charge_work(1).map_err(digest::Error::Resource)?;
359 let candidate = self
360 .candidates
361 .as_ref()
362 .expect("prepared original candidates")[ordinal];
363 if candidate.kind != kind {
364 continue;
365 }
366 comparisons
367 .try_reserve(1)
368 .map_err(digest::Error::Resource)?;
369 let canonical = self
370 .sources
371 .as_ref()
372 .expect("prepared original source identities")[ordinal];
373 let agreement = self.compare(canonical, candidate.bytes, method, expected, work)?;
374 comparisons
375 .try_push(MaterialDigest {
376 source: candidate.source,
377 embedded: candidate.embedded,
378 agreement,
379 })
380 .map_err(digest::Error::Resource)?;
381 }
382 Ok(())
383 }
384
385 pub(super) fn method(
386 &mut self,
387 identity: FormatDigest<P::Identity>,
388 work: &W,
389 ) -> Result<usize, digest::Error<RangesError, P::Error>> {
390 for (index, existing) in self.identities.iter().enumerate() {
391 work.charge_work(1).map_err(digest::Error::Resource)?;
392 if self
393 .provider
394 .compare(identity.identity(), existing.identity(), work)
395 .map_err(digest::Error::Provider)?
396 == Ordering::Equal
397 {
398 return Ok(index);
399 }
400 }
401 let index = self.identities.len();
402 self.identities
403 .try_push(identity)
404 .map_err(digest::Error::Resource)?;
405 Ok(index)
406 }
407
408 pub(super) fn compare(
409 &mut self,
410 source: usize,
411 bytes: &[u8],
412 method: usize,
413 expected: &[u8],
414 work: &W,
415 ) -> Result<Agreement, digest::Error<RangesError, P::Error>> {
416 let key = CacheKey { source, method };
417 if let Some(output) = self
418 .entries
419 .get(&key, work)
420 .map_err(digest::Error::Resource)?
421 {
422 let output = output.as_ref().expect("completed prior cache entry");
423 return self.matches(&self.identities[method], output, expected, work);
424 }
425 self.entries
427 .try_insert(key, None)
428 .map_err(digest::Error::Resource)?;
429 let identity = &self.identities[method];
430 let output =
431 digest::compute_resolved::<P, _, W>(identity, &mut [bytes], self.provider, work)?;
432 let answer = self.matches(identity, &output, expected, work)?;
433 *self
434 .entries
435 .get_mut(&key, work)
436 .map_err(digest::Error::Resource)?
437 .expect("admitted cache entry") = Some(output);
438 Ok(answer)
439 }
440
441 fn matches(
442 &self,
443 identity: &FormatDigest<P::Identity>,
444 output: &Resolution<P::Output>,
445 expected: &[u8],
446 work: &W,
447 ) -> Result<Agreement, digest::Error<RangesError, P::Error>> {
448 let output = match output {
449 Resolution::Supported(output) => output,
450 Resolution::Unsupported => return Ok(Agreement::Unsupported),
451 Resolution::Malformed => return Ok(Agreement::MalformedMethod),
452 };
453 work.charge_work(1).map_err(digest::Error::Resource)?;
454 self.provider
455 .matches(identity.identity(), output, expected, work)
456 .map(|same| {
457 if same {
458 Agreement::Matches
459 } else {
460 Agreement::Differs
461 }
462 })
463 .map_err(digest::Error::Provider)
464 }
465}
466
467pub fn compare_reference_catalogues<
479 'references,
480 'source,
481 'p,
482 X: BinaryXml,
483 P: DigestProvider<'p, X>,
484 W: Work,
485>(
486 xml: &X,
487 prepared: &'references Prepared<X>,
488 view: &material::View<'source, X::Id, X::Document, X::Generation>,
489 provider: &'p P,
490 work: &W,
491) -> ReferenceCatalogueResult<'references, 'source, X, P::Error> {
492 compare_catalogues(xml, prepared, view, None, provider, work)
493}
494
495pub fn compare_reference_catalogues_with_tokens<
505 'references,
506 'source,
507 'p,
508 X: BinaryXml,
509 P: DigestProvider<'p, X>,
510 W: Work,
511>(
512 xml: &X,
513 prepared: &'references Prepared<X>,
514 view: &material::View<'source, X::Id, X::Document, X::Generation>,
515 tokens: &CarriedMaterial<'source, X::Id, X::Document, X::Generation>,
516 provider: &'p P,
517 work: &W,
518) -> ReferenceCatalogueResult<'references, 'source, X, P::Error> {
519 compare_catalogues(xml, prepared, view, Some(tokens), provider, work)
520}
521
522fn compare_catalogues<'references, 'source, 'p, X: BinaryXml, P: DigestProvider<'p, X>, W: Work>(
523 xml: &X,
524 prepared: &'references Prepared<X>,
525 view: &material::View<'source, X::Id, X::Document, X::Generation>,
526 tokens: Option<&CarriedMaterial<'source, X::Id, X::Document, X::Generation>>,
527 provider: &'p P,
528 work: &W,
529) -> ReferenceCatalogueResult<'references, 'source, X, P::Error> {
530 let original = prepared.bound().containers();
531 let location = Location {
532 snapshot: original.snapshot(),
533 node: original.signature(),
534 };
535 let access = Access::new(xml, location.snapshot, work);
536 access
537 .query(location.node, |_, _, _| Ok(()))
538 .map_err(|cause| binary::Error::Access { location, cause })?;
539 if !(CandidatePools { xml: view, tokens })
540 .same_source(location, &access)
541 .map_err(|cause| binary::Error::Callback {
542 location,
543 cause: ReferenceDigestError::Digest(digest::Error::Resource(cause)),
544 })?
545 {
546 return Err(binary::Error::Callback {
547 location,
548 cause: ReferenceDigestError::Foreign,
549 });
550 }
551 let resource = |cause| binary::Error::Callback {
552 location,
553 cause: ReferenceDigestError::Digest(digest::Error::Resource(cause)),
554 };
555 let mut memo = Memo::<P, W>::new(provider, work);
556 let mut rows = work.vector();
557 let mut comparisons = work.vector();
558 for (property, references) in prepared.validation_references().iter().enumerate() {
559 work.charge_work(1).map_err(resource)?;
560 rows.try_reserve_exact(references.entries().len())
561 .map_err(resource)?;
562 for (ordinal, entry) in references.entries().iter().enumerate() {
563 work.charge_work(1).map_err(resource)?;
564 let start = comparisons.len();
565 let kind = match entry.kind() {
566 ReferenceKind::Certificate => Some(material::Kind::Certificate),
567 ReferenceKind::Crl => Some(material::Kind::Crl),
568 ReferenceKind::Ocsp => Some(material::Kind::Ocsp),
569 ReferenceKind::Other => None,
570 };
571 let search = if let Some(kind) = kind {
572 if let Some((method, value)) = entry.digest() {
573 let reading = digest::check_with(
574 xml,
575 digest::Reference {
576 snapshot: references.snapshot(),
577 method,
578 value,
579 },
580 provider,
581 work,
582 |identity, expected, work| {
583 memo.row(
584 identity,
585 expected,
586 kind,
587 CandidatePools { xml: view, tokens },
588 &mut comparisons,
589 work,
590 )
591 },
592 )
593 .map_err(|cause| cause.map_callback(ReferenceDigestError::Digest))?;
594 match reading {
595 digest::Checked::Value(()) => ReferenceSearch::Compared,
596 digest::Checked::Answer(answer) => ReferenceSearch::Unavailable(answer),
597 }
598 } else {
599 ReferenceSearch::IdentifierOnly
600 }
601 } else {
602 ReferenceSearch::OtherFormat
603 };
604 rows.try_push(ReferenceRow {
605 property,
606 entry: ordinal,
607 node: entry.node(),
608 search,
609 range: start..comparisons.len(),
610 })
611 .map_err(resource)?;
612 }
613 }
614 access
615 .query(location.node, |_, _, _| Ok(()))
616 .map_err(|cause| binary::Error::Access { location, cause })?;
617 Ok(ReferenceDigests {
618 prepared,
619 sources: view.sources(),
620 tokens: tokens.map(|carried| carried.view.sources()),
621 rows: rows.into_owned(),
622 comparisons: comparisons.into_owned(),
623 })
624}