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 pub(crate) fn of(
63 content: &Octets<'_>,
64 parent: &Reader<'_>,
65 field: &'static str,
66 ) -> Result<Self> {
67 let ranges = if let [piece] = content.pieces() {
68 Ranges::One(piece.span())
69 } else {
70 let execution = parent.execution();
71 let refusal = |reason| Error::resource(reason, field, content.span().start);
72 execution
73 .charge_work(content.pieces().len() as u64)
74 .map_err(refusal)?;
75 let mut ranges = execution.vector();
76 ranges
77 .try_reserve_exact(content.pieces().len())
78 .map_err(refusal)?;
79 for piece in content.pieces() {
80 ranges.try_push(piece.span()).map_err(refusal)?;
81 }
82 Ranges::Many(ranges.into_owned())
83 };
84 Ok(Self {
85 ranges,
86 carrier: content.span(),
87 length: content.len() as u64,
88 empty_end: content.content_span().end(),
89 field,
90 })
91 }
92
93 fn mapped(
94 content: &Octets<'_>,
95 parent: &Reader<'_>,
96 field: &'static str,
97 origin: &Self,
98 ) -> Result<Self> {
99 let work = parent.execution();
100 let start = origin.project_offset(content.span().start, &work)?;
101 let end = origin.project_offset(content.span().end(), &work)?;
102 let empty_end = origin.project_offset(content.content_span().end(), &work)?;
103 if origin.coordinates() == Coordinates::Original {
104 return Self::of(content, parent, field);
107 }
108 let carrier = Span {
109 start,
110 length: end - start,
111 };
112 let refusal = |reason| Error::resource(reason, field, carrier.start);
113 work.charge_work(content.pieces().len() as u64)
114 .map_err(refusal)?;
115 let mut ranges = work.vector();
116 for piece in content.pieces() {
117 origin.append_projection(piece.span(), &work, &mut ranges)?;
118 }
119 Ok(Self {
120 ranges: Ranges::Many(ranges.into_owned()),
121 carrier,
122 length: content.len() as u64,
123 empty_end,
124 field,
125 })
126 }
127
128 #[must_use]
130 pub fn coordinates(&self) -> Coordinates {
131 match self.ranges {
132 Ranges::One(_) => Coordinates::Original,
133 Ranges::Many(_) => Coordinates::Logical,
134 }
135 }
136
137 #[must_use]
139 pub fn carrier(&self) -> Span {
140 self.carrier
141 }
142
143 fn ranges(&self) -> &[Span] {
144 match &self.ranges {
145 Ranges::One(span) => core::slice::from_ref(span),
146 Ranges::Many(spans) => spans,
147 }
148 }
149
150 fn logical_offset(&self, offset: u64) -> Result<u64> {
151 match self.ranges {
152 Ranges::One(span) if offset >= span.start && offset <= span.end() => {
153 Ok(offset - span.start)
154 }
155 Ranges::Many(_) if offset <= self.length => Ok(offset),
156 _ => Err(self.invalid()),
157 }
158 }
159
160 fn invalid(&self) -> Error {
161 Error::schema(ErrorCode::InvalidLocation, self.field, self.carrier.start)
162 }
163
164 fn admit(&self, execution: &impl Work) -> Result<()> {
165 execution
166 .charge_work(self.ranges().len() as u64)
167 .map_err(|reason| Error::resource(reason, self.field, self.carrier.start))
168 }
169
170 fn point(&self, logical: u64) -> u64 {
171 let mut base = 0;
172 for span in self.ranges() {
173 if logical < base + span.length {
174 return span.start + logical - base;
175 }
176 base += span.length;
177 }
178 self.ranges()
179 .last()
180 .map_or(self.empty_end, |span| span.end())
181 }
182
183 pub fn project_offset(&self, offset: u64, execution: &impl Work) -> Result<u64> {
190 let logical = self.logical_offset(offset)?;
191 self.admit(execution)?;
192 Ok(self.point(logical))
193 }
194
195 pub fn project_span(&self, span: Span, execution: &impl Work) -> Result<OwnedVec<Span>> {
201 let mut output = execution.vector();
202 self.append_projection(span, execution, &mut output)?;
203 Ok(output.into_owned())
204 }
205
206 fn append_projection<W: Work>(
207 &self,
208 span: Span,
209 execution: &W,
210 output: &mut OwnedVec<Span, W>,
211 ) -> Result<()> {
212 let end = span
213 .start
214 .checked_add(span.length)
215 .ok_or_else(|| self.invalid())?;
216 let start = self.logical_offset(span.start)?;
217 let end = self.logical_offset(end)?;
218 self.admit(execution)?;
219 let refusal = |reason| Error::resource(reason, self.field, self.carrier.start);
220 if start == end {
221 output
222 .try_push(Span {
223 start: self.point(start),
224 length: 0,
225 })
226 .map_err(refusal)?;
227 } else {
228 let mut base = 0;
229 for original in self.ranges() {
230 let next = base + original.length;
231 let low = start.max(base);
232 let high = end.min(next);
233 if low < high {
234 output
235 .try_push(Span {
236 start: original.start + low - base,
237 length: high - low,
238 })
239 .map_err(refusal)?;
240 }
241 base = next;
242 }
243 }
244 Ok(())
245 }
246}
247
248#[derive(Debug)]
250pub enum ContentError<E> {
251 Processing(Error),
253 Decoding {
255 source: Error,
257 context: SourceMap,
259 },
260 Callback {
269 source: E,
271 context: SourceMap,
273 },
274}
275
276impl Octets<'_> {
277 #[expect(
287 clippy::result_large_err,
288 reason = "the failure owns both the original cause and its source map; boxing would require another admitted allocation on the refusal path"
289 )]
290 pub fn with_nested_reader<R, E>(
291 &self,
292 parent: &Reader<'_>,
293 encoding: Encoding,
294 field: &'static str,
295 callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
296 ) -> core::result::Result<R, ContentError<E>> {
297 self.nested(parent, encoding, field, None, callback)
298 }
299
300 #[expect(
308 clippy::result_large_err,
309 reason = "retain original cause and source context without another allocation on refusal"
310 )]
311 pub fn with_nested_reader_mapped<R, E>(
312 &self,
313 parent: &Reader<'_>,
314 source: &SourceMap,
315 encoding: Encoding,
316 field: &'static str,
317 callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
318 ) -> core::result::Result<R, ContentError<E>> {
319 self.nested(parent, encoding, field, Some(source), callback)
320 }
321
322 #[expect(
323 clippy::result_large_err,
324 reason = "the owned source context must accompany its unchanged original cause"
325 )]
326 fn nested<R, E>(
327 &self,
328 parent: &Reader<'_>,
329 encoding: Encoding,
330 field: &'static str,
331 origin: Option<&SourceMap>,
332 callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
333 ) -> core::result::Result<R, ContentError<E>> {
334 if !core::ptr::eq(self.parser(), parent.parser()) {
335 let at = origin.map_or(self.span().start, |source| source.carrier().start);
338 return Err(ContentError::Processing(Error::pkix(
339 structured_pkix::Error::new(structured_pkix::ErrorCode::ForeignReader, field, at),
340 )));
341 }
342 let context = match origin {
343 Some(origin) => SourceMap::mapped(self, parent, field, origin),
344 None => SourceMap::of(self, parent, field),
345 }
346 .map_err(ContentError::Processing)?;
347 let joined;
348 let (bytes, base) = if let [piece] = self.pieces() {
349 let base = if context.coordinates() == Coordinates::Original {
350 piece.span().start
351 } else {
352 0
353 };
354 (piece.as_bytes(), base)
355 } else {
356 joined = self
357 .copy_in(parent, field, context.carrier().start)
358 .map_err(ContentError::Processing)?;
359 (&*joined, 0)
360 };
361 let reader =
362 self.parser()
363 .read_nested_query_in(parent, bytes, base, encoding, self.outer_depth());
364 let mut reader = match reader {
365 Ok(reader) => reader,
366 Err(source) => {
367 return Err(ContentError::Decoding {
368 source: Error::encoding(source, field),
369 context,
370 });
371 }
372 };
373 match callback(&mut reader, &context) {
374 Ok(output) => Ok(output),
375 Err(source) => Err(ContentError::Callback { source, context }),
376 }
377 }
378}