Skip to main content

structured_pades/material/
credentials.rs

1//! Every actual credential's dependencies in a whole or historical PDF scope.
2
3use super::Unified;
4use crate::{
5    basic::Basic,
6    credential::{Complete, Group},
7    query::Query,
8    range::RangeSource,
9    timestamp::{Node, Pades},
10};
11use structured_asn1::memory::{Memory, MemoryError, OwnedVec, Work};
12use structured_cades::material::credentials::{Need, Provider};
13use structured_ltv::{
14    baseline::CredentialMaterial,
15    material::{
16        ArchiveError, Archived, Coverage, CoverageError, DependencyError, DependencyReport,
17        DependencyVisit, IndexError, Missing,
18    },
19    pki::{CounterReceipts, CredentialGroup},
20    source::{Query as _, ScopeError, Snapshot},
21    timestamp::{CheckedActor, TimestampRequirements, TimestampScope},
22};
23
24/// Complete actual credential dependency report in the original PDF domain.
25pub type CheckedReport<'a, 'r, 'm, 'd, 'p, D, G, E> =
26    Report<'a, Complete<'r, 'm, 'd, 'p, D, G, E>, D, G>;
27
28/// Exact historical input or ordinary whole-signature incorporation.
29pub enum Scope<'a, 'c, 'd, D, G, W: Work = Memory> {
30    /// No archive or `PoE` is implied.
31    Whole,
32    /// Both private receipts belong to one actual checked original input.
33    Before {
34        /// Exact source-derived covered items.
35        coverage: &'a Coverage<Node, D, G, W, Pades>,
36        /// Actual mathematically checked original token and authority binding.
37        actor: &'a CheckedActor<'c, 'd, Node, D, G, Pades>,
38    },
39}
40impl<D, G> Scope<'_, '_, '_, D, G> {
41    /// Whole-source material only, with no implicit archive selection.
42    #[must_use]
43    pub fn whole() -> Self {
44        Self::Whole
45    }
46}
47/// All interpretation and availability facts for one original credential.
48pub struct Entry {
49    /// Actual original credential in its PDF enclosure.
50    pub node: Node,
51    /// Complete original dependency visitation result.
52    pub dependencies: DependencyReport,
53}
54/// Same authenticated original receipt and all scope-required dependencies.
55pub struct Report<'a, C, D, G> {
56    credentials: &'a C,
57    scope: TimestampScope<Node, D, G>,
58    entries: OwnedVec<Entry>,
59    complete: bool,
60}
61impl<C, D, G> Report<'_, C, D, G> {
62    /// Every scope-required original credential, including negative results.
63    #[must_use]
64    pub fn entries(&self) -> &[Entry] {
65        &self.entries
66    }
67}
68impl<C, D: Copy, G: Copy> CredentialMaterial for Report<'_, C, D, G> {
69    type Id = Node;
70    type Document = D;
71    type Generation = G;
72    type Credentials = C;
73    fn scope(&self) -> TimestampScope<Node, D, G> {
74        self.scope
75    }
76    fn snapshot(&self) -> Snapshot<D, G> {
77        self.scope.location().snapshot
78    }
79    fn credentials(&self) -> &C {
80        self.credentials
81    }
82    fn all_requirements_met(&self) -> bool {
83        self.complete
84    }
85}
86/// Original scope/lookup failure versus a handler's unchanged opaque cause.
87#[derive(Debug)]
88pub enum Error<E> {
89    /// Another actual source, root, counter set or archive was supplied.
90    Foreign {
91        /// Actual selecting source node.
92        node: Node,
93    },
94    /// Source generation or current admission refused.
95    Scope(ScopeError<Node>),
96    /// Original complete-counter scope membership refusal.
97    Obligation(structured_ltv::timestamp::ObligationError<Node>),
98    /// Original exact-artifact lookup refused.
99    Index(IndexError<Node>),
100    /// Original archive-input binding or material query refused.
101    Archive(ArchiveError<Node>),
102    /// Original coverage/evidence preflight refused.
103    Coverage(CoverageError<Node>),
104    /// Current work/storage refused before copying or dispatch.
105    Resource {
106        /// Actual original credential/root.
107        node: Node,
108        /// Original resource cause.
109        cause: MemoryError,
110    },
111    /// Original carried reader context refused.
112    Cms(structured_cms::Error),
113    /// Original credential-format owner refused.
114    Provider {
115        /// Actual original credential.
116        node: Node,
117        /// Unchanged handler cause.
118        cause: E,
119    },
120    /// A handler returned a visitor refusal without an actual failed visit.
121    CallbackProtocol {
122        /// Actual original credential.
123        node: Node,
124    },
125}
126
127fn dependency<D: Copy + Eq, G: Copy + Eq, E, S, W: Work, O: Work>(
128    need: Need<'_, '_, '_, '_>,
129    mapping: crate::binding::Mapping<'_, '_, D, G>,
130    index: &Unified<'_, '_, D, G>,
131    scope: &Scope<'_, '_, '_, D, G, W>,
132    open: &structured_asn1::memory::OwnedMap<Node, (), O>,
133    node: Node,
134    query: &Query<'_, '_, S>,
135) -> Result<Option<Missing>, Error<E>>
136where
137    S: RangeSource<Document = D, Generation = G>,
138{
139    match need {
140        Need::Missing => Ok(Some(Missing::Absent)),
141        Need::Known(artifact) => {
142            if let Scope::Before { coverage, actor } = scope {
143                return index
144                    .index
145                    .archived(artifact, coverage, actor.verified(), query)
146                    .map(|value| match value {
147                        Archived::Present(_) => None,
148                        Archived::Missing => Some(Missing::Absent),
149                        Archived::Uncovered => Some(Missing::Uncovered),
150                    })
151                    .map_err(Error::Archive);
152            }
153            let mut copies = index
154                .index
155                .artifact(artifact, query)
156                .map_err(Error::Index)?;
157            Ok(
158                if copies.next().transpose().map_err(Error::Index)?.is_some() {
159                    None
160                } else {
161                    Some(Missing::Absent)
162                },
163            )
164        }
165        Need::Open(location) => {
166            if location.snapshot != mapping.cms {
167                return Err(Error::Foreign { node });
168            }
169            let node = mapping.node(location.node);
170            if open
171                .get(&node, query.execution())
172                .map_err(|cause| Error::Resource { node, cause })?
173                .is_none()
174            {
175                return Ok(Some(Missing::Absent));
176            }
177            if let Scope::Before { coverage, actor } = scope {
178                return coverage
179                    .covers(actor.verified(), node, query)
180                    .map(|covered| {
181                        if covered {
182                            None
183                        } else {
184                            Some(Missing::Uncovered)
185                        }
186                    })
187                    .map_err(Error::Coverage);
188            }
189            Ok(None)
190        }
191    }
192}
193
194fn scope_binding<
195    D: Copy + Eq,
196    G: Copy + Eq,
197    E,
198    S: RangeSource<Document = D, Generation = G>,
199    W: Work,
200>(
201    scope: &Scope<'_, '_, '_, D, G, W>,
202    required: &TimestampRequirements<Node, D, G, Pades>,
203    index: &Unified<'_, '_, D, G>,
204    root: Node,
205    query: &Query<'_, '_, S>,
206) -> Result<(), Error<E>> {
207    match (scope, required.scope()) {
208        (Scope::Whole, TimestampScope::All(_)) if core::ptr::eq(required, index.required) => {}
209        (Scope::Before { coverage, actor }, TimestampScope::BeforeArchive(location))
210            if location == actor.verified().token() && actor.verified().signature() == root =>
211        {
212            coverage
213                .require_evidence(actor.verified(), actor.verified().property(), query)
214                .map_err(Error::Coverage)?;
215        }
216        _ => return Err(Error::Foreign { node: root }),
217    }
218    Ok(())
219}
220
221/// Checks all actual root credentials and each in-scope counter group. This
222/// reuses the shared sticky visitor and exact index, not another credential
223/// grammar or authenticator. A later DSS cannot satisfy an earlier input.
224pub fn check<'a, 'r, 'm, 'd, 'p, D: Copy + Eq, G: Copy + Eq, E, P, S, W>(
225    basic: &Basic<'_, '_, '_, 'm, 'd, '_, '_, 'p, D, G>,
226    credentials: &'a Complete<'r, 'm, 'd, 'p, D, G, E>,
227    required: &TimestampRequirements<Node, D, G, Pades>,
228    index: &Unified<'_, 'd, D, G>,
229    scope: &Scope<'_, '_, '_, D, G, W>,
230    provider: &P,
231    query: &Query<'_, '_, S>,
232) -> Result<CheckedReport<'a, 'r, 'm, 'd, 'p, D, G, E>, Error<P::Error>>
233where
234    P: Provider<E>,
235    S: RangeSource<Document = D, Generation = G>,
236    W: Work,
237{
238    let bound = basic.binding();
239    let root = bound.mapping.root;
240    let snapshot = bound.mapping.snapshot;
241    query.admit(root, 1).map_err(Error::Scope)?;
242    if !query.belongs(bound.prepared().container.signature().owner().value.session)
243        || !query.belongs(index.session)
244        || query.snapshot() != snapshot
245        || credentials.mapping.snapshot != snapshot
246        || credentials.mapping.cms != bound.mapping.cms
247        || credentials.mapping.root != root
248        || required.signature() != root
249        || required.scope().location().snapshot != snapshot
250    {
251        return Err(Error::Foreign { node: root });
252    }
253    index
254        .index
255        .require_signature(root, query)
256        .map_err(Error::Index)?;
257    scope_binding(scope, required, index, root, query)?;
258    required
259        .includes_counters(basic.counters(), query)
260        .map_err(Error::Scope)?;
261    let mut open = query.execution().map();
262    for value in index.index.open() {
263        query.admit(root, 1).map_err(Error::Scope)?;
264        let node = bound.mapping.node(value.node);
265        open.try_insert(node, ())
266            .map_err(|cause| Error::Resource { node, cause })?;
267    }
268    let fields = bound.prepared().cms.signer().fields_source();
269    let reader = fields.reader_in(query.execution()).map_err(Error::Cms)?;
270    let mut entries = query.execution().vector();
271    let mut complete = true;
272    let mut visit = |group: &Group<'_, 'm, 'd, 'p, D, G, E>| -> Result<(), Error<P::Error>> {
273        for credential in group.common.entries() {
274            query.admit(root, 1).map_err(Error::Scope)?;
275            let node = bound.mapping.node(credential.source().node);
276            let mut visiting =
277                DependencyVisit::new(credential.source().node, query.execution(), |need| {
278                    dependency(need, bound.mapping, index, scope, &open, node, query)
279                })
280                .map_err(|cause| Error::Resource { node, cause })?;
281            let answer = provider.visit(credential, index.index.open(), &reader, |need| {
282                visiting.observe(need)
283            });
284            let dependencies = visiting
285                .finish(answer)
286                .map_err(|cause| dependency_error(node, cause))?;
287            if dependencies.completeness != structured_ltv::material::Completeness::Complete
288                || !dependencies.findings.is_empty()
289            {
290                complete = false;
291            }
292            entries
293                .try_push(Entry { node, dependencies })
294                .map_err(|cause| Error::Resource { node, cause })?;
295        }
296        Ok(())
297    };
298    if let Some(group) = &credentials.root {
299        visit(group)?;
300    }
301    let mut counter = 0;
302    for group in &credentials.counters {
303        loop {
304            query.admit(root, 1).map_err(Error::Scope)?;
305            if counter == basic.counters().len() {
306                return Err(Error::Foreign {
307                    node: group.signature(),
308                });
309            }
310            let ordinal = counter;
311            counter += 1;
312            if basic.counters().counter(ordinal).signature != group.signature() {
313                continue;
314            }
315            if required
316                .counter_in_scope(ordinal, query)
317                .map_err(Error::Obligation)?
318            {
319                visit(group)?;
320            }
321            break;
322        }
323    }
324    query.admit(root, 1).map_err(Error::Scope)?;
325    Ok(Report {
326        credentials,
327        scope: required.scope(),
328        entries: entries.into_owned(),
329        complete,
330    })
331}
332
333fn dependency_error<N, E>(node: Node, cause: DependencyError<N, Error<E>, E>) -> Error<E> {
334    match cause {
335        DependencyError::Processing { cause, .. } => cause,
336        DependencyError::Resource { cause, .. } => Error::Resource { node, cause },
337        DependencyError::Provider { cause, .. } => Error::Provider { node, cause },
338        DependencyError::CallbackProtocol { .. } => Error::CallbackProtocol { node },
339    }
340}