Skip to main content

structured_pades/
material.rs

1//! Revision-bound DSS/VRI material over actual decoded PDF stream objects.
2
3use crate::arena::{Kind, NodeId};
4use crate::document::{Document, Reading};
5use crate::error::{Error, ErrorKind, ReadFailure, Syntax};
6use crate::model::{admit, get};
7use crate::range::{RangeSource, Stamp};
8use crate::source::Location;
9use structured_asn1::memory::{Memory, OwnedMap, OwnedVec, Work};
10mod history;
11mod membership;
12mod parsed;
13pub use history::{History, HistoryError, ParsedHistory, Unified};
14pub mod checks;
15pub mod credentials;
16pub mod vri;
17pub use parsed::{MaterialIndex, Parsed, Placement, TimestampValue, TokenChoice, Value};
18
19/// Syntax of a validation object, independent of authentication or usability.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum KindOf {
22    /// An X.509 certificate stream.
23    Certificate,
24    /// A whole X.509 `CertificateList` stream.
25    Crl,
26    /// A whole RFC6960 `OCSPResponse` stream.
27    Ocsp,
28    /// A VRI RFC3161 time-stamp token stream.
29    Timestamp,
30}
31
32/// Whole-DER material, retaining its actual stream definition and historical
33/// visibility. An OCSP value is the complete `OCSPResponse`, never just its body.
34pub struct Item<'s> {
35    kind: KindOf,
36    bytes: OwnedVec<u8, Memory>,
37    location: Location,
38    number: u64,
39    generation: u16,
40    session: &'s crate::Session,
41}
42
43impl Item<'_> {
44    /// Format named by the original DSS/VRI array.
45    #[must_use]
46    pub fn kind(&self) -> KindOf {
47        self.kind
48    }
49    /// Whole original decoded DER value, without re-encoding.
50    #[must_use]
51    pub fn as_bytes(&self) -> &[u8] {
52        &self.bytes
53    }
54    /// Physical encoded stream enclosure of these logical DER bytes.
55    #[must_use]
56    pub fn location(&self) -> Location {
57        self.location
58    }
59    /// Indirect identity selected in the material's historical revision.
60    #[must_use]
61    pub fn identity(&self) -> (u64, u16) {
62        (self.number, self.generation)
63    }
64
65    /// Opens this exact retained material using original and current budgets.
66    pub fn read<'a, W: Work + 'a>(
67        &'a self,
68        operation: &W,
69    ) -> Result<(Decoded<'a>, structured_asn1::Reader<'a>), MaterialError> {
70        let mut reader = self
71            .session
72            .carried(&self.bytes, operation)
73            .map_err(MaterialError::Encoding)?;
74        if self.kind == KindOf::Timestamp {
75            let token = structured_cms::timestamp::Token::read_from(&mut reader)
76                .map_err(MaterialError::Cms)?;
77            if !reader.is_empty() {
78                return Err(MaterialError::Pdf(Error {
79                    kind: ErrorKind::Syntax(Syntax::Stream),
80                    location: self.location,
81                }));
82            }
83            return Ok((Decoded::Timestamp(token), reader));
84        }
85        let element = reader
86            .next_element()
87            .map_err(MaterialError::Encoding)?
88            .ok_or(MaterialError::Pdf(Error {
89                kind: ErrorKind::Syntax(Syntax::Stream),
90                location: self.location,
91            }))?;
92        if element.as_bytes().len() != self.bytes.len() {
93            return Err(MaterialError::Pdf(Error {
94                kind: ErrorKind::Syntax(Syntax::Stream),
95                location: self.location,
96            }));
97        }
98        let decoded = match self.kind {
99            KindOf::Certificate => Decoded::Certificate(
100                structured_pkix::Certificate::read_element(&reader, &element)
101                    .map_err(MaterialError::Pkix)?,
102            ),
103            KindOf::Crl => Decoded::Crl(
104                structured_pkix::crl::CertificateList::read_element(&reader, &element)
105                    .map_err(MaterialError::Pkix)?,
106            ),
107            KindOf::Ocsp => Decoded::Ocsp(
108                structured_pkix::ocsp::Response::read_element(&reader, &element)
109                    .map_err(MaterialError::Pkix)?,
110            ),
111            KindOf::Timestamp => unreachable!("timestamp decoded before element consumption"),
112        };
113        Ok((decoded, reader))
114    }
115}
116
117/// Original material parser result; parsing does not establish issuer trust.
118pub enum Decoded<'a> {
119    /// Actual parsed certificate, without a path/trust result.
120    Certificate(structured_pkix::Certificate<'a>),
121    /// Actual parsed CRL, without issuer authentication or status usability.
122    Crl(structured_pkix::crl::CertificateList<'a>),
123    /// Whole response, retaining unsigned protocol failures separately.
124    Ocsp(structured_pkix::ocsp::Response<'a>),
125    /// Parsed VRI timestamp, without an imprint or authority assertion.
126    Timestamp(structured_cms::timestamp::Token<'a>),
127}
128
129/// Original error coordinates remain logical within [`Item::location`].
130#[derive(Debug)]
131pub enum MaterialError {
132    /// PDF material placement or extent violation.
133    Pdf(Error),
134    /// Original carried DER syntax or resource refusal.
135    Encoding(structured_asn1::Error),
136    /// Original certificate/status syntax or resource refusal.
137    Pkix(structured_pkix::Error),
138    /// Original VRI timestamp syntax or resource refusal.
139    Cms(structured_cms::Error),
140}
141
142/// Terminal material failure retaining the actual encoded item's enclosure.
143#[derive(Debug)]
144pub struct MaterialFailure {
145    /// Original encoded stream body, independent of inner logical offsets.
146    pub location: Location,
147    /// Unchanged grammar, resource or caller-misuse cause.
148    pub cause: MaterialError,
149}
150impl MaterialError {
151    /// Processing/resource/source misuse is never a malformed-value finding.
152    #[must_use]
153    pub fn is_terminal(&self) -> bool {
154        match self {
155            Self::Pdf(cause) => matches!(
156                cause.kind,
157                ErrorKind::Resource(_) | ErrorKind::Limit(_) | ErrorKind::Foreign
158            ),
159            Self::Encoding(cause) => cause.is_resource(),
160            Self::Pkix(cause) => {
161                cause.is_resource() || cause.code() == structured_pkix::ErrorCode::ForeignReader
162            }
163            Self::Cms(cause) => cause.is_terminal(),
164        }
165    }
166}
167
168/// One original VRI dictionary. Material indices refer only to THIS owning
169/// historical material set; a later DSS cannot fill an earlier VRI silently.
170pub struct Vri {
171    name: [u8; 40],
172    location: Location,
173    certs: OwnedVec<usize, Memory>,
174    crls: OwnedVec<usize, Memory>,
175    ocsp: OwnedVec<usize, Memory>,
176    update: Option<crate::date::Date>,
177    timestamp: Option<usize>,
178    incorporated: bool,
179}
180
181/// Original VRI generation-time method, without asserting its trusted time.
182#[derive(Debug, Clone, Copy)]
183pub enum GenerationTime {
184    /// Written TU is a claimed label, not a timestamp proof.
185    Claimed(crate::date::Date),
186    /// Written TS points into this same historical Validation item pool.
187    Token(usize),
188    /// Neither field was written; a covering subsequent document timestamp
189    /// must still be established from its actual source and checked input.
190    DocumentTimestampRequired,
191}
192
193impl Vri {
194    /// Every referenced certificate/status value is also present in the
195    /// corresponding DSS array in this SAME historical revision. This checks
196    /// exact decoded values, not just object-number equality or a later DSS.
197    #[must_use]
198    pub fn all_material_in_dss(&self) -> bool {
199        self.incorporated
200    }
201    /// Uppercase SHA-1 name written in the original VRI index.
202    #[must_use]
203    pub fn name(&self) -> &[u8; 40] {
204        &self.name
205    }
206    /// Actual original VRI dictionary key location.
207    #[must_use]
208    pub fn location(&self) -> Location {
209        self.location
210    }
211    /// Certificate occurrences in the same historical validation owner.
212    #[must_use]
213    pub fn certificates(&self) -> &[usize] {
214        &self.certs
215    }
216    /// CRL occurrences in the same historical validation owner.
217    #[must_use]
218    pub fn crls(&self) -> &[usize] {
219        &self.crls
220    }
221    /// OCSP occurrences in the same historical validation owner.
222    #[must_use]
223    pub fn ocsp(&self) -> &[usize] {
224        &self.ocsp
225    }
226    /// Whether TU, rather than TS, was written in this original VRI.
227    #[must_use]
228    pub fn update_time(&self) -> Option<crate::date::Date> {
229        self.update
230    }
231    /// Actual VRI timestamp item in the same validation owner.
232    #[must_use]
233    pub fn timestamp(&self) -> Option<usize> {
234        self.timestamp
235    }
236    /// EN319142-1 §5.4.2.3(c)'s three distinct methods. This preserves the
237    /// unmet subsequent-timestamp obligation instead of fabricating a time.
238    #[must_use]
239    pub fn generation_time(&self) -> GenerationTime {
240        if let Some(value) = self.update {
241            return GenerationTime::Claimed(value);
242        }
243        if let Some(value) = self.timestamp {
244            return GenerationTime::Token(value);
245        }
246        GenerationTime::DocumentTimestampRequired
247    }
248}
249
250/// Complete material occurrence census in one explicitly selected revision.
251/// Absence is represented independently of an empty but present dictionary.
252pub struct Validation<'s, D, G> {
253    session: &'s crate::Session,
254    stamp: Stamp<D, G>,
255    revision: usize,
256    present: bool,
257    items: OwnedVec<Item<'s>, Memory>,
258    vri: OwnedVec<Vri, Memory>,
259    vri_names: OwnedMap<vri::Name, usize, Memory>,
260    incorporated: bool,
261}
262
263impl<D: Copy + Eq, G: Copy + Eq> Validation<'_, D, G> {
264    /// Immutable source identity of all retained material occurrences.
265    #[must_use]
266    pub fn stamp(&self) -> Stamp<D, G> {
267        self.stamp
268    }
269    /// Historical ordinal, never an implicit latest-revision selection.
270    #[must_use]
271    pub fn revision(&self) -> usize {
272        self.revision
273    }
274    /// Distinguishes an absent DSS from an explicitly present empty one.
275    #[must_use]
276    pub fn is_present(&self) -> bool {
277        self.present
278    }
279    /// All original DSS and VRI material occurrences, without copying.
280    #[must_use]
281    pub fn items(&self) -> &[Item<'_>] {
282        &self.items
283    }
284    /// Every original VRI, independently of whether its name matches a signature.
285    #[must_use]
286    pub fn vri(&self) -> &[Vri] {
287        &self.vri
288    }
289    /// All original VRI validation values also occur in their corresponding
290    /// DSS arrays in this revision, independently of DER readability/trust.
291    #[must_use]
292    pub fn all_vri_material_in_dss(&self) -> bool {
293        self.incorporated
294    }
295}
296
297impl<'s, S: RangeSource> Document<'s, S> {
298    async fn dss_items<W: Work, Q: Work>(
299        &self,
300        revision: usize,
301        dss: &Reading<'s, S::Document, S::Generation>,
302        items: &mut OwnedVec<Item<'s>, W>,
303        work: &Q,
304    ) -> Result<(), ReadFailure<S::Error>> {
305        for (key, kind) in [
306            (b"Certs".as_slice(), KindOf::Certificate),
307            (b"CRLs", KindOf::Crl),
308            (b"OCSPs", KindOf::Ocsp),
309        ] {
310            if let Some(array) = get(&dss.value.arena, dss.value.root, key, work)? {
311                let Some(references) = array_references(self, dss, array, work).await? else {
312                    continue;
313                };
314                for &(number, generation) in &references {
315                    let item = material(self, revision, number, generation, kind, work).await?;
316                    items.try_push(item).map_err(|cause| Error {
317                        kind: ErrorKind::Resource(cause),
318                        location: dss.value.root().location(),
319                    })?;
320                }
321            }
322        }
323        Ok(())
324    }
325
326    /// Reads the historical catalog's DSS/VRI and every referenced material
327    /// stream using the common DER readers. This performs no status selection,
328    /// authentication, network retrieval or latest-revision substitution.
329    ///
330    /// # Panics
331    /// Panics if the checked original file extent cannot be retained as a location.
332    pub async fn validation<W: Work>(
333        &self,
334        revision: usize,
335        operation: &W,
336    ) -> Result<Validation<'s, S::Document, S::Generation>, ReadFailure<S::Error>> {
337        let work = self.session.query(operation);
338        let root = self.root(revision, &work).await?;
339        let dss = self.select(root, b"DSS", &work).await?;
340        let present = dss.is_some();
341        let mut items = work.vector();
342        let mut vri = work.vector();
343        let mut vri_names = work.map();
344        let mut incorporated = true;
345        if let Some(dss) = dss {
346            type_name(self, &dss, dss.value.root, b"DSS", &work).await?;
347            self.dss_items(revision, &dss, &mut items, &work).await?;
348            let membership =
349                membership::Membership::new(&items, dss.value.root().location(), &work)?;
350            if let Some(index_owner) = self.select(dss, b"VRI", &work).await? {
351                let Kind::Dictionary(_, fields) =
352                    index_owner.value.arena.nodes[index_owner.value.root.0].kind
353                else {
354                    return Err(Error {
355                        kind: ErrorKind::Syntax(Syntax::Dictionary),
356                        location: index_owner.value.root().location(),
357                    }
358                    .into());
359                };
360                for ordinal in fields.start..fields.start + fields.length {
361                    admit(&work, index_owner.value.root().location(), 1)?;
362                    let entry = index_owner.value.arena.fields[ordinal];
363                    let resolved;
364                    let (owner, node) = if let Kind::Reference { number, generation } =
365                        index_owner.value.arena.nodes[entry.value.0].kind
366                    {
367                        resolved = self.object(revision, number, generation, &work).await?;
368                        let Some(owner) = &resolved else { continue };
369                        if matches!(owner.value.arena.nodes[owner.value.root.0].kind, Kind::Null) {
370                            continue;
371                        }
372                        (owner, owner.value.root)
373                    } else {
374                        (&index_owner, entry.value)
375                    };
376                    let (name, location) = vri_name(&index_owner.value.arena, entry.key, &work)?;
377                    let (certs, crls, ocsp, update, timestamp) =
378                        vri_members(self, owner, node, &mut items, &work).await?;
379                    if vri_names
380                        .try_insert(vri::Name(name), vri.len())
381                        .map_err(|cause| Error {
382                            kind: ErrorKind::Resource(cause),
383                            location,
384                        })?
385                        .is_some()
386                    {
387                        return Err(Error {
388                            kind: ErrorKind::Syntax(Syntax::Dictionary),
389                            location,
390                        }
391                        .into());
392                    }
393                    let member_incorporated =
394                        incorporation(&membership, &items, [&certs, &crls, &ocsp], &work)?;
395                    if !member_incorporated {
396                        incorporated = false;
397                    }
398                    vri.try_push(Vri {
399                        name,
400                        location,
401                        certs,
402                        crls,
403                        ocsp,
404                        update,
405                        timestamp,
406                        incorporated: member_incorporated,
407                    })
408                    .map_err(|cause| Error {
409                        kind: ErrorKind::Resource(cause),
410                        location,
411                    })?;
412                }
413            }
414        }
415        self.check(
416            &work,
417            Location::new(crate::source::Source::File, 0, self.stamp.length).expect("file range"),
418        )?;
419        Ok(Validation {
420            session: self.session,
421            stamp: self.stamp,
422            revision,
423            present,
424            items: items.into_owned(),
425            vri: vri.into_owned(),
426            vri_names: vri_names.into_owned(),
427            incorporated,
428        })
429    }
430}
431
432fn incorporation<W: Work>(
433    membership: &membership::Membership<W>,
434    items: &[Item<'_>],
435    pools: [&[usize]; 3],
436    work: &impl Work,
437) -> crate::Result<bool> {
438    let mut complete = true;
439    for pool in pools {
440        for &ordinal in pool {
441            if !membership.contains(items, &items[ordinal], work)? {
442                complete = false;
443            }
444        }
445    }
446    Ok(complete)
447}
448
449fn vri_name(
450    arena: &crate::arena::Arena<impl Work>,
451    key_node: NodeId,
452    work: &impl Work,
453) -> crate::Result<([u8; 40], Location)> {
454    let Kind::Name(key) = arena.nodes[key_node.0].kind else {
455        unreachable!("dictionary name")
456    };
457    let location = arena.nodes[key_node.0].location;
458    if key.length != 40 {
459        return Err(Error {
460            kind: ErrorKind::Syntax(Syntax::Name),
461            location,
462        });
463    }
464    admit(work, location, 40)?;
465    let mut name = [0_u8; 40];
466    for (output, &byte) in name.iter_mut().zip(&arena.names[key.start..key.start + 40]) {
467        if !matches!(byte, b'0'..=b'9' | b'A'..=b'F') {
468            return Err(Error {
469                kind: ErrorKind::Syntax(Syntax::Name),
470                location,
471            });
472        }
473        *output = byte;
474    }
475    Ok((name, location))
476}
477
478async fn array_references<S: RangeSource>(
479    document: &Document<'_, S>,
480    owner: &Reading<'_, S::Document, S::Generation>,
481    node: NodeId,
482    work: &impl Work,
483) -> Result<Option<OwnedVec<(u64, u16), Memory>>, ReadFailure<S::Error>> {
484    let Some(selected) = document.resolve_value(owner, node, work).await? else {
485        return Ok(None);
486    };
487    let (owner, node) = selected.parts();
488    let arena = &owner.value.arena;
489    let Kind::Array(children) = arena.nodes[node.0].kind else {
490        return Err(Error {
491            kind: ErrorKind::Syntax(Syntax::Dictionary),
492            location: arena.nodes[node.0].location,
493        }
494        .into());
495    };
496    let mut output = work.vector();
497    let mut next = children.first;
498    while let Some(index) = next {
499        admit(work, arena.nodes[node.0].location, 1)?;
500        let link = arena.links[index];
501        next = link.next;
502        let Kind::Reference { number, generation } = arena.nodes[link.value.0].kind else {
503            return Err(Error {
504                kind: ErrorKind::Syntax(Syntax::Object),
505                location: arena.nodes[link.value.0].location,
506            }
507            .into());
508        };
509        output
510            .try_push((number, generation))
511            .map_err(|cause| Error {
512                kind: ErrorKind::Resource(cause),
513                location: arena.nodes[link.value.0].location,
514            })?;
515    }
516    Ok(Some(output.into_owned()))
517}
518
519async fn material<'s, S: RangeSource>(
520    document: &Document<'s, S>,
521    revision: usize,
522    number: u64,
523    generation: u16,
524    kind: KindOf,
525    work: &impl Work,
526) -> Result<Item<'s>, ReadFailure<S::Error>> {
527    let reading = document
528        .object(revision, number, generation, work)
529        .await?
530        .ok_or(Error {
531            kind: ErrorKind::Syntax(Syntax::Object),
532            location: Location::new(crate::source::Source::File, 0, 0).expect("file point"),
533        })?;
534    material_reading(document, &reading, number, generation, kind, work).await
535}
536
537async fn material_reading<'s, S: RangeSource>(
538    document: &Document<'s, S>,
539    reading: &Reading<'s, S::Document, S::Generation>,
540    number: u64,
541    generation: u16,
542    kind: KindOf,
543    work: &impl Work,
544) -> Result<Item<'s>, ReadFailure<S::Error>> {
545    let decoded = document.stream_data(reading, false, work).await?;
546    let item = Item {
547        kind,
548        bytes: decoded.bytes,
549        location: Location::new(
550            crate::source::Source::File,
551            decoded.body.start,
552            decoded.body.length,
553        )
554        .expect("checked stream extent"),
555        number,
556        generation,
557        session: document.session,
558    };
559    Ok(item)
560}
561
562type VriMembers = (
563    OwnedVec<usize>,
564    OwnedVec<usize>,
565    OwnedVec<usize>,
566    Option<crate::date::Date>,
567    Option<usize>,
568);
569
570async fn vri_members<'s, S: RangeSource, W: Work>(
571    document: &Document<'s, S>,
572    owner: &Reading<'s, S::Document, S::Generation>,
573    node: NodeId,
574    items: &mut OwnedVec<Item<'s>, W>,
575    work: &impl Work,
576) -> Result<VriMembers, ReadFailure<S::Error>> {
577    let location = owner.value.arena.nodes[node.0].location;
578    type_name(document, owner, node, b"VRI", work).await?;
579    let mut certs = work.vector();
580    let mut crls = work.vector();
581    let mut ocsp = work.vector();
582    for (key, kind, output) in [
583        (b"Cert".as_slice(), KindOf::Certificate, &mut certs),
584        (b"CRL", KindOf::Crl, &mut crls),
585        (b"OCSP", KindOf::Ocsp, &mut ocsp),
586    ] {
587        if let Some(array) = get(&owner.value.arena, node, key, work)? {
588            let Some(references) = array_references(document, owner, array, work).await? else {
589                continue;
590            };
591            if references.is_empty() {
592                return Err(Error {
593                    kind: ErrorKind::Syntax(Syntax::Dictionary),
594                    location,
595                }
596                .into());
597            }
598            for &(number, generation) in &references {
599                let item =
600                    material(document, owner.revision, number, generation, kind, work).await?;
601                let index = items.len();
602                output.try_push(index).map_err(|cause| Error {
603                    kind: ErrorKind::Resource(cause),
604                    location,
605                })?;
606                items.try_push(item).map_err(|cause| Error {
607                    kind: ErrorKind::Resource(cause),
608                    location,
609                })?;
610            }
611        }
612    }
613    let (update, timestamp) = vri_generation(document, owner, node, items, work).await?;
614    Ok((
615        certs.into_owned(),
616        crls.into_owned(),
617        ocsp.into_owned(),
618        update,
619        timestamp,
620    ))
621}
622
623async fn vri_generation<'s, S: RangeSource, W: Work>(
624    document: &Document<'s, S>,
625    owner: &Reading<'s, S::Document, S::Generation>,
626    node: NodeId,
627    items: &mut OwnedVec<Item<'s>, W>,
628    work: &impl Work,
629) -> Result<(Option<crate::date::Date>, Option<usize>), ReadFailure<S::Error>> {
630    let location = owner.value.arena.nodes[node.0].location;
631    let update = if let Some(value) = get(&owner.value.arena, node, b"TU", work)? {
632        document.resolve_value(owner, value, work).await?
633    } else {
634        None
635    };
636    let timestamp_node = get(&owner.value.arena, node, b"TS", work)?;
637    let timestamp = if let Some(value) = timestamp_node {
638        document.resolve_value(owner, value, work).await?
639    } else {
640        None
641    };
642    // EN319142-1 §5.4.2.3(c) also permits a subsequent document timestamp;
643    // inspection preserves that unfulfilled method when both fields are absent.
644    // https://www.etsi.org/deliver/etsi_en/319100_319199/31914201/01.02.01_60/en_31914201v010201p.pdf
645    if update.is_some() && timestamp.is_some() {
646        return Err(Error {
647            kind: ErrorKind::Syntax(Syntax::Dictionary),
648            location,
649        }
650        .into());
651    }
652    let update = if let Some(value) = update {
653        let (owner, node) = value.parts();
654        let bytes = document.string(owner, node, 22, work).await?;
655        Some(crate::date::Date::read(
656            &bytes,
657            owner.value.arena.nodes[node.0].location,
658            work,
659        )?)
660    } else {
661        None
662    };
663    let timestamp = if let Some(value) = timestamp {
664        let node = timestamp_node.expect("a resolved timestamp has an original field");
665        let Kind::Reference { number, generation } = owner.value.arena.nodes[node.0].kind else {
666            return Err(Error {
667                kind: ErrorKind::Syntax(Syntax::Stream),
668                location: owner.value.arena.nodes[node.0].location,
669            }
670            .into());
671        };
672        let (reading, _) = value.parts();
673        let item = material_reading(
674            document,
675            reading,
676            number,
677            generation,
678            KindOf::Timestamp,
679            work,
680        )
681        .await?;
682        let index = items.len();
683        items.try_push(item).map_err(|cause| Error {
684            kind: ErrorKind::Resource(cause),
685            location,
686        })?;
687        Some(index)
688    } else {
689        None
690    };
691    Ok((update, timestamp))
692}
693
694async fn type_name<'s, S: RangeSource>(
695    document: &Document<'s, S>,
696    owner: &Reading<'s, S::Document, S::Generation>,
697    dictionary: NodeId,
698    expected: &[u8],
699    work: &impl Work,
700) -> Result<(), ReadFailure<S::Error>> {
701    // EN319142-1 §§5.4.2.2–5.4.2.3 constrain a written Type. ISO32000-1
702    // §§7.3.9–7.3.10 retain indirect-name and absent/null equivalence.
703    // https://www.etsi.org/deliver/etsi_en/319100_319199/31914201/01.02.01_60/en_31914201v010201p.pdf
704    let Some(node) = get(&owner.value.arena, dictionary, b"Type", work)? else {
705        return Ok(());
706    };
707    let Some(selected) = document.resolve_value(owner, node, work).await? else {
708        return Ok(());
709    };
710    let (owner, node) = selected.parts();
711    let arena = &owner.value.arena;
712    let value = arena.nodes[node.0];
713    admit(work, value.location, expected.len() as u64 + 1)?;
714    if matches!(value.kind, Kind::Null) {
715        return Ok(());
716    }
717    if let Kind::Name(name) = value.kind
718        && name.length == expected.len()
719        && &arena.names[name.start..name.start + name.length] == expected
720    {
721        return Ok(());
722    }
723    Err(Error {
724        kind: ErrorKind::Syntax(Syntax::Dictionary),
725        location: value.location,
726    }
727    .into())
728}