Skip to main content

structured_cades/
source.rs

1//! Immutable original CMS identities shared by every `CAdES` stage.
2
3use structured_asn1::memory::{HashWork, MemoryError};
4use structured_asn1::{Element, Encoding, Parser, Reader, Span};
5use structured_cms::{SignedData, signer::SignerInfo};
6use structured_ltv::source::{Location, Snapshot, Source};
7
8/// Identity of the borrowed immutable `SignedData` model. No bytes are hashed,
9/// copied or compared to answer identity; independently parsed copies differ.
10#[derive(Clone, Copy)]
11pub struct DocumentId<'model, 'document>(&'model SignedData<'document>);
12
13impl<'m, 'd> DocumentId<'m, 'd> {
14    pub(crate) fn source(self) -> Document<'m, 'd> {
15        Document { message: self.0 }
16    }
17}
18
19impl core::fmt::Debug for DocumentId<'_, '_> {
20    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
21        f.debug_tuple("DocumentId").field(&self.0.span()).finish()
22    }
23}
24
25impl PartialEq for DocumentId<'_, '_> {
26    fn eq(&self, other: &Self) -> bool {
27        core::ptr::eq(self.0, other.0)
28    }
29}
30
31impl Eq for DocumentId<'_, '_> {}
32
33/// One original CMS node in this immutable document. Offsets are coordinates
34/// within its original CMS source, not fabricated physical XML/PDF positions.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub struct Node {
37    start: u64,
38    length: u64,
39}
40
41impl Node {
42    pub(crate) fn of(span: Span) -> Self {
43        Self {
44            start: span.start,
45            length: span.length,
46        }
47    }
48
49    /// Exact original node range. Joined token internals keep their own CMS
50    /// `SourceMap`; this range identifies their original enclosing carrier.
51    #[must_use]
52    pub fn span(self) -> Span {
53        Span {
54            start: self.start,
55            length: self.length,
56        }
57    }
58}
59
60impl HashWork for Node {
61    fn hash_work(&self) -> Result<u64, MemoryError> {
62        Ok(2)
63    }
64}
65
66/// Original node plus immutable document identity.
67pub type Original<'model, 'document> = Location<Node, DocumentId<'model, 'document>, ()>;
68
69/// Saved original field context. The element cannot be rebound to invented
70/// encoding/depth or another parser; opening it uses the current operation's
71/// execution while retaining its original source window and origin ceilings.
72#[derive(Debug, Clone, Copy)]
73pub struct Value<'a> {
74    parser: &'a Parser,
75    element: Element<'a>,
76    encoding: Encoding,
77    outer: u32,
78    field: &'static str,
79}
80
81impl<'a> Value<'a> {
82    pub(crate) fn of(reader: &Reader<'a>, element: Element<'a>, field: &'static str) -> Self {
83        debug_assert!(reader.depth() > 0);
84        Self {
85            parser: reader.parser(),
86            element,
87            encoding: reader.encoding(),
88            outer: reader.depth() - 1,
89            field,
90        }
91    }
92    /// Whole original field; no content has been joined or re-encoded.
93    #[must_use]
94    pub fn element(&self) -> Element<'a> {
95        self.element
96    }
97    /// Opens the original whole field with origin and current query ceilings.
98    /// A later source switch never replaces the current operation with a
99    /// broader ancestor or an account detached from that operation.
100    pub fn reader_in<'query>(&self, operation: &Reader<'query>) -> crate::Result<Reader<'query>>
101    where
102        'a: 'query,
103    {
104        self.parser
105            .read_nested_query_in(
106                operation,
107                self.element.as_bytes(),
108                self.element.span().start,
109                self.encoding,
110                self.outer,
111            )
112            .map_err(|cause| crate::Error::encoding(cause, self.field))
113    }
114}
115
116/// Borrowed original document, without an extra parser or storage owner.
117/// Holding this borrow prevents mutation of its parsed model. Two wrappers
118/// over the SAME model share identity; separately parsed equal bytes do not.
119pub struct Document<'model, 'document> {
120    message: &'model SignedData<'document>,
121}
122
123impl<'m, 'd> Document<'m, 'd> {
124    /// Checks producing-parser provenance before exposing this identity domain.
125    pub fn new(message: &'m SignedData<'d>, parent: &Reader<'_>) -> structured_cms::Result<Self> {
126        message.require_session(parent, "cades")?;
127        Ok(Self { message })
128    }
129
130    /// Original full CMS container, including material and parallel signers.
131    #[must_use]
132    pub fn message(&self) -> &'m SignedData<'d> {
133        self.message
134    }
135
136    /// Locates one of the root container's actual signers, refusing substitutes.
137    pub fn signer(&self, signer: &SignerInfo<'_>) -> structured_cms::Result<Original<'m, 'd>> {
138        self.message.require_signer(signer, "cades.signer")?;
139        Ok(self.locate(signer.span()))
140    }
141
142    pub(crate) fn locate(&self, span: Span) -> Original<'m, 'd> {
143        Location {
144            snapshot: self.snapshot(),
145            node: Node::of(span),
146        }
147    }
148}
149
150impl<'m, 'd> Source for Document<'m, 'd> {
151    type Id = Node;
152    type Document = DocumentId<'m, 'd>;
153    type Generation = ();
154
155    fn snapshot(&self) -> Snapshot<DocumentId<'m, 'd>, ()> {
156        Snapshot {
157            // The reference travels with each receipt's identity. A dropped
158            // model/address reused by another parse cannot create an ABA match.
159            document: DocumentId(self.message),
160            generation: (),
161        }
162    }
163}