Skip to main content

structured_ltv/material/
coverage.rs

1//! Original whole items covered by one checked timestamp input.
2
3use crate::source::{Query, ScopeError, Snapshot, equal_id};
4use crate::timestamp::{Purpose, Verified};
5use core::marker::PhantomData;
6use structured_asn1::memory::{HashWork, MemoryError, OwnedMap, Work};
7
8/// The original items one timestamp's prepared input covers in their entirety.
9///
10/// The format layer builds it from the same input selection its token check
11/// used: `XAdES` from canonicalized elements, `CAdES` from `ats-hash-index-v3`.
12/// Transformed or partial input grants no whole-item coverage. Selection alone
13/// does not establish that the timestamp was checked. Only the holder of issuer
14/// `I` builds it, and it binds only evidence of the same issuer.
15pub struct Coverage<N: HashWork, D, G, W: Work, I> {
16    snapshot: Snapshot<D, G>,
17    signature: N,
18    timestamp: N,
19    purpose: Purpose,
20    items: OwnedMap<N, bool, W>,
21    issuer: PhantomData<I>,
22}
23
24/// Source/current admission and foreign evidence are independent of absence.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum CoverageError<N> {
27    /// Source generation or current operation refused.
28    Scope(ScopeError<N>),
29    /// Item index storage/work was not admitted.
30    Resource {
31        /// Original root/input node whose admission failed.
32        node: N,
33        /// Original capacity/work refusal.
34        cause: MemoryError,
35    },
36    /// Input/evidence/current view refer to different signature/property/source.
37    Foreign {
38        /// Original requested item.
39        node: N,
40    },
41}
42
43impl<N: Copy + Eq + HashWork, D: Copy + Eq, G: Copy + Eq, W: Work, I> Coverage<N, D, G, W, I> {
44    /// The coverage of one timestamp's input over `signature`.
45    ///
46    /// `items` maps each visited original item to whether it is itself a whole
47    /// covered item; a `false` entry records a visited container that carries
48    /// covered descendants but is not an item claim. Requiring the issuer value
49    /// keeps a caller of the format from supplying a fabricated selection.
50    #[must_use]
51    pub fn new(
52        _issuer: I,
53        snapshot: Snapshot<D, G>,
54        signature: N,
55        timestamp: N,
56        purpose: Purpose,
57        items: OwnedMap<N, bool, W>,
58    ) -> Self {
59        Self {
60            snapshot,
61            signature,
62            timestamp,
63            purpose,
64            items,
65            issuer: PhantomData,
66        }
67    }
68
69    /// Tests prepared whole-item selection under a current source query.
70    /// True alone is not proof that any timestamp was mathematically checked.
71    pub fn selects<Q: Query<Id = N, Document = D, Generation = G>>(
72        &self,
73        node: N,
74        query: &Q,
75    ) -> Result<bool, CoverageError<N>> {
76        if self.snapshot != query.snapshot() {
77            return Err(CoverageError::Foreign { node });
78        }
79        query.admit(node, 1).map_err(CoverageError::Scope)?;
80        self.items
81            .get(&node, query.execution())
82            .map(|value| value == Some(&true))
83            .map_err(|cause| CoverageError::Resource { node, cause })
84    }
85
86    /// Binds item selection to the successful token evidence for this exact
87    /// original input. This establishes mathematical coverage only;
88    /// TSA/path/status/chronology acceptance remains separate.
89    pub fn covers<Q: Query<Id = N, Document = D, Generation = G>>(
90        &self,
91        evidence: &Verified<N, D, G, I>,
92        node: N,
93        query: &Q,
94    ) -> Result<bool, CoverageError<N>> {
95        self.require_evidence(evidence, node, query)?;
96        self.selects(node, query)
97    }
98
99    /// Requires `evidence` to be the checked token of exactly this input.
100    pub fn require_evidence<Q: Query<Id = N, Document = D, Generation = G>>(
101        &self,
102        evidence: &Verified<N, D, G, I>,
103        node: N,
104        query: &Q,
105    ) -> Result<(), CoverageError<N>> {
106        if self.snapshot != query.snapshot() {
107            return Err(CoverageError::Foreign { node });
108        }
109        query.admit(node, 1).map_err(CoverageError::Scope)?;
110        self.require_identity(
111            evidence.token().snapshot,
112            evidence.signature(),
113            evidence.property(),
114            evidence.purpose(),
115            node,
116            query.execution(),
117        )
118    }
119
120    fn require_identity<Q: Work>(
121        &self,
122        snapshot: Snapshot<D, G>,
123        signature: N,
124        timestamp: N,
125        purpose: Purpose,
126        node: N,
127        execution: &Q,
128    ) -> Result<(), CoverageError<N>> {
129        let resource = |cause| CoverageError::Resource { node, cause };
130        if snapshot != self.snapshot
131            || !equal_id(signature, self.signature, execution).map_err(resource)?
132            || !equal_id(timestamp, self.timestamp, execution).map_err(resource)?
133            || purpose != self.purpose
134        {
135            return Err(CoverageError::Foreign { node });
136        }
137        Ok(())
138    }
139}
140
141#[cfg(test)]
142mod tests;