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    token: Option<N>,
20    purpose: Purpose,
21    items: OwnedMap<N, bool, W>,
22    issuer: PhantomData<I>,
23}
24
25#[derive(Clone, Copy)]
26struct Identity<N, D, G> {
27    snapshot: Snapshot<D, G>,
28    signature: N,
29    property: N,
30    token: N,
31    purpose: Purpose,
32}
33
34/// Source/current admission and foreign evidence are independent of absence.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum CoverageError<N> {
37    /// Source generation or current operation refused.
38    Scope(ScopeError<N>),
39    /// Item index storage/work was not admitted.
40    Resource {
41        /// Original root/input node whose admission failed.
42        node: N,
43        /// Original capacity/work refusal.
44        cause: MemoryError,
45    },
46    /// Input/evidence/current view refer to different signature/property/token/source.
47    Foreign {
48        /// Original requested item.
49        node: N,
50    },
51}
52
53impl<N: Copy + Eq + HashWork, D: Copy + Eq, G: Copy + Eq, W: Work, I> Coverage<N, D, G, W, I> {
54    /// Coverage shared by every checked token of ONE property over `signature`.
55    /// The format issuer must establish that all those token values use the
56    /// SAME original input selection (for example `XAdES` property-defined input).
57    /// Use [`Self::for_token`] when selection depends on a particular member,
58    /// as it does for successive historical `CAdES` archives in one Attribute.
59    ///
60    /// `items` maps each visited original item to whether it is itself a whole
61    /// covered item; a `false` entry records a visited container that carries
62    /// covered descendants but is not an item claim. Requiring the issuer value
63    /// keeps a caller of the format from supplying a fabricated selection.
64    #[must_use]
65    pub fn new(
66        _issuer: I,
67        snapshot: Snapshot<D, G>,
68        signature: N,
69        timestamp: N,
70        purpose: Purpose,
71        items: OwnedMap<N, bool, W>,
72    ) -> Self {
73        Self {
74            snapshot,
75            signature,
76            timestamp,
77            token: None,
78            purpose,
79            items,
80            issuer: PhantomData,
81        }
82    }
83
84    /// Exact token-bound selection when sibling tokens of the same property
85    /// have DIFFERENT inputs. Another sibling's valid proof cannot authenticate
86    /// this selection merely because its property, signature and source match.
87    #[must_use]
88    pub fn for_token(
89        issuer: I,
90        snapshot: Snapshot<D, G>,
91        signature: N,
92        property: N,
93        token: N,
94        purpose: Purpose,
95        items: OwnedMap<N, bool, W>,
96    ) -> Self {
97        let mut result = Self::new(issuer, snapshot, signature, property, purpose, items);
98        result.token = Some(token);
99        result
100    }
101
102    /// Original identity of this input selection for material/census joins.
103    /// Member-specific inputs use the TOKEN node, while a property-shared
104    /// selection uses the property. Matching only the enclosing property would
105    /// conflate successive archives carried by one multivalued Attribute.
106    #[must_use]
107    pub fn subject(&self) -> N {
108        match self.token {
109            Some(token) => token,
110            None => self.timestamp,
111        }
112    }
113
114    /// Tests prepared whole-item selection under a current source query.
115    /// True alone is not proof that any timestamp was mathematically checked.
116    pub fn selects<Q: Query<Id = N, Document = D, Generation = G>>(
117        &self,
118        node: N,
119        query: &Q,
120    ) -> Result<bool, CoverageError<N>> {
121        if self.snapshot != query.snapshot() {
122            return Err(CoverageError::Foreign { node });
123        }
124        query.admit(node, 1).map_err(CoverageError::Scope)?;
125        self.items
126            .get(&node, query.execution())
127            .map(|value| value == Some(&true))
128            .map_err(|cause| CoverageError::Resource { node, cause })
129    }
130
131    /// Binds item selection to the successful token evidence for this exact
132    /// original input. This establishes mathematical coverage only;
133    /// TSA/path/status/chronology acceptance remains separate.
134    pub fn covers<Q: Query<Id = N, Document = D, Generation = G>>(
135        &self,
136        evidence: &Verified<N, D, G, I>,
137        node: N,
138        query: &Q,
139    ) -> Result<bool, CoverageError<N>> {
140        self.require_evidence(evidence, node, query)?;
141        self.selects(node, query)
142    }
143
144    /// Requires `evidence` to be the checked token of exactly this input.
145    pub fn require_evidence<Q: Query<Id = N, Document = D, Generation = G>>(
146        &self,
147        evidence: &Verified<N, D, G, I>,
148        node: N,
149        query: &Q,
150    ) -> Result<(), CoverageError<N>> {
151        if self.snapshot != query.snapshot() {
152            return Err(CoverageError::Foreign { node });
153        }
154        query.admit(node, 1).map_err(CoverageError::Scope)?;
155        self.require_identity(
156            Identity {
157                snapshot: evidence.token().snapshot,
158                signature: evidence.signature(),
159                property: evidence.property(),
160                token: evidence.token().node,
161                purpose: evidence.purpose(),
162            },
163            node,
164            query.execution(),
165        )
166    }
167
168    fn require_identity<Q: Work>(
169        &self,
170        identity: Identity<N, D, G>,
171        node: N,
172        execution: &Q,
173    ) -> Result<(), CoverageError<N>> {
174        let resource = |cause| CoverageError::Resource { node, cause };
175        if identity.snapshot != self.snapshot
176            || !equal_id(identity.signature, self.signature, execution).map_err(resource)?
177            || !equal_id(identity.property, self.timestamp, execution).map_err(resource)?
178            || identity.purpose != self.purpose
179        {
180            return Err(CoverageError::Foreign { node });
181        }
182        if let Some(token) = self.token
183            && !equal_id(identity.token, token, execution).map_err(resource)?
184        {
185            return Err(CoverageError::Foreign { node });
186        }
187        Ok(())
188    }
189}
190
191#[cfg(test)]
192mod tests;