Skip to main content

structured_xades/identifier/
meaning.rs

1//! Qualifier interpretation over the XML owner's complete normalized URI.
2
3use super::{Qualifier, Reading};
4use crate::reference::{LexicalSource, VisitError};
5use crate::scalar::{UriValue, UriXml};
6use crate::xml::{Access, AccessError, Xml};
7use structured_asn1::memory::{MemoryError, Work};
8use structured_asn1::value::OwnedOid;
9
10/// Established syntax/qualifier interpretation, not permanent assignment,
11/// authenticated coverage or trust. URI spelling stays in its original source.
12#[derive(Debug)]
13pub enum Meaning {
14    /// Generic URI, or `OIDAsURI`'s explicitly non-URN URI. No universal OID-to-URI
15    /// resolver is specified here; no encoded OID is invented from such a URI.
16    Uri,
17    /// RFC3061 OID with independent original-account storage after the XML
18    /// visitor's temporary joined/collapsed string has been released.
19    Oid(OwnedOid),
20}
21
22/// Original XML, base syntax, qualifier and OID failures retain separate causes.
23#[derive(Debug)]
24pub enum MeaningError<E, N> {
25    /// Original source/generation/current-operation refusal.
26    Access(AccessError<E, N>),
27    /// No valid complete original anyURI was established.
28    Value {
29        /// Original Identifier element.
30        node: N,
31        /// Requested base-datatype outcome, never a fabricated malformed fault.
32        fact: crate::scalar::Fact,
33    },
34    /// `OIDAsURN` did not use the RFC3061 namespace, or `OIDAsURI` used a URN.
35    Qualifier {
36        /// Original Identifier element.
37        node: N,
38    },
39    /// Original arbitrary-width ASN.1 OID conversion or resource refusal.
40    Oid {
41        /// Original Identifier element containing the normalized URI.
42        node: N,
43        /// Offset is relative to the normalized dotted-decimal suffix, NOT an
44        /// invented physical XML byte coordinate. Original node stays attached.
45        cause: structured_asn1::Error,
46    },
47}
48
49/// Complete interpretation in this XML owner's original identity/error domain.
50pub type MeaningResult<X> = Result<Meaning, MeaningError<<X as Xml>::Error, <X as Xml>::Id>>;
51
52/// Complete original identifier and documentation check, before trust/policy.
53#[derive(Debug)]
54pub enum CheckError<E, N> {
55    /// Original documentation grammar/source refusal.
56    Structure(crate::properties::Error<E, N>),
57    /// Identifier qualifier/OID/original URI interpretation refusal.
58    Meaning(MeaningError<E, N>),
59    /// A documentation reference's original base-datatype refusal.
60    Scalar(crate::scalar::Error<E, N>),
61}
62
63impl<E, N> From<crate::properties::Error<E, N>> for CheckError<E, N> {
64    fn from(cause: crate::properties::Error<E, N>) -> Self {
65        Self::Structure(cause)
66    }
67}
68
69/// Complete identifier result using original source and error types.
70pub type CheckResult<X> = Result<Meaning, CheckError<<X as Xml>::Error, <X as Xml>::Id>>;
71
72/// Check qualifier meaning AND every original documentation URI using the same
73/// documentation grammar as structural reading. Failure drops a partially
74/// retained numeric OID; original XML and prior capacity owners remain intact.
75pub fn check<X: UriXml + crate::scalar::ScalarXml, W: Work>(
76    access: &Access<'_, X, W>,
77    reading: &Reading<X::Id>,
78) -> CheckResult<X> {
79    let meaning = check_value(access, reading).map_err(CheckError::Meaning)?;
80    super::documentation_with(access, reading.documentation, |node| {
81        crate::scalar::check(
82            access,
83            node,
84            LexicalSource::SimpleContent,
85            crate::scalar::Datatype::AnyUri,
86        )
87        .map_err(CheckError::Scalar)
88    })?;
89    Ok(meaning)
90}
91
92/// Interpret an already structurally selected original Identifier without
93/// reparsing XML or retaining a temporary normalized string. Documentation URI
94/// validation is the enclosing reader's separate obligation.
95///
96/// EN319132-1 V1.3.1 5.1.2 selects RFC3061 for `OIDAsURN` and explicitly excludes
97/// URNs from `OIDAsURI`. RFC3061 2 permits ONLY unpadded decimal arcs; 3 includes
98/// uppercase URN:OID. The existing ASN.1 numericOID kernel enforces those arc
99/// rules and ASN.1 roots, including widths beyond machine integers. No percent
100/// decoding, URI canonicalization, network lookup or OID assignment is implied.
101/// <https://www.rfc-editor.org/rfc/rfc3061.html#section-2>
102/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
103pub fn check_value<X: UriXml, W: Work>(
104    access: &Access<'_, X, W>,
105    reading: &Reading<X::Id>,
106) -> MeaningResult<X> {
107    let node = reading.identifier;
108    let result = access.query_fallible(node, |xml, snapshot, work| {
109        xml.with_uri(
110            snapshot,
111            node,
112            LexicalSource::SimpleContent,
113            work,
114            |value| {
115                use crate::scalar::Fact;
116                let text = match value {
117                    UriValue::Valid(text) => text,
118                    value => {
119                        let fact = match value {
120                            UriValue::Absent => Fact::Absent,
121                            UriValue::ComplexContent => Fact::ComplexContent,
122                            UriValue::Malformed => Fact::Malformed,
123                            UriValue::Unsupported => Fact::Unsupported,
124                            UriValue::Valid(_) => unreachable!("successful URI selected above"),
125                        };
126                        return Err(MeaningError::Value { node, fact });
127                    }
128                };
129                match reading.qualifier {
130                    None => Ok(Meaning::Uri),
131                    Some(Qualifier::Uri) => {
132                        if prefix(text, "urn:", work).map_err(|cause| resource(node, cause))? {
133                            return Err(MeaningError::Qualifier { node });
134                        }
135                        Ok(Meaning::Uri)
136                    }
137                    Some(Qualifier::Urn) => {
138                        if !prefix(text, "urn:oid:", work).map_err(|cause| resource(node, cause))? {
139                            return Err(MeaningError::Qualifier { node });
140                        }
141                        OwnedOid::parse_in(&text[8..], work)
142                            .map(Meaning::Oid)
143                            .map_err(|cause| MeaningError::Oid { node, cause })
144                    }
145                }
146            },
147        )
148    });
149    match result {
150        Ok(value) => Ok(value),
151        Err(AccessError::Xml(VisitError::Visitor(cause))) => Err(cause),
152        Err(AccessError::Xml(VisitError::Processing(cause))) => {
153            Err(MeaningError::Access(AccessError::Xml(cause)))
154        }
155        Err(AccessError::Changed { node }) => {
156            Err(MeaningError::Access(AccessError::Changed { node }))
157        }
158        Err(AccessError::Resource { node, cause }) => Err(resource(node, cause)),
159    }
160}
161
162fn prefix<W: Work>(text: &str, expected: &str, work: &W) -> Result<bool, MemoryError> {
163    if text.len() < expected.len() {
164        return Ok(false);
165    }
166    work.charge_work(expected.len() as u64)?;
167    Ok(text.as_bytes()[..expected.len()].eq_ignore_ascii_case(expected.as_bytes()))
168}
169
170fn resource<E, N>(node: N, cause: MemoryError) -> MeaningError<E, N> {
171    MeaningError::Access(AccessError::Resource { node, cause })
172}