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, Work};
4use structured_asn1::{Element, Encoding, Parser, Reader, ScopedReader, 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    /// Decodes the original field under a short current operation. Parsed
98    /// values may retain original input borrows without retaining this cursor
99    /// or its operation; original depth and both source/current ceilings apply.
100    pub fn reader_scoped_in<'operation, W: Work>(
101        &self,
102        operation: &'operation W,
103    ) -> crate::Result<ScopedReader<'a, 'operation>>
104    where
105        'a: 'operation,
106    {
107        operation
108            .read_scoped_nested(
109                self.parser,
110                self.element.as_bytes(),
111                self.element.span().start,
112                self.encoding,
113                self.outer,
114            )
115            .map_err(|cause| crate::Error::encoding(cause, self.field))
116    }
117    /// Opens the original whole field with origin and current query ceilings.
118    /// A later source switch never replaces the current operation with a
119    /// broader ancestor or an account detached from that operation.
120    pub fn reader_in<'query>(&self, operation: &Reader<'query>) -> crate::Result<Reader<'query>>
121    where
122        'a: 'query,
123    {
124        self.parser
125            .read_nested_query_in(
126                operation,
127                self.element.as_bytes(),
128                self.element.span().start,
129                self.encoding,
130                self.outer,
131            )
132            .map_err(|cause| crate::Error::encoding(cause, self.field))
133    }
134}
135
136/// Borrowed original document, without an extra parser or storage owner.
137/// Holding this borrow prevents mutation of its parsed model. Two wrappers
138/// over the SAME model share identity; separately parsed equal bytes do not.
139pub struct Document<'model, 'document> {
140    message: &'model SignedData<'document>,
141}
142
143impl<'m, 'd> Document<'m, 'd> {
144    /// Checks producing-parser provenance before exposing this identity domain.
145    pub fn new(message: &'m SignedData<'d>, parent: &Reader<'_>) -> structured_cms::Result<Self> {
146        message.require_session(parent, "cades")?;
147        Ok(Self { message })
148    }
149
150    /// Original full CMS container, including material and parallel signers.
151    #[must_use]
152    pub fn message(&self) -> &'m SignedData<'d> {
153        self.message
154    }
155
156    /// Locates one of the root container's actual signers, refusing substitutes.
157    pub fn signer(&self, signer: &SignerInfo<'_>) -> structured_cms::Result<Original<'m, 'd>> {
158        self.message.require_signer(signer, "cades.signer")?;
159        Ok(self.locate(signer.span()))
160    }
161
162    pub(crate) fn locate(&self, span: Span) -> Original<'m, 'd> {
163        Location {
164            snapshot: self.snapshot(),
165            node: Node::of(span),
166        }
167    }
168}
169
170impl<'m, 'd> Source for Document<'m, 'd> {
171    type Id = Node;
172    type Document = DocumentId<'m, 'd>;
173    type Generation = ();
174
175    fn snapshot(&self) -> Snapshot<DocumentId<'m, 'd>, ()> {
176        Snapshot {
177            // The reference travels with each receipt's identity. A dropped
178            // model/address reused by another parse cannot create an ABA match.
179            document: DocumentId(self.message),
180            generation: (),
181        }
182    }
183}