Skip to main content

structured_cades/evidence/
coverage.rs

1//! Whole-item coverage from the SAME executed source recipe and sealed record.
2use super::record::{Checked, Verified};
3use crate::{
4    Cades, Error, ISSUER,
5    material::Index,
6    source::{DocumentId, Node},
7    timestamp,
8};
9use structured_asn1::{
10    Reader, Span,
11    memory::{Memory, OwnedVec, SortError, Work},
12};
13use structured_ltv::{
14    material::{Coverage, IndexError},
15    source::{Scope, Source as _},
16};
17const FIELD: &str = "cades.evidence.coverage";
18type Items<'m, 'd> = Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades>;
19
20/// Source/query or original material-index refusal; never missing material.
21#[derive(Debug)]
22pub enum CoverageError {
23    /// Original source, work or storage refusal.
24    Processing(Error),
25    /// Exact source-index refusal, including a fused traversal failure.
26    Index(IndexError<Node>),
27    /// A selected timestamp/index belongs to another sealed proof or source.
28    Foreign {
29        /// Actual selecting proof property.
30        property: Node,
31    },
32}
33/// Same original checked TSA with its own actual, source-derived coverage.
34/// A later renewal covers the original input and earlier complete timestamps;
35/// it does not claim coverage of its OWN carried certificates or future TSAs.
36pub struct Archive<'view, 'prepared, 'm, 'd, 'path> {
37    report: &'view Checked<'prepared, 'm, 'd, 'path>,
38    timestamp: &'view timestamp::Checked<'m, 'd, 'path>,
39    coverage: Items<'m, 'd>,
40}
41impl<'m, 'd, 'path> Archive<'_, '_, 'm, 'd, 'path> {
42    /// Same complete checked timestamp, without rechecking mathematics.
43    #[must_use]
44    pub fn timestamp(&self) -> &timestamp::Checked<'m, 'd, 'path> {
45        self.timestamp
46    }
47    /// Exact whole-item selection; successful evidence must still bind it.
48    #[must_use]
49    pub fn coverage(&self) -> &Items<'m, 'd> {
50        &self.coverage
51    }
52    /// Full earlier TSA/counter census from this actual protected input.
53    pub fn requirements(
54        &self,
55        operation: &Reader<'d>,
56    ) -> Result<
57        structured_ltv::timestamp::TimestampRequirements<Node, DocumentId<'m, 'd>, (), Cades>,
58        timestamp::ArchiveScopeError,
59    > {
60        timestamp::requirements::derive(
61            self.timestamp,
62            &self.coverage,
63            self.report.prepared,
64            operation,
65        )
66    }
67}
68impl<'prepared, 'm, 'd, 'path> Verified<'_, 'prepared, 'm, 'd, 'path> {
69    /// Bind one actual global timestamp ordinal to its executed recipe. The
70    /// provided index is this preparation's existing whole material index;
71    /// no material/certificate/status grammar is parsed again.
72    pub fn archive<'view, W: Work>(
73        &'view self,
74        ordinal: usize,
75        index: &Index<'_, 'm, 'd, W>,
76        operation: &Reader<'d>,
77    ) -> Result<Archive<'view, 'prepared, 'm, 'd, 'path>, CoverageError> {
78        let report = self.report();
79        let prepared = report.prepared;
80        let property = report.required.property;
81        let resource =
82            |cause| CoverageError::Processing(Error::resource(cause, FIELD, property.span().start));
83        prepared
84            .document()
85            .message()
86            .require_session(operation, FIELD)
87            .map_err(|cause| CoverageError::Processing(Error::Cms(cause)))?;
88        let work = operation.execution();
89        work.charge_work(3).map_err(resource)?;
90        if !report.timestamps.contains(&ordinal)
91            || index.snapshot() != prepared.document().snapshot()
92            || index.signature() != Node::of(prepared.signer().span())
93        {
94            return Err(CoverageError::Foreign { property });
95        }
96        let entry = prepared
97            .parsed
98            .proofs
99            .entries
100            .get(report.proof)
101            .ok_or(CoverageError::Foreign { property })?;
102        if entry.required.property != property {
103            return Err(CoverageError::Foreign { property });
104        }
105        let relative = ordinal - report.timestamps.start;
106        let timestamp = &report.checks[report.checks.len() - 1 - relative];
107        let actor = timestamp
108            .actor()
109            .ok_or(CoverageError::Foreign { property })?;
110        let mut spans = work.vector();
111        let base = report
112            .covered
113            .as_ref()
114            .ok_or(CoverageError::Foreign { property })?;
115        spans.try_extend_from_slice(base).map_err(resource)?;
116        for previous in report.timestamps.start..ordinal {
117            work.charge_work(1).map_err(resource)?;
118            let value = prepared.timestamps()[previous]
119                .value()
120                .ok_or(CoverageError::Foreign { property })?;
121            spans.try_push(value.span()).map_err(resource)?;
122        }
123        normalize(&mut spans, &work).map_err(resource)?;
124        let document = prepared.document();
125        let query = Scope::new(document, document.snapshot(), &work);
126        let mut items = work.map();
127        for occurrence in index
128            .all_occurrences(&query)
129            .map_err(CoverageError::Index)?
130        {
131            let occurrence = occurrence.map_err(CoverageError::Index)?;
132            let node = occurrence.location().node;
133            if selects(&spans, node.span(), &work).map_err(resource)? {
134                items.try_insert(node, true).map_err(resource)?;
135            }
136        }
137        for counter in prepared
138            .counters()
139            .iter()
140            .chain(prepared.parallel_counters())
141        {
142            work.charge_work(1).map_err(resource)?;
143            if selects(&spans, counter.node().span(), &work).map_err(resource)? {
144                items.try_insert(counter.node(), true).map_err(resource)?;
145            }
146        }
147        for entry in prepared.timestamps() {
148            work.charge_work(1).map_err(resource)?;
149            let node = entry
150                .value()
151                .map_or(entry.property(), |value| Node::of(value.span()));
152            if node == actor.verified().token().node {
153                continue;
154            }
155            if selects(&spans, node.span(), &work).map_err(resource)? {
156                items.try_insert(node, true).map_err(resource)?;
157            }
158            if selects(&spans, entry.property().span(), &work).map_err(resource)? {
159                items.try_insert(entry.property(), true).map_err(resource)?;
160            }
161        }
162        let coverage = Coverage::for_token(
163            ISSUER,
164            document.snapshot(),
165            report.required.signature,
166            property,
167            actor.verified().token().node,
168            structured_ltv::timestamp::Purpose::Archive,
169            items.into_owned(),
170        );
171        Ok(Archive {
172            report,
173            timestamp,
174            coverage,
175        })
176    }
177}
178pub(super) fn normalize_owned<W: Work>(
179    spans: OwnedVec<Span>,
180    work: &W,
181    at: u64,
182) -> Result<OwnedVec<Span>, Error> {
183    let mut spans = spans
184        .with_execution(work)
185        .map_err(|_| Error::Foreign { at })?;
186    normalize(&mut spans, work).map_err(|cause| Error::resource(cause, FIELD, at))?;
187    Ok(spans.into_owned())
188}
189fn normalize<W: Work>(
190    spans: &mut OwnedVec<Span, W>,
191    work: &W,
192) -> Result<(), structured_asn1::memory::MemoryError> {
193    spans
194        .try_sort_unstable_by(|a, b| {
195            work.charge_work(3)?;
196            Ok::<_, structured_asn1::memory::MemoryError>(
197                a.start.cmp(&b.start).then_with(|| b.length.cmp(&a.length)),
198            )
199        })
200        .map_err(|cause| match cause {
201            SortError::Resource(cause) | SortError::Comparison(cause) => cause,
202        })?;
203    let mut written = 0;
204    for read in 0..spans.len() {
205        work.charge_work(3)?;
206        let current = spans[read];
207        if written != 0 && current.start <= spans[written - 1].end() {
208            let previous = &mut spans[written - 1];
209            if current.end() > previous.end() {
210                previous.length = current.end() - previous.start;
211            }
212        } else {
213            spans[written] = current;
214            written += 1;
215        }
216    }
217    while spans.len() > written {
218        work.charge_work(1)?;
219        spans.pop();
220    }
221    Ok(())
222}
223pub(super) fn selects<W: Work>(
224    spans: &[Span],
225    node: Span,
226    work: &W,
227) -> Result<bool, structured_asn1::memory::MemoryError> {
228    let mut low = 0;
229    let mut high = spans.len();
230    while low < high {
231        work.charge_work(2)?;
232        let middle = low + (high - low) / 2;
233        if spans[middle].start <= node.start {
234            low = middle + 1;
235        } else {
236            high = middle;
237        }
238    }
239    if low == 0 {
240        return Ok(false);
241    }
242    work.charge_work(1)?;
243    Ok(spans[low - 1].end() >= node.end())
244}