Skip to main content

structured_cms/
octets.rs

1//! Iterative BER OCTET STRING views with source spans and retained accounting.
2
3use crate::{Error, ErrorCode, Result};
4use structured_asn1::memory::{OwnedVec, Work};
5use structured_asn1::{Element, Parser, Reader, Span, Tag, universal};
6
7/// One original content range, excluding ASN.1 tag and length octets.
8#[derive(Debug, Clone, Copy)]
9pub struct Piece<'a> {
10    bytes: &'a [u8],
11    span: Span,
12}
13
14impl<'a> Piece<'a> {
15    /// The original content bytes; no join or re-encoding is performed.
16    #[must_use]
17    pub fn as_bytes(self) -> &'a [u8] {
18        self.bytes
19    }
20
21    /// The content's original document range.
22    #[must_use]
23    pub fn span(self) -> Span {
24        self.span
25    }
26}
27
28#[derive(Debug)]
29enum Storage<'a> {
30    One([Piece<'a>; 1]),
31    Many(OwnedVec<Piece<'a>>),
32}
33
34/// Borrowed logical octets with independently accounted segment metadata.
35///
36/// Primitive content stays inline and allocates no catalogue. Constructed BER
37/// content retains its original disjoint ranges, flattened only as metadata.
38/// The catalogue can outlive the producing reader; its bytes remain borrowed.
39#[derive(Debug)]
40pub struct Octets<'a> {
41    parser: &'a Parser,
42    outer_depth: u32,
43    carrier: Span,
44    contents: Span,
45    storage: Storage<'a>,
46    length: usize,
47    field: &'static str,
48}
49
50impl<'a> Octets<'a> {
51    /// Reads an OCTET STRING using the original parser's encoding and ceilings.
52    ///
53    /// X.690 §8.7 permits constructed BER strings composed of OCTET STRING
54    /// fragments. The iterative traversal preserves inherited depth and source
55    /// positions, admitting frame/catalogue growth before allocation. A DER
56    /// reader still refuses constructed forms under its own encoding contract.
57    /// <https://www.itu.int/rec/T-REC-X.690-202102-I/en>
58    pub fn read(parent: &Reader<'a>, element: &Element<'a>, field: &'static str) -> Result<Self> {
59        Self::read_tagged(parent, element, universal::OCTET_STRING, false, field)
60    }
61
62    /// Reads `[0] IMPLICIT OCTET STRING`, the CMS subject-key identifier.
63    ///
64    /// Only the outer tag is replaced; constructed children remain universal
65    /// OCTET STRINGs (X.690 §§8.7, 8.14). Both forms use the same segment walker.
66    pub(crate) fn read_identifier(
67        parent: &Reader<'a>,
68        element: &Element<'a>,
69        field: &'static str,
70    ) -> Result<Self> {
71        Self::read_tagged(parent, element, 0, true, field)
72    }
73
74    fn read_tagged(
75        parent: &Reader<'a>,
76        element: &Element<'a>,
77        number: u32,
78        context: bool,
79        field: &'static str,
80    ) -> Result<Self> {
81        let primitive = if context {
82            Tag::context(false, number)
83        } else {
84            Tag::primitive(number)
85        };
86        let constructed = if context {
87            Tag::context(true, number)
88        } else {
89            Tag::constructed(number)
90        };
91        if element.tag() == primitive {
92            return Ok(Self {
93                parser: parent.parser(),
94                outer_depth: parent.depth(),
95                carrier: element.span(),
96                contents: element.contents_span(),
97                storage: Storage::One([Piece {
98                    bytes: element.contents(),
99                    span: element.contents_span(),
100                }]),
101                length: element.contents().len(),
102                field,
103            });
104        }
105        if element.tag() != constructed {
106            return Err(Error::schema(
107                ErrorCode::UnexpectedTag,
108                field,
109                element.span().start,
110            ));
111        }
112        if parent.encoding().is_der() {
113            return Err(Error::schema(
114                ErrorCode::UnexpectedTag,
115                field,
116                element.span().start,
117            ));
118        }
119        let execution = parent.execution();
120        let mut pieces = execution.vector();
121        let mut frames = execution.vector();
122        frames
123            .try_push(
124                parent
125                    .enter(element)
126                    .map_err(|error| Error::encoding(error, field))?,
127            )
128            .map_err(|reason| Error::resource(reason, field, element.span().start))?;
129        let mut length = 0_usize;
130        while let Some(reader) = frames.last_mut() {
131            let Some(child) = reader
132                .next_element()
133                .map_err(|error| Error::encoding(error, field))?
134            else {
135                frames.pop();
136                continue;
137            };
138            if child.tag() == Tag::primitive(universal::OCTET_STRING) {
139                length = length.checked_add(child.contents().len()).ok_or_else(|| {
140                    Error::schema(ErrorCode::LengthOverflow, field, child.span().start)
141                })?;
142                pieces
143                    .try_push(Piece {
144                        bytes: child.contents(),
145                        span: child.contents_span(),
146                    })
147                    .map_err(|reason| Error::resource(reason, field, child.span().start))?;
148            } else if child.tag() == Tag::constructed(universal::OCTET_STRING) {
149                let nested = reader
150                    .enter(&child)
151                    .map_err(|error| Error::encoding(error, field))?;
152                frames
153                    .try_push(nested)
154                    .map_err(|reason| Error::resource(reason, field, child.span().start))?;
155            } else {
156                return Err(Error::schema(
157                    ErrorCode::UnexpectedTag,
158                    field,
159                    child.span().start,
160                ));
161            }
162        }
163        Ok(Self {
164            parser: parent.parser(),
165            outer_depth: parent.depth(),
166            carrier: element.span(),
167            contents: element.contents_span(),
168            storage: Storage::Many(pieces.into_owned()),
169            length,
170            field,
171        })
172    }
173
174    /// The original primitive content ranges in logical order.
175    #[must_use]
176    pub fn pieces(&self) -> &[Piece<'a>] {
177        match &self.storage {
178            Storage::One(piece) => piece,
179            Storage::Many(pieces) => pieces,
180        }
181    }
182
183    /// The logical octet count, recorded during parsing without a later scan.
184    #[must_use]
185    pub fn len(&self) -> usize {
186        self.length
187    }
188
189    /// Whether the logical content is empty, including constructed empty input.
190    #[must_use]
191    pub fn is_empty(&self) -> bool {
192        self.length == 0
193    }
194
195    /// The original whole carrying element, including its tag and length.
196    #[must_use]
197    pub fn span(&self) -> Span {
198        self.carrier
199    }
200
201    pub(crate) fn content_span(&self) -> Span {
202        self.contents
203    }
204
205    pub(crate) fn outer_depth(&self) -> u32 {
206        self.outer_depth
207    }
208
209    pub(crate) fn parser(&self) -> &'a Parser {
210        self.parser
211    }
212
213    /// The borrowed bytes when the logical value occupies one original range.
214    #[must_use]
215    pub fn as_one(&self) -> Option<&'a [u8]> {
216        match self.pieces() {
217            [piece] => Some(piece.bytes),
218            _ => None,
219        }
220    }
221
222    /// Copies logical content into independently retained accounted storage.
223    ///
224    /// This is an explicit materialization; inspection can keep using borrowed
225    /// ranges, and an already owned result should be moved instead of copied.
226    /// The original producing session is required before any admission or scan.
227    /// Metadata traversal, exact capacity and each copy use both its local and
228    /// current aggregate ceilings. The result retains charge after reader drop.
229    /// `field` and `at` locate the actual enclosing operation, including an
230    /// empty constructed value. Refusal releases partial output, not the source.
231    pub fn copy_in(
232        &self,
233        parent: &Reader<'_>,
234        field: &'static str,
235        at: u64,
236    ) -> Result<OwnedVec<u8>> {
237        if !core::ptr::eq(self.parser, parent.parser()) {
238            return Err(Error::pkix(structured_pkix::Error::new(
239                structured_pkix::ErrorCode::ForeignReader,
240                field,
241                at,
242            )));
243        }
244        let execution = parent.execution();
245        let refusal = |reason| Error::resource(reason, field, at);
246        execution
247            .charge_work(self.pieces().len() as u64)
248            .map_err(refusal)?;
249        let mut output = execution.vector();
250        // The logical length was checked during the one structural traversal.
251        output.try_reserve_exact(self.length).map_err(refusal)?;
252        for piece in self.pieces() {
253            output.try_extend_from_slice(piece.bytes).map_err(refusal)?;
254        }
255        Ok(output.into_owned())
256    }
257
258    pub(crate) fn same_value(
259        &self,
260        other: &Octets<'_>,
261        parent: &Reader<'_>,
262        field: &'static str,
263        at: u64,
264    ) -> Result<bool> {
265        let execution = parent.execution();
266        execution
267            .charge_work(1)
268            .map_err(|reason| Error::resource(reason, field, at))?;
269        if self.length != other.length {
270            return Ok(false);
271        }
272        for amount in [self.pieces().len(), other.pieces().len(), self.length] {
273            execution
274                .charge_work(amount as u64)
275                .map_err(|reason| Error::resource(reason, field, at))?;
276        }
277        Ok(self
278            .pieces()
279            .iter()
280            .flat_map(|piece| piece.bytes.iter())
281            .eq(other.pieces().iter().flat_map(|piece| piece.bytes.iter())))
282    }
283}
284
285/// Why feeding attached ranges could not finish.
286#[derive(Debug)]
287pub enum SourceError {
288    /// Metadata traversal refused the current operation's resources.
289    Processing(Error),
290    /// The stream driver refused a chunk and retains its original cause.
291    Receiver(crate::stream::Refused),
292}
293
294impl crate::stream::Source for &Octets<'_> {
295    type Error = SourceError;
296
297    fn stream<W: Work>(
298        &mut self,
299        receiver: &mut impl crate::stream::Receiver,
300        execution: &W,
301    ) -> core::result::Result<(), Self::Error> {
302        for piece in self.pieces() {
303            execution.charge_work(1).map_err(|reason| {
304                SourceError::Processing(Error::resource(reason, self.field, piece.span.start))
305            })?;
306            receiver.write(piece.bytes).map_err(SourceError::Receiver)?;
307        }
308        Ok(())
309    }
310}