Skip to main content

structured_asic/archive/
inputs.rs

1//! Complete original time-assertion census for one protected archive input.
2
3use super::{ArchiveError, Graph, MemberId, Role, resource};
4use crate::layout::{Form, Inspection, Principal};
5use structured_asn1::{
6    memory::{Execution, OwnedMap, OwnedVec, Work},
7    range::RangeSource,
8};
9use structured_ltv::timestamp::Purpose;
10
11type SourceResult<T, S> = Result<T, ArchiveError<<S as RangeSource>::Error>>;
12type Associations<S> =
13    OwnedMap<usize, InputAssociation<<S as RangeSource>::Document, <S as RangeSource>::Generation>>;
14type MemberOf<S> = MemberId<<S as RangeSource>::Document, <S as RangeSource>::Generation>;
15
16/// Actual membership in this original manifest's input, independently of time
17/// claims, digest agreement or trusted chronology.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum AssertionScope {
20    /// This timestamp file is declared as a whole protected input object.
21    Declared,
22    /// This is the token that protects the selected manifest, not an earlier TSA.
23    Current,
24    /// The complete input does not declare this original timestamp occurrence.
25    Outside,
26    /// Missing/broken graph prevents a complete original membership claim.
27    Unavailable,
28}
29/// Original source-derived input association, not a selected successful check.
30#[derive(Debug, Clone, Copy)]
31pub enum InputAssociation<D, G> {
32    /// Exactly one original manifest or explicit simple-container data input.
33    Known {
34        /// Actual original input member.
35        member: MemberId<D, G>,
36        /// Selected input role, independently of mathematical evidence.
37        purpose: Purpose,
38    },
39    /// No original source establishes this assertion's input.
40    Missing,
41    /// Several original source declarations select this same token.
42    Ambiguous,
43}
44/// Every original timestamp file remains represented, including unavailable and
45/// out-of-scope occurrences. Later success cannot erase an earlier obligation.
46#[derive(Debug, Clone, Copy)]
47pub struct AssertionInput<D, G> {
48    /// Actual original token member, not its filename after successful checking.
49    pub token: MemberId<D, G>,
50    /// This current protected input's factual membership selection.
51    pub scope: AssertionScope,
52    /// Original input association, retaining missing/ambiguous sources.
53    pub association: InputAssociation<D, G>,
54}
55/// Full original assertion-source census. Complete input geometry and known
56/// associations do not prove token mathematics, historical trust or material.
57pub struct TimestampInputs<D, G> {
58    entries: OwnedVec<AssertionInput<D, G>>,
59    complete: bool,
60}
61impl<D, G> TimestampInputs<D, G> {
62    /// Every original token occurrence, in central-directory order.
63    #[must_use]
64    pub fn entries(&self) -> &[AssertionInput<D, G>] {
65        &self.entries
66    }
67    /// All declared original assertions have a unique source-defined input.
68    #[must_use]
69    pub fn complete(&self) -> bool {
70        self.complete
71    }
72}
73impl<'v, 's, N: Copy, D: Copy + Eq, G: Copy + Eq, S: RangeSource> Graph<'v, 's, N, D, G, S> {
74    fn input_associations(
75        &self,
76        layout: &Inspection<'_, 'v, 's, S>,
77        execution: &Execution<'_>,
78    ) -> SourceResult<Associations<S>, S> {
79        let refusal = |cause| resource(self.archive.directory_span(), cause);
80        let mut associations = execution.map();
81        for fact in &self.facts {
82            execution.charge_work(1).map_err(refusal)?;
83            let catalogue = fact.catalogue();
84            let Some(token) = catalogue.signature_member(execution)? else {
85                continue;
86            };
87            let role = layout
88                .census()
89                .occurrence(token.id().ordinal(), execution)?
90                .map(|(_, role)| role);
91            if role != Some(Role::Timestamp) {
92                continue;
93            }
94            let source = catalogue.base();
95            let purpose = match layout
96                .census()
97                .occurrence(source.id().ordinal(), execution)?
98                .map(|(_, role)| role)
99            {
100                Some(Role::ArchiveManifest) => Purpose::Archive,
101                Some(Role::Manifest) => Purpose::Content,
102                _ => continue,
103            };
104            if associations
105                .try_insert(
106                    token.id().ordinal(),
107                    InputAssociation::Known {
108                        member: source.id(),
109                        purpose,
110                    },
111                )
112                .map_err(refusal)?
113                .is_some()
114            {
115                associations
116                    .try_insert(token.id().ordinal(), InputAssociation::Ambiguous)
117                    .map_err(refusal)?;
118            }
119        }
120        Ok(associations.into_owned())
121    }
122    fn simple_data(
123        layout: &Inspection<'_, 'v, 's, S>,
124        execution: &Execution<'_>,
125    ) -> SourceResult<Option<MemberOf<S>>, S> {
126        if layout.form() != Form::Simple(Principal::Timestamp) {
127            return Ok(None);
128        }
129        let mut simple = None;
130        for ordinal in 0..layout.census().len() {
131            let Some((member, role)) = layout.census().occurrence(ordinal, execution)? else {
132                continue;
133            };
134            if role != Role::Data {
135                continue;
136            }
137            if simple.is_some() {
138                return Ok(None);
139            }
140            simple = Some(member.id());
141        }
142        Ok(simple)
143    }
144    /// Includes ordinary manifest assertions as well as archive-manifest
145    /// renewals. The simple timestamp's implied input is admitted only under
146    /// the explicitly selected S-timestamp form and its one actual data file.
147    pub fn timestamp_inputs<W: Work>(
148        &self,
149        index: usize,
150        layout: &Inspection<'_, 'v, 's, S>,
151        work: &W,
152    ) -> SourceResult<TimestampInputs<S::Document, S::Generation>, S> {
153        if !core::ptr::eq(layout.census().archive(), self.archive) {
154            return Err(ArchiveError::Foreign {
155                range: self.archive.directory_span(),
156            });
157        }
158        let execution = self.archive.session().query(work);
159        self.archive
160            .admit(&execution, self.archive.directory_span())?;
161        let refusal = |cause| resource(self.archive.directory_span(), cause);
162        let current = self.nodes.get(index).ok_or(ArchiveError::Foreign {
163            range: self.archive.directory_span(),
164        })?;
165        let associations = self.input_associations(layout, &execution)?;
166        let mut declared = execution.map();
167        for member in current.objects().iter().flatten() {
168            declared.try_insert(member.ordinal(), ()).map_err(refusal)?;
169        }
170        let simple = Self::simple_data(layout, &execution)?;
171        let geometry = self.geometry_complete();
172        let mut complete = geometry;
173        let mut entries = execution.vector();
174        for ordinal in 0..layout.census().len() {
175            let Some((member, role)) = layout.census().occurrence(ordinal, &execution)? else {
176                continue;
177            };
178            if role != Role::Timestamp {
179                continue;
180            }
181            let mut association = match associations.get(&ordinal, &execution).map_err(refusal)? {
182                Some(value) => *value,
183                None => InputAssociation::Missing,
184            };
185            if let Some(data) = simple
186                && matches!(association, InputAssociation::Missing)
187            {
188                execution
189                    .charge_work(member.name().len() as u64)
190                    .map_err(refusal)?;
191                if member.name() == b"META-INF/timestamp.tst" {
192                    association = InputAssociation::Known {
193                        member: data,
194                        purpose: Purpose::Content,
195                    };
196                }
197            }
198            let scope = if current.timestamp == Some(member.id()) {
199                AssertionScope::Current
200            } else if !geometry {
201                AssertionScope::Unavailable
202            } else if declared
203                .get(&ordinal, &execution)
204                .map_err(refusal)?
205                .is_some()
206            {
207                AssertionScope::Declared
208            } else {
209                AssertionScope::Outside
210            };
211            if scope == AssertionScope::Declared
212                && !matches!(association, InputAssociation::Known { .. })
213            {
214                complete = false;
215            }
216            entries
217                .try_push(AssertionInput {
218                    token: member.id(),
219                    scope,
220                    association,
221                })
222                .map_err(refusal)?;
223        }
224        self.archive
225            .admit(&execution, self.archive.directory_span())?;
226        Ok(TimestampInputs {
227            entries: entries.into_owned(),
228            complete,
229        })
230    }
231}