1use super::{Index, Open};
7use crate::{
8 Cades,
9 counters::Paths,
10 credential::{Checked, Complete},
11 source::{DocumentId, Node, Original},
12 timestamp::ArchiveView,
13};
14use structured_asn1::{
15 Reader, ScopedReader,
16 memory::{MemoryError, OwnedMap, OwnedVec, Work},
17};
18pub use structured_ltv::material::{Completeness, Finding, Missing, VisitError};
19use structured_ltv::{
20 baseline::CredentialMaterial,
21 material::{ArchiveError, Archived, IndexError},
22 pki::{CounterReceipts, CredentialReceipts},
23 source::{Query, Snapshot},
24 timestamp::{ObligationError, TimestampRequirements, TimestampScope},
25};
26
27pub type Need<'value, 'source, 'model, 'document> =
29 structured_ltv::material::Dependency<'value, 'source, Node, DocumentId<'model, 'document>, ()>;
30
31pub trait Provider<E> {
39 type Error;
41 fn visit<'m, 'd, V>(
43 &self,
44 credential: &Checked<'m, 'd, '_, E>,
45 open: &[Open<'d>],
46 operation: &ScopedReader<'d, '_>,
47 visitor: impl for<'value, 'source> FnMut(Need<'value, 'source, 'm, 'd>) -> Result<(), V>,
48 ) -> Result<Completeness, VisitError<Self::Error, V>>;
49}
50
51pub struct Entry<'model, 'document> {
53 source: Original<'model, 'document>,
54 completeness: Completeness,
55 findings: OwnedVec<Finding>,
56}
57impl<'m, 'd> Entry<'m, 'd> {
58 #[must_use]
60 pub fn source(&self) -> Original<'m, 'd> {
61 self.source
62 }
63 #[must_use]
65 pub fn completeness(&self) -> Completeness {
66 self.completeness
67 }
68 #[must_use]
70 pub fn findings(&self) -> &[Finding] {
71 &self.findings
72 }
73}
74
75pub struct Report<'auth, 'receipt, 'model, 'document, 'path, E> {
77 credentials: &'auth Complete<'receipt, 'model, 'document, 'path, E>,
78 scope: TimestampScope<Node, DocumentId<'model, 'document>, ()>,
79 entries: OwnedVec<Entry<'model, 'document>>,
80 complete: bool,
81}
82impl<'m, 'd, E> Report<'_, '_, 'm, 'd, '_, E> {
83 #[must_use]
85 pub fn entries(&self) -> &[Entry<'m, 'd>] {
86 &self.entries
87 }
88 #[must_use]
90 pub fn all_requirements_met(&self) -> bool {
91 self.complete
92 }
93}
94impl<'r, 'm, 'd, 'p, E> CredentialMaterial for Report<'_, 'r, 'm, 'd, 'p, E> {
95 type Id = Node;
96 type Document = DocumentId<'m, 'd>;
97 type Generation = ();
98 type Credentials = Complete<'r, 'm, 'd, 'p, E>;
99 fn scope(&self) -> TimestampScope<Node, Self::Document, ()> {
100 self.scope
101 }
102 fn snapshot(&self) -> Snapshot<Self::Document, ()> {
103 self.credentials.snapshot()
104 }
105 fn credentials(&self) -> &Self::Credentials {
106 self.credentials
107 }
108 fn all_requirements_met(&self) -> bool {
109 self.complete
110 }
111}
112
113#[derive(Debug)]
115pub enum Error<E> {
116 Foreign {
118 node: Node,
120 },
121 Processing(crate::Error),
123 Obligation(ObligationError<Node>),
125 Index(IndexError<Node>),
127 Archive(ArchiveError<Node>),
129 Provider {
131 node: Node,
133 cause: E,
135 },
136 Resource {
138 node: Node,
140 cause: MemoryError,
142 },
143 CallbackProtocol {
145 node: Node,
147 },
148}
149
150pub struct Scope<'a, 'm, 'd, 'p> {
153 archive: Option<ArchiveView<'a, 'm, 'd, 'p>>,
154}
155impl<'a, 'm, 'd, 'p> Scope<'a, 'm, 'd, 'p> {
156 #[must_use]
158 pub fn whole() -> Self {
159 Self { archive: None }
160 }
161 #[must_use]
164 pub fn before_archive(archive: impl Into<ArchiveView<'a, 'm, 'd, 'p>>) -> Self {
165 Self {
166 archive: Some(archive.into()),
167 }
168 }
169 fn require<E, Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>, W: Work>(
170 &self,
171 required: &TimestampRequirements<Node, DocumentId<'m, 'd>, (), Cades>,
172 index: &Index<'_, 'm, 'd, W>,
173 query: &Q,
174 ) -> Result<(), Error<E>> {
175 let location = required.scope().location();
176 if location.snapshot != query.snapshot() {
177 return Err(Error::Foreign {
178 node: location.node,
179 });
180 }
181 index
182 .require_signature(required.signature(), query)
183 .map_err(Error::Index)?;
184 match (required.scope(), self.archive) {
185 (TimestampScope::All(_), None) => Ok(()),
186 (TimestampScope::BeforeArchive(_), Some(archive)) => {
187 let actor = archive.timestamp().actor().ok_or(Error::Foreign {
188 node: location.node,
189 })?;
190 let evidence = actor.verified();
191 query
192 .admit(location.node, 3)
193 .map_err(|cause| Error::Index(IndexError::Scope(cause)))?;
194 if evidence.signature() != required.signature()
195 || archive.coverage().subject() != location.node
196 || evidence.token().snapshot != location.snapshot
197 {
198 return Err(Error::Foreign {
199 node: location.node,
200 });
201 }
202 archive
203 .coverage()
204 .require_evidence(evidence, location.node, query)
205 .map_err(|cause| Error::Archive(ArchiveError::Coverage(cause)))
206 }
207 _ => Err(Error::Foreign {
208 node: location.node,
209 }),
210 }
211 }
212 fn missing<E, Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>, W: Work>(
213 &self,
214 need: Need<'_, '_, 'm, 'd>,
215 index: &Index<'_, 'm, 'd, W>,
216 open: &OwnedMap<Node, (), Q::Work>,
217 query: &Q,
218 ) -> Result<Option<Missing>, Error<E>> {
219 match need {
220 Need::Missing => Ok(Some(Missing::Absent)),
221 Need::Known(artifact) => {
222 if let Some(archive) = self.archive {
223 let actor = archive.timestamp().actor().ok_or(Error::Foreign {
224 node: index.signature(),
225 })?;
226 return index
227 .archived(artifact, archive.coverage(), actor.verified(), query)
228 .map(|answer| match answer {
229 Archived::Present(_) => None,
230 Archived::Missing => Some(Missing::Absent),
231 Archived::Uncovered => Some(Missing::Uncovered),
232 })
233 .map_err(Error::Archive);
234 }
235 let mut copies = index.artifact(artifact, query).map_err(Error::Index)?;
236 copies
237 .next()
238 .transpose()
239 .map(|value| {
240 if value.is_some() {
241 None
242 } else {
243 Some(Missing::Absent)
244 }
245 })
246 .map_err(Error::Index)
247 }
248 Need::Open(location) => {
249 if location.snapshot != query.snapshot() {
250 return Err(Error::Foreign {
251 node: location.node,
252 });
253 }
254 if open
255 .get(&location.node, query.execution())
256 .map_err(|cause| Error::Resource {
257 node: location.node,
258 cause,
259 })?
260 .is_none()
261 {
262 return Ok(Some(Missing::Absent));
263 }
264 if let Some(archive) = self.archive {
265 let actor = archive.timestamp().actor().ok_or(Error::Foreign {
266 node: location.node,
267 })?;
268 return archive
269 .coverage()
270 .covers(actor.verified(), location.node, query)
271 .map(|covered| {
272 if covered {
273 None
274 } else {
275 Some(Missing::Uncovered)
276 }
277 })
278 .map_err(|cause| Error::Archive(ArchiveError::Coverage(cause)));
279 }
280 Ok(None)
281 }
282 }
283 }
284}
285
286pub fn check<'a, 'r, 'm, 'd, 'p, E, P: Provider<E>, W: Work>(
290 credentials: &'a Complete<'r, 'm, 'd, 'p, E>,
291 counters: &Paths<'_, 'm, 'd, 'p>,
292 required: &TimestampRequirements<Node, DocumentId<'m, 'd>, (), Cades>,
293 index: &Index<'_, 'm, 'd, W>,
294 scope: &Scope<'_, 'm, 'd, '_>,
295 provider: &P,
296 operation: &Reader<'d>,
297) -> Result<Report<'a, 'r, 'm, 'd, 'p, E>, Error<P::Error>> {
298 let document = credentials.snapshot().document.source();
299 document
300 .message()
301 .require_session(operation, "cades.credential.material")
302 .map_err(|cause| Error::Processing(crate::Error::Cms(cause)))?;
303 let execution = operation.execution();
304 let query = &structured_ltv::source::Scope::new(&document, credentials.snapshot(), &execution);
305 scope.require(required, index, query)?;
306 let node = credentials.signature();
307 query
308 .admit(node, 3)
309 .map_err(|cause| Error::Index(IndexError::Scope(cause)))?;
310 if credentials.snapshot() != query.snapshot()
311 || counters.snapshot() != query.snapshot()
312 || node != required.signature()
313 || node != counters.signature()
314 {
315 return Err(Error::Foreign { node });
316 }
317 required
318 .includes_counters(counters, query)
319 .map_err(|cause| Error::Index(IndexError::Scope(cause)))?;
320 let work = query.execution();
321 let mut open = work.map();
322 for entry in index.open() {
323 work.charge_work(1).map_err(|cause| Error::Resource {
324 node: entry.node,
325 cause,
326 })?;
327 open.try_insert(entry.node, ())
328 .map_err(|cause| Error::Resource {
329 node: entry.node,
330 cause,
331 })?;
332 }
333 let mut selected = work.vector();
334 for entry in credentials.report.root() {
335 selected
336 .try_push(entry)
337 .map_err(|cause| Error::Resource { node, cause })?;
338 }
339 let mut group = 0;
340 for ordinal in 0..counters.len() {
341 work.charge_work(2)
342 .map_err(|cause| Error::Resource { node, cause })?;
343 let Some((signature, entries)) = credentials
344 .report
345 .counter(group, work)
346 .map_err(|cause| Error::Resource { node, cause })?
347 else {
348 break;
349 };
350 if signature != counters.counter(ordinal).signature {
351 continue;
352 }
353 if required
354 .counter_in_scope(ordinal, query)
355 .map_err(Error::Obligation)?
356 {
357 for entry in entries {
358 selected
359 .try_push(entry)
360 .map_err(|cause| Error::Resource { node, cause })?;
361 }
362 }
363 group += 1;
364 }
365 if group != credentials.report.counters() {
366 return Err(Error::Foreign { node });
367 }
368 let mut entries = work.vector();
369 entries
370 .try_reserve_exact(selected.len())
371 .map_err(|cause| Error::Resource { node, cause })?;
372 let mut complete = true;
373 for credential in selected {
374 let entry = one(credential, index, scope, provider, operation, query, &open)?;
375 if entry.completeness != Completeness::Complete || !entry.findings.is_empty() {
376 complete = false;
377 }
378 entries
379 .try_push(entry)
380 .map_err(|cause| Error::Resource { node, cause })?;
381 }
382 query
383 .admit(node, 1)
384 .map_err(|cause| Error::Index(IndexError::Scope(cause)))?;
385 Ok(Report {
386 credentials,
387 scope: required.scope(),
388 entries: entries.into_owned(),
389 complete,
390 })
391}
392
393fn one<'m, 'd, E, P: Provider<E>, Q, W: Work>(
394 credential: &Checked<'m, 'd, '_, E>,
395 index: &Index<'_, 'm, 'd, W>,
396 scope: &Scope<'_, 'm, 'd, '_>,
397 provider: &P,
398 operation: &Reader<'d>,
399 query: &Q,
400 open: &OwnedMap<Node, (), Q::Work>,
401) -> Result<Entry<'m, 'd>, Error<P::Error>>
402where
403 Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
404{
405 let work = query.execution();
406 let node = credential.source().node;
407 let mut visit = structured_ltv::material::DependencyVisit::new(node, work, |need| {
408 scope.missing(need, index, open, query)
409 })
410 .map_err(|cause| Error::Resource { node, cause })?;
411 let answer = provider.visit(credential, index.open(), operation, |need| {
412 visit.observe(need)
413 });
414 let result = visit.finish(answer).map_err(|cause| match cause {
415 structured_ltv::material::DependencyError::Processing { cause, .. } => cause,
416 structured_ltv::material::DependencyError::Resource { node, cause } => {
417 Error::Resource { node, cause }
418 }
419 structured_ltv::material::DependencyError::Provider { node, cause } => {
420 Error::Provider { node, cause }
421 }
422 structured_ltv::material::DependencyError::CallbackProtocol { node } => {
423 Error::CallbackProtocol { node }
424 }
425 })?;
426 Ok(Entry {
427 source: credential.source(),
428 completeness: result.completeness,
429 findings: result.findings,
430 })
431}