1use crate::{Error, ErrorCode, Octets, Result};
4use structured_asn1::memory::{OwnedVec, Work};
5use structured_asn1::{Encoding, Reader, Span};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Coordinates {
10 Original,
12 Logical,
14}
15
16#[derive(Debug)]
17enum Ranges {
18 One(Span),
19 Many(OwnedVec<Span>),
20}
21
22#[derive(Debug)]
29pub struct SourceMap {
30 ranges: Ranges,
31 carrier: Span,
32 length: u64,
33 empty_end: u64,
34 field: &'static str,
35}
36
37impl SourceMap {
38 pub fn try_copy_in<W: Work>(&self, work: &W) -> Result<Self> {
43 let fail = |reason| Error::resource(reason, self.field, self.carrier.start);
44 work.charge_work(1).map_err(fail)?;
45 let ranges = match &self.ranges {
46 Ranges::One(span) => Ranges::One(*span),
47 Ranges::Many(spans) => {
48 let mut copy = work.vector();
49 copy.try_extend_from_slice(spans).map_err(fail)?;
50 Ranges::Many(copy.into_owned())
51 }
52 };
53 Ok(Self {
54 ranges,
55 carrier: self.carrier,
56 length: self.length,
57 empty_end: self.empty_end,
58 field: self.field,
59 })
60 }
61
62 fn of(content: &Octets<'_>, parent: &Reader<'_>, field: &'static str) -> Result<Self> {
63 let ranges = if let [piece] = content.pieces() {
64 Ranges::One(piece.span())
65 } else {
66 let execution = parent.execution();
67 let refusal = |reason| Error::resource(reason, field, content.span().start);
68 execution
69 .charge_work(content.pieces().len() as u64)
70 .map_err(refusal)?;
71 let mut ranges = execution.vector();
72 ranges
73 .try_reserve_exact(content.pieces().len())
74 .map_err(refusal)?;
75 for piece in content.pieces() {
76 ranges.try_push(piece.span()).map_err(refusal)?;
77 }
78 Ranges::Many(ranges.into_owned())
79 };
80 Ok(Self {
81 ranges,
82 carrier: content.span(),
83 length: content.len() as u64,
84 empty_end: content.content_span().end(),
85 field,
86 })
87 }
88
89 fn mapped(
90 content: &Octets<'_>,
91 parent: &Reader<'_>,
92 field: &'static str,
93 origin: &Self,
94 ) -> Result<Self> {
95 let work = parent.execution();
96 let start = origin.project_offset(content.span().start, &work)?;
97 let end = origin.project_offset(content.span().end(), &work)?;
98 let empty_end = origin.project_offset(content.content_span().end(), &work)?;
99 if origin.coordinates() == Coordinates::Original {
100 return Self::of(content, parent, field);
103 }
104 let carrier = Span {
105 start,
106 length: end - start,
107 };
108 let refusal = |reason| Error::resource(reason, field, carrier.start);
109 work.charge_work(content.pieces().len() as u64)
110 .map_err(refusal)?;
111 let mut ranges = work.vector();
112 for piece in content.pieces() {
113 origin.append_projection(piece.span(), &work, &mut ranges)?;
114 }
115 Ok(Self {
116 ranges: Ranges::Many(ranges.into_owned()),
117 carrier,
118 length: content.len() as u64,
119 empty_end,
120 field,
121 })
122 }
123
124 #[must_use]
126 pub fn coordinates(&self) -> Coordinates {
127 match self.ranges {
128 Ranges::One(_) => Coordinates::Original,
129 Ranges::Many(_) => Coordinates::Logical,
130 }
131 }
132
133 #[must_use]
135 pub fn carrier(&self) -> Span {
136 self.carrier
137 }
138
139 fn ranges(&self) -> &[Span] {
140 match &self.ranges {
141 Ranges::One(span) => core::slice::from_ref(span),
142 Ranges::Many(spans) => spans,
143 }
144 }
145
146 fn logical_offset(&self, offset: u64) -> Result<u64> {
147 match self.ranges {
148 Ranges::One(span) if offset >= span.start && offset <= span.end() => {
149 Ok(offset - span.start)
150 }
151 Ranges::Many(_) if offset <= self.length => Ok(offset),
152 _ => Err(self.invalid()),
153 }
154 }
155
156 fn invalid(&self) -> Error {
157 Error::schema(ErrorCode::InvalidLocation, self.field, self.carrier.start)
158 }
159
160 fn admit(&self, execution: &impl Work) -> Result<()> {
161 execution
162 .charge_work(self.ranges().len() as u64)
163 .map_err(|reason| Error::resource(reason, self.field, self.carrier.start))
164 }
165
166 fn point(&self, logical: u64) -> u64 {
167 let mut base = 0;
168 for span in self.ranges() {
169 if logical < base + span.length {
170 return span.start + logical - base;
171 }
172 base += span.length;
173 }
174 self.ranges()
175 .last()
176 .map_or(self.empty_end, |span| span.end())
177 }
178
179 pub fn project_offset(&self, offset: u64, execution: &impl Work) -> Result<u64> {
186 let logical = self.logical_offset(offset)?;
187 self.admit(execution)?;
188 Ok(self.point(logical))
189 }
190
191 pub fn project_span(&self, span: Span, execution: &impl Work) -> Result<OwnedVec<Span>> {
197 let mut output = execution.vector();
198 self.append_projection(span, execution, &mut output)?;
199 Ok(output.into_owned())
200 }
201
202 fn append_projection<W: Work>(
203 &self,
204 span: Span,
205 execution: &W,
206 output: &mut OwnedVec<Span, W>,
207 ) -> Result<()> {
208 let end = span
209 .start
210 .checked_add(span.length)
211 .ok_or_else(|| self.invalid())?;
212 let start = self.logical_offset(span.start)?;
213 let end = self.logical_offset(end)?;
214 self.admit(execution)?;
215 let refusal = |reason| Error::resource(reason, self.field, self.carrier.start);
216 if start == end {
217 output
218 .try_push(Span {
219 start: self.point(start),
220 length: 0,
221 })
222 .map_err(refusal)?;
223 } else {
224 let mut base = 0;
225 for original in self.ranges() {
226 let next = base + original.length;
227 let low = start.max(base);
228 let high = end.min(next);
229 if low < high {
230 output
231 .try_push(Span {
232 start: original.start + low - base,
233 length: high - low,
234 })
235 .map_err(refusal)?;
236 }
237 base = next;
238 }
239 }
240 Ok(())
241 }
242}
243
244#[derive(Debug)]
246pub enum ContentError<E> {
247 Processing(Error),
249 Decoding {
251 source: Error,
253 context: SourceMap,
255 },
256 Callback {
265 source: E,
267 context: SourceMap,
269 },
270}
271
272impl Octets<'_> {
273 #[expect(
283 clippy::result_large_err,
284 reason = "the failure owns both the original cause and its source map; boxing would require another admitted allocation on the refusal path"
285 )]
286 pub fn with_nested_reader<R, E>(
287 &self,
288 parent: &Reader<'_>,
289 encoding: Encoding,
290 field: &'static str,
291 callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
292 ) -> core::result::Result<R, ContentError<E>> {
293 self.nested(parent, encoding, field, None, callback)
294 }
295
296 #[expect(
304 clippy::result_large_err,
305 reason = "retain original cause and source context without another allocation on refusal"
306 )]
307 pub fn with_nested_reader_mapped<R, E>(
308 &self,
309 parent: &Reader<'_>,
310 source: &SourceMap,
311 encoding: Encoding,
312 field: &'static str,
313 callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
314 ) -> core::result::Result<R, ContentError<E>> {
315 self.nested(parent, encoding, field, Some(source), callback)
316 }
317
318 #[expect(
319 clippy::result_large_err,
320 reason = "the owned source context must accompany its unchanged original cause"
321 )]
322 fn nested<R, E>(
323 &self,
324 parent: &Reader<'_>,
325 encoding: Encoding,
326 field: &'static str,
327 origin: Option<&SourceMap>,
328 callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
329 ) -> core::result::Result<R, ContentError<E>> {
330 if !core::ptr::eq(self.parser(), parent.parser()) {
331 let at = origin.map_or(self.span().start, |source| source.carrier().start);
334 return Err(ContentError::Processing(Error::pkix(
335 structured_pkix::Error::new(structured_pkix::ErrorCode::ForeignReader, field, at),
336 )));
337 }
338 let context = match origin {
339 Some(origin) => SourceMap::mapped(self, parent, field, origin),
340 None => SourceMap::of(self, parent, field),
341 }
342 .map_err(ContentError::Processing)?;
343 let joined;
344 let (bytes, base) = if let [piece] = self.pieces() {
345 let base = if context.coordinates() == Coordinates::Original {
346 piece.span().start
347 } else {
348 0
349 };
350 (piece.as_bytes(), base)
351 } else {
352 joined = self
353 .copy_in(parent, field, context.carrier().start)
354 .map_err(ContentError::Processing)?;
355 (&*joined, 0)
356 };
357 let reader =
358 self.parser()
359 .read_nested_query_in(parent, bytes, base, encoding, self.outer_depth());
360 let mut reader = match reader {
361 Ok(reader) => reader,
362 Err(source) => {
363 return Err(ContentError::Decoding {
364 source: Error::encoding(source, field),
365 context,
366 });
367 }
368 };
369 match callback(&mut reader, &context) {
370 Ok(output) => Ok(output),
371 Err(source) => Err(ContentError::Callback { source, context }),
372 }
373 }
374}