1mod declaration;
4mod types;
5
6use crate::container::{Layout, XADES};
7use crate::pki::CounterPaths;
8use crate::prepare::Prepared;
9use crate::reference::{LexicalSource, VisitError};
10use crate::scalar::{self, Datatype, QNameFact, QNameXml, ScalarXml};
11use crate::timestamp::XADES_141;
12use crate::xml::{Access, AccessError, Name, Node, Snapshot, Xml};
13use structured_asn1::memory::{MemoryError, Work};
14
15pub trait AttributeXml: Xml + QNameXml {
18 fn visit_attributes<VE, W: Work>(
23 &self,
24 snapshot: Snapshot<Self::Document, Self::Generation>,
25 node: Self::Id,
26 execution: &W,
27 visitor: impl for<'view> FnMut(Name<'view>) -> Result<(), VE>,
28 ) -> Result<(), VisitError<Self::Error, VE>>;
29}
30
31pub fn visit<X: AttributeXml, W: Work, E>(
35 access: &Access<'_, X, W>,
36 node: X::Id,
37 visitor: impl for<'view> FnMut(Name<'view>) -> Result<(), E>,
38) -> crate::xml::VisitResult<(), X, E> {
39 access.query_fallible(node, |xml, snapshot, work| {
40 xml.visit_attributes(snapshot, node, work, visitor)
41 })
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum InstanceAttribute {
48 Type,
50 SchemaLocation,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum TypeProblem {
57 Absent,
59 ComplexContent,
61 Malformed,
63 UnboundPrefix,
65}
66
67#[derive(Debug)]
69pub enum Error<E, N> {
70 Foreign,
72 Access(AccessError<E, N>),
74 Scalar(scalar::Error<E, N>),
76 Unexpected {
78 node: N,
80 attribute: usize,
82 },
83 Missing {
85 node: N,
87 local: &'static str,
89 },
90 InstanceUnassessed {
92 node: N,
94 kind: InstanceAttribute,
96 },
97 Type {
99 node: N,
101 problem: TypeProblem,
103 },
104 Unassessed {
106 node: N,
108 },
109}
110
111pub type AttributeResult<X> = Result<(), Error<<X as Xml>::Error, <X as Xml>::Id>>;
113
114#[derive(Clone, Copy)]
115enum Rule {
116 None,
117 Id,
118 Target,
119 Encoding,
120 Uri,
121 IdUri,
122 RequiredUri,
123 Include,
124 Format,
125 Identifier,
126 Order,
127 Open,
128}
129
130#[derive(Clone, Copy)]
131enum Context {
132 Ordinary,
133 Renewed,
134 CertifiedV2,
135 CertV2,
136}
137
138struct Declaration {
139 rule: Rule,
140 descend: bool,
141 context: Context,
142 schema_type: Name<'static>,
143}
144
145#[derive(Clone, Copy)]
146enum Field {
147 Typed(Datatype),
148 Required(Datatype),
149 Declared,
150}
151
152fn field(rule: Rule, local: &str) -> Option<Field> {
153 Some(match (rule, local) {
154 (Rule::Id | Rule::Target | Rule::Encoding | Rule::IdUri | Rule::RequiredUri, "Id") => {
155 Field::Typed(Datatype::Id)
156 }
157 (Rule::Target, "Target")
158 | (Rule::Format, "ObjectReference")
159 | (Rule::Include | Rule::RequiredUri, "URI") => Field::Required(Datatype::AnyUri),
160 (Rule::Encoding, "Encoding") | (Rule::Uri | Rule::IdUri, "URI") => {
161 Field::Typed(Datatype::AnyUri)
162 }
163 (Rule::Order, "Order") => Field::Required(Datatype::Integer),
164 (Rule::Identifier, "Qualifier") | (Rule::Include, "referencedData") => Field::Declared,
165 _ => return None,
166 })
167}
168
169use self::declaration::declaration;
170
171pub fn check_prepared<X: AttributeXml + ScalarXml, W: Work>(
176 prepared: &Prepared<X>,
177 access: &Access<'_, X, W>,
178) -> AttributeResult<X> {
179 let layout = prepared.bound().containers();
180 if layout.snapshot() != access.snapshot() {
181 return Err(Error::Foreign);
182 }
183 check_layout(layout, access)?;
184 access
185 .query(layout.signature(), |_, _, _| Ok(()))
186 .map_err(Error::Access)
187}
188
189pub fn check<X: AttributeXml + ScalarXml, W: Work>(
199 prepared: &Prepared<X>,
200 counters: &CounterPaths<'_, '_, X::Id, X::Document, X::Generation>,
201 access: &Access<'_, X, W>,
202) -> AttributeResult<X> {
203 require_participants(prepared, counters, access)?;
204 check_prepared(prepared, access)?;
205 check_counter_nodes(prepared, counters, access)
206}
207
208fn require_participants<X: AttributeXml + ScalarXml, W: Work>(
209 prepared: &Prepared<X>,
210 counters: &CounterPaths<'_, '_, X::Id, X::Document, X::Generation>,
211 access: &Access<'_, X, W>,
212) -> AttributeResult<X> {
213 let layout = prepared.bound().containers();
214 if layout.snapshot() != access.snapshot()
215 || counters.snapshot() != access.snapshot()
216 || !access
217 .equal_id(layout.signature(), counters.signature())
218 .map_err(|cause| resource(layout.signature(), cause))?
219 {
220 return Err(Error::Foreign);
221 }
222 Ok(())
223}
224
225pub(crate) fn check_counters<X: AttributeXml + ScalarXml, W: Work>(
228 prepared: &Prepared<X>,
229 counters: &CounterPaths<'_, '_, X::Id, X::Document, X::Generation>,
230 access: &Access<'_, X, W>,
231) -> AttributeResult<X> {
232 require_participants(prepared, counters, access)?;
233 check_counter_nodes(prepared, counters, access)
234}
235
236fn check_counter_nodes<X: AttributeXml + ScalarXml, W: Work>(
237 prepared: &Prepared<X>,
238 counters: &CounterPaths<'_, '_, X::Id, X::Document, X::Generation>,
239 access: &Access<'_, X, W>,
240) -> AttributeResult<X> {
241 let layout = prepared.bound().containers();
242 for actor in counters.actors() {
243 access
244 .execution()
245 .charge_work(1)
246 .map_err(|cause| resource(layout.signature(), cause))?;
247 if let Some(layout) = actor.containers() {
248 check_layout(layout, access)?;
249 }
250 }
251 access
252 .query(layout.signature(), |_, _, _| Ok(()))
253 .map_err(Error::Access)
254}
255
256struct Frame<N> {
257 parent: N,
258 next: Option<N>,
259 context: Context,
260}
261
262fn check_layout<X: AttributeXml + ScalarXml, W: Work>(
263 layout: &Layout<X>,
264 access: &Access<'_, X, W>,
265) -> AttributeResult<X> {
266 let mut frames = access.execution().vector();
267 let mut next = Some((layout.qualifying(), Context::Ordinary));
268 loop {
269 if let Some((node, parent)) = next.take() {
270 let selected = access
271 .inspect(node, |value| {
272 let Node::Element(name) = value else {
273 return Ok(None);
274 };
275 let length = name
276 .namespace
277 .len()
278 .checked_add(name.local.len())
279 .and_then(|cost| u64::try_from(cost).ok())
280 .ok_or(MemoryError::WorkExceeded)?;
281 access.execution().charge_work(length)?;
282 if name.namespace != XADES && name.namespace != XADES_141 {
283 return Ok(None);
284 }
285 Ok(Some(declaration(name, parent)))
286 })
287 .map_err(Error::Access)?
288 .map_err(|cause| resource(node, cause))?;
289 if let Some(selected) = selected {
290 let selected = selected.ok_or(Error::Unassessed { node })?;
291 check_node(node, selected.rule, selected.schema_type, access)?;
292 if selected.descend {
293 let child = access.first_child(node).map_err(Error::Access)?;
294 frames
295 .try_push(Frame {
296 parent: node,
297 next: child,
298 context: selected.context,
299 })
300 .map_err(|cause| resource(node, cause))?;
301 }
302 }
303 }
304 access
305 .execution()
306 .charge_work(1)
307 .map_err(|cause| resource(layout.qualifying(), cause))?;
308 let Some(frame) = frames.last_mut() else {
309 break;
310 };
311 if let Some(child) = frame.next {
312 frame.next = access
313 .next_sibling(frame.parent, child)
314 .map_err(Error::Access)?;
315 next = Some((child, frame.context));
316 } else {
317 frames.pop();
318 }
319 }
320 Ok(())
321}
322
323fn resource<E, N>(node: N, cause: MemoryError) -> Error<E, N> {
324 Error::Access(AccessError::Resource { node, cause })
325}
326
327fn check_node<X: AttributeXml + ScalarXml, W: Work>(
328 node: X::Id,
329 rule: Rule,
330 schema_type: Name<'static>,
331 access: &Access<'_, X, W>,
332) -> AttributeResult<X> {
333 let mut ordinal = 0usize;
334 let mut required = false;
335 let visited = access.query_fallible(node, |xml, snapshot, work| {
336 xml.visit_attributes(snapshot, node, work, |name| {
337 let cost = u64::try_from(name.local.len())
338 .ok()
339 .and_then(|length| length.checked_add(1))
340 .ok_or_else(|| resource(node, MemoryError::WorkExceeded))?;
341 work.charge_work(cost)
342 .map_err(|cause| resource(node, cause))?;
343 let current = ordinal;
344 ordinal = ordinal
345 .checked_add(1)
346 .ok_or_else(|| resource(node, MemoryError::WorkExceeded))?;
347 if access
348 .equal(name.namespace, "http://www.w3.org/2001/XMLSchema-instance")
349 .map_err(|cause| resource(node, cause))?
350 {
351 if name.local == "nil" {
358 return Err(Error::Unexpected {
359 node,
360 attribute: current,
361 });
362 }
363 if name.local == "noNamespaceSchemaLocation" {
364 return scalar::check(
369 access,
370 node,
371 LexicalSource::Attribute(name),
372 Datatype::AnyUri,
373 )
374 .map_err(Error::Scalar);
375 }
376 let kind = match name.local {
377 "type" => Some(InstanceAttribute::Type),
378 "schemaLocation" => Some(InstanceAttribute::SchemaLocation),
379 _ => None,
380 };
381 if let Some(kind) = kind {
382 if matches!(kind, InstanceAttribute::Type)
383 && same_declared_type(node, name, schema_type, access)?
384 {
385 return Ok(());
386 }
387 return Err(Error::InstanceUnassessed { node, kind });
388 }
389 }
390 if matches!(rule, Rule::Open) {
391 return Ok(());
392 }
393 if !name.namespace.is_empty() {
394 return Err(Error::Unexpected {
395 node,
396 attribute: current,
397 });
398 }
399 let selected = field(rule, name.local).ok_or(Error::Unexpected {
400 node,
401 attribute: current,
402 })?;
403 if matches!(selected, Field::Required(_)) {
404 required = true;
405 }
406 if let Field::Typed(datatype) | Field::Required(datatype) = selected {
407 scalar::check(access, node, LexicalSource::Attribute(name), datatype)
408 .map_err(Error::Scalar)?;
409 }
410 Ok(())
411 })
412 });
413 match visited {
414 Ok(()) => (),
415 Err(AccessError::Xml(VisitError::Processing(cause))) => {
416 return Err(Error::Access(AccessError::Xml(cause)));
417 }
418 Err(AccessError::Xml(VisitError::Visitor(cause))) => return Err(cause),
419 Err(AccessError::Changed { node }) => {
420 return Err(Error::Access(AccessError::Changed { node }));
421 }
422 Err(AccessError::Resource { node, cause }) => return Err(resource(node, cause)),
423 }
424 if !required {
425 let local = match rule {
426 Rule::Target => Some("Target"),
427 Rule::Include | Rule::RequiredUri => Some("URI"),
428 Rule::Format => Some("ObjectReference"),
429 Rule::Order => Some("Order"),
430 _ => None,
431 };
432 if let Some(local) = local {
433 return Err(Error::Missing { node, local });
434 }
435 }
436 Ok(())
437}
438
439fn same_declared_type<X: AttributeXml, W: Work>(
440 node: X::Id,
441 attribute: Name<'_>,
442 expected: Name<'static>,
443 access: &Access<'_, X, W>,
444) -> Result<bool, Error<X::Error, X::Id>> {
445 enum Reading {
446 Compared(bool),
447 Problem(TypeProblem),
448 }
449 let reading = access.query_fallible(node, |xml, snapshot, work| {
454 xml.with_qname(
455 snapshot,
456 node,
457 LexicalSource::Attribute(attribute),
458 work,
459 |read| {
460 let value = match read.fact {
461 QNameFact::Value(value) => value,
462 QNameFact::Unsupported => return Ok(Reading::Compared(false)),
463 QNameFact::Absent => return Ok(Reading::Problem(TypeProblem::Absent)),
464 QNameFact::ComplexContent => {
465 return Ok(Reading::Problem(TypeProblem::ComplexContent));
466 }
467 QNameFact::Malformed => return Ok(Reading::Problem(TypeProblem::Malformed)),
468 QNameFact::UnboundPrefix(_) => {
469 return Ok(Reading::Problem(TypeProblem::UnboundPrefix));
470 }
471 };
472 let Some(namespace) = value.namespace else {
473 return Ok(Reading::Compared(false));
474 };
475 if !access.equal(namespace, expected.namespace)? {
476 return Ok(Reading::Compared(false));
477 }
478 access
479 .equal(value.local_name, expected.local)
480 .map(Reading::Compared)
481 },
482 )
483 });
484 match reading {
485 Ok(Reading::Compared(value)) => Ok(value),
486 Ok(Reading::Problem(problem)) => Err(Error::Type { node, problem }),
487 Err(AccessError::Xml(VisitError::Processing(cause))) => {
488 Err(Error::Access(AccessError::Xml(cause)))
489 }
490 Err(AccessError::Xml(VisitError::Visitor(cause))) => Err(resource(node, cause)),
491 Err(AccessError::Changed { node }) => Err(Error::Access(AccessError::Changed { node })),
492 Err(AccessError::Resource { node, cause }) => Err(resource(node, cause)),
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499
500 #[test]
501 fn certified_role_attribute_type_depends_on_its_original_parent_declaration() {
502 let parent = declaration::declaration(
506 Name {
507 namespace: XADES,
508 local: "CertifiedRolesV2",
509 },
510 Context::Ordinary,
511 )
512 .unwrap();
513 let wrapper = declaration::declaration(
514 Name {
515 namespace: XADES,
516 local: "CertifiedRole",
517 },
518 parent.context,
519 )
520 .unwrap();
521 assert!(matches!(wrapper.rule, Rule::None));
522 assert!(wrapper.descend);
523 let legacy = declaration::declaration(
524 Name {
525 namespace: XADES,
526 local: "CertifiedRole",
527 },
528 Context::Ordinary,
529 )
530 .unwrap();
531 assert!(matches!(legacy.rule, Rule::Encoding));
532 assert!(!legacy.descend);
533 }
534}