bacnet_encoding/constructed/tagged.rs
1//! Decode helpers for tagged fields, shared by the constructed codecs, the
2//! formal Error bodies in `apdu/` and the service parameters bacnet-services
3//! decodes.
4//!
5//! Each helper reads one field at an offset and returns what it read with the
6//! offset just past it. Each takes the `what` label its codec passes (the
7//! production, sometimes narrowed to one alternative or member), so every
8//! codec words a refusal the same way:
9//!
10//! - a tag of the wrong number, class or form: `{what}: expected context tag
11//! [n]`, `{what}: expected opening tag [n]`, `{what}: expected closing tag
12//! [n]` or `{what}: expected application-tagged Unsigned`, reported at the
13//! offending tag;
14//! - a fixed-size primitive of the wrong length: `{what}: [n] REAL has 5
15//! contents octets, expected 4`;
16//! - an Unsigned or ENUMERATED too large for its field: `{what}: [n] value
17//! 70000 exceeds u16`, or `{what}: Unsigned exceeds u16` for an
18//! application-tagged one;
19//! - bytes left after a value that must fill its input: `{what}: 2 trailing
20//! byte(s)`.
21//!
22//! One error-kind rule holds across the crate's decoders (#1333):
23//!
24//! - Contents that run past the end of the data fail with
25//! [`Error::BufferTooShort`], wherever the member stands: at the top level,
26//! or inside a constructed frame, where [`tags::extract_context_value`]
27//! finds it while [`decode_ctx_constructed`] or `decode_framed_value`
28//! extracts the frame.
29//! - A fixed-size member has its length checked against its header before
30//! its contents are read, so a wrong length is [`Error::Decoding`] even
31//! when the data also stops early. That covers the context-tagged ones (an
32//! object identifier, REAL, BOOLEAN, or any type read with
33//! [`decode_ctx_fixed`]), the application-tagged ones read with
34//! [`decode_app_fixed`] or [`decode_app_object_id`], and the REAL, Double,
35//! Date, Time and object identifier that
36//! `primitives::decode_application_value` reads.
37//! - Every other refusal is [`Error::Decoding`], including a tag header cut
38//! short, which [`tags::decode_tag`] refuses, and a frame whose closing tag
39//! never comes.
40//!
41//! Every label is a `&'static str`, and each refusal is a [`DecodeError`]
42//! that builds its text only when displayed (#1646). The readers that return
43//! only borrowed octets and numbers need no allocator; those returning a
44//! copy, a `String` or a `PropertyValue` need `alloc`.
45//!
46//! An [`Error::Decoding`] carries the [`DecodingKind`] of the fault, which a
47//! responder turns into a Reject reason (#1446):
48//!
49//! - [`DecodingKind::Missing`]: the data ends where a required member, a
50//! tag header's remaining octets or a frame's closing tag is due, or the
51//! tag found there shows the member was left out (see [`misplaced_kind`]);
52//! - [`DecodingKind::InvalidTag`]: any other tag that doesn't fit where it
53//! stands, a malformed tag header, or nesting deeper than the walk takes;
54//! - [`DecodingKind::Trailing`]: octets after the last member of a value
55//! that must fill its input ([`expect_end`]) or close its frame
56//! ([`expect_closing`]), unless they open with a closing tag or a header
57//! that doesn't decode, which is [`DecodingKind::InvalidTag`];
58//! - [`DecodingKind::OutOfRange`]: an Unsigned or ENUMERATED too large for
59//! its field;
60//! - [`DecodingKind::Overflow`]: more items than a framed value takes, or a
61//! tag length past its sanity bound;
62//! - [`DecodingKind::InvalidEncoding`]: contents whose encoding isn't valid
63//! for their datatype, such as a fixed-size member of the wrong length,
64//! the kind [`Error::decoding`] makes.
65//!
66//! The helpers another crate needs are public: the peeks for an optional
67//! member, a frame's opening and closing tags and its body, the context
68//! readers for contents, fixed-size contents, Unsigned or ENUMERATED values,
69//! REAL, BOOLEAN, BIT STRING, OCTET STRING, CharacterString and object
70//! identifiers, the optional-member wrapper, the application readers, the
71//! trailing-data check, and the kinds a misplaced tag is ([`misplaced_kind`],
72//! [`misplaced_tag`], [`unclosed_kind`]). The rest stay private to this
73//! crate.
74//!
75//! ```
76//! use bacnet_encoding::constructed::tagged::{decode_ctx_unsigned, expect_end, next_is_context};
77//! use bacnet_types::error::Error;
78//!
79//! // A required `[0]` Unsigned 7 and no optional `[1]` after it.
80//! let data = [0x09, 0x07];
81//! let (value, end) = decode_ctx_unsigned::<u32>(&data, 0, 0, "Example")?;
82//! assert_eq!((value, end), (7, 2));
83//! assert!(!next_is_context(&data, end, 1)?);
84//! expect_end(&data, end, end, "Example")?;
85//!
86//! // The same member announcing two contents octets but holding one.
87//! let cut = decode_ctx_unsigned::<u32>(&[0x0A, 0x07], 0, 0, "Example");
88//! assert!(matches!(cut, Err(Error::BufferTooShort { need: 3, have: 2 })));
89//! # Ok::<(), Error>(())
90//! ```
91
92#[cfg(all(feature = "alloc", not(feature = "std")))]
93use alloc::{string::String, vec::Vec};
94
95use bacnet_types::error::{DecodeError, DecodingKind, Error};
96use bacnet_types::primitives::ObjectIdentifier;
97#[cfg(feature = "alloc")]
98use bacnet_types::primitives::PropertyValue;
99
100#[cfg(feature = "alloc")]
101use super::MAX_FRAMED_ITEMS;
102use crate::primitives;
103use crate::tags::{self, MAX_CONTEXT_NESTING_DEPTH, Tag, TagClass};
104
105// ---------------------------------------------------------------------------
106// Peeking at the next tag
107// ---------------------------------------------------------------------------
108
109/// Whether the tag at `offset` passes `test`; `false` at the end of the data.
110fn next_tag_is(data: &[u8], offset: usize, test: impl FnOnce(&Tag) -> bool) -> Result<bool, Error> {
111 if offset >= data.len() {
112 return Ok(false);
113 }
114 Ok(test(&tags::decode_tag(data, offset)?.0))
115}
116
117/// Whether a primitive context tag `tag` starts at `offset`; `false` at the
118/// end of the data, so an optional member may be the last one. A malformed
119/// tag there is an error.
120pub fn next_is_context(data: &[u8], offset: usize, tag: u8) -> Result<bool, Error> {
121 next_tag_is(data, offset, |t| t.is_context(tag))
122}
123
124/// Whether an opening context tag `tag` starts at `offset`; `false` at the
125/// end of the data. A malformed tag there is an error.
126pub fn next_is_opening(data: &[u8], offset: usize, tag: u8) -> Result<bool, Error> {
127 next_tag_is(data, offset, |t| t.is_opening_tag(tag))
128}
129
130/// Whether an application tag `number` (one of [`tags::app_tag`]) starts at
131/// `offset`; `false` at the end of the data, so an optional
132/// application-tagged member may be the last one. A malformed tag there is an
133/// error.
134pub fn next_is_application(data: &[u8], offset: usize, number: u8) -> Result<bool, Error> {
135 next_tag_is(data, offset, |t| {
136 t.class == TagClass::Application && t.number == number
137 })
138}
139
140/// Whether a closing context tag `tag` starts at `offset`, for walking the
141/// items inside a frame. Unlike [`next_is_context`], running out of data is
142/// an error: the frame never closed.
143#[cfg(feature = "alloc")]
144pub(crate) fn next_is_closing(data: &[u8], offset: usize, tag: u8) -> Result<bool, Error> {
145 Ok(tags::decode_tag(data, offset)?.0.is_closing_tag(tag))
146}
147
148// ---------------------------------------------------------------------------
149// Frames and whole inputs
150// ---------------------------------------------------------------------------
151
152/// The kind of fault the tag `found`, at `at` in `data`, is where a required
153/// member is due.
154///
155/// `expected` is the member's context tag number, or `None` for an
156/// application-tagged member or a CHOICE none of whose alternatives came.
157/// The member was left out, [`DecodingKind::Missing`], when the tag closes
158/// the frame open around it, so the frame ended first, or is a context tag
159/// numbered above `expected`, since members follow in tag order. Any other
160/// tag doesn't fit there, [`DecodingKind::InvalidTag`]: a closing tag that
161/// closes no open frame (one at the top level, or of another number) among
162/// them.
163pub fn misplaced_kind(data: &[u8], at: usize, found: &Tag, expected: Option<u8>) -> DecodingKind {
164 let later = expected.is_some_and(|number| {
165 found.class == TagClass::Context && !found.is_closing && found.number > number
166 });
167 if later || (found.is_closing && closes_open_frame(data, at, found.number)) {
168 DecodingKind::Missing
169 } else {
170 DecodingKind::InvalidTag
171 }
172}
173
174/// Whether a closing tag `number` at `at` closes the innermost frame open
175/// there, found by walking the tags of `data` from its start. A walk that
176/// doesn't land on `at` (contents that aren't tags) says no, and so does one
177/// that finds more than [`MAX_CONTEXT_NESTING_DEPTH`] frames open at once.
178/// The depth counts from the start of `data`, the frames around the member's
179/// production included, so well-formed input nested less than that deep in
180/// its own production can still reach past it. Saying no is the safe
181/// answer: the tag is then [`DecodingKind::InvalidTag`] rather than
182/// [`DecodingKind::Missing`], and the input is refused either way.
183fn closes_open_frame(data: &[u8], at: usize, number: u8) -> bool {
184 let mut open = [0u8; MAX_CONTEXT_NESTING_DEPTH];
185 let mut depth = 0;
186 let mut pos = 0;
187 while pos < at {
188 let Ok((tag, next)) = tags::decode_tag(data, pos) else {
189 return false;
190 };
191 pos = if tag.is_opening {
192 let Some(slot) = open.get_mut(depth) else {
193 return false;
194 };
195 *slot = tag.number;
196 depth += 1;
197 next
198 } else if tag.is_closing {
199 if depth == 0 || open[depth - 1] != tag.number {
200 return false;
201 }
202 depth -= 1;
203 next
204 } else if tag.class == TagClass::Application && tag.number == tags::app_tag::BOOLEAN {
205 next
206 } else {
207 next.saturating_add(tag.length as usize)
208 };
209 }
210 pos == at && depth > 0 && open[depth - 1] == number
211}
212
213/// The error for the tag `found`, at `offset` in `data`, where a required
214/// member is due, of the kind [`misplaced_kind`] gives, described by
215/// `label`. [`misplaced`] gives the same fault as a [`DecodeError`], to
216/// name a context or fill the label's placeholders.
217pub fn misplaced_tag(
218 data: &[u8],
219 found: &Tag,
220 expected: Option<u8>,
221 offset: usize,
222 label: &'static str,
223) -> Error {
224 misplaced(data, found, expected, offset, label).into()
225}
226
227/// [`misplaced_tag`]'s fault as a [`DecodeError`], for a caller that adds a
228/// context or values before returning it.
229pub fn misplaced(
230 data: &[u8],
231 found: &Tag,
232 expected: Option<u8>,
233 offset: usize,
234 label: &'static str,
235) -> DecodeError {
236 DecodeError::new(misplaced_kind(data, offset, found, expected), offset, label)
237}
238
239/// The refusal of the tag `found` at `offset` where `what`'s member is due:
240/// context tag `expected`, or for `None` an application-tagged one. `label`
241/// names the member through its one placeholder, which `named` fills: the
242/// context tag's number, or the application tag's.
243#[cold]
244fn misplaced_member(
245 data: &[u8],
246 found: &Tag,
247 expected: Option<u8>,
248 offset: usize,
249 what: &'static str,
250 (label, named): (&'static str, u8),
251) -> Error {
252 DecodeError::new(misplaced_kind(data, offset, found, expected), offset, label)
253 .within(what)
254 .with_value(named.into())
255 .into()
256}
257
258/// The kind of fault the tag `found` is where a frame's closing tag is due:
259/// another closing tag is [`DecodingKind::InvalidTag`]; any other tag is a
260/// member the frame holds beyond its last one, [`DecodingKind::Trailing`].
261pub fn unclosed_kind(found: &Tag) -> DecodingKind {
262 if found.is_closing {
263 DecodingKind::InvalidTag
264 } else {
265 DecodingKind::Trailing
266 }
267}
268
269/// Require an opening context tag `tag` at `offset`; return the offset of its
270/// content.
271pub fn expect_opening(
272 data: &[u8],
273 offset: usize,
274 tag: u8,
275 what: &'static str,
276) -> Result<usize, Error> {
277 let (t, pos) = tags::decode_tag(data, offset)?;
278 if !t.is_opening_tag(tag) {
279 return Err(misplaced_member(
280 data,
281 &t,
282 Some(tag),
283 offset,
284 what,
285 ("expected opening tag [{}]", tag),
286 ));
287 }
288 Ok(pos)
289}
290
291/// Require a closing context tag `tag` at `offset`; return the offset past it.
292/// Any other tag there is of the kind [`unclosed_kind`] gives.
293pub fn expect_closing(
294 data: &[u8],
295 offset: usize,
296 tag: u8,
297 what: &'static str,
298) -> Result<usize, Error> {
299 let (t, pos) = tags::decode_tag(data, offset)?;
300 if !t.is_closing_tag(tag) {
301 return Err(
302 DecodeError::new(unclosed_kind(&t), offset, "expected closing tag [{}]")
303 .within(what)
304 .with_value(tag.into())
305 .into(),
306 );
307 }
308 Ok(pos)
309}
310
311/// Require constructed context tag `tag` at `offset`; return its body (the
312/// octets between its opening and closing tags, with nested frames balanced)
313/// and the offset past its closing tag.
314///
315/// A member inside the frame whose contents run past the end of the data is
316/// found while the frame is extracted, and is [`Error::BufferTooShort`], as
317/// it would be outside the frame. Once the body is extracted, every member
318/// in it fits.
319pub fn decode_ctx_constructed<'a>(
320 data: &'a [u8],
321 offset: usize,
322 tag: u8,
323 what: &'static str,
324) -> Result<(&'a [u8], usize), Error> {
325 let content = expect_opening(data, offset, tag, what)?;
326 tags::extract_context_value(data, content, tag)
327}
328
329/// Decode an `ABSTRACT-SYNTAX.&Type` value framed in an opening/closing
330/// context tag `tag` pair whose content starts at `content`; returns it and
331/// the offset past the closing tag.
332///
333/// One application element decodes as itself and any other count, none
334/// included, as a [`PropertyValue::List`] (an array written or read whole). A
335/// context-tagged element decodes to [`PropertyValue::ApplicationData`], so
336/// the value re-encodes to the same octets. Only with `alloc`.
337#[cfg(feature = "alloc")]
338pub(crate) fn decode_framed_value(
339 data: &[u8],
340 content: usize,
341 tag: u8,
342 what: &'static str,
343) -> Result<(PropertyValue, usize), Error> {
344 let (inner, end) = tags::extract_context_value(data, content, tag)?;
345 let body = &data[..content + inner.len()];
346 let mut values = Vec::new();
347 let mut offset = content;
348 while offset < body.len() {
349 if values.len() >= MAX_FRAMED_ITEMS {
350 return Err(DecodeError::new(
351 DecodingKind::Overflow,
352 offset,
353 "value exceeds item limit",
354 )
355 .within(what)
356 .into());
357 }
358 let (value, next) = primitives::decode_application_value(body, offset)?;
359 values.push(value);
360 offset = next;
361 }
362 let value = match <[PropertyValue; 1]>::try_from(values) {
363 Ok([value]) => value,
364 Err(values) => PropertyValue::List(values),
365 };
366 Ok((value, end))
367}
368
369/// Require a value that stopped at `end` to have used all of `data`.
370///
371/// Octets left over are [`DecodingKind::Trailing`], arguments past the
372/// value's last member, unless they open with a closing tag, which closes
373/// nothing, or with a tag header that doesn't decode: those are
374/// [`DecodingKind::InvalidTag`].
375///
376/// `at` is the offset the error reports: `end` itself when `data` is the
377/// whole input, or the frame's own offset in the larger input when `data` is
378/// a frame body sliced out of it (offsets into the slice would mislead).
379pub fn expect_end(data: &[u8], end: usize, at: usize, what: &'static str) -> Result<(), Error> {
380 if end == data.len() {
381 return Ok(());
382 }
383 let kind = match tags::decode_tag(data, end) {
384 Ok((tag, _)) if !tag.is_closing => DecodingKind::Trailing,
385 _ => DecodingKind::InvalidTag,
386 };
387 Err(DecodeError::new(kind, at, "{} trailing byte(s)")
388 .within(what)
389 .with_value(data.len().saturating_sub(end) as u64)
390 .into())
391}
392
393// ---------------------------------------------------------------------------
394// Primitive contents
395// ---------------------------------------------------------------------------
396
397/// The `length` contents octets of a primitive that start at `start`, and the
398/// offset past them; contents that run past the end of `data` are
399/// [`Error::BufferTooShort`].
400///
401/// [`tags::decode_tag`] caps a length at 1 MiB and puts `start` inside
402/// `data`, so the end can't overflow; saturating keeps that true for any
403/// caller.
404#[inline]
405pub(crate) fn contents(data: &[u8], start: usize, length: u32) -> Result<(&[u8], usize), Error> {
406 let end = usize::try_from(length).map_or(usize::MAX, |length| start.saturating_add(length));
407 if end > data.len() {
408 return Err(Error::buffer_too_short(end, data.len()));
409 }
410 Ok((&data[start..end], end))
411}
412
413/// Require a primitive context tag `tag` at `offset`; return its header and
414/// the offset of its contents. `misplaced` is the refusal's label, its `{}`
415/// the tag number.
416#[inline]
417fn ctx_header(
418 data: &[u8],
419 offset: usize,
420 tag: u8,
421 misplaced: &'static str,
422 what: &'static str,
423) -> Result<(Tag, usize), Error> {
424 let (t, start) = tags::decode_tag(data, offset)?;
425 if !t.is_context(tag) {
426 return Err(misplaced_member(
427 data,
428 &t,
429 Some(tag),
430 offset,
431 what,
432 (misplaced, tag),
433 ));
434 }
435 Ok((t, start))
436}
437
438/// Require a primitive context tag `tag` at `offset`; return its contents and
439/// the offset past them.
440#[inline]
441pub fn decode_ctx_primitive<'a>(
442 data: &'a [u8],
443 offset: usize,
444 tag: u8,
445 what: &'static str,
446) -> Result<(&'a [u8], usize), Error> {
447 let (t, start) = ctx_header(data, offset, tag, "expected context tag [{}]", what)?;
448 contents(data, start, t.length)
449}
450
451/// A fixed-size primitive type [`decode_ctx_fixed`] reads: how many contents
452/// octets it has, and the labels of its two refusals.
453///
454/// The consts cover the types the shared readers take. A codec with another
455/// one builds its own, its labels in the same form:
456///
457/// ```
458/// use bacnet_encoding::constructed::tagged::{decode_ctx_fixed, Fixed};
459///
460/// const MASK: Fixed = Fixed {
461/// octets: 4,
462/// misplaced: "expected context tag [{}] broadcast mask",
463/// wrong_length: "[{}] broadcast mask has {} contents octets, expected 4",
464/// };
465/// let refused = decode_ctx_fixed(&[0x1B, 255, 255, 0], 0, 1, MASK, "BDT entry").unwrap_err();
466/// assert_eq!(
467/// refused.to_string(),
468/// "decoding error at offset 0: BDT entry: [1] broadcast mask has 3 contents octets, expected 4"
469/// );
470/// ```
471#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
472pub struct Fixed {
473 /// The contents octets every value of the type has.
474 pub octets: u32,
475 /// The label when another tag stands where the member's is due; its `{}`
476 /// is the member's tag number.
477 pub misplaced: &'static str,
478 /// The label when the member's header announces another length; its two
479 /// `{}` are the tag number and the length announced.
480 pub wrong_length: &'static str,
481}
482
483/// [`Fixed`] for the type `$name` of `$octets` octets.
484macro_rules! fixed {
485 ($name:literal, $octets:literal) => {
486 Fixed {
487 octets: $octets,
488 misplaced: concat!("expected context tag [{}] ", $name),
489 wrong_length: concat!(
490 "[{}] ",
491 $name,
492 " has {} contents octets, expected ",
493 $octets
494 ),
495 }
496 };
497}
498
499impl Fixed {
500 /// A REAL: four octets.
501 pub const REAL: Self = fixed!("REAL", 4);
502 /// A Double: eight octets.
503 pub const DOUBLE: Self = fixed!("Double", 8);
504 /// A BOOLEAN: one octet.
505 pub const BOOLEAN: Self = fixed!("BOOLEAN", 1);
506 /// A Date: four octets.
507 pub const DATE: Self = fixed!("Date", 4);
508 /// A Time: four octets.
509 pub const TIME: Self = fixed!("Time", 4);
510 /// An object identifier: four octets.
511 pub const OBJECT_IDENTIFIER: Self = fixed!("object identifier", 4);
512}
513
514/// Require a primitive context tag `tag` at `offset` holding exactly
515/// `fixed.octets` contents octets of that type; return those contents and the
516/// offset past them. A header announcing any other length is
517/// [`Error::Decoding`] even when the data also stops early.
518#[inline]
519pub fn decode_ctx_fixed<'a>(
520 data: &'a [u8],
521 offset: usize,
522 tag: u8,
523 fixed: Fixed,
524 what: &'static str,
525) -> Result<(&'a [u8], usize), Error> {
526 let (t, start) = ctx_header(data, offset, tag, fixed.misplaced, what)?;
527 if t.length != fixed.octets {
528 return Err(ctx_length_error(offset, tag, fixed, what, t.length));
529 }
530 contents(data, start, fixed.octets)
531}
532
533/// Report a wrong fixed length before attempting to read its contents.
534#[cold]
535fn ctx_length_error(
536 offset: usize,
537 tag: u8,
538 fixed: Fixed,
539 what: &'static str,
540 length: u32,
541) -> Error {
542 DecodeError::new(DecodingKind::InvalidEncoding, offset, fixed.wrong_length)
543 .within(what)
544 .with_values(tag.into(), length.into())
545 .into()
546}
547
548/// Require a primitive context tag `tag` at `offset` holding a REAL.
549pub fn decode_ctx_real(
550 data: &[u8],
551 offset: usize,
552 tag: u8,
553 what: &'static str,
554) -> Result<(f32, usize), Error> {
555 let (octets, end) = decode_ctx_fixed(data, offset, tag, Fixed::REAL, what)?;
556 Ok((primitives::decode_real(octets)?, end))
557}
558
559/// Require a primitive context tag `tag` at `offset` holding a BOOLEAN: one
560/// contents octet, 0 or 1 (Clause 20.2.3).
561pub fn decode_ctx_boolean(
562 data: &[u8],
563 offset: usize,
564 tag: u8,
565 what: &'static str,
566) -> Result<(bool, usize), Error> {
567 let (octets, end) = decode_ctx_fixed(data, offset, tag, Fixed::BOOLEAN, what)?;
568 match octets[0] {
569 0 => Ok((false, end)),
570 1 => Ok((true, end)),
571 _ => Err(DecodeError::new(
572 DecodingKind::InvalidEncoding,
573 end - 1,
574 "[{}] BOOLEAN contents must be 0 or 1",
575 )
576 .within(what)
577 .with_value(tag.into())
578 .into()),
579 }
580}
581
582/// Require a primitive context tag `tag` at `offset` holding an object
583/// identifier, which is always four octets.
584#[inline]
585pub fn decode_ctx_object_id(
586 data: &[u8],
587 offset: usize,
588 tag: u8,
589 what: &'static str,
590) -> Result<(ObjectIdentifier, usize), Error> {
591 let (octets, end) = decode_ctx_fixed(data, offset, tag, Fixed::OBJECT_IDENTIFIER, what)?;
592 Ok((ObjectIdentifier::decode(octets)?, end))
593}
594
595/// Require a primitive context tag `tag` at `offset` holding a BIT STRING;
596/// returns its unused-bit count and a copy of its data octets. Only with
597/// `alloc`; [`decode_ctx_bit_string_ref`] borrows the octets.
598#[cfg(feature = "alloc")]
599pub fn decode_ctx_bit_string(
600 data: &[u8],
601 offset: usize,
602 tag: u8,
603 what: &'static str,
604) -> Result<((u8, Vec<u8>), usize), Error> {
605 let ((unused, bits), end) = decode_ctx_bit_string_ref(data, offset, tag, what)?;
606 Ok(((unused, bits.to_vec()), end))
607}
608
609/// Require a primitive context tag `tag` at `offset` holding a BIT STRING;
610/// returns its unused-bit count and data octets, borrowed from `data`.
611pub fn decode_ctx_bit_string_ref<'a>(
612 data: &'a [u8],
613 offset: usize,
614 tag: u8,
615 what: &'static str,
616) -> Result<((u8, &'a [u8]), usize), Error> {
617 let (t, start) = ctx_header(
618 data,
619 offset,
620 tag,
621 "expected context tag [{}] BIT STRING",
622 what,
623 )?;
624 let (octets, end) = contents(data, start, t.length)?;
625 Ok((primitives::decode_bit_string_ref(octets)?, end))
626}
627
628/// Require a primitive context tag `tag` at `offset` holding an OCTET STRING;
629/// returns a copy of its octets. Only with `alloc`;
630/// [`decode_ctx_octet_string_ref`] borrows them.
631#[cfg(feature = "alloc")]
632pub fn decode_ctx_octet_string(
633 data: &[u8],
634 offset: usize,
635 tag: u8,
636 what: &'static str,
637) -> Result<(Vec<u8>, usize), Error> {
638 let (octets, end) = decode_ctx_octet_string_ref(data, offset, tag, what)?;
639 Ok((octets.to_vec(), end))
640}
641
642/// Require a primitive context tag `tag` at `offset` holding an OCTET STRING;
643/// returns its octets, borrowed from `data`.
644pub fn decode_ctx_octet_string_ref<'a>(
645 data: &'a [u8],
646 offset: usize,
647 tag: u8,
648 what: &'static str,
649) -> Result<(&'a [u8], usize), Error> {
650 let (t, start) = ctx_header(
651 data,
652 offset,
653 tag,
654 "expected context tag [{}] OCTET STRING",
655 what,
656 )?;
657 contents(data, start, t.length)
658}
659
660/// Require a primitive context tag `tag` at `offset` holding a
661/// CharacterString. Only with `alloc`; [`decode_ctx_character_string_ref`]
662/// reads it without converting it.
663#[cfg(feature = "alloc")]
664pub fn decode_ctx_character_string(
665 data: &[u8],
666 offset: usize,
667 tag: u8,
668 what: &'static str,
669) -> Result<(String, usize), Error> {
670 let (octets, end) = decode_ctx_primitive(data, offset, tag, what)?;
671 Ok((primitives::decode_character_string(octets)?, end))
672}
673
674/// Require a primitive context tag `tag` at `offset` holding a
675/// CharacterString; returns it borrowed from `data`, its character set
676/// checked (see [`primitives::CharacterStringRef::decode`]).
677pub fn decode_ctx_character_string_ref<'a>(
678 data: &'a [u8],
679 offset: usize,
680 tag: u8,
681 what: &'static str,
682) -> Result<(primitives::CharacterStringRef<'a>, usize), Error> {
683 let (octets, end) = decode_ctx_primitive(data, offset, tag, what)?;
684 Ok((primitives::CharacterStringRef::decode(octets)?, end))
685}
686
687/// Read the optional member under primitive context tag `tag` with `decode`
688/// when that tag comes next; otherwise `None` with the offset unchanged.
689/// What `decode` returns may borrow from `data`, so [`decode_ctx_primitive`]
690/// yields the member's contents.
691pub fn decode_optional_ctx<'a, T>(
692 data: &'a [u8],
693 offset: usize,
694 tag: u8,
695 what: &'static str,
696 decode: impl FnOnce(&'a [u8], usize, u8, &'static str) -> Result<(T, usize), Error>,
697) -> Result<(Option<T>, usize), Error> {
698 if !next_is_context(data, offset, tag)? {
699 return Ok((None, offset));
700 }
701 let (value, end) = decode(data, offset, tag, what)?;
702 Ok((Some(value), end))
703}
704
705// ---------------------------------------------------------------------------
706// Unsigned and ENUMERATED
707// ---------------------------------------------------------------------------
708
709/// An unsigned integer type a decoded Unsigned or ENUMERATED is narrowed to:
710/// `u8`, `u16`, `u32` or `u64`. Sealed, so no other type implements it.
711pub trait UnsignedWidth: sealed::Sealed + TryFrom<u64> {
712 /// The type's name, for the error when a value doesn't fit it.
713 const NAME: &'static str;
714 /// The label when a context-tagged value doesn't fit: its two `{}` are
715 /// the tag number and the value.
716 const CONTEXT_EXCEEDS: &'static str;
717 /// The label when an application-tagged Unsigned doesn't fit.
718 const UNSIGNED_EXCEEDS: &'static str;
719 /// The label when an application-tagged ENUMERATED doesn't fit.
720 const ENUMERATED_EXCEEDS: &'static str;
721}
722
723/// [`UnsignedWidth`] for `$ty`.
724macro_rules! width {
725 ($ty:ident) => {
726 impl UnsignedWidth for $ty {
727 const NAME: &'static str = stringify!($ty);
728 const CONTEXT_EXCEEDS: &'static str =
729 concat!("[{}] value {} exceeds ", stringify!($ty));
730 const UNSIGNED_EXCEEDS: &'static str = concat!("Unsigned exceeds ", stringify!($ty));
731 const ENUMERATED_EXCEEDS: &'static str =
732 concat!("ENUMERATED exceeds ", stringify!($ty));
733 }
734 };
735}
736
737mod sealed {
738 /// Keeps [`UnsignedWidth`](super::UnsignedWidth) to the widths this
739 /// module implements it for.
740 pub trait Sealed {}
741
742 impl Sealed for u8 {}
743 impl Sealed for u16 {}
744 impl Sealed for u32 {}
745 impl Sealed for u64 {}
746}
747
748width!(u8);
749width!(u16);
750width!(u32);
751width!(u64);
752
753/// Narrow `value`, read from context tag `tag` at `offset`, to `T`.
754fn narrow<T: UnsignedWidth>(
755 value: u64,
756 offset: usize,
757 tag: u8,
758 what: &'static str,
759) -> Result<T, Error> {
760 T::try_from(value).map_err(|_| {
761 DecodeError::new(DecodingKind::OutOfRange, offset, T::CONTEXT_EXCEEDS)
762 .within(what)
763 .with_values(tag.into(), value)
764 .into()
765 })
766}
767
768/// Require a primitive context tag `tag` at `offset` holding an Unsigned
769/// that fits `T`. An ENUMERATED's contents are encoded the same way, so this
770/// reads those too. Leading zero octets are accepted, as
771/// [`primitives::decode_unsigned`] accepts them.
772#[inline]
773pub fn decode_ctx_unsigned<T: UnsignedWidth>(
774 data: &[u8],
775 offset: usize,
776 tag: u8,
777 what: &'static str,
778) -> Result<(T, usize), Error> {
779 let (octets, end) = decode_ctx_primitive(data, offset, tag, what)?;
780 let value = primitives::decode_unsigned(octets)?;
781 Ok((narrow(value, offset, tag, what)?, end))
782}
783
784/// [`decode_ctx_unsigned`] for a codec that must re-encode what it read octet
785/// for octet: the contents must also be the shortest encoding (see
786/// [`decode_canonical_unsigned`]).
787pub fn decode_ctx_canonical_unsigned<T: UnsignedWidth>(
788 data: &[u8],
789 offset: usize,
790 tag: u8,
791 what: &'static str,
792) -> Result<(T, usize), Error> {
793 let (octets, end) = decode_ctx_primitive(data, offset, tag, what)?;
794 let value = decode_canonical_unsigned(octets, offset, what)?;
795 Ok((narrow(value, offset, tag, what)?, end))
796}
797
798/// The value of Unsigned or ENUMERATED contents in their shortest encoding:
799/// one to eight octets, and no leading zero octet unless it is the only one.
800/// Errors report `offset`, the field's tag.
801pub fn decode_canonical_unsigned(
802 octets: &[u8],
803 offset: usize,
804 what: &'static str,
805) -> Result<u64, Error> {
806 if octets.is_empty() || octets.len() > 8 {
807 return Err(DecodeError::new(
808 DecodingKind::InvalidEncoding,
809 offset,
810 "has {} contents octets, expected one to eight",
811 )
812 .within(what)
813 .with_value(octets.len() as u64)
814 .into());
815 }
816 if octets.len() > 1 && octets[0] == 0 {
817 return Err(DecodeError::new(
818 DecodingKind::InvalidEncoding,
819 offset,
820 "must use the shortest Unsigned/Enumerated encoding",
821 )
822 .within(what)
823 .into());
824 }
825 primitives::decode_unsigned(octets)
826}
827
828// ---------------------------------------------------------------------------
829// Application-tagged items
830// ---------------------------------------------------------------------------
831
832/// Require an application tag `number` (one of [`tags::app_tag`]) at
833/// `offset`; return its contents and the offset past them.
834///
835/// A BOOLEAN is refused with [`Error::Decoding`] whatever the data holds: its
836/// value is the tag's length field, so it has no contents to return (see
837/// [`Tag::is_boolean_true`]).
838///
839/// The label is static, so a read that succeeds builds none (#1645), and a
840/// refusal builds its text only when displayed (#1646).
841///
842/// ```
843/// use bacnet_encoding::constructed::tagged::decode_app_primitive;
844/// use bacnet_encoding::tags::app_tag;
845///
846/// // A record that is an Unsigned rather than an OCTET STRING.
847/// let error = decode_app_primitive(&[0x21, 0x01], 0, app_tag::OCTET_STRING, "record").unwrap_err();
848/// assert_eq!(
849/// error.to_string(),
850/// "decoding error at offset 0: record: expected application-tagged OCTET STRING",
851/// );
852/// ```
853pub fn decode_app_primitive<'a>(
854 data: &'a [u8],
855 offset: usize,
856 number: u8,
857 what: &'static str,
858) -> Result<(&'a [u8], usize), Error> {
859 let (t, start) = app_header(data, offset, number, what)?;
860 contents(data, start, t.length)
861}
862
863/// Require an application tag `number` at `offset`; return its header and
864/// the offset of its contents. A BOOLEAN has none (see
865/// [`decode_app_primitive`]).
866fn app_header(
867 data: &[u8],
868 offset: usize,
869 number: u8,
870 what: &'static str,
871) -> Result<(Tag, usize), Error> {
872 if number == tags::app_tag::BOOLEAN {
873 return Err(DecodeError::new(
874 DecodingKind::InvalidEncoding,
875 offset,
876 "an application-tagged BOOLEAN has no contents to read",
877 )
878 .within(what)
879 .into());
880 }
881 let (t, start) = tags::decode_tag(data, offset)?;
882 if t.class != TagClass::Application || t.number != number {
883 return Err(misplaced_member(
884 data,
885 &t,
886 None,
887 offset,
888 what,
889 ("expected application-tagged {t}", number),
890 ));
891 }
892 Ok((t, start))
893}
894
895/// Require an application tag `number` at `offset` holding exactly `octets`
896/// contents octets; return them and the offset past them. As with
897/// [`decode_ctx_fixed`], a header announcing any other length is
898/// [`Error::Decoding`] even when the data also stops early.
899pub fn decode_app_fixed<'a>(
900 data: &'a [u8],
901 offset: usize,
902 number: u8,
903 octets: u32,
904 what: &'static str,
905) -> Result<(&'a [u8], usize), Error> {
906 let (t, start) = app_header(data, offset, number, what)?;
907 if t.length != octets {
908 let refused = match primitives::fixed_length_label(number) {
909 Some((label, _)) => DecodeError::new(DecodingKind::InvalidEncoding, offset, label)
910 .with_value(t.length.into()),
911 None => DecodeError::new(
912 DecodingKind::InvalidEncoding,
913 offset,
914 "application value has {} contents octets, expected {}",
915 )
916 .with_values(t.length.into(), octets.into()),
917 };
918 return Err(refused.within(what).into());
919 }
920 contents(data, start, octets)
921}
922
923/// Require an application-tagged object identifier, four contents octets,
924/// at `offset`.
925pub fn decode_app_object_id(
926 data: &[u8],
927 offset: usize,
928 what: &'static str,
929) -> Result<(ObjectIdentifier, usize), Error> {
930 let (octets, end) = decode_app_fixed(data, offset, tags::app_tag::OBJECT_IDENTIFIER, 4, what)?;
931 Ok((ObjectIdentifier::decode(octets)?, end))
932}
933
934/// Decode one application-tagged Unsigned that fits `T`. Leading zero octets
935/// are accepted.
936pub fn decode_app_unsigned<T: UnsignedWidth>(
937 data: &[u8],
938 offset: usize,
939 what: &'static str,
940) -> Result<(T, usize), Error> {
941 let (octets, end) = decode_app_primitive(data, offset, tags::app_tag::UNSIGNED, what)?;
942 let value = primitives::decode_unsigned(octets)?;
943 Ok((
944 narrow_app(value, end - octets.len(), T::UNSIGNED_EXCEEDS, what)?,
945 end,
946 ))
947}
948
949/// Decode one application-tagged BIT STRING (`SEQUENCE OF BIT STRING` item).
950/// Only with `alloc`: it copies the data octets.
951#[cfg(feature = "alloc")]
952pub fn decode_app_bit_string(
953 data: &[u8],
954 offset: usize,
955 what: &'static str,
956) -> Result<((u8, Vec<u8>), usize), Error> {
957 let (octets, end) = decode_app_primitive(data, offset, tags::app_tag::BIT_STRING, what)?;
958 Ok((primitives::decode_bit_string(octets)?, end))
959}
960
961/// Decode one application-tagged ENUMERATED (a `SEQUENCE OF` item, or an
962/// error class or code) that fits `T`. Leading zero octets are accepted.
963pub fn decode_app_enumerated<T: UnsignedWidth>(
964 data: &[u8],
965 offset: usize,
966 what: &'static str,
967) -> Result<(T, usize), Error> {
968 let (octets, end) = decode_app_primitive(data, offset, tags::app_tag::ENUMERATED, what)?;
969 let value = primitives::decode_unsigned(octets)?;
970 Ok((
971 narrow_app(value, end - octets.len(), T::ENUMERATED_EXCEEDS, what)?,
972 end,
973 ))
974}
975
976/// [`decode_app_enumerated`] for a codec that must re-encode what it read
977/// octet for octet: the contents must also be the shortest encoding (see
978/// [`decode_canonical_unsigned`]).
979#[cfg(feature = "alloc")]
980pub(crate) fn decode_app_canonical_enumerated<T: UnsignedWidth>(
981 data: &[u8],
982 offset: usize,
983 what: &'static str,
984) -> Result<(T, usize), Error> {
985 let (octets, end) = decode_app_primitive(data, offset, tags::app_tag::ENUMERATED, what)?;
986 let value = decode_canonical_unsigned(octets, offset, what)?;
987 Ok((
988 narrow_app(value, end - octets.len(), T::ENUMERATED_EXCEEDS, what)?,
989 end,
990 ))
991}
992
993/// Narrow an application-tagged value whose contents start at `contents` to
994/// `T`, `label` naming the refusal.
995fn narrow_app<T: UnsignedWidth>(
996 value: u64,
997 contents: usize,
998 label: &'static str,
999 what: &'static str,
1000) -> Result<T, Error> {
1001 T::try_from(value).map_err(|_| {
1002 DecodeError::new(DecodingKind::OutOfRange, contents, label)
1003 .within(what)
1004 .into()
1005 })
1006}
1007
1008/// Decode one application-tagged CharacterString. Only with `alloc`;
1009/// [`decode_app_character_string_ref`] reads it without converting it.
1010#[cfg(feature = "alloc")]
1011pub fn decode_app_character_string(
1012 data: &[u8],
1013 offset: usize,
1014 what: &'static str,
1015) -> Result<(String, usize), Error> {
1016 let (octets, end) = decode_app_primitive(data, offset, tags::app_tag::CHARACTER_STRING, what)?;
1017 Ok((primitives::decode_character_string(octets)?, end))
1018}
1019
1020/// Decode one application-tagged CharacterString, borrowed from `data`, its
1021/// character set checked (see [`primitives::CharacterStringRef::decode`]).
1022pub fn decode_app_character_string_ref<'a>(
1023 data: &'a [u8],
1024 offset: usize,
1025 what: &'static str,
1026) -> Result<(primitives::CharacterStringRef<'a>, usize), Error> {
1027 let (octets, end) = decode_app_primitive(data, offset, tags::app_tag::CHARACTER_STRING, what)?;
1028 Ok((primitives::CharacterStringRef::decode(octets)?, end))
1029}
1030
1031#[cfg(all(test, feature = "alloc"))]
1032#[path = "tagged_frame_tests.rs"]
1033mod frame_tests;