structured_pades/baseline/
profile.rs1use super::{LongTermOf, Seal, Seals, SealsError};
4use crate::{query::Query, range::RangeSource};
5use structured_ltv::source::Query as _;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Selection {
10 AtSource(usize),
12 Current,
14}
15
16pub struct Assessment<'s, L, R, D, G> {
20 long_term: Option<&'s L>,
21 seals: &'s Seals<'s, R, D, G>,
22 selection: Selection,
23 ordinal: Option<usize>,
24 revision: Option<usize>,
25 long_term_complete: bool,
26 material_covered: Option<bool>,
27}
28impl<'s, L, R, D, G> Assessment<'s, L, R, D, G> {
29 #[must_use]
31 pub fn selection(&self) -> Selection {
32 self.selection
33 }
34 #[must_use]
36 pub fn ordinal(&self) -> Option<usize> {
37 self.ordinal
38 }
39 #[must_use]
41 pub fn revision(&self) -> Option<usize> {
42 self.revision
43 }
44 #[must_use]
46 pub fn long_term(&self) -> Option<&'s L> {
47 self.long_term
48 }
49 #[must_use]
51 pub fn seals(&self) -> &'s Seals<'s, R, D, G> {
52 self.seals
53 }
54 #[must_use]
56 pub fn all_long_term_requirements_met(&self) -> bool {
57 self.long_term_complete
58 }
59 #[must_use]
62 pub fn material_covered(&self) -> Option<bool> {
63 self.material_covered
64 }
65 #[must_use]
68 pub fn all_profile_requirements_met(&self) -> bool {
69 self.revision.is_some() && self.long_term_complete && self.material_covered == Some(true)
70 }
71}
72
73pub type AssessmentOf<'s, 'a, 'cl, 'r, 'c, 'd, 'auth, 'v, C, D, G> = Assessment<
75 's,
76 LongTermOf<'s, 'a, 'cl, 'r, 'c, 'd, 'auth, C, D, G>,
77 Seal<'a, 'cl, 'r, 'v, 'c, 'd, 'auth, D, G, C>,
78 D,
79 G,
80>;
81
82pub fn assess<'s, 'a, 'cl, 'r, 'c, 'd, 'auth, 'v, C, S: RangeSource>(
89 selection: Selection,
90 long_term: Option<
91 &'s LongTermOf<'s, 'a, 'cl, 'r, 'c, 'd, 'auth, C, S::Document, S::Generation>,
92 >,
93 seals: &'s super::archives::SealsOf<
94 's,
95 'a,
96 'cl,
97 'r,
98 'v,
99 'c,
100 'd,
101 'auth,
102 S::Document,
103 S::Generation,
104 C,
105 >,
106 query: &Query<'_, '_, S>,
107) -> Result<
108 AssessmentOf<'s, 'a, 'cl, 'r, 'c, 'd, 'auth, 'v, C, S::Document, S::Generation>,
109 SealsError,
110> {
111 let root = seals.required.signature();
112 query.admit(root, 1).map_err(SealsError::Scope)?;
113 if !query.belongs(seals.session)
114 || seals.required.scope().location().snapshot != query.snapshot()
115 {
116 return Err(SealsError::Foreign { node: root });
117 }
118 let ordinal = match selection {
119 Selection::Current => seals.sources.len().checked_sub(1),
120 Selection::AtSource(ordinal) => {
121 if ordinal >= seals.sources.len() {
122 return Err(SealsError::Foreign { node: root });
123 }
124 Some(ordinal)
125 }
126 };
127 let revision = ordinal.and_then(|ordinal| seals.sources[ordinal].revision);
128 let material_covered = ordinal.and_then(|ordinal| {
129 seals.reports[ordinal].map(structured_ltv::baseline::ArchiveMaterial::all_material_covered)
130 });
131 let mut long_term_complete = false;
132 if let Some(long_term) = long_term {
133 let signature = long_term
134 .timed()
135 .basic()
136 .binding()
137 .prepared()
138 .container
139 .signature();
140 let required = long_term.timed().paths().required();
141 if !query.belongs(signature.owner().value.session)
142 || required.signature() != root
143 || required.scope().location().snapshot != query.snapshot()
144 {
145 return Err(SealsError::Foreign { node: root });
146 }
147 let (Some(revision), Some(ordinal)) = (revision, ordinal) else {
148 return Err(SealsError::Foreign { node: root });
149 };
150 if long_term.x509().revision() != revision {
151 return Err(SealsError::Foreign { node: root });
152 }
153 let prefix =
154 seals.required.entries().len() - seals.sources.len() - seals.vri_revisions.len();
155 let pdf_end = prefix + ordinal + 1;
156 let vri_start = prefix + seals.sources.len();
157 let mut cursor = 0;
158 for (index, expected) in seals.required.entries().iter().enumerate() {
159 query.admit(root, 5).map_err(SealsError::Scope)?;
160 if index >= pdf_end
161 && (index < vri_start || seals.vri_revisions[index - vri_start] > revision)
162 {
163 continue;
164 }
165 let Some(actual) = required.entries().get(cursor) else {
166 return Err(SealsError::Foreign { node: root });
167 };
168 if actual.signature != expected.signature
169 || actual.property != expected.property
170 || actual.token != expected.token
171 || actual.purpose != expected.purpose
172 {
173 return Err(SealsError::Foreign { node: root });
174 }
175 cursor += 1;
176 }
177 if cursor != required.entries().len() {
178 return Err(SealsError::Foreign { node: root });
179 }
180 long_term_complete = long_term.timed().all_static_time_requirements_met()
181 && long_term.all_material_requirements_met();
182 }
183 query.admit(root, 1).map_err(SealsError::Scope)?;
184 Ok(Assessment {
185 long_term,
186 seals,
187 selection,
188 ordinal,
189 revision,
190 long_term_complete,
191 material_covered,
192 })
193}