1use super::{Census, Role};
4use crate::{
5 error::{Error, resource},
6 zip::{FaultKind, MemberId},
7};
8use structured_asn1::{
9 Span,
10 memory::{OwnedVec, Work},
11 range::RangeSource,
12};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Principal {
17 Cades,
19 Xades,
21 Timestamp,
23 Evidence,
25 XmlEvidence,
27}
28impl Principal {
29 fn name(self) -> &'static [u8] {
30 match self {
31 Self::Cades => b"META-INF/signature.p7s",
32 Self::Xades => b"META-INF/signatures.xml",
33 Self::Timestamp => b"META-INF/timestamp.tst",
34 Self::Evidence => b"META-INF/evidencerecord.ers",
35 Self::XmlEvidence => b"META-INF/evidencerecord.xml",
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum Form {
43 Simple(Principal),
45 ExtendedXades,
47 ExtendedCadesAndAssertions,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum Rules {
54 General,
56 BaselinePart1,
58 AdditionalPart2,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum Strength {
65 Required,
67 Recommended,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum Requirement {
74 Zip(FaultKind),
76 DataCount,
78 SimpleDataPath,
80 PrincipalCount,
82 PrincipalFamily,
84 MimetypeCount,
86 MimetypeHeader,
88 BaselineSimpleExtra,
90 BaselineOdfManifest,
92 ReservedName,
94}
95
96#[derive(Debug, Clone, Copy)]
98pub struct Issue<D, G> {
99 pub member: Option<MemberId<D, G>>,
101 pub range: Span,
103 pub requirement: Requirement,
105 pub strength: Strength,
107}
108
109pub struct Inspection<'c, 'v, 's, S: RangeSource> {
112 census: &'c Census<'v, 's, S>,
113 form: Form,
114 rules: Rules,
115 defined: bool,
116 required: bool,
117 issues: OwnedVec<Issue<S::Document, S::Generation>>,
118}
119
120struct Issues<D, G, W: Work> {
121 required: bool,
122 values: OwnedVec<Issue<D, G>, W>,
123}
124impl<D, G, W: Work> Issues<D, G, W> {
125 fn push<E>(
126 &mut self,
127 member: Option<MemberId<D, G>>,
128 range: Span,
129 requirement: Requirement,
130 strength: Strength,
131 ) -> Result<(), Error<E>> {
132 self.values
133 .try_push(Issue {
134 member,
135 range,
136 requirement,
137 strength,
138 })
139 .map_err(|cause| resource(range, cause))?;
140 if strength == Strength::Required {
141 self.required = false;
142 }
143 Ok(())
144 }
145}
146
147impl<'c, 'v, 's, S: RangeSource> Inspection<'c, 'v, 's, S> {
148 #[expect(
155 clippy::too_many_lines,
156 reason = "the complete layout rule table is evaluated in one paid pass, preserving independent per-occurrence and aggregate findings"
157 )]
158 pub fn read<W: Work>(
159 census: &'c Census<'v, 's, S>,
160 form: Form,
161 rules: Rules,
162 work: &W,
163 ) -> Result<Self, Error<S::Error>> {
164 let archive = census.archive;
165 let execution = archive.session().query(work);
166 archive.admit(&execution, archive.directory_span())?;
167 let defined = match rules {
168 Rules::General => true,
169 Rules::BaselinePart1 => matches!(
170 form,
171 Form::Simple(Principal::Cades | Principal::Xades) | Form::ExtendedXades
172 ),
173 Rules::AdditionalPart2 => matches!(
174 form,
175 Form::Simple(Principal::Timestamp | Principal::Evidence | Principal::XmlEvidence)
176 | Form::ExtendedCadesAndAssertions
177 ),
178 };
179 let baseline = rules == Rules::BaselinePart1 && defined;
182 let mut result = Issues {
183 required: true,
184 values: execution.vector(),
185 };
186 let mut data = 0;
187 let mut principal = 0;
188 let mut mimetypes = 0;
189 let mut odf_manifests = 0;
190 for ordinal in 0..census.len() {
191 let (member, role) = census
192 .occurrence(ordinal, &execution)?
193 .expect("bounded original ordinal");
194 let name = member.name();
195 execution
196 .charge_work(name.len() as u64 + 1)
197 .map_err(|cause| resource(member.name_span(), cause))?;
198 match role {
199 Role::Data => {
200 data += 1;
201 if matches!(form, Form::Simple(_)) && name.contains(&b'/') {
202 result.push(
203 Some(member.id()),
204 member.name_span(),
205 Requirement::SimpleDataPath,
206 Strength::Required,
207 )?;
208 }
209 }
210 Role::Mimetype => mimetypes += 1,
211 Role::OdfManifest => odf_manifests += 1,
212 _ => {}
213 }
214 match form {
215 Form::Simple(selected) => {
216 if name == selected.name() {
217 principal += 1;
218 } else if matches!(
219 name,
220 b"META-INF/signature.p7s"
221 | b"META-INF/signatures.xml"
222 | b"META-INF/timestamp.tst"
223 | b"META-INF/evidencerecord.ers"
224 | b"META-INF/evidencerecord.xml"
225 ) {
226 result.push(
227 Some(member.id()),
228 member.name_span(),
229 Requirement::PrincipalFamily,
230 Strength::Required,
231 )?;
232 }
233 if baseline
236 && name != selected.name()
237 && role != Role::Data
238 && role != Role::Mimetype
239 && name != b"META-INF/"
240 {
241 result.push(
242 Some(member.id()),
243 member.name_span(),
244 Requirement::BaselineSimpleExtra,
245 Strength::Required,
246 )?;
247 }
248 }
249 Form::ExtendedXades => {
250 if role == Role::Xades {
251 principal += 1;
252 } else if matches!(role, Role::Cades | Role::Timestamp) {
253 result.push(
254 Some(member.id()),
255 member.name_span(),
256 Requirement::PrincipalFamily,
257 Strength::Required,
258 )?;
259 }
260 }
261 Form::ExtendedCadesAndAssertions => {
262 if matches!(
263 role,
264 Role::Cades | Role::Timestamp | Role::Evidence | Role::XmlEvidence
265 ) {
266 principal += 1;
267 } else if role == Role::Xades {
268 result.push(
269 Some(member.id()),
270 member.name_span(),
271 Requirement::PrincipalFamily,
272 Strength::Required,
273 )?;
274 }
275 }
276 }
277 if !matches!(form, Form::Simple(_)) && role == Role::Reserved {
278 result.push(
279 Some(member.id()),
280 member.name_span(),
281 Requirement::ReservedName,
282 Strength::Required,
283 )?;
284 }
285 }
286 if (matches!(form, Form::Simple(_)) && data != 1) || data == 0 {
287 result.push(
288 None,
289 archive.directory_span(),
290 Requirement::DataCount,
291 Strength::Required,
292 )?;
293 }
294 if (matches!(form, Form::Simple(_)) && principal != 1) || principal == 0 {
295 result.push(
296 None,
297 archive.directory_span(),
298 Requirement::PrincipalCount,
299 Strength::Required,
300 )?;
301 }
302 if mimetypes > 1 {
303 result.push(
304 None,
305 archive.directory_span(),
306 Requirement::MimetypeCount,
307 Strength::Required,
308 )?;
309 }
310 if baseline && form == Form::ExtendedXades && odf_manifests != 1 {
311 result.push(
312 None,
313 archive.directory_span(),
314 Requirement::BaselineOdfManifest,
315 Strength::Required,
316 )?;
317 }
318 for header in census.mimetypes(&execution) {
319 let header = header?;
320 if !header.first() || !header.stored() || !header.no_extra() || !header.unencrypted() {
321 let member = archive.original(header.member, &execution)?;
322 result.push(
323 Some(header.member),
324 member.central_span(),
325 Requirement::MimetypeHeader,
326 Strength::Required,
327 )?;
328 }
329 }
330 for fault in archive.faults(&execution)? {
331 let fault = fault?;
332 let strength = match fault.kind {
333 FaultKind::Utf8
334 | FaultKind::Name
335 | FaultKind::LocalMismatch
336 | FaultKind::Descriptor
337 | FaultKind::Overlap
338 | FaultKind::LocalBounds
339 | FaultKind::StoredLength
340 | FaultKind::Zip64 => Some(Strength::Required),
341 FaultKind::Compression => Some(if baseline {
342 Strength::Required
343 } else {
344 Strength::Recommended
345 }),
346 FaultKind::IsoProfile | FaultKind::DirectorySignature | FaultKind::Encrypted
347 if baseline =>
348 {
349 Some(Strength::Required)
350 }
351 _ => None,
352 };
353 if let Some(strength) = strength {
354 let member = if let Some(ordinal) = fault.member {
355 Some(
356 archive
357 .member(ordinal, &execution)?
358 .expect("original fault ordinal")
359 .id(),
360 )
361 } else {
362 None
363 };
364 result.push(member, fault.range, Requirement::Zip(fault.kind), strength)?;
365 }
366 }
367 archive.admit(&execution, archive.directory_span())?;
368 Ok(Self {
369 census,
370 form,
371 rules,
372 defined,
373 required: result.required,
374 issues: result.values.into_owned(),
375 })
376 }
377
378 #[must_use]
380 pub fn census(&self) -> &'c Census<'v, 's, S> {
381 self.census
382 }
383 #[must_use]
385 pub fn form(&self) -> Form {
386 self.form
387 }
388 #[must_use]
390 pub fn rules(&self) -> Rules {
391 self.rules
392 }
393 #[must_use]
395 pub fn defined(&self) -> bool {
396 self.defined
397 }
398 #[must_use]
401 pub fn required_layout(&self) -> Option<bool> {
402 if self.defined {
403 Some(self.required)
404 } else {
405 None
406 }
407 }
408 pub fn issues<'q, W: Work>(
410 &'q self,
411 work: &'q W,
412 ) -> impl Iterator<Item = IssueResult<S>> + use<'q, 'c, 'v, 's, S, W> {
413 let mut at = 0;
414 let mut stopped = false;
415 core::iter::from_fn(move || {
416 if stopped || at == self.issues.len() {
417 return None;
418 }
419 if let Err(cause) = self
420 .census
421 .archive
422 .admit(work, self.census.archive.directory_span())
423 {
424 stopped = true;
425 return Some(Err(cause));
426 }
427 let issue = self.issues[at];
428 at += 1;
429 Some(Ok(issue))
430 })
431 }
432}
433
434type IssueResult<S> = crate::error::SourceResult<
435 Issue<<S as RangeSource>::Document, <S as RangeSource>::Generation>,
436 S,
437>;