1use crate::{
4 error::{Error as ArchiveError, resource},
5 layout::{Census, Role},
6 manifest::Boolean,
7 xml::producer::Facts,
8 zip::{Archive, MemberId},
9};
10use structured_asn1::{
11 memory::{OwnedMap, OwnedVec, Work},
12 range::RangeSource,
13};
14
15mod check;
16mod inputs;
17mod preservation;
18pub use check::{Mathematics, ObjectsState, TimestampCheck, TimestampState};
19pub use inputs::{AssertionInput, AssertionScope, InputAssociation, TimestampInputs};
20pub use preservation::{ActorCheck, ActorResult, CoverageResult, RequirementsResult};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum FindingKind {
25 Latest,
27 Uninspected,
29 Syntax,
31 Timestamp,
33 Target,
35 Rootfile,
37 Cycle,
39 Unreachable,
41 Dropped,
43}
44
45#[derive(Debug, Clone, Copy)]
47pub struct Finding<D, G> {
48 pub manifest: Option<MemberId<D, G>>,
50 pub reference: Option<usize>,
52 pub related: Option<MemberId<D, G>>,
54 pub kind: FindingKind,
56}
57
58#[derive(Debug)]
60pub enum Error<E, D, G> {
61 Archive(ArchiveError<E>),
63 DuplicateFacts(MemberId<D, G>),
65 ForeignFacts(MemberId<D, G>),
67 DuplicateTimestamp(MemberId<D, G>),
69}
70type ResultOf<T, S> = Result<
71 T,
72 Error<<S as RangeSource>::Error, <S as RangeSource>::Document, <S as RangeSource>::Generation>,
73>;
74
75pub struct Node<D, G> {
77 member: MemberId<D, G>,
78 facts: Option<usize>,
79 previous: Option<usize>,
80 timestamp: Option<MemberId<D, G>>,
81 objects: Option<OwnedVec<Option<MemberId<D, G>>>>,
82}
83impl<D: Copy, G: Copy> Node<D, G> {
84 #[must_use]
86 pub fn member(&self) -> MemberId<D, G> {
87 self.member
88 }
89 #[must_use]
91 pub fn facts(&self) -> Option<usize> {
92 self.facts
93 }
94 #[must_use]
96 pub fn previous(&self) -> Option<usize> {
97 self.previous
98 }
99 #[must_use]
101 pub fn timestamp(&self) -> Option<MemberId<D, G>> {
102 self.timestamp
103 }
104 #[must_use]
106 pub fn objects(&self) -> &[Option<MemberId<D, G>>] {
107 match &self.objects {
108 Some(objects) => objects,
109 None => &[],
110 }
111 }
112}
113
114pub struct Graph<'v, 's, N, D, G, S: RangeSource> {
118 archive: &'v Archive<'s, S>,
119 facts: OwnedVec<Facts<'v, 's, N, D, G, S>>,
120 nodes: OwnedVec<Node<S::Document, S::Generation>>,
121 order: OwnedVec<usize>,
122 findings: OwnedVec<Finding<S::Document, S::Generation>>,
123 latest: Option<usize>,
124 covered: OwnedMap<usize, ()>,
125}
126impl<'v, 's, N: Copy, D: Copy + Eq, G: Copy + Eq, S: RangeSource> Graph<'v, 's, N, D, G, S> {
127 #[expect(
136 clippy::too_many_lines,
137 reason = "one transactional graph pass keeps source admission, predecessor cycle detection and cumulative prior coverage in their required order"
138 )]
139 pub fn inspect<W: Work>(
140 census: &Census<'v, 's, S>,
141 facts: OwnedVec<Facts<'v, 's, N, D, G, S>>,
142 work: &W,
143 ) -> ResultOf<Self, S> {
144 const LATEST: &[u8] = b"META-INF/ASiCArchiveManifest.xml";
145 let archive = census.archive();
146 archive
147 .admit(work, archive.directory_span())
148 .map_err(Error::Archive)?;
149 let execution = archive.session().query(work);
150 let refusal = |cause| Error::Archive(resource(archive.directory_span(), cause));
151 for fact in &facts {
154 execution.charge_work(1).map_err(refusal)?;
155 if !core::ptr::eq(fact.catalogue().base().archive(), archive) {
156 return Err(Error::ForeignFacts(fact.catalogue().base().id()));
157 }
158 }
159 let mut by_source = execution.map();
160 for (index, fact) in facts.iter().enumerate() {
161 let id = fact.catalogue().base().id();
162 if !matches!(
163 census
164 .occurrence(id.ordinal(), &execution)
165 .map_err(Error::Archive)?
166 .map(|(_, role)| role),
167 Some(Role::ArchiveManifest | Role::Manifest)
168 ) {
169 return Err(Error::ForeignFacts(id));
170 }
171 if by_source
172 .try_insert(id.ordinal(), index)
173 .map_err(refusal)?
174 .is_some()
175 {
176 return Err(Error::DuplicateFacts(id));
177 }
178 }
179 let mut nodes = execution.vector();
180 let mut by_member = execution.map();
181 let mut findings = execution.vector();
182 let mut latest = None;
183 let mut latest_count = 0usize;
184 for ordinal in 0..census.len() {
185 let (member, role) = census
186 .occurrence(ordinal, &execution)
187 .map_err(Error::Archive)?
188 .expect("census length bounds its original ordinal");
189 if role != Role::ArchiveManifest {
190 continue;
191 }
192 let index = nodes.len();
193 let id = member.id();
194 execution.charge_work(1).map_err(refusal)?;
195 if member.name().len() == LATEST.len() {
196 execution
197 .charge_work(LATEST.len() as u64)
198 .map_err(refusal)?;
199 if member.name() == LATEST {
200 latest_count += 1;
201 latest = Some(index);
202 }
203 }
204 let fact = by_source
205 .get(&ordinal, &execution)
206 .map_err(refusal)?
207 .copied();
208 if fact.is_none() {
209 findings
210 .try_push(Finding {
211 manifest: Some(id),
212 reference: None,
213 related: None,
214 kind: FindingKind::Uninspected,
215 })
216 .map_err(refusal)?;
217 }
218 nodes
219 .try_push(Node {
220 member: id,
221 facts: fact,
222 previous: None,
223 timestamp: None,
224 objects: None,
225 })
226 .map_err(refusal)?;
227 by_member.try_insert(ordinal, index).map_err(refusal)?;
228 }
229 if latest_count != 1 {
230 latest = None;
231 findings
232 .try_push(Finding {
233 manifest: None,
234 reference: None,
235 related: None,
236 kind: FindingKind::Latest,
237 })
238 .map_err(refusal)?;
239 }
240 for node in &mut nodes {
241 let Some(index) = node.facts else {
242 continue;
243 };
244 let catalogue = facts[index].catalogue();
245 let id = node.member;
246 if !catalogue.reading().faults().is_empty() {
247 findings
248 .try_push(Finding {
249 manifest: Some(id),
250 reference: None,
251 related: None,
252 kind: FindingKind::Syntax,
253 })
254 .map_err(refusal)?;
255 }
256 if let Some(token) = catalogue
257 .signature_member(&execution)
258 .map_err(Error::Archive)?
259 && census
260 .occurrence(token.id().ordinal(), &execution)
261 .map_err(Error::Archive)?
262 .map(|(_, role)| role)
263 == Some(Role::Timestamp)
264 {
265 node.timestamp = Some(token.id());
266 }
267 if node.timestamp.is_none() {
268 findings
269 .try_push(Finding {
270 manifest: Some(id),
271 reference: None,
272 related: None,
273 kind: FindingKind::Timestamp,
274 })
275 .map_err(refusal)?;
276 }
277 let mut objects = execution.vector();
278 objects
279 .try_reserve_exact(catalogue.objects().len())
280 .map_err(refusal)?;
281 let mut roots = 0usize;
282 for (ordinal, object) in catalogue.reading().objects().iter().enumerate() {
283 let target = catalogue
284 .object_member(ordinal, &execution)
285 .map_err(Error::Archive)?
286 .map(|member| member.id());
287 objects.try_push(target).map_err(refusal)?;
288 if target.is_none() {
289 findings
290 .try_push(Finding {
291 manifest: Some(id),
292 reference: Some(ordinal),
293 related: None,
294 kind: FindingKind::Target,
295 })
296 .map_err(refusal)?;
297 }
298 match object.rootfile {
299 Boolean::Value(true) => {
300 roots += 1;
301 let predecessor = match target {
302 Some(target) => by_member
303 .get(&target.ordinal(), &execution)
304 .map_err(refusal)?
305 .copied(),
306 None => None,
307 };
308 if predecessor.is_none() {
309 findings
310 .try_push(Finding {
311 manifest: Some(id),
312 reference: Some(ordinal),
313 related: target,
314 kind: FindingKind::Rootfile,
315 })
316 .map_err(refusal)?;
317 }
318 node.previous = predecessor;
319 }
320 Boolean::Malformed => {
321 findings
322 .try_push(Finding {
323 manifest: Some(id),
324 reference: Some(ordinal),
325 related: target,
326 kind: FindingKind::Rootfile,
327 })
328 .map_err(refusal)?;
329 }
330 Boolean::Absent | Boolean::Value(false) => {}
331 }
332 }
333 if roots > 1 {
334 node.previous = None;
335 findings
336 .try_push(Finding {
337 manifest: Some(id),
338 reference: None,
339 related: None,
340 kind: FindingKind::Rootfile,
341 })
342 .map_err(refusal)?;
343 }
344 node.objects = Some(objects.into_owned());
345 }
346 let mut order = execution.vector();
347 let mut visited = execution.map();
348 let mut current = latest;
349 while let Some(index) = current {
350 execution.charge_work(1).map_err(refusal)?;
351 if visited.try_insert(index, ()).map_err(refusal)?.is_some() {
352 findings
353 .try_push(Finding {
354 manifest: Some(nodes[index].member),
355 reference: None,
356 related: Some(nodes[index].member),
357 kind: FindingKind::Cycle,
358 })
359 .map_err(refusal)?;
360 break;
361 }
362 order.try_push(index).map_err(refusal)?;
363 current = nodes[index].previous;
364 }
365 for (index, node) in nodes.iter().enumerate() {
366 if visited.get(&index, &execution).map_err(refusal)?.is_none() {
367 findings
368 .try_push(Finding {
369 manifest: Some(node.member),
370 reference: None,
371 related: None,
372 kind: FindingKind::Unreachable,
373 })
374 .map_err(refusal)?;
375 }
376 }
377 execution.charge_work(order.len() as u64).map_err(refusal)?;
378 order.reverse();
379 let mut required = execution.map();
380 let mut covered = execution.map();
381 for index in &order {
382 let node = &nodes[*index];
383 let mut actual = execution.map();
384 for target in node.objects().iter().flatten() {
385 actual.try_insert(target.ordinal(), ()).map_err(refusal)?;
386 }
387 for (ordinal, ()) in required.try_iter(&execution).map_err(refusal)? {
388 if actual.get(ordinal, &execution).map_err(refusal)?.is_none() {
389 let related = archive
390 .member(*ordinal, &execution)
391 .map_err(Error::Archive)?
392 .expect("required set only contains admitted original members")
393 .id();
394 findings
395 .try_push(Finding {
396 manifest: Some(node.member),
397 reference: None,
398 related: Some(related),
399 kind: FindingKind::Dropped,
400 })
401 .map_err(refusal)?;
402 }
403 }
404 for target in node.objects().iter().flatten() {
405 required.try_insert(target.ordinal(), ()).map_err(refusal)?;
406 }
407 required
408 .try_insert(node.member.ordinal(), ())
409 .map_err(refusal)?;
410 if let Some(token) = node.timestamp {
411 required.try_insert(token.ordinal(), ()).map_err(refusal)?;
412 }
413 covered = actual;
414 }
415 archive
416 .admit(&execution, archive.directory_span())
417 .map_err(Error::Archive)?;
418 Ok(Self {
419 archive,
420 facts,
421 nodes: nodes.into_owned(),
422 order: order.into_owned(),
423 findings: findings.into_owned(),
424 latest,
425 covered: covered.into_owned(),
426 })
427 }
428 #[must_use]
430 pub fn facts(&self) -> &[Facts<'v, 's, N, D, G, S>] {
431 &self.facts
432 }
433 #[must_use]
435 pub fn nodes(&self) -> &[Node<S::Document, S::Generation>] {
436 &self.nodes
437 }
438 #[must_use]
440 pub fn order(&self) -> &[usize] {
441 &self.order
442 }
443 #[must_use]
445 pub fn findings(&self) -> &[Finding<S::Document, S::Generation>] {
446 &self.findings
447 }
448 #[must_use]
450 pub fn latest(&self) -> Option<usize> {
451 self.latest
452 }
453 #[must_use]
455 pub fn geometry_complete(&self) -> bool {
456 self.latest.is_some() && self.findings.is_empty()
457 }
458 pub fn declares_all<W: Work>(
462 &self,
463 requested: &[MemberId<S::Document, S::Generation>],
464 work: &W,
465 ) -> Result<bool, ArchiveError<S::Error>> {
466 let execution = self.archive.session().query(work);
467 self.archive
468 .admit(&execution, self.archive.directory_span())?;
469 for id in requested {
470 self.archive.original(*id, &execution)?;
471 if self
472 .covered
473 .get(&id.ordinal(), &execution)
474 .map_err(|cause| resource(self.archive.directory_span(), cause))?
475 .is_none()
476 {
477 return Ok(false);
478 }
479 }
480 Ok(self.latest.is_some())
481 }
482}