1use bacnet_types::error::Error;
10use bytes::{BufMut, BytesMut};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14#[repr(u8)]
15pub enum TagClass {
16 Application = 0,
18 Context = 1,
20}
21
22pub mod app_tag {
24 pub const NULL: u8 = 0;
26 pub const BOOLEAN: u8 = 1;
28 pub const UNSIGNED: u8 = 2;
30 pub const SIGNED: u8 = 3;
32 pub const REAL: u8 = 4;
34 pub const DOUBLE: u8 = 5;
36 pub const OCTET_STRING: u8 = 6;
38 pub const CHARACTER_STRING: u8 = 7;
40 pub const BIT_STRING: u8 = 8;
42 pub const ENUMERATED: u8 = 9;
44 pub const DATE: u8 = 10;
46 pub const TIME: u8 = 11;
48 pub const OBJECT_IDENTIFIER: u8 = 12;
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct Tag {
55 pub number: u8,
57 pub class: TagClass,
59 pub length: u32,
62 pub is_opening: bool,
64 pub is_closing: bool,
66}
67
68impl Tag {
69 pub fn is_boolean_true(&self) -> bool {
74 self.class == TagClass::Application && self.number == app_tag::BOOLEAN && self.length != 0
75 }
76
77 pub fn is_context(&self, number: u8) -> bool {
79 self.class == TagClass::Context
80 && self.number == number
81 && !self.is_opening
82 && !self.is_closing
83 }
84
85 pub fn is_opening_tag(&self, number: u8) -> bool {
87 self.class == TagClass::Context && self.number == number && self.is_opening
88 }
89
90 pub fn is_closing_tag(&self, number: u8) -> bool {
92 self.class == TagClass::Context && self.number == number && self.is_closing
93 }
94}
95
96pub fn encode_tag(buf: &mut BytesMut, tag_number: u8, class: TagClass, length: u32) {
105 let cls_bit = (class as u8) << 3;
106
107 if tag_number <= 14 && length <= 4 {
108 buf.put_u8((tag_number << 4) | cls_bit | (length as u8));
109 return;
110 }
111
112 let tag_nibble = if tag_number <= 14 {
113 tag_number << 4
114 } else {
115 0xF0
116 };
117
118 if length <= 4 {
119 buf.put_u8(tag_nibble | cls_bit | (length as u8));
120 if tag_number > 14 {
121 buf.put_u8(tag_number);
122 }
123 return;
124 }
125
126 buf.put_u8(tag_nibble | cls_bit | 5);
127 if tag_number > 14 {
128 buf.put_u8(tag_number);
129 }
130
131 if length <= 253 {
132 buf.put_u8(length as u8);
133 } else if length <= 65535 {
134 buf.put_u8(254);
135 buf.put_u16(length as u16);
136 } else {
137 buf.put_u8(255);
138 buf.put_u32(length);
139 }
140}
141
142pub fn encode_opening_tag(buf: &mut BytesMut, tag_number: u8) {
144 if tag_number <= 14 {
145 buf.put_u8((tag_number << 4) | 0x0E);
146 } else {
147 buf.put_u8(0xFE);
148 buf.put_u8(tag_number);
149 }
150}
151
152pub fn encode_closing_tag(buf: &mut BytesMut, tag_number: u8) {
154 if tag_number <= 14 {
155 buf.put_u8((tag_number << 4) | 0x0F);
156 } else {
157 buf.put_u8(0xFF);
158 buf.put_u8(tag_number);
159 }
160}
161
162const MAX_TAG_LENGTH: u32 = 1_048_576;
169
170pub const MAX_CONTEXT_NESTING_DEPTH: usize = 32;
172
173pub fn decode_tag(data: &[u8], offset: usize) -> Result<(Tag, usize), Error> {
183 if offset >= data.len() {
184 return Err(Error::missing(
185 offset,
186 "tag decode: offset beyond buffer length",
187 ));
188 }
189
190 let initial = data[offset];
191 let mut pos = offset + 1;
192
193 let mut tag_number = (initial >> 4) & 0x0F;
194 let class = if (initial >> 3) & 0x01 == 1 {
195 TagClass::Context
196 } else {
197 TagClass::Application
198 };
199 let lvt = initial & 0x07;
200 if class == TagClass::Application && lvt > 5 {
201 return Err(Error::invalid_tag(offset, "reserved application tag L/V/T"));
202 }
203
204 if tag_number == 0x0F {
205 if pos >= data.len() {
206 return Err(Error::missing(pos, "truncated extended tag number"));
207 }
208 tag_number = data[pos];
209 if !(15..=254).contains(&tag_number) {
210 return Err(Error::invalid_tag(
211 pos,
212 format!("invalid extended tag number {tag_number}"),
213 ));
214 }
215 pos += 1;
216 }
217
218 if class == TagClass::Context {
219 if lvt == 6 {
220 return Ok((
221 Tag {
222 number: tag_number,
223 class,
224 length: 0,
225 is_opening: true,
226 is_closing: false,
227 },
228 pos,
229 ));
230 }
231 if lvt == 7 {
232 return Ok((
233 Tag {
234 number: tag_number,
235 class,
236 length: 0,
237 is_opening: false,
238 is_closing: true,
239 },
240 pos,
241 ));
242 }
243 }
244
245 let length = if lvt < 5 {
246 lvt as u32
247 } else {
248 if pos >= data.len() {
249 return Err(Error::missing(pos, "truncated extended length"));
250 }
251 let ext = data[pos];
252 pos += 1;
253
254 match ext {
255 0..=4 => {
256 return Err(Error::invalid_tag(
257 pos - 1,
258 format!("non-canonical extended tag length {ext}"),
259 ));
260 }
261 5..=253 => ext as u32,
262 254 => {
263 if pos + 2 > data.len() {
264 return Err(Error::missing(pos, "truncated 2-byte extended length"));
265 }
266 let len = u16::from_be_bytes([data[pos], data[pos + 1]]) as u32;
267 if len < 254 {
268 return Err(Error::invalid_tag(
269 pos,
270 format!("non-canonical 2-byte extended tag length {len}"),
271 ));
272 }
273 pos += 2;
274 len
275 }
276 255 => {
277 if pos + 4 > data.len() {
278 return Err(Error::missing(pos, "truncated 4-byte extended length"));
279 }
280 let len =
281 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
282 if len < 65_536 {
283 return Err(Error::invalid_tag(
284 pos,
285 format!("non-canonical 4-byte extended tag length {len}"),
286 ));
287 }
288 pos += 4;
289 len
290 }
291 }
292 };
293
294 if length > MAX_TAG_LENGTH {
295 return Err(Error::overflow(
296 offset,
297 format!("tag length ({length}) exceeds sanity limit ({MAX_TAG_LENGTH})"),
298 ));
299 }
300
301 Ok((
302 Tag {
303 number: tag_number,
304 class,
305 length,
306 is_opening: false,
307 is_closing: false,
308 },
309 pos,
310 ))
311}
312
313pub fn extract_context_value(
329 data: &[u8],
330 offset: usize,
331 tag_number: u8,
332) -> Result<(&[u8], usize), Error> {
333 let value_start = offset;
334 let mut pos = offset;
335 let mut depth: usize = 1;
336 let mut open_tags = [0u8; MAX_CONTEXT_NESTING_DEPTH];
337 open_tags[0] = tag_number;
338
339 while depth > 0 && pos < data.len() {
340 let (tag, new_pos) = decode_tag(data, pos)?;
341
342 if tag.is_opening {
343 if depth == MAX_CONTEXT_NESTING_DEPTH {
344 return Err(Error::invalid_tag(
345 pos,
346 format!(
347 "context tag nesting depth exceeds maximum ({MAX_CONTEXT_NESTING_DEPTH})"
348 ),
349 ));
350 }
351 open_tags[depth] = tag.number;
352 depth += 1;
353 pos = new_pos;
354 } else if tag.is_closing {
355 let expected = open_tags[depth - 1];
356 if tag.number != expected {
357 return Err(Error::invalid_tag(
358 pos,
359 format!(
360 "closing tag {} does not match opening tag {expected}",
361 tag.number
362 ),
363 ));
364 }
365 depth -= 1;
366 if depth == 0 {
367 let value_end = pos;
368 return Ok((&data[value_start..value_end], new_pos));
369 }
370 pos = new_pos;
371 } else if tag.class == TagClass::Application && tag.number == app_tag::BOOLEAN {
372 pos = new_pos;
373 } else {
374 let content_end = new_pos
375 .checked_add(tag.length as usize)
376 .ok_or_else(|| Error::decoding(new_pos, "tag length overflow"))?;
377 if content_end > data.len() {
378 return Err(Error::buffer_too_short(content_end, data.len()));
379 }
380 pos = content_end;
381 }
382 }
383
384 Err(Error::missing(
385 offset,
386 format!("missing closing tag {tag_number}"),
387 ))
388}
389
390pub fn extract_raw_context(
407 data: &[u8],
408 offset: usize,
409 tag_number: u8,
410) -> Result<(&[u8], usize), Error> {
411 let wide = tag_number > 14;
412 let open_first = if wide { 0xFE } else { (tag_number << 4) | 0x0E };
413 let close_first = if wide { 0xFF } else { (tag_number << 4) | 0x0F };
414 let width = if wide { 2 } else { 1 };
415
416 let value_start = offset;
417 let mut pos = offset;
418 let mut depth: usize = 1;
419
420 while pos < data.len() {
421 let b = data[pos];
422 if b == open_first && (!wide || data.get(pos + 1) == Some(&tag_number)) {
423 depth += 1;
424 pos += width;
425 continue;
426 }
427 if b == close_first && (!wide || data.get(pos + 1) == Some(&tag_number)) {
428 depth -= 1;
429 if depth == 0 {
430 return Ok((&data[value_start..pos], pos + width));
431 }
432 pos += width;
433 continue;
434 }
435 pos += 1;
436 }
437
438 Err(Error::missing(
439 value_start,
440 format!("missing closing tag {tag_number}"),
441 ))
442}
443
444#[cfg(test)]
449mod tests {
450 use super::*;
451
452 fn encode_to_vec(tag_number: u8, class: TagClass, length: u32) -> Vec<u8> {
453 let mut buf = BytesMut::with_capacity(8);
454 encode_tag(&mut buf, tag_number, class, length);
455 buf.to_vec()
456 }
457
458 #[test]
459 fn encode_single_byte_application_tag() {
460 assert_eq!(encode_to_vec(0, TagClass::Application, 0), vec![0x00]);
462 assert_eq!(encode_to_vec(2, TagClass::Application, 1), vec![0x21]);
464 assert_eq!(encode_to_vec(4, TagClass::Application, 4), vec![0x44]);
466 assert_eq!(encode_to_vec(12, TagClass::Application, 4), vec![0xC4]);
468 }
469
470 #[test]
471 fn encode_single_byte_context_tag() {
472 assert_eq!(encode_to_vec(0, TagClass::Context, 1), vec![0x09]);
474 assert_eq!(encode_to_vec(1, TagClass::Context, 4), vec![0x1C]);
476 assert_eq!(encode_to_vec(2, TagClass::Context, 0), vec![0x28]);
478 }
479
480 #[test]
481 fn encode_extended_tag_number() {
482 let encoded = encode_to_vec(15, TagClass::Application, 1);
484 assert_eq!(encoded, vec![0xF1, 15]);
485
486 let encoded = encode_to_vec(20, TagClass::Context, 2);
488 assert_eq!(encoded, vec![0xFA, 20]);
489 }
490
491 #[test]
492 fn encode_extended_length() {
493 let encoded = encode_to_vec(2, TagClass::Application, 100);
495 assert_eq!(encoded, vec![0x25, 100]);
496
497 let encoded = encode_to_vec(2, TagClass::Application, 1000);
499 assert_eq!(encoded, vec![0x25, 254, 0x03, 0xE8]);
500
501 let encoded = encode_to_vec(2, TagClass::Application, 100000);
503 assert_eq!(encoded, vec![0x25, 255, 0x00, 0x01, 0x86, 0xA0]);
504 }
505
506 #[test]
507 fn encode_extended_tag_and_length() {
508 let encoded = encode_to_vec(20, TagClass::Context, 100);
510 assert_eq!(encoded, vec![0xFD, 20, 100]);
511 }
512
513 #[test]
514 fn encode_opening_closing_tags() {
515 let mut buf = BytesMut::new();
516
517 encode_opening_tag(&mut buf, 0);
519 assert_eq!(&buf[..], &[0x0E]);
520
521 buf.clear();
522 encode_closing_tag(&mut buf, 0);
524 assert_eq!(&buf[..], &[0x0F]);
525
526 buf.clear();
527 encode_opening_tag(&mut buf, 3);
529 assert_eq!(&buf[..], &[0x3E]);
530
531 buf.clear();
532 encode_opening_tag(&mut buf, 20);
534 assert_eq!(&buf[..], &[0xFE, 20]);
535
536 buf.clear();
537 encode_closing_tag(&mut buf, 20);
539 assert_eq!(&buf[..], &[0xFF, 20]);
540 }
541
542 #[test]
543 fn decode_single_byte_tag() {
544 let (tag, pos) = decode_tag(&[0x21], 0).unwrap();
546 assert_eq!(tag.number, 2);
547 assert_eq!(tag.class, TagClass::Application);
548 assert_eq!(tag.length, 1);
549 assert!(!tag.is_opening);
550 assert!(!tag.is_closing);
551 assert_eq!(pos, 1);
552 }
553
554 #[test]
555 fn decode_context_tag() {
556 let (tag, pos) = decode_tag(&[0x1C], 0).unwrap();
558 assert_eq!(tag.number, 1);
559 assert_eq!(tag.class, TagClass::Context);
560 assert_eq!(tag.length, 4);
561 assert_eq!(pos, 1);
562 }
563
564 #[test]
565 fn decode_opening_closing_tags() {
566 let (tag, _) = decode_tag(&[0x0E], 0).unwrap();
567 assert!(tag.is_opening);
568 assert_eq!(tag.number, 0);
569
570 let (tag, _) = decode_tag(&[0x0F], 0).unwrap();
571 assert!(tag.is_closing);
572 assert_eq!(tag.number, 0);
573
574 let (tag, _) = decode_tag(&[0x3E], 0).unwrap();
575 assert!(tag.is_opening);
576 assert_eq!(tag.number, 3);
577 }
578
579 #[test]
580 fn decode_extended_tag_number() {
581 let (tag, pos) = decode_tag(&[0xF1, 20], 0).unwrap();
583 assert_eq!(tag.number, 20);
584 assert_eq!(tag.class, TagClass::Application);
585 assert_eq!(tag.length, 1);
586 assert_eq!(pos, 2);
587 }
588
589 #[test]
590 fn decode_extended_length() {
591 let (tag, pos) = decode_tag(&[0x25, 100], 0).unwrap();
593 assert_eq!(tag.number, 2);
594 assert_eq!(tag.length, 100);
595 assert_eq!(pos, 2);
596
597 let (tag, pos) = decode_tag(&[0x25, 254, 0x03, 0xE8], 0).unwrap();
599 assert_eq!(tag.number, 2);
600 assert_eq!(tag.length, 1000);
601 assert_eq!(pos, 4);
602
603 let (tag, pos) = decode_tag(&[0x25, 255, 0x00, 0x01, 0x86, 0xA0], 0).unwrap();
605 assert_eq!(tag.number, 2);
606 assert_eq!(tag.length, 100000);
607 assert_eq!(pos, 6);
608 }
609
610 #[test]
611 fn decode_tag_round_trip() {
612 let cases = [
614 (0u8, TagClass::Application, 0u32),
615 (1, TagClass::Application, 1),
616 (4, TagClass::Application, 4),
617 (12, TagClass::Application, 4),
618 (0, TagClass::Context, 1),
619 (3, TagClass::Context, 4),
620 (15, TagClass::Application, 1),
621 (20, TagClass::Context, 2),
622 (2, TagClass::Application, 100),
623 (2, TagClass::Application, 1000),
624 (20, TagClass::Context, 100),
625 ];
626
627 for (tag_num, class, length) in cases {
628 let encoded = encode_to_vec(tag_num, class, length);
629 let (decoded, _) = decode_tag(&encoded, 0).unwrap();
630 assert_eq!(
631 decoded.number, tag_num,
632 "tag number mismatch for ({tag_num}, {class:?}, {length})"
633 );
634 assert_eq!(
635 decoded.class, class,
636 "class mismatch for ({tag_num}, {class:?}, {length})"
637 );
638 assert_eq!(
639 decoded.length, length,
640 "length mismatch for ({tag_num}, {class:?}, {length})"
641 );
642 }
643 }
644
645 #[test]
646 fn decode_tag_empty_buffer() {
647 assert!(decode_tag(&[], 0).is_err());
648 }
649
650 #[test]
651 fn decode_tag_truncated_extended() {
652 assert!(decode_tag(&[0xF1], 0).is_err());
654 }
655
656 #[test]
657 fn decode_tag_excessive_length() {
658 let data = [0x25, 255, 0x10, 0x00, 0x00, 0x00]; assert!(decode_tag(&data, 0).is_err());
661 }
662
663 #[test]
664 fn boolean_tag_detection() {
665 let (tag, _) = decode_tag(&[0x11], 0).unwrap();
667 assert!(tag.is_boolean_true());
668
669 let (tag, _) = decode_tag(&[0x10], 0).unwrap();
671 assert!(!tag.is_boolean_true());
672
673 let (tag, _) = decode_tag(&[0x21], 0).unwrap();
675 assert!(!tag.is_boolean_true());
676 }
677
678 #[test]
679 fn extract_context_value_simple() {
680 let data = [0x0E, 0x21, 42, 0x0F];
682 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
683 assert_eq!(value, &[0x21, 42]);
684 assert_eq!(pos, 4);
685 }
686
687 #[test]
690 fn extract_raw_context_non_tlv_payload() {
691 let data = [0x9E, 0xDE, 0xAD, 0xBE, 0xEF, 0x9F];
694 let (value, pos) = extract_raw_context(&data, 1, 9).unwrap();
695 assert_eq!(value, &[0xDE, 0xAD, 0xBE, 0xEF]);
696 assert_eq!(pos, 6);
697 }
698
699 #[test]
700 fn extract_raw_context_nested_same_tag() {
701 let data = [0x2E, 0x2E, 0x01, 0x2F, 0x02, 0x2F];
703 let (value, pos) = extract_raw_context(&data, 1, 2).unwrap();
704 assert_eq!(value, &[0x2E, 0x01, 0x2F, 0x02]);
705 assert_eq!(pos, 6);
706 }
707
708 #[test]
709 fn extract_raw_context_extended_tag() {
710 let data = [0xFE, 200, 0x01, 0x02, 0xFF, 200];
712 let (value, pos) = extract_raw_context(&data, 2, 200).unwrap();
713 assert_eq!(value, &[0x01, 0x02]);
714 assert_eq!(pos, 6);
715 }
716
717 #[test]
718 fn extract_raw_context_missing_closing() {
719 let data = [0x9E, 0xDE, 0xAD];
720 assert!(extract_raw_context(&data, 1, 9).is_err());
721 }
722
723 #[test]
724 fn extract_raw_context_truncated_extended_closing() {
725 let data = [0xFE, 200, 0x01, 0x02, 0xFF];
727 assert!(extract_raw_context(&data, 2, 200).is_err());
728 }
729
730 #[test]
731 fn extract_context_value_nested() {
732 let data = [0x0E, 0x1E, 0x21, 42, 0x1F, 0x0F];
734 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
735 assert_eq!(value, &[0x1E, 0x21, 42, 0x1F]);
736 assert_eq!(pos, 6);
737 }
738
739 #[test]
740 fn extract_context_value_missing_close() {
741 let data = [0x0E, 0x21, 42]; assert!(extract_context_value(&data, 1, 0).is_err());
743 }
744
745 #[test]
746 fn tag_is_context_helper() {
747 let (tag, _) = decode_tag(&[0x09], 0).unwrap(); assert!(tag.is_context(0));
749 assert!(!tag.is_context(1));
750 }
751
752 #[test]
755 fn extract_context_value_mismatched_closing_tag() {
756 let data = [0x0E, 0x21, 42, 0x1F]; assert!(extract_context_value(&data, 1, 0).is_err());
759 }
760
761 #[test]
762 fn extract_context_value_mismatched_nested_closing_tag() {
763 let data = [0x0E, 0x1E, 0x2F, 0x0F];
765 assert!(extract_context_value(&data, 1, 0).is_err());
766 }
767
768 #[test]
769 fn extract_context_value_deeply_nested() {
770 let data = [
773 0x0E, 0x1E, 0x2E, 0x21, 42, 0x2F, 0x1F, 0x0F, ];
781 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
782 assert_eq!(value, &data[1..7]); assert_eq!(pos, 8);
784 }
785
786 #[test]
787 fn extract_context_value_nesting_depth_exceeded() {
788 let mut data = Vec::new();
790 for i in 0..MAX_CONTEXT_NESTING_DEPTH {
793 let tag = (i % 15) as u8; data.push((tag << 4) | 0x0E); }
796 let result = extract_context_value(&data, 0, 0);
798 assert!(result.is_err());
799 }
800
801 #[test]
802 fn decode_tag_at_nonzero_offset() {
803 let data = [0xFF, 0xFF, 0x21, 0x00]; let (tag, pos) = decode_tag(&data, 2).unwrap();
806 assert_eq!(tag.number, 2);
807 assert_eq!(tag.class, TagClass::Application);
808 assert_eq!(tag.length, 1);
809 assert_eq!(pos, 3);
810 }
811
812 #[test]
813 fn decode_tag_offset_beyond_buffer() {
814 let data = [0x21];
815 assert!(decode_tag(&data, 5).is_err());
816 }
817
818 #[test]
819 fn decode_tag_truncated_2byte_extended_length() {
820 let data = [0x25, 254, 0x03]; assert!(decode_tag(&data, 0).is_err());
823 }
824
825 #[test]
826 fn decode_tag_truncated_4byte_extended_length() {
827 let data = [0x25, 255, 0x00, 0x01];
829 assert!(decode_tag(&data, 0).is_err());
830 }
831
832 #[test]
833 fn extract_context_value_with_boolean_inside() {
834 let data = [0x0E, 0x11, 0x0F]; let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
838 assert_eq!(value, &[0x11]); assert_eq!(pos, 3);
840 }
841
842 #[test]
843 fn extract_context_value_tag_data_overflows_buffer() {
844 let data = [0x0E, 0x25, 100, 0x01, 0x02, 0x0F];
847 assert!(matches!(
848 extract_context_value(&data, 1, 0),
849 Err(Error::BufferTooShort { need: 103, have: 6 })
850 ));
851 }
852
853 #[test]
854 fn opening_closing_tag_extended_round_trip() {
855 let mut buf = BytesMut::new();
856 for tag_num in [15u8, 20, 100, 254] {
858 buf.clear();
859 encode_opening_tag(&mut buf, tag_num);
860 let (tag, pos1) = decode_tag(&buf, 0).unwrap();
861 assert!(tag.is_opening);
862 assert_eq!(tag.number, tag_num);
863 assert_eq!(pos1, 2); buf.clear();
866 encode_closing_tag(&mut buf, tag_num);
867 let (tag, pos2) = decode_tag(&buf, 0).unwrap();
868 assert!(tag.is_closing);
869 assert_eq!(tag.number, tag_num);
870 assert_eq!(pos2, 2);
871 }
872 }
873}