1use super::{ISSUER, Node, Pades};
4use crate::{
5 basic::Basic,
6 document::Document,
7 error::{Error, ErrorKind},
8 model::admit,
9 range::RangeSource,
10 signature::{Candidate, Coverage, Failure, Signature, SubFilter},
11};
12use structured_asn1::{
13 Span,
14 memory::{OwnedVec, Work},
15};
16use structured_ltv::{
17 pki::CounterReceipts,
18 source::Location as Original,
19 timestamp::{CheckedActor, Purpose, RequiredToken, TimestampRequirements, TimestampScope},
20};
21
22pub type RequirementsRead<'a, 's, D, G, E> =
24 Result<Requirements<'a, 's, D, G>, crate::error::ReadFailure<E>>;
25
26pub struct Requirements<'a, 's, D, G> {
30 all: TimestampRequirements<Node, D, G, Pades>,
31 pdf: OwnedVec<DocumentTimestamp<'a, 's, D, G>>,
32 counters: OwnedVec<Node>,
33 pub(super) root: Signature<'a, 's, D, G>,
34 revision: Option<usize>,
35 vri: OwnedVec<VriTimestamp>,
36 total_pdf: usize,
37}
38
39#[derive(Debug, Clone, Copy)]
41pub struct VriTimestamp {
42 pub ordinal: usize,
44 pub revision: usize,
46 pub vri: usize,
48 pub item: usize,
50}
51
52pub enum DocumentTimestamp<'a, 's, D, G> {
54 Readable(Signature<'a, 's, D, G>),
56 Unreadable(&'a Failure),
58}
59impl<'a, 's, D: Copy + Eq, G: Copy + Eq> DocumentTimestamp<'a, 's, D, G> {
60 #[must_use]
62 pub fn location(&self) -> crate::source::Location {
63 match self {
64 Self::Readable(value) => value.location(),
65 Self::Unreadable(value) => value.location,
66 }
67 }
68 #[must_use]
70 pub fn signature(&self) -> Option<Signature<'a, 's, D, G>> {
71 match self {
72 Self::Readable(value) => Some(*value),
73 Self::Unreadable(_) => None,
74 }
75 }
76 #[must_use]
78 pub fn failure(&self) -> Option<&'a Failure> {
79 match self {
80 Self::Readable(_) => None,
81 Self::Unreadable(value) => Some(value),
82 }
83 }
84 pub(crate) fn token(&self) -> Node {
85 match self {
86 Self::Readable(value) => Node::of(value.contents()),
87 Self::Unreadable(value) => match value.contents {
88 Some(location) => Node::of(location),
89 None => Node::missing_contents(value.location),
90 },
91 }
92 }
93}
94impl<'a, 's, D: Copy + Eq, G: Copy + Eq> Requirements<'a, 's, D, G> {
95 #[must_use]
97 pub fn signature(&self) -> Signature<'a, 's, D, G> {
98 self.root
99 }
100 #[must_use]
102 pub fn all(&self) -> &TimestampRequirements<Node, D, G, Pades> {
103 &self.all
104 }
105 #[must_use]
108 pub fn document_timestamps(&self) -> &[DocumentTimestamp<'a, 's, D, G>] {
109 &self.pdf
110 }
111
112 #[must_use]
115 pub fn revision(&self) -> Option<usize> {
116 self.revision
117 }
118 #[must_use]
121 pub fn vri_timestamps(&self) -> &[VriTimestamp] {
122 &self.vri
123 }
124 pub(crate) fn total_pdf(&self) -> usize {
125 self.total_pdf
126 }
127
128 pub fn at_source_revision(&self, ordinal: usize, operation: &impl Work) -> crate::Result<Self> {
133 let location = self.root.location();
134 let work = self.root.owner().value.session.query(operation);
135 admit(&work, location, 1)?;
136 if self.revision.is_some() {
137 return Err(Error {
138 kind: ErrorKind::Foreign,
139 location,
140 });
141 }
142 let selected = self
143 .pdf
144 .get(ordinal)
145 .and_then(DocumentTimestamp::signature)
146 .ok_or(Error {
147 kind: ErrorKind::Foreign,
148 location,
149 })?;
150 let Coverage::Revision(revision) = selected.coverage() else {
151 return Err(Error {
152 kind: ErrorKind::Syntax(crate::error::Syntax::Container),
153 location: selected.location(),
154 });
155 };
156 if !matches!(self.root.coverage(), Coverage::Revision(root) if root <= revision) {
157 return Err(Error {
158 kind: ErrorKind::Foreign,
159 location: selected.location(),
160 });
161 }
162 let mut entries = work.vector();
163 let mut pdf = work.vector();
164 let prefix = self.all.entries().len() - self.pdf.len() - self.vri.len();
167 for entry in &self.all.entries()[..=prefix + ordinal] {
168 admit(&work, location, 1)?;
169 entries.try_push(*entry).map_err(|cause| Error {
170 kind: ErrorKind::Resource(cause),
171 location,
172 })?;
173 }
174 for original in &self.pdf[..=ordinal] {
175 admit(&work, original.location(), 1)?;
176 let original = match original {
177 DocumentTimestamp::Readable(value) => DocumentTimestamp::Readable(*value),
178 DocumentTimestamp::Unreadable(value) => DocumentTimestamp::Unreadable(value),
179 };
180 pdf.try_push(original).map_err(|cause| Error {
181 kind: ErrorKind::Resource(cause),
182 location,
183 })?;
184 }
185 let mut vri = work.vector();
186 for (index, original) in self.vri.iter().enumerate() {
187 admit(&work, location, 1)?;
188 if original.revision > revision {
189 continue;
190 }
191 entries
192 .try_push(self.all.entries()[prefix + self.pdf.len() + index])
193 .map_err(|cause| Error {
194 kind: ErrorKind::Resource(cause),
195 location,
196 })?;
197 vri.try_push(*original).map_err(|cause| Error {
198 kind: ErrorKind::Resource(cause),
199 location,
200 })?;
201 }
202 let mut counters = work.vector();
203 let mut kept = work.vector();
204 for counter in &self.counters {
205 admit(&work, location, 1)?;
206 counters.try_push(*counter).map_err(|cause| Error {
207 kind: ErrorKind::Resource(cause),
208 location,
209 })?;
210 kept.try_push(*counter).map_err(|cause| Error {
211 kind: ErrorKind::Resource(cause),
212 location,
213 })?;
214 }
215 let all = TimestampRequirements::with_counters(
216 ISSUER,
217 self.all.signature(),
218 self.all.scope(),
219 entries.into_owned(),
220 counters.into_owned(),
221 None,
222 );
223 Ok(Self {
224 all,
225 pdf: pdf.into_owned(),
226 counters: kept.into_owned(),
227 root: self.root,
228 revision: Some(revision),
229 vri: vri.into_owned(),
230 total_pdf: self.total_pdf,
231 })
232 }
233
234 pub(crate) fn first_existence_timestamp(
235 &self,
236 operation: &impl Work,
237 ) -> crate::Result<Option<Node>> {
238 let work = self.root.owner().value.session.query(operation);
239 for original in &self.pdf {
240 admit(&work, original.location(), 1)?;
241 let Some(signature) = original.signature() else {
242 continue;
243 };
244 if matches!(signature.coverage(), Coverage::Revision(_))
245 && covers(signature, self.root.contents().enclosing(), &work)?
246 {
247 return Ok(Some(Node::of(signature.location())));
248 }
249 }
250 Ok(None)
251 }
252 pub fn before_archive(
256 &self,
257 ordinal: usize,
258 actor: &CheckedActor<'_, '_, Node, D, G, Pades>,
259 operation: &impl Work,
260 ) -> crate::Result<TimestampRequirements<Node, D, G, Pades>> {
261 let at = self.archive(ordinal, actor, operation)?;
262 let location = at.location();
263 let work = self.root.owner().value.session.query(operation);
264 let proof = actor.verified();
265 let mut entries = work.vector();
266 for entry in self.all.entries() {
267 admit(&work, location, 1)?;
268 if entry.token == proof.token().node {
269 continue;
270 }
271 if covers_node(at, entry.token, &work)? {
272 entries.try_push(*entry).map_err(|cause| Error {
273 kind: ErrorKind::Resource(cause),
274 location,
275 })?;
276 }
277 }
278 let mut counters = work.vector();
279 let mut included = work.vector();
280 for &counter in &self.counters {
281 admit(&work, location, 1)?;
282 counters.try_push(counter).map_err(|cause| Error {
283 kind: ErrorKind::Resource(cause),
284 location,
285 })?;
286 included
287 .try_push(covers(at, counter.span(), &work)?)
288 .map_err(|cause| Error {
289 kind: ErrorKind::Resource(cause),
290 location,
291 })?;
292 }
293 Ok(TimestampRequirements::with_counters(
294 ISSUER,
295 self.all.signature(),
296 TimestampScope::BeforeArchive(Original {
297 snapshot: proof.token().snapshot,
298 node: proof.token().node,
299 }),
300 entries.into_owned(),
301 counters.into_owned(),
302 Some(included.into_owned()),
303 ))
304 }
305
306 pub(super) fn archive(
307 &self,
308 ordinal: usize,
309 actor: &CheckedActor<'_, '_, Node, D, G, Pades>,
310 operation: &impl Work,
311 ) -> crate::Result<Signature<'a, 's, D, G>> {
312 let work = self.root.owner().value.session.query(operation);
313 admit(&work, self.root.location(), 1)?;
314 let at = self
315 .pdf
316 .get(ordinal)
317 .and_then(DocumentTimestamp::signature)
318 .ok_or(Error {
319 kind: ErrorKind::Foreign,
320 location: self.root.location(),
321 })?;
322 let proof = actor.verified();
323 if proof.token().snapshot != self.root.owner().value.stamp.snapshot
324 || proof.signature() != self.all.signature()
325 || proof.property() != Node::of(at.location())
326 || proof.token().node != Node::of(at.contents())
327 || proof.purpose() != Purpose::Archive
328 {
329 return Err(Error {
330 kind: ErrorKind::Foreign,
331 location: at.location(),
332 });
333 }
334 Ok(at)
335 }
336}
337
338pub(crate) fn covers_node<D: Copy + Eq, G: Copy + Eq>(
339 signature: Signature<'_, '_, D, G>,
340 node: Node,
341 work: &impl Work,
342) -> crate::Result<bool> {
343 admit(work, signature.location(), 1)?;
344 if let Some(material) = node.material_revision() {
345 return Ok(
352 matches!(signature.coverage(), Coverage::Revision(revision) if material <= revision),
353 );
354 }
355 if node.is_missing_contents() {
356 return Ok(false);
357 }
358 covers(signature, node.span(), work)
359}
360
361pub(crate) fn covers<D: Copy + Eq, G: Copy + Eq>(
362 signature: Signature<'_, '_, D, G>,
363 item: Span,
364 work: &impl Work,
365) -> crate::Result<bool> {
366 for range in signature.ranges() {
367 admit(work, signature.location(), 1)?;
368 if range.start <= item.start
369 && item.start - range.start <= range.length
370 && item.length <= range.length - (item.start - range.start)
371 {
372 return Ok(true);
373 }
374 }
375 Ok(false)
376}
377
378fn document_timestamps<'a, 's, D: Copy + Eq, G: Copy + Eq, W: Work>(
379 root: Signature<'a, 's, D, G>,
380 mapping: crate::binding::Mapping<'_, '_, D, G>,
381 entries: &mut OwnedVec<RequiredToken<Node>, W>,
382 work: &W,
383) -> crate::Result<OwnedVec<DocumentTimestamp<'a, 's, D, G>, W>> {
384 let location = root.location();
385 let mut pdf = work.vector();
386 for index in 0..root.catalogue().candidates() {
387 admit(work, location, 1)?;
388 let timestamp = match root
389 .catalogue()
390 .candidate(index)
391 .expect("bounded complete census")
392 {
393 Candidate::Readable(timestamp) => {
394 if !timestamp.is_document_timestamp()
395 && timestamp.sub_filter() != Some(SubFilter::Rfc3161)
396 {
397 continue;
398 }
399 DocumentTimestamp::Readable(timestamp)
400 }
401 Candidate::Unreadable(timestamp) => {
402 if !timestamp.document_timestamp {
403 continue;
404 }
405 DocumentTimestamp::Unreadable(timestamp)
406 }
407 };
408 let property = Node::of(timestamp.location());
409 entries
410 .try_push(RequiredToken {
411 signature: mapping.root,
412 property,
413 token: timestamp.token(),
414 purpose: Purpose::Archive,
415 })
416 .map_err(|cause| Error {
417 kind: ErrorKind::Resource(cause),
418 location: timestamp.location(),
419 })?;
420 let at = timestamp.location();
421 pdf.try_push(timestamp).map_err(|cause| Error {
422 kind: ErrorKind::Resource(cause),
423 location: at,
424 })?;
425 }
426 Ok(pdf)
427}
428
429fn project_requirements<D: Copy, G: Copy, W: Work>(
430 mapping: crate::binding::Mapping<'_, '_, D, G>,
431 source: &[RequiredToken<structured_cades::source::Node>],
432 entries: &mut OwnedVec<RequiredToken<Node>, W>,
433 location: crate::source::Location,
434 work: &impl Work,
435) -> crate::Result<()> {
436 for original in source {
437 admit(work, location, 1)?;
438 entries
439 .try_push(RequiredToken {
440 signature: mapping.node(original.signature),
441 property: mapping.node(original.property),
442 token: mapping.node(original.token),
443 purpose: original.purpose,
444 })
445 .map_err(|cause| Error {
446 kind: ErrorKind::Resource(cause),
447 location,
448 })?;
449 }
450 Ok(())
451}
452
453impl<'s, S: RangeSource> Document<'s, S> {
454 pub fn timestamp_requirements<'a, 'r, 'v, 'p, 'm, 'd, 'path, W: Work>(
460 &self,
461 basic: &Basic<'r, 'v, 'p, 'm, 'd, 'a, 's, 'path, S::Document, S::Generation>,
462 history: &crate::material::History<'_, 's, S::Document, S::Generation>,
463 operation: &W,
464 ) -> RequirementsRead<'a, 's, S::Document, S::Generation, S::Error> {
465 let bound = basic.binding();
466 let mapping = bound.mapping;
467 let prepared = bound.prepared();
468 let root = prepared.container.signature();
469 let location = root.location();
470 let work = self.session.query(operation);
471 if mapping.snapshot != self.stamp.snapshot
472 || !core::ptr::eq(root.owner().value.session, self.session)
473 || history.root.location() != root.location()
474 || history.root.owner().value.stamp != self.stamp
475 || !core::ptr::eq(history.root.owner().value.session, self.session)
476 {
477 return Err(Error {
478 kind: ErrorKind::Foreign,
479 location,
480 }
481 .into());
482 }
483 self.check(&work, location)?;
484 let mut entries = work.vector();
485 project_requirements(
486 mapping,
487 prepared.cms.timestamp_requirements().entries(),
488 &mut entries,
489 location,
490 &work,
491 )?;
492 let pdf = document_timestamps(root, mapping, &mut entries, &work)?;
493 let mut vri = work.vector();
494 for window in history.revisions() {
495 for (index, value) in window.vri().iter().enumerate() {
496 admit(&work, value.location(), 1)?;
497 let Some(item) = value.timestamp() else {
498 continue;
499 };
500 let property = Node::dss(value.location(), window.revision());
501 let token = Node::dss(window.items()[item].location(), window.revision());
502 entries
503 .try_push(RequiredToken {
504 signature: mapping.root,
505 property,
506 token,
507 purpose: Purpose::ValidationData,
508 })
509 .map_err(|cause| Error {
510 kind: ErrorKind::Resource(cause),
511 location: value.location(),
512 })?;
513 vri.try_push(VriTimestamp {
514 ordinal: vri.len(),
515 revision: window.revision(),
516 vri: index,
517 item,
518 })
519 .map_err(|cause| Error {
520 kind: ErrorKind::Resource(cause),
521 location: value.location(),
522 })?;
523 }
524 }
525 let mut counters = work.vector();
526 let mut kept = work.vector();
527 for ordinal in 0..basic.counters().len() {
528 admit(&work, location, 1)?;
529 let node = basic.counters().counter(ordinal).signature;
530 counters.try_push(node).map_err(|cause| Error {
531 kind: ErrorKind::Resource(cause),
532 location,
533 })?;
534 kept.try_push(node).map_err(|cause| Error {
535 kind: ErrorKind::Resource(cause),
536 location,
537 })?;
538 }
539 self.check(&work, location)?;
540 let all = TimestampRequirements::with_counters(
541 ISSUER,
542 mapping.root,
543 TimestampScope::All(Original {
544 snapshot: mapping.snapshot,
545 node: mapping.root,
546 }),
547 entries.into_owned(),
548 counters.into_owned(),
549 None,
550 );
551 let total_pdf = pdf.len();
552 Ok(Requirements {
553 all,
554 pdf: pdf.into_owned(),
555 counters: kept.into_owned(),
556 root,
557 revision: None,
558 vri: vri.into_owned(),
559 total_pdf,
560 })
561 }
562}