1use bacnet_types::error::{DecodeError, DecodingKind, Error};
10mod encode;
11
12#[cfg(feature = "alloc")]
13pub use encode::{encode_closing_tag, encode_opening_tag, encode_tag};
14pub use encode::{encode_closing_tag_to_sink, encode_opening_tag_to_sink, encode_tag_to_sink};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[repr(u8)]
19pub enum TagClass {
20 Application = 0,
22 Context = 1,
24}
25
26pub mod app_tag {
28 pub const NULL: u8 = 0;
30 pub const BOOLEAN: u8 = 1;
32 pub const UNSIGNED: u8 = 2;
34 pub const SIGNED: u8 = 3;
36 pub const REAL: u8 = 4;
38 pub const DOUBLE: u8 = 5;
40 pub const OCTET_STRING: u8 = 6;
42 pub const CHARACTER_STRING: u8 = 7;
44 pub const BIT_STRING: u8 = 8;
46 pub const ENUMERATED: u8 = 9;
48 pub const DATE: u8 = 10;
50 pub const TIME: u8 = 11;
52 pub const OBJECT_IDENTIFIER: u8 = 12;
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct Tag {
59 pub number: u8,
61 pub class: TagClass,
63 pub length: u32,
66 pub is_opening: bool,
68 pub is_closing: bool,
70}
71
72impl Tag {
73 pub fn is_boolean_true(&self) -> bool {
78 self.class == TagClass::Application && self.number == app_tag::BOOLEAN && self.length != 0
79 }
80
81 pub fn is_context(&self, number: u8) -> bool {
83 self.class == TagClass::Context
84 && self.number == number
85 && !self.is_opening
86 && !self.is_closing
87 }
88
89 pub fn is_opening_tag(&self, number: u8) -> bool {
91 self.class == TagClass::Context && self.number == number && self.is_opening
92 }
93
94 pub fn is_closing_tag(&self, number: u8) -> bool {
96 self.class == TagClass::Context && self.number == number && self.is_closing
97 }
98}
99
100const MAX_TAG_LENGTH: u32 = 1_048_576;
107
108pub const MAX_CONTEXT_NESTING_DEPTH: usize = 32;
110
111pub fn decode_tag(data: &[u8], offset: usize) -> Result<(Tag, usize), Error> {
121 if offset >= data.len() {
122 return Err(Error::missing(
123 offset,
124 "tag decode: offset beyond buffer length",
125 ));
126 }
127
128 let initial = data[offset];
129 let mut pos = offset + 1;
130
131 let mut tag_number = (initial >> 4) & 0x0F;
132 let class = if (initial >> 3) & 0x01 == 1 {
133 TagClass::Context
134 } else {
135 TagClass::Application
136 };
137 let lvt = initial & 0x07;
138 if class == TagClass::Application && lvt > 5 {
139 return Err(Error::invalid_tag(offset, "reserved application tag L/V/T"));
140 }
141
142 if tag_number == 0x0F {
143 if pos >= data.len() {
144 return Err(Error::missing(pos, "truncated extended tag number"));
145 }
146 tag_number = data[pos];
147 if !(15..=254).contains(&tag_number) {
148 return Err(header_fault(
149 pos,
150 "invalid extended tag number {}",
151 tag_number.into(),
152 ));
153 }
154 pos += 1;
155 }
156
157 if class == TagClass::Context {
158 if lvt == 6 {
159 return Ok((
160 Tag {
161 number: tag_number,
162 class,
163 length: 0,
164 is_opening: true,
165 is_closing: false,
166 },
167 pos,
168 ));
169 }
170 if lvt == 7 {
171 return Ok((
172 Tag {
173 number: tag_number,
174 class,
175 length: 0,
176 is_opening: false,
177 is_closing: true,
178 },
179 pos,
180 ));
181 }
182 }
183
184 let length = if lvt < 5 {
185 lvt as u32
186 } else {
187 if pos >= data.len() {
188 return Err(Error::missing(pos, "truncated extended length"));
189 }
190 let ext = data[pos];
191 pos += 1;
192
193 match ext {
194 0..=4 => {
195 return Err(header_fault(
196 pos - 1,
197 "non-canonical extended tag length {}",
198 ext.into(),
199 ));
200 }
201 5..=253 => ext as u32,
202 254 => {
203 if pos + 2 > data.len() {
204 return Err(Error::missing(pos, "truncated 2-byte extended length"));
205 }
206 let len = u16::from_be_bytes([data[pos], data[pos + 1]]) as u32;
207 if len < 254 {
208 return Err(header_fault(
209 pos,
210 "non-canonical 2-byte extended tag length {}",
211 len.into(),
212 ));
213 }
214 pos += 2;
215 len
216 }
217 255 => {
218 if pos + 4 > data.len() {
219 return Err(Error::missing(pos, "truncated 4-byte extended length"));
220 }
221 let len =
222 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
223 if len < 65_536 {
224 return Err(header_fault(
225 pos,
226 "non-canonical 4-byte extended tag length {}",
227 len.into(),
228 ));
229 }
230 pos += 4;
231 len
232 }
233 }
234 };
235
236 if length > MAX_TAG_LENGTH {
237 return Err(DecodeError::new(
238 DecodingKind::Overflow,
239 offset,
240 "tag length ({}) exceeds sanity limit ({})",
241 )
242 .with_values(length.into(), MAX_TAG_LENGTH.into())
243 .into());
244 }
245
246 Ok((
247 Tag {
248 number: tag_number,
249 class,
250 length,
251 is_opening: false,
252 is_closing: false,
253 },
254 pos,
255 ))
256}
257
258#[cold]
261fn header_fault(offset: usize, label: &'static str, value: u64) -> Error {
262 DecodeError::new(DecodingKind::InvalidTag, offset, label)
263 .with_value(value)
264 .into()
265}
266
267#[cold]
270fn unclosed(offset: usize, tag_number: u8) -> Error {
271 DecodeError::new(DecodingKind::Missing, offset, "missing closing tag {}")
272 .with_value(tag_number.into())
273 .into()
274}
275
276pub fn extract_context_value(
292 data: &[u8],
293 offset: usize,
294 tag_number: u8,
295) -> Result<(&[u8], usize), Error> {
296 let value_start = offset;
297 let mut pos = offset;
298 let mut depth: usize = 1;
299 let mut open_tags = [0u8; MAX_CONTEXT_NESTING_DEPTH];
300 open_tags[0] = tag_number;
301
302 while depth > 0 && pos < data.len() {
303 let (tag, new_pos) = decode_tag(data, pos)?;
304
305 if tag.is_opening {
306 if depth == MAX_CONTEXT_NESTING_DEPTH {
307 return Err(DecodeError::new(
308 DecodingKind::InvalidTag,
309 pos,
310 "context tag nesting depth exceeds maximum ({})",
311 )
312 .with_value(MAX_CONTEXT_NESTING_DEPTH as u64)
313 .into());
314 }
315 open_tags[depth] = tag.number;
316 depth += 1;
317 pos = new_pos;
318 } else if tag.is_closing {
319 let expected = open_tags[depth - 1];
320 if tag.number != expected {
321 return Err(DecodeError::new(
322 DecodingKind::InvalidTag,
323 pos,
324 "closing tag {} does not match opening tag {}",
325 )
326 .with_values(tag.number.into(), expected.into())
327 .into());
328 }
329 depth -= 1;
330 if depth == 0 {
331 let value_end = pos;
332 return Ok((&data[value_start..value_end], new_pos));
333 }
334 pos = new_pos;
335 } else if tag.class == TagClass::Application && tag.number == app_tag::BOOLEAN {
336 pos = new_pos;
337 } else {
338 let content_end = new_pos
339 .checked_add(tag.length as usize)
340 .ok_or_else(|| Error::decoding(new_pos, "tag length overflow"))?;
341 if content_end > data.len() {
342 return Err(Error::buffer_too_short(content_end, data.len()));
343 }
344 pos = content_end;
345 }
346 }
347
348 Err(unclosed(offset, tag_number))
349}
350
351pub fn extract_raw_context(
368 data: &[u8],
369 offset: usize,
370 tag_number: u8,
371) -> Result<(&[u8], usize), Error> {
372 let wide = tag_number > 14;
373 let open_first = if wide { 0xFE } else { (tag_number << 4) | 0x0E };
374 let close_first = if wide { 0xFF } else { (tag_number << 4) | 0x0F };
375 let width = if wide { 2 } else { 1 };
376
377 let value_start = offset;
378 let mut pos = offset;
379 let mut depth: usize = 1;
380
381 while pos < data.len() {
382 let b = data[pos];
383 if b == open_first && (!wide || data.get(pos + 1) == Some(&tag_number)) {
384 depth += 1;
385 pos += width;
386 continue;
387 }
388 if b == close_first && (!wide || data.get(pos + 1) == Some(&tag_number)) {
389 depth -= 1;
390 if depth == 0 {
391 return Ok((&data[value_start..pos], pos + width));
392 }
393 pos += width;
394 continue;
395 }
396 pos += 1;
397 }
398
399 Err(unclosed(value_start, tag_number))
400}
401
402#[cfg(all(test, feature = "alloc"))]
408mod tests {
409 use super::*;
410 use bytes::BytesMut;
411
412 fn encode_to_vec(tag_number: u8, class: TagClass, length: u32) -> Vec<u8> {
413 let mut buf = BytesMut::with_capacity(8);
414 encode_tag(&mut buf, tag_number, class, length);
415 buf.to_vec()
416 }
417
418 #[test]
419 fn encode_single_byte_application_tag() {
420 assert_eq!(encode_to_vec(0, TagClass::Application, 0), vec![0x00]);
422 assert_eq!(encode_to_vec(2, TagClass::Application, 1), vec![0x21]);
424 assert_eq!(encode_to_vec(4, TagClass::Application, 4), vec![0x44]);
426 assert_eq!(encode_to_vec(12, TagClass::Application, 4), vec![0xC4]);
428 }
429
430 #[test]
431 fn encode_single_byte_context_tag() {
432 assert_eq!(encode_to_vec(0, TagClass::Context, 1), vec![0x09]);
434 assert_eq!(encode_to_vec(1, TagClass::Context, 4), vec![0x1C]);
436 assert_eq!(encode_to_vec(2, TagClass::Context, 0), vec![0x28]);
438 }
439
440 #[test]
441 fn encode_extended_tag_number() {
442 let encoded = encode_to_vec(15, TagClass::Application, 1);
444 assert_eq!(encoded, vec![0xF1, 15]);
445
446 let encoded = encode_to_vec(20, TagClass::Context, 2);
448 assert_eq!(encoded, vec![0xFA, 20]);
449 }
450
451 #[test]
452 fn encode_extended_length() {
453 let encoded = encode_to_vec(2, TagClass::Application, 100);
455 assert_eq!(encoded, vec![0x25, 100]);
456
457 let encoded = encode_to_vec(2, TagClass::Application, 1000);
459 assert_eq!(encoded, vec![0x25, 254, 0x03, 0xE8]);
460
461 let encoded = encode_to_vec(2, TagClass::Application, 100000);
463 assert_eq!(encoded, vec![0x25, 255, 0x00, 0x01, 0x86, 0xA0]);
464 }
465
466 #[test]
467 fn encode_extended_tag_and_length() {
468 let encoded = encode_to_vec(20, TagClass::Context, 100);
470 assert_eq!(encoded, vec![0xFD, 20, 100]);
471 }
472
473 #[test]
474 fn encode_opening_closing_tags() {
475 let mut buf = BytesMut::new();
476
477 encode_opening_tag(&mut buf, 0);
479 assert_eq!(&buf[..], &[0x0E]);
480
481 buf.clear();
482 encode_closing_tag(&mut buf, 0);
484 assert_eq!(&buf[..], &[0x0F]);
485
486 buf.clear();
487 encode_opening_tag(&mut buf, 3);
489 assert_eq!(&buf[..], &[0x3E]);
490
491 buf.clear();
492 encode_opening_tag(&mut buf, 20);
494 assert_eq!(&buf[..], &[0xFE, 20]);
495
496 buf.clear();
497 encode_closing_tag(&mut buf, 20);
499 assert_eq!(&buf[..], &[0xFF, 20]);
500 }
501
502 #[test]
503 fn decode_single_byte_tag() {
504 let (tag, pos) = decode_tag(&[0x21], 0).unwrap();
506 assert_eq!(tag.number, 2);
507 assert_eq!(tag.class, TagClass::Application);
508 assert_eq!(tag.length, 1);
509 assert!(!tag.is_opening);
510 assert!(!tag.is_closing);
511 assert_eq!(pos, 1);
512 }
513
514 #[test]
515 fn decode_context_tag() {
516 let (tag, pos) = decode_tag(&[0x1C], 0).unwrap();
518 assert_eq!(tag.number, 1);
519 assert_eq!(tag.class, TagClass::Context);
520 assert_eq!(tag.length, 4);
521 assert_eq!(pos, 1);
522 }
523
524 #[test]
525 fn decode_opening_closing_tags() {
526 let (tag, _) = decode_tag(&[0x0E], 0).unwrap();
527 assert!(tag.is_opening);
528 assert_eq!(tag.number, 0);
529
530 let (tag, _) = decode_tag(&[0x0F], 0).unwrap();
531 assert!(tag.is_closing);
532 assert_eq!(tag.number, 0);
533
534 let (tag, _) = decode_tag(&[0x3E], 0).unwrap();
535 assert!(tag.is_opening);
536 assert_eq!(tag.number, 3);
537 }
538
539 #[test]
540 fn decode_extended_tag_number() {
541 let (tag, pos) = decode_tag(&[0xF1, 20], 0).unwrap();
543 assert_eq!(tag.number, 20);
544 assert_eq!(tag.class, TagClass::Application);
545 assert_eq!(tag.length, 1);
546 assert_eq!(pos, 2);
547 }
548
549 #[test]
550 fn decode_extended_length() {
551 let (tag, pos) = decode_tag(&[0x25, 100], 0).unwrap();
553 assert_eq!(tag.number, 2);
554 assert_eq!(tag.length, 100);
555 assert_eq!(pos, 2);
556
557 let (tag, pos) = decode_tag(&[0x25, 254, 0x03, 0xE8], 0).unwrap();
559 assert_eq!(tag.number, 2);
560 assert_eq!(tag.length, 1000);
561 assert_eq!(pos, 4);
562
563 let (tag, pos) = decode_tag(&[0x25, 255, 0x00, 0x01, 0x86, 0xA0], 0).unwrap();
565 assert_eq!(tag.number, 2);
566 assert_eq!(tag.length, 100000);
567 assert_eq!(pos, 6);
568 }
569
570 #[test]
571 fn decode_tag_round_trip() {
572 let cases = [
574 (0u8, TagClass::Application, 0u32),
575 (1, TagClass::Application, 1),
576 (4, TagClass::Application, 4),
577 (12, TagClass::Application, 4),
578 (0, TagClass::Context, 1),
579 (3, TagClass::Context, 4),
580 (15, TagClass::Application, 1),
581 (20, TagClass::Context, 2),
582 (2, TagClass::Application, 100),
583 (2, TagClass::Application, 1000),
584 (20, TagClass::Context, 100),
585 ];
586
587 for (tag_num, class, length) in cases {
588 let encoded = encode_to_vec(tag_num, class, length);
589 let (decoded, _) = decode_tag(&encoded, 0).unwrap();
590 assert_eq!(
591 decoded.number, tag_num,
592 "tag number mismatch for ({tag_num}, {class:?}, {length})"
593 );
594 assert_eq!(
595 decoded.class, class,
596 "class mismatch for ({tag_num}, {class:?}, {length})"
597 );
598 assert_eq!(
599 decoded.length, length,
600 "length mismatch for ({tag_num}, {class:?}, {length})"
601 );
602 }
603 }
604
605 #[test]
606 fn decode_tag_empty_buffer() {
607 assert!(decode_tag(&[], 0).is_err());
608 }
609
610 #[test]
611 fn decode_tag_truncated_extended() {
612 assert!(decode_tag(&[0xF1], 0).is_err());
614 }
615
616 #[test]
617 fn decode_tag_excessive_length() {
618 let data = [0x25, 255, 0x10, 0x00, 0x00, 0x00]; assert!(decode_tag(&data, 0).is_err());
621 }
622
623 #[test]
624 fn boolean_tag_detection() {
625 let (tag, _) = decode_tag(&[0x11], 0).unwrap();
627 assert!(tag.is_boolean_true());
628
629 let (tag, _) = decode_tag(&[0x10], 0).unwrap();
631 assert!(!tag.is_boolean_true());
632
633 let (tag, _) = decode_tag(&[0x21], 0).unwrap();
635 assert!(!tag.is_boolean_true());
636 }
637
638 #[test]
639 fn extract_context_value_simple() {
640 let data = [0x0E, 0x21, 42, 0x0F];
642 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
643 assert_eq!(value, &[0x21, 42]);
644 assert_eq!(pos, 4);
645 }
646
647 #[test]
650 fn extract_raw_context_non_tlv_payload() {
651 let data = [0x9E, 0xDE, 0xAD, 0xBE, 0xEF, 0x9F];
654 let (value, pos) = extract_raw_context(&data, 1, 9).unwrap();
655 assert_eq!(value, &[0xDE, 0xAD, 0xBE, 0xEF]);
656 assert_eq!(pos, 6);
657 }
658
659 #[test]
660 fn extract_raw_context_nested_same_tag() {
661 let data = [0x2E, 0x2E, 0x01, 0x2F, 0x02, 0x2F];
663 let (value, pos) = extract_raw_context(&data, 1, 2).unwrap();
664 assert_eq!(value, &[0x2E, 0x01, 0x2F, 0x02]);
665 assert_eq!(pos, 6);
666 }
667
668 #[test]
669 fn extract_raw_context_extended_tag() {
670 let data = [0xFE, 200, 0x01, 0x02, 0xFF, 200];
672 let (value, pos) = extract_raw_context(&data, 2, 200).unwrap();
673 assert_eq!(value, &[0x01, 0x02]);
674 assert_eq!(pos, 6);
675 }
676
677 #[test]
678 fn extract_raw_context_missing_closing() {
679 let data = [0x9E, 0xDE, 0xAD];
680 assert!(extract_raw_context(&data, 1, 9).is_err());
681 }
682
683 #[test]
684 fn extract_raw_context_truncated_extended_closing() {
685 let data = [0xFE, 200, 0x01, 0x02, 0xFF];
687 assert!(extract_raw_context(&data, 2, 200).is_err());
688 }
689
690 #[test]
691 fn extract_context_value_nested() {
692 let data = [0x0E, 0x1E, 0x21, 42, 0x1F, 0x0F];
694 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
695 assert_eq!(value, &[0x1E, 0x21, 42, 0x1F]);
696 assert_eq!(pos, 6);
697 }
698
699 #[test]
700 fn extract_context_value_missing_close() {
701 let data = [0x0E, 0x21, 42]; assert!(extract_context_value(&data, 1, 0).is_err());
703 }
704
705 #[test]
706 fn tag_is_context_helper() {
707 let (tag, _) = decode_tag(&[0x09], 0).unwrap(); assert!(tag.is_context(0));
709 assert!(!tag.is_context(1));
710 }
711
712 #[test]
715 fn extract_context_value_mismatched_closing_tag() {
716 let data = [0x0E, 0x21, 42, 0x1F]; assert!(extract_context_value(&data, 1, 0).is_err());
719 }
720
721 #[test]
722 fn extract_context_value_mismatched_nested_closing_tag() {
723 let data = [0x0E, 0x1E, 0x2F, 0x0F];
725 assert!(extract_context_value(&data, 1, 0).is_err());
726 }
727
728 #[test]
729 fn extract_context_value_deeply_nested() {
730 let data = [
733 0x0E, 0x1E, 0x2E, 0x21, 42, 0x2F, 0x1F, 0x0F, ];
741 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
742 assert_eq!(value, &data[1..7]); assert_eq!(pos, 8);
744 }
745
746 #[test]
747 fn extract_context_value_nesting_depth_exceeded() {
748 let mut data = Vec::new();
750 for i in 0..MAX_CONTEXT_NESTING_DEPTH {
753 let tag = (i % 15) as u8; data.push((tag << 4) | 0x0E); }
756 let result = extract_context_value(&data, 0, 0);
758 assert!(result.is_err());
759 }
760
761 #[test]
762 fn decode_tag_at_nonzero_offset() {
763 let data = [0xFF, 0xFF, 0x21, 0x00]; let (tag, pos) = decode_tag(&data, 2).unwrap();
766 assert_eq!(tag.number, 2);
767 assert_eq!(tag.class, TagClass::Application);
768 assert_eq!(tag.length, 1);
769 assert_eq!(pos, 3);
770 }
771
772 #[test]
773 fn decode_tag_offset_beyond_buffer() {
774 let data = [0x21];
775 assert!(decode_tag(&data, 5).is_err());
776 }
777
778 #[test]
779 fn decode_tag_truncated_2byte_extended_length() {
780 let data = [0x25, 254, 0x03]; assert!(decode_tag(&data, 0).is_err());
783 }
784
785 #[test]
786 fn decode_tag_truncated_4byte_extended_length() {
787 let data = [0x25, 255, 0x00, 0x01];
789 assert!(decode_tag(&data, 0).is_err());
790 }
791
792 #[test]
793 fn extract_context_value_with_boolean_inside() {
794 let data = [0x0E, 0x11, 0x0F]; let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
798 assert_eq!(value, &[0x11]); assert_eq!(pos, 3);
800 }
801
802 #[test]
803 fn extract_context_value_tag_data_overflows_buffer() {
804 let data = [0x0E, 0x25, 100, 0x01, 0x02, 0x0F];
807 assert!(matches!(
808 extract_context_value(&data, 1, 0),
809 Err(Error::BufferTooShort { need: 103, have: 6 })
810 ));
811 }
812
813 #[test]
814 fn opening_closing_tag_extended_round_trip() {
815 let mut buf = BytesMut::new();
816 for tag_num in [15u8, 20, 100, 254] {
818 buf.clear();
819 encode_opening_tag(&mut buf, tag_num);
820 let (tag, pos1) = decode_tag(&buf, 0).unwrap();
821 assert!(tag.is_opening);
822 assert_eq!(tag.number, tag_num);
823 assert_eq!(pos1, 2); buf.clear();
826 encode_closing_tag(&mut buf, tag_num);
827 let (tag, pos2) = decode_tag(&buf, 0).unwrap();
828 assert!(tag.is_closing);
829 assert_eq!(tag.number, tag_num);
830 assert_eq!(pos2, 2);
831 }
832 }
833}