Skip to main content

structured_asic/
manifest.rs

1//! Original manifest structure, separate from authenticated member binding.
2
3use structured_asn1::memory::{MemoryError, OwnedVec, Work};
4use structured_xades::{
5    attributes::AttributeXml,
6    reference::VisitError,
7    scalar::ScalarXml,
8    xml::{Access, AccessError, Name, Node, Snapshot, Xml},
9};
10
11mod fields;
12
13/// Normative namespace of `ASiC` manifest and wrapper elements.
14pub const NAMESPACE: &str = "http://uri.etsi.org/02918/v1.2.1#";
15pub(crate) const DS: &str = "http://www.w3.org/2000/09/xmldsig#";
16
17/// One declared field retains ambiguity instead of selecting a convenient copy.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Slot<N> {
20    /// The required field was not written.
21    Absent,
22    /// One original field exists.
23    One(N),
24    /// Multiple fields exist; their original locations remain in diagnostics.
25    Ambiguous,
26}
27
28/// Original optional XSD boolean. Absence and written false remain distinct.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Boolean {
31    /// The attribute was not written; no schema default is fabricated.
32    Absent,
33    /// One of the four valid lexical forms, with schema whitespace processing.
34    Value(bool),
35    /// A present attribute has no XSD boolean value.
36    Malformed,
37}
38
39/// Original data-object instruction, not proof its digest or URI succeeded.
40#[derive(Debug, Clone, Copy)]
41pub struct Object<N> {
42    /// Original `DataObjectReference` element.
43    pub node: N,
44    /// Original `XMLDSig` method selection, including ambiguous occurrences.
45    pub method: Slot<N>,
46    /// Original digest-value element.
47    pub value: Slot<N>,
48    /// Original optional OCF rootfile declaration.
49    pub rootfile: Boolean,
50}
51
52/// An original extension stays available even when its semantics are unknown.
53#[derive(Debug, Clone, Copy)]
54pub struct Extension<N> {
55    /// Original enclosing manifest or data-object instruction.
56    pub owner: N,
57    /// Original extension element, preserving its entire XML-owned payload.
58    pub node: N,
59    /// Schema-required Critical attribute and its actual lexical outcome.
60    pub critical: Boolean,
61}
62
63/// Readable structure problems cannot disappear behind a successful digest.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum FaultKind {
66    /// Non-whitespace text in an element-only schema.
67    Text,
68    /// A child or attribute has no declaration in this selected type.
69    Unexpected,
70    /// A required child or attribute is absent.
71    Missing,
72    /// A singleton child occurs more than once.
73    Duplicate,
74    /// Required original child order is violated.
75    Order,
76    /// A present value fails its requested base datatype.
77    Scalar,
78    /// Schema-instance interpretation remains unassessed, never valid by default.
79    InstanceUnassessed,
80}
81
82/// Original XML location, without a synthetic document byte offset.
83#[derive(Debug, Clone, Copy)]
84pub struct Fault<N> {
85    /// Original offending element, attribute owner or text node.
86    pub node: N,
87    /// Exact structural problem.
88    pub kind: FaultKind,
89}
90
91/// Processing refusal is separate from completed schema findings.
92#[derive(Debug)]
93pub enum Error<E, N> {
94    /// Original XML, source generation or common admission refusal.
95    Access(AccessError<E, N>),
96    /// Original attribute traversal or visitor refusal.
97    Attributes(AccessError<VisitError<E, MemoryError>, N>),
98    /// Actual storage/work refused before metadata work.
99    Resource {
100        /// Original owning node.
101        node: N,
102        /// Unchanged resource cause.
103        cause: MemoryError,
104    },
105    /// The selected root is not the `ASiCManifest` element.
106    Root(N),
107}
108
109type ResultOf<T, X> = Result<T, Error<<X as Xml>::Error, <X as Xml>::Id>>;
110/// Complete original manifest reading or its unchanged processing refusal.
111pub type InspectionResult<X> =
112    ResultOf<Reading<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>, X>;
113
114/// Complete original manifest instruction census. This is a schema inspection,
115/// not proof this XML came from any particular ZIP member or is authenticated.
116pub struct Reading<N, D, G> {
117    snapshot: Snapshot<D, G>,
118    root: N,
119    signatures: OwnedVec<N>,
120    objects: OwnedVec<Object<N>>,
121    extensions: OwnedVec<Extension<N>>,
122    faults: OwnedVec<Fault<N>>,
123}
124
125pub(crate) struct Builder<N, W: Work> {
126    signatures: OwnedVec<N, W>,
127    objects: OwnedVec<Object<N>, W>,
128    extensions: OwnedVec<Extension<N>, W>,
129    faults: OwnedVec<Fault<N>, W>,
130}
131
132impl<N: Copy, W: Work> Builder<N, W> {
133    fn fault<E>(&mut self, node: N, kind: FaultKind) -> Result<(), Error<E, N>> {
134        self.faults
135            .try_push(Fault { node, kind })
136            .map_err(|cause| Error::Resource { node, cause })
137    }
138}
139
140impl<N: Copy, D: Copy + Eq, G: Copy + Eq> Reading<N, D, G> {
141    /// Original XML source identity, not an `ASiC` member association.
142    #[must_use]
143    pub fn snapshot(&self) -> Snapshot<D, G> {
144        self.snapshot
145    }
146    /// Original root element.
147    #[must_use]
148    pub fn root(&self) -> N {
149        self.root
150    }
151    /// Original instructions, including duplicates and malformed slots.
152    #[must_use]
153    pub fn objects(&self) -> &[Object<N>] {
154        &self.objects
155    }
156    /// All original signature-reference occurrences.
157    #[must_use]
158    pub fn signatures(&self) -> &[N] {
159        &self.signatures
160    }
161    /// Original extension payload locations and explicit criticality.
162    #[must_use]
163    pub fn extensions(&self) -> &[Extension<N>] {
164        &self.extensions
165    }
166    /// Every readable schema finding in discovery order.
167    #[must_use]
168    pub fn faults(&self) -> &[Fault<N>] {
169        &self.faults
170    }
171}
172
173/// Reads the attached EN319162-1 V1.1.1 A.3/A.4 schema through existing XML
174/// access and scalar kernels. It retains every original instruction even when
175/// another has malformed syntax; processing/resource refusals remain terminal.
176pub fn inspect<X: AttributeXml + ScalarXml, W: Work>(
177    access: &Access<'_, X, W>,
178    root: X::Id,
179) -> InspectionResult<X> {
180    if !access
181        .has_name(root, name("ASiCManifest"))
182        .map_err(Error::Access)?
183    {
184        return Err(Error::Root(root));
185    }
186    let work = access.execution();
187    let mut output = Builder {
188        signatures: work.vector(),
189        objects: work.vector(),
190        extensions: work.vector(),
191        faults: work.vector(),
192    };
193    fields::attributes(access, root, &[], &mut output)?;
194    let mut stage = 0;
195    let mut extension = false;
196    walk(access, root, |node, element| {
197        if !element {
198            return output.fault(node, FaultKind::Text);
199        }
200        if access
201            .has_name(node, name("SigReference"))
202            .map_err(Error::Access)?
203        {
204            if stage != 0 {
205                output.fault(node, FaultKind::Order)?;
206            }
207            if !output.signatures.is_empty() {
208                output.fault(node, FaultKind::Duplicate)?;
209            }
210            fields::attributes(access, node, &["URI", "MimeType"], &mut output)?;
211            fields::uri(access, node, &mut output)?;
212            fields::empty(access, node, &mut output)?;
213            output
214                .signatures
215                .try_push(node)
216                .map_err(|cause| Error::Resource { node, cause })?;
217        } else if access
218            .has_name(node, name("DataObjectReference"))
219            .map_err(Error::Access)?
220        {
221            if stage > 1 || output.signatures.is_empty() {
222                output.fault(node, FaultKind::Order)?;
223            }
224            stage = stage.max(1);
225            let value = fields::object(access, node, &mut output)?;
226            output
227                .objects
228                .try_push(value)
229                .map_err(|cause| Error::Resource { node, cause })?;
230        } else if access
231            .has_name(node, name("ASiCManifestExtensions"))
232            .map_err(Error::Access)?
233        {
234            if extension {
235                output.fault(node, FaultKind::Duplicate)?;
236            }
237            if output.objects.is_empty() {
238                output.fault(node, FaultKind::Order)?;
239            }
240            stage = 2;
241            extension = true;
242            fields::extensions(access, root, node, &mut output)?;
243        } else {
244            output.fault(node, FaultKind::Unexpected)?;
245        }
246        Ok(())
247    })?;
248    if output.signatures.is_empty() || output.objects.is_empty() {
249        output.fault(root, FaultKind::Missing)?;
250    }
251    Ok(Reading {
252        snapshot: access.snapshot(),
253        root,
254        signatures: output.signatures.into_owned(),
255        objects: output.objects.into_owned(),
256        extensions: output.extensions.into_owned(),
257        faults: output.faults.into_owned(),
258    })
259}
260
261fn name(local: &'static str) -> Name<'static> {
262    Name {
263        namespace: NAMESPACE,
264        local,
265    }
266}
267
268fn walk<X: Xml, W: Work>(
269    access: &Access<'_, X, W>,
270    parent: X::Id,
271    mut visit: impl FnMut(X::Id, bool) -> ResultOf<(), X>,
272) -> ResultOf<(), X> {
273    let mut child = access.first_child(parent).map_err(Error::Access)?;
274    while let Some(node) = child {
275        let class = access
276            .inspect(node, |read| match read {
277                Node::Element(_) => Ok(Some(true)),
278                Node::Text(text) => {
279                    access.execution().charge_work(text.len() as u64)?;
280                    for byte in text.bytes() {
281                        if !matches!(byte, b' ' | b'\r' | b'\n' | b'\t') {
282                            return Ok(Some(false));
283                        }
284                    }
285                    Ok(None)
286                }
287                Node::Other => Ok(None),
288            })
289            .map_err(Error::Access)?
290            .map_err(|cause| Error::Resource { node, cause })?;
291        if let Some(element) = class {
292            visit(node, element)?;
293        }
294        child = access.next_sibling(parent, node).map_err(Error::Access)?;
295    }
296    Ok(())
297}