1use super::{Parsed, Placement, Validation};
4use crate::{
5 basic::Basic,
6 binding::InnerQuery,
7 document::Document,
8 error::{Error, ErrorKind, ReadFailure},
9 query::Query,
10 range::RangeSource,
11 signature::Signature,
12 timestamp::{ISSUER, Node, Pades},
13};
14use structured_asn1::memory::{Execution, Memory, OwnedVec, Work};
15use structured_ltv::{
16 material::{ArtifactSource, Index, IndexError, ProjectionError},
17 source::{Location, Query as _},
18};
19mod token;
20
21pub struct History<'a, 's, D, G> {
24 pub(crate) root: Signature<'a, 's, D, G>,
25 pub(super) values: OwnedVec<Validation<'s, D, G>, Memory>,
26}
27pub struct ParsedHistory<'h, 'a, 's, D, G> {
29 pub(super) owner: &'h History<'a, 's, D, G>,
30 pub(super) values: OwnedVec<Parsed<'h, 's, D, G>, Memory>,
31}
32
33type UnifiedIndex<'q, 'd, D, G> =
34 Index<'q, Node, D, G, Placement, structured_cades::material::Open<'d>, Execution<'q>, Pades>;
35
36struct TokenCoverage {
37 complete: bool,
38 generation_revision: Option<usize>,
39}
40
41enum TokenIndexError {
42 Foreign(Error),
43 Index(IndexError<Node>),
44}
45impl From<TokenIndexError> for HistoryError {
46 fn from(value: TokenIndexError) -> Self {
47 match value {
48 TokenIndexError::Foreign(cause) => Self::Foreign(cause),
49 TokenIndexError::Index(cause) => Self::Index(cause),
50 }
51 }
52}
53
54pub struct Unified<'q, 'd, D, G> {
57 pub(crate) session: &'q crate::Session,
58 pub(crate) index: UnifiedIndex<'q, 'd, D, G>,
59 pub(crate) root: Node,
60 pub(crate) complete: bool,
61 pub(crate) required: &'q structured_ltv::timestamp::TimestampRequirements<Node, D, G, Pades>,
62 pub(crate) revision: usize,
63 associations: super::vri::Associations,
64}
65impl<D, G> Unified<'_, '_, D, G> {
66 #[must_use]
69 pub fn revision(&self) -> usize {
70 self.revision
71 }
72 #[must_use]
75 pub fn all_source_requirements_met(&self) -> bool {
76 self.complete
77 }
78 #[must_use]
80 pub fn vri_associations(&self) -> &super::vri::Associations {
81 &self.associations
82 }
83}
84
85#[derive(Debug)]
87pub enum HistoryError {
88 Material(super::MaterialFailure),
90 CmsIndex {
92 enclosure: crate::source::Location,
94 cause: IndexError<structured_cades::source::Node>,
96 },
97 Projection(ProjectionError<Node, Error>),
99 Index(IndexError<Node>),
101 Foreign(Error),
103}
104
105impl<'s, S: RangeSource> Document<'s, S> {
106 pub async fn validation_history<'a, W: Work>(
109 &self,
110 root: Signature<'a, 's, S::Document, S::Generation>,
111 operation: &W,
112 ) -> Result<History<'a, 's, S::Document, S::Generation>, ReadFailure<S::Error>> {
113 let work = self.session.query(operation);
114 let location = root.location();
115 self.check(&work, location)?;
116 if !core::ptr::eq(root.owner().value.session, self.session)
117 || root.owner().value.stamp != self.stamp
118 {
119 return Err(Error {
120 kind: ErrorKind::Foreign,
121 location,
122 }
123 .into());
124 }
125 let mut values = work.vector();
126 for revision in 0..self.revisions() {
127 crate::model::admit(&work, location, 1)?;
128 let value = self.validation(revision, &work).await?;
129 values.try_push(value).map_err(|cause| Error {
130 kind: ErrorKind::Resource(cause),
131 location,
132 })?;
133 }
134 Ok(History {
135 root,
136 values: values.into_owned(),
137 })
138 }
139}
140impl<'a, 's, D: Copy + Eq, G: Copy + Eq> History<'a, 's, D, G> {
141 #[must_use]
143 pub fn revisions(&self) -> &[Validation<'s, D, G>] {
144 &self.values
145 }
146 #[expect(
148 clippy::result_large_err,
149 reason = "historical material failure preserves the complete cause and actual original item location without an error box"
150 )]
151 pub fn parse<'h>(
152 &'h self,
153 operation: &'h impl Work,
154 ) -> Result<ParsedHistory<'h, 'a, 's, D, G>, HistoryError> {
155 let work = self.root.owner().value.session.query(operation);
156 let mut values = work.vector();
157 for value in &self.values {
158 let value = value.parse(operation).map_err(HistoryError::Material)?;
159 values.try_push(value).map_err(|cause| {
160 HistoryError::Foreign(Error {
161 kind: ErrorKind::Resource(cause),
162 location: self.root.location(),
163 })
164 })?;
165 }
166 Ok(ParsedHistory {
167 owner: self,
168 values: values.into_owned(),
169 })
170 }
171}
172impl<'h, 's, D: Copy + Eq, G: Copy + Eq> ParsedHistory<'h, '_, 's, D, G> {
173 #[must_use]
175 pub fn revisions(&self) -> &[Parsed<'h, 's, D, G>] {
176 &self.values
177 }
178 fn append_document_tokens<
179 'q,
180 'm: 'q,
181 'd: 'q,
182 'tokens: 'q,
183 S: RangeSource<Document = D, Generation = G>,
184 >(
185 timestamps: &'q crate::timestamp::sources::Models<'tokens, '_, '_, 's, D, G>,
186 mapping: crate::binding::Mapping<'m, 'd, D, G>,
187 prefix: usize,
188 index: &mut UnifiedIndex<'q, 'd, D, G>,
189 query: &Query<'q, 's, S>,
190 ) -> Result<TokenCoverage, TokenIndexError>
191 where
192 'h: 'q,
193 {
194 let mut complete = true;
195 let mut generation_revision = None;
196 for (timestamp, value) in timestamps.values.iter().enumerate() {
197 index.admit(query).map_err(TokenIndexError::Index)?;
198 let crate::timestamp::sources::Model::Known { token, revocations } = value else {
199 complete = false;
200 continue;
201 };
202 let Some(crate::timestamp::sources::Slot::Value(container)) = timestamps
203 .sources
204 .get(timestamp, query.execution())
205 .map_err(TokenIndexError::Foreign)?
206 else {
207 unreachable!("a parsed token retains its original container")
208 };
209 if let crate::signature::Coverage::Revision(covered) = container.signature().coverage()
210 && generation_revision.is_none_or(|previous| covered > previous)
211 {
212 generation_revision = Some(covered);
213 }
214 let source_ordinal = prefix.checked_add(timestamp).ok_or(TokenIndexError::Index(
215 IndexError::Resource {
216 signature: mapping.root,
217 cause: structured_asn1::memory::MemoryError::CapacityOverflow,
218 },
219 ))?;
220 let contents = timestamps
221 .contents(timestamp)
222 .expect("readable original token source");
223 let location = Location {
224 snapshot: mapping.snapshot,
225 node: Node::of(contents),
226 };
227 if !token::append(
228 index,
229 token,
230 revocations,
231 source_ordinal,
232 location,
233 |choice| Placement::DocumentTimestamp { timestamp, choice },
234 query,
235 )
236 .map_err(TokenIndexError::Index)?
237 {
238 complete = false;
239 }
240 }
241 Ok(TokenCoverage {
242 complete,
243 generation_revision,
244 })
245 }
246
247 fn append_vri_tokens<
248 'q,
249 'm: 'q,
250 'd: 'q,
251 'tokens: 'q,
252 S: RangeSource<Document = D, Generation = G>,
253 >(
254 &'q self,
255 timestamps: &'q crate::timestamp::sources::Models<'tokens, '_, '_, 's, D, G>,
256 mapping: crate::binding::Mapping<'m, 'd, D, G>,
257 prefix: usize,
258 index: &mut UnifiedIndex<'q, 'd, D, G>,
259 query: &Query<'q, 's, S>,
260 ) -> Result<bool, TokenIndexError>
261 where
262 'h: 'q,
263 {
264 let mut complete = true;
265 for source in timestamps.vri_timestamps() {
266 index.admit(query).map_err(TokenIndexError::Index)?;
267 let parsed = &self.values[source.revision];
268 let super::Value::Timestamp(value) = &parsed.values()[source.item] else {
269 complete = false;
270 continue;
271 };
272 let source_ordinal = prefix
273 .checked_add(timestamps.total_pdf())
274 .and_then(|prefix| prefix.checked_add(source.ordinal))
275 .ok_or(TokenIndexError::Index(IndexError::Resource {
276 signature: mapping.root,
277 cause: structured_asn1::memory::MemoryError::CapacityOverflow,
278 }))?;
279 let item = &self.owner.values[source.revision].items()[source.item];
280 let location = Location {
281 snapshot: mapping.snapshot,
282 node: Node::dss(item.location(), source.revision),
283 };
284 if !token::append(
285 index,
286 &value.token,
287 &value.revocations,
288 source_ordinal,
289 location,
290 |choice| Placement::VriTimestamp {
291 revision: source.revision,
292 vri: source.vri,
293 choice,
294 },
295 query,
296 )
297 .map_err(TokenIndexError::Index)?
298 {
299 complete = false;
300 }
301 }
302 Ok(complete)
303 }
304
305 fn selected_revision<S: RangeSource<Document = D, Generation = G>>(
306 &self,
307 basic: &Basic<'_, '_, '_, '_, '_, '_, 's, '_, D, G>,
308 pool: &structured_cades::material::Pool<'_, '_, '_>,
309 timestamps: &crate::timestamp::sources::Models<'_, '_, '_, 's, D, G>,
310 query: &Query<'_, 's, S>,
311 ) -> crate::Result<usize> {
312 let signature = self.owner.root;
313 let location = signature.location();
314 let mapping = basic.binding().mapping;
315 let foreign = || Error {
316 kind: ErrorKind::Foreign,
317 location,
318 };
319 if !query.belongs(signature.owner().value.session)
320 || query.snapshot() != mapping.snapshot
321 || mapping.root != Node::of(location)
322 || !core::ptr::eq(pool.prepared(), basic.binding().prepared().cms)
323 {
324 return Err(foreign());
325 }
326 if timestamps.root() != mapping.root || timestamps.snapshot() != mapping.snapshot {
327 return Err(foreign());
328 }
329 debug_assert!(
330 !self.values.is_empty(),
331 "a parsed document has an original revision"
332 );
333 let revision = timestamps.revision().unwrap_or(self.values.len() - 1);
334 if revision >= self.values.len()
335 || !matches!(signature.coverage(), crate::signature::Coverage::Revision(root) if root <= revision)
336 {
337 return Err(foreign());
338 }
339 Ok(revision)
340 }
341
342 fn source_complete<'q, 'm, 'd, S: RangeSource<Document = D, Generation = G>>(
343 &self,
344 pool: &structured_cades::material::Pool<'_, 'm, 'd>,
345 revision: usize,
346 index: &UnifiedIndex<'q, 'd, D, G>,
347 query: &Query<'q, 's, S>,
348 ) -> Result<bool, IndexError<Node>> {
349 let mut complete = true;
350 for value in pool.open() {
351 index.admit(query)?;
352 if value.cause.is_some() {
353 complete = false;
354 }
355 }
356 for value in &self.values[..=revision] {
357 index.admit(query)?;
358 if !value.all_values_readable() || !value.all_vri_material_in_dss() {
359 complete = false;
360 }
361 }
362 Ok(complete)
363 }
364
365 fn generation_covered<'q, 'd, S: RangeSource<Document = D, Generation = G>>(
366 &self,
367 revision: usize,
368 generation_revision: Option<usize>,
369 index: &UnifiedIndex<'q, 'd, D, G>,
370 query: &Query<'q, 's, S>,
371 ) -> Result<bool, IndexError<Node>> {
372 let mut complete = true;
373 for validation in &self.owner.values[..=revision] {
377 for vri in validation.vri() {
378 index.admit(query)?;
379 if validation.revision == 0
380 || matches!(
381 vri.generation_time(),
382 super::GenerationTime::DocumentTimestampRequired
383 ) && generation_revision.is_none_or(|covered| covered < validation.revision)
384 {
385 complete = false;
386 }
387 }
388 }
389 Ok(complete)
390 }
391
392 #[expect(
398 clippy::result_large_err,
399 reason = "indexing retains exact inner and enclosing refusal contexts; resource failure must not require another allocation"
400 )]
401 pub async fn index<'q, 'm: 'q, 'd, 'tokens: 'q, 'p, S, P>(
402 &'q self,
403 basic: &Basic<'_, '_, '_, 'm, 'd, '_, 's, '_, D, G>,
404 pool: &'q structured_cades::material::Pool<'_, 'm, 'd>,
405 timestamps: &'q crate::timestamp::sources::Models<'tokens, '_, '_, 's, D, G>,
406 provider: &'p P,
407 query: &Query<'q, 's, S>,
408 ) -> Result<Unified<'q, 'd, D, G>, super::vri::SourceError<S::Error, P::Error>>
409 where
410 S: RangeSource<Document = D, Generation = G>,
411 P: structured_pkix::algorithm::digest::AsnDigest<'p>,
412 'd: 'q,
413 'h: 'q,
414 {
415 let signature = self.owner.root;
416 let location = signature.location();
417 let mapping = basic.binding().mapping;
418 let revision = self
419 .selected_revision(basic, pool, timestamps, query)
420 .map_err(HistoryError::Foreign)?;
421 let inner_query = InnerQuery {
422 outer: query,
423 mapping,
424 };
425 let original = pool
426 .index(&inner_query)
427 .map_err(|cause| HistoryError::CmsIndex {
428 enclosure: mapping.carrier,
429 cause,
430 })?;
431 let mut ordinal = 0;
432 let mut index = original
433 .into_projected(ISSUER, mapping.root, query, |occurrence| {
434 if occurrence.location().snapshot != mapping.cms {
435 return Err(Error {
436 kind: ErrorKind::Foreign,
437 location,
438 });
439 }
440 let source = match occurrence.source() {
441 ArtifactSource::Material(_) => ArtifactSource::Material(ordinal),
442 source => source,
443 };
444 ordinal += 1;
445 Ok((
446 source,
447 Location {
448 snapshot: mapping.snapshot,
449 node: mapping.node(occurrence.location().node),
450 },
451 Placement::Cms(occurrence.placement()),
452 ))
453 })
454 .map_err(HistoryError::Projection)?;
455 for parsed in &self.values[..=revision] {
456 parsed
457 .append(&mut index, &mut ordinal, query)
458 .map_err(HistoryError::Index)?;
459 }
460 let mut complete = self.owner.values[revision].is_present();
465 if !self
466 .source_complete(pool, revision, &index, query)
467 .map_err(HistoryError::Index)?
468 {
469 complete = false;
470 }
471 let prefix = pool.prepared().timestamps().len();
472 let tokens = Self::append_document_tokens(timestamps, mapping, prefix, &mut index, query)
473 .map_err(HistoryError::from)?;
474 let generation_revision = tokens.generation_revision;
475 if !tokens.complete {
476 complete = false;
477 }
478 if !self
479 .generation_covered(revision, generation_revision, &index, query)
480 .map_err(HistoryError::Index)?
481 {
482 complete = false;
483 }
484 if !self
485 .append_vri_tokens(timestamps, mapping, prefix, &mut index, query)
486 .map_err(HistoryError::from)?
487 {
488 complete = false;
489 }
490 let associations = super::vri::sources::associate(
491 self,
492 basic.binding().prepared().container,
493 pool,
494 timestamps,
495 provider,
496 query,
497 revision,
498 )
499 .await?;
500 if !associations.all_requirements_met() {
501 complete = false;
502 }
503 Ok(Unified {
504 session: signature.owner().value.session,
505 index,
506 root: mapping.root,
507 complete,
508 required: timestamps.requirements(),
509 revision,
510 associations,
511 })
512 }
513}