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;
25 pub const BOOLEAN: u8 = 1;
26 pub const UNSIGNED: u8 = 2;
27 pub const SIGNED: u8 = 3;
28 pub const REAL: u8 = 4;
29 pub const DOUBLE: u8 = 5;
30 pub const OCTET_STRING: u8 = 6;
31 pub const CHARACTER_STRING: u8 = 7;
32 pub const BIT_STRING: u8 = 8;
33 pub const ENUMERATED: u8 = 9;
34 pub const DATE: u8 = 10;
35 pub const TIME: u8 = 11;
36 pub const OBJECT_IDENTIFIER: u8 = 12;
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct Tag {
42 pub number: u8,
44 pub class: TagClass,
46 pub length: u32,
49 pub is_opening: bool,
51 pub is_closing: bool,
53}
54
55impl Tag {
56 pub fn is_boolean_true(&self) -> bool {
61 self.class == TagClass::Application && self.number == app_tag::BOOLEAN && self.length != 0
62 }
63
64 pub fn is_context(&self, number: u8) -> bool {
66 self.class == TagClass::Context
67 && self.number == number
68 && !self.is_opening
69 && !self.is_closing
70 }
71
72 pub fn is_opening_tag(&self, number: u8) -> bool {
74 self.class == TagClass::Context && self.number == number && self.is_opening
75 }
76
77 pub fn is_closing_tag(&self, number: u8) -> bool {
79 self.class == TagClass::Context && self.number == number && self.is_closing
80 }
81}
82
83pub fn encode_tag(buf: &mut BytesMut, tag_number: u8, class: TagClass, length: u32) {
92 let cls_bit = (class as u8) << 3;
93
94 if tag_number <= 14 && length <= 4 {
95 buf.put_u8((tag_number << 4) | cls_bit | (length as u8));
97 return;
98 }
99
100 let tag_nibble = if tag_number <= 14 {
102 tag_number << 4
103 } else {
104 0xF0 };
106
107 if length <= 4 {
108 buf.put_u8(tag_nibble | cls_bit | (length as u8));
109 if tag_number > 14 {
110 buf.put_u8(tag_number);
111 }
112 return;
113 }
114
115 buf.put_u8(tag_nibble | cls_bit | 5);
117 if tag_number > 14 {
118 buf.put_u8(tag_number);
119 }
120
121 if length <= 253 {
122 buf.put_u8(length as u8);
123 } else if length <= 65535 {
124 buf.put_u8(254);
125 buf.put_u16(length as u16);
126 } else {
127 buf.put_u8(255);
128 buf.put_u32(length);
129 }
130}
131
132pub fn encode_opening_tag(buf: &mut BytesMut, tag_number: u8) {
134 if tag_number <= 14 {
135 buf.put_u8((tag_number << 4) | 0x0E);
136 } else {
137 buf.put_u8(0xFE);
138 buf.put_u8(tag_number);
139 }
140}
141
142pub fn encode_closing_tag(buf: &mut BytesMut, tag_number: u8) {
144 if tag_number <= 14 {
145 buf.put_u8((tag_number << 4) | 0x0F);
146 } else {
147 buf.put_u8(0xFF);
148 buf.put_u8(tag_number);
149 }
150}
151
152const MAX_TAG_LENGTH: u32 = 1_048_576;
159
160pub const MAX_CONTEXT_NESTING_DEPTH: usize = 32;
162
163pub fn decode_tag(data: &[u8], offset: usize) -> Result<(Tag, usize), Error> {
167 if offset >= data.len() {
168 return Err(Error::decoding(
169 offset,
170 "tag decode: offset beyond buffer length",
171 ));
172 }
173
174 let initial = data[offset];
175 let mut pos = offset + 1;
176
177 let mut tag_number = (initial >> 4) & 0x0F;
179 let class = if (initial >> 3) & 0x01 == 1 {
180 TagClass::Context
181 } else {
182 TagClass::Application
183 };
184 let lvt = initial & 0x07;
185
186 if tag_number == 0x0F {
188 if pos >= data.len() {
189 return Err(Error::decoding(pos, "truncated extended tag number"));
190 }
191 tag_number = data[pos];
192 pos += 1;
195 }
196
197 if class == TagClass::Context {
199 if lvt == 6 {
200 return Ok((
201 Tag {
202 number: tag_number,
203 class,
204 length: 0,
205 is_opening: true,
206 is_closing: false,
207 },
208 pos,
209 ));
210 }
211 if lvt == 7 {
212 return Ok((
213 Tag {
214 number: tag_number,
215 class,
216 length: 0,
217 is_opening: false,
218 is_closing: true,
219 },
220 pos,
221 ));
222 }
223 }
224
225 let length = if lvt < 5 {
227 lvt as u32
228 } else {
229 if pos >= data.len() {
231 return Err(Error::decoding(pos, "truncated extended length"));
232 }
233 let ext = data[pos];
234 pos += 1;
235
236 match ext {
237 0..=253 => ext as u32,
238 254 => {
239 if pos + 2 > data.len() {
240 return Err(Error::decoding(pos, "truncated 2-byte extended length"));
241 }
242 let len = u16::from_be_bytes([data[pos], data[pos + 1]]) as u32;
243 pos += 2;
244 len
245 }
246 255 => {
247 if pos + 4 > data.len() {
248 return Err(Error::decoding(pos, "truncated 4-byte extended length"));
249 }
250 let len =
251 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
252 pos += 4;
253 len
254 }
255 }
256 };
257
258 if length > MAX_TAG_LENGTH {
260 return Err(Error::decoding(
261 offset,
262 format!("tag length ({length}) exceeds sanity limit ({MAX_TAG_LENGTH})"),
263 ));
264 }
265
266 Ok((
267 Tag {
268 number: tag_number,
269 class,
270 length,
271 is_opening: false,
272 is_closing: false,
273 },
274 pos,
275 ))
276}
277
278pub fn extract_context_value(
285 data: &[u8],
286 offset: usize,
287 tag_number: u8,
288) -> Result<(&[u8], usize), Error> {
289 let value_start = offset;
290 let mut pos = offset;
291 let mut depth: usize = 1;
292
293 while depth > 0 && pos < data.len() {
294 let (tag, new_pos) = decode_tag(data, pos)?;
295
296 if tag.is_opening {
297 depth += 1;
298 if depth > MAX_CONTEXT_NESTING_DEPTH {
299 return Err(Error::decoding(
300 pos,
301 format!(
302 "context tag nesting depth exceeds maximum ({MAX_CONTEXT_NESTING_DEPTH})"
303 ),
304 ));
305 }
306 pos = new_pos;
307 } else if tag.is_closing {
308 depth -= 1;
309 if depth == 0 {
310 if tag.number != tag_number {
311 return Err(Error::decoding(
312 pos,
313 format!(
314 "closing tag {} does not match opening tag {tag_number}",
315 tag.number
316 ),
317 ));
318 }
319 let value_end = pos;
320 return Ok((&data[value_start..value_end], new_pos));
321 }
322 pos = new_pos;
323 } else {
324 if tag.class == TagClass::Application && tag.number == app_tag::BOOLEAN {
326 pos = new_pos;
328 } else {
329 let content_end = new_pos
330 .checked_add(tag.length as usize)
331 .ok_or_else(|| Error::decoding(new_pos, "tag length overflow"))?;
332 if content_end > data.len() {
333 return Err(Error::decoding(
334 new_pos,
335 format!(
336 "tag data overflows buffer: need {} bytes at offset {new_pos}",
337 tag.length
338 ),
339 ));
340 }
341 pos = content_end;
342 }
343 }
344 }
345
346 Err(Error::decoding(
347 offset,
348 format!("missing closing tag {tag_number}"),
349 ))
350}
351
352pub fn decode_optional_context(
358 data: &[u8],
359 offset: usize,
360 tag_number: u8,
361) -> Result<(Option<&[u8]>, usize), Error> {
362 if offset >= data.len() {
363 return Ok((None, offset));
364 }
365
366 let (tag, new_pos) = decode_tag(data, offset)?;
367 if tag.is_context(tag_number) {
368 let end = new_pos
369 .checked_add(tag.length as usize)
370 .ok_or_else(|| Error::decoding(new_pos, "tag length overflow"))?;
371 if end > data.len() {
372 return Err(Error::buffer_too_short(end, data.len()));
373 }
374 Ok((Some(&data[new_pos..end]), end))
375 } else {
376 Ok((None, offset))
377 }
378}
379
380#[cfg(test)]
385mod tests {
386 use super::*;
387
388 fn encode_to_vec(tag_number: u8, class: TagClass, length: u32) -> Vec<u8> {
389 let mut buf = BytesMut::with_capacity(8);
390 encode_tag(&mut buf, tag_number, class, length);
391 buf.to_vec()
392 }
393
394 #[test]
395 fn encode_single_byte_application_tag() {
396 assert_eq!(encode_to_vec(0, TagClass::Application, 0), vec![0x00]);
398 assert_eq!(encode_to_vec(2, TagClass::Application, 1), vec![0x21]);
400 assert_eq!(encode_to_vec(4, TagClass::Application, 4), vec![0x44]);
402 assert_eq!(encode_to_vec(12, TagClass::Application, 4), vec![0xC4]);
404 }
405
406 #[test]
407 fn encode_single_byte_context_tag() {
408 assert_eq!(encode_to_vec(0, TagClass::Context, 1), vec![0x09]);
410 assert_eq!(encode_to_vec(1, TagClass::Context, 4), vec![0x1C]);
412 assert_eq!(encode_to_vec(2, TagClass::Context, 0), vec![0x28]);
414 }
415
416 #[test]
417 fn encode_extended_tag_number() {
418 let encoded = encode_to_vec(15, TagClass::Application, 1);
420 assert_eq!(encoded, vec![0xF1, 15]);
421
422 let encoded = encode_to_vec(20, TagClass::Context, 2);
424 assert_eq!(encoded, vec![0xFA, 20]);
425 }
426
427 #[test]
428 fn encode_extended_length() {
429 let encoded = encode_to_vec(2, TagClass::Application, 100);
431 assert_eq!(encoded, vec![0x25, 100]);
432
433 let encoded = encode_to_vec(2, TagClass::Application, 1000);
435 assert_eq!(encoded, vec![0x25, 254, 0x03, 0xE8]);
436
437 let encoded = encode_to_vec(2, TagClass::Application, 100000);
439 assert_eq!(encoded, vec![0x25, 255, 0x00, 0x01, 0x86, 0xA0]);
440 }
441
442 #[test]
443 fn encode_extended_tag_and_length() {
444 let encoded = encode_to_vec(20, TagClass::Context, 100);
446 assert_eq!(encoded, vec![0xFD, 20, 100]);
447 }
448
449 #[test]
450 fn encode_opening_closing_tags() {
451 let mut buf = BytesMut::new();
452
453 encode_opening_tag(&mut buf, 0);
455 assert_eq!(&buf[..], &[0x0E]);
456
457 buf.clear();
458 encode_closing_tag(&mut buf, 0);
460 assert_eq!(&buf[..], &[0x0F]);
461
462 buf.clear();
463 encode_opening_tag(&mut buf, 3);
465 assert_eq!(&buf[..], &[0x3E]);
466
467 buf.clear();
468 encode_opening_tag(&mut buf, 20);
470 assert_eq!(&buf[..], &[0xFE, 20]);
471
472 buf.clear();
473 encode_closing_tag(&mut buf, 20);
475 assert_eq!(&buf[..], &[0xFF, 20]);
476 }
477
478 #[test]
479 fn decode_single_byte_tag() {
480 let (tag, pos) = decode_tag(&[0x21], 0).unwrap();
482 assert_eq!(tag.number, 2);
483 assert_eq!(tag.class, TagClass::Application);
484 assert_eq!(tag.length, 1);
485 assert!(!tag.is_opening);
486 assert!(!tag.is_closing);
487 assert_eq!(pos, 1);
488 }
489
490 #[test]
491 fn decode_context_tag() {
492 let (tag, pos) = decode_tag(&[0x1C], 0).unwrap();
494 assert_eq!(tag.number, 1);
495 assert_eq!(tag.class, TagClass::Context);
496 assert_eq!(tag.length, 4);
497 assert_eq!(pos, 1);
498 }
499
500 #[test]
501 fn decode_opening_closing_tags() {
502 let (tag, _) = decode_tag(&[0x0E], 0).unwrap();
503 assert!(tag.is_opening);
504 assert_eq!(tag.number, 0);
505
506 let (tag, _) = decode_tag(&[0x0F], 0).unwrap();
507 assert!(tag.is_closing);
508 assert_eq!(tag.number, 0);
509
510 let (tag, _) = decode_tag(&[0x3E], 0).unwrap();
511 assert!(tag.is_opening);
512 assert_eq!(tag.number, 3);
513 }
514
515 #[test]
516 fn decode_extended_tag_number() {
517 let (tag, pos) = decode_tag(&[0xF1, 20], 0).unwrap();
519 assert_eq!(tag.number, 20);
520 assert_eq!(tag.class, TagClass::Application);
521 assert_eq!(tag.length, 1);
522 assert_eq!(pos, 2);
523 }
524
525 #[test]
526 fn decode_extended_length() {
527 let (tag, pos) = decode_tag(&[0x25, 100], 0).unwrap();
529 assert_eq!(tag.number, 2);
530 assert_eq!(tag.length, 100);
531 assert_eq!(pos, 2);
532
533 let (tag, pos) = decode_tag(&[0x25, 254, 0x03, 0xE8], 0).unwrap();
535 assert_eq!(tag.number, 2);
536 assert_eq!(tag.length, 1000);
537 assert_eq!(pos, 4);
538
539 let (tag, pos) = decode_tag(&[0x25, 255, 0x00, 0x01, 0x86, 0xA0], 0).unwrap();
541 assert_eq!(tag.number, 2);
542 assert_eq!(tag.length, 100000);
543 assert_eq!(pos, 6);
544 }
545
546 #[test]
547 fn decode_tag_round_trip() {
548 let cases = [
550 (0u8, TagClass::Application, 0u32),
551 (1, TagClass::Application, 1),
552 (4, TagClass::Application, 4),
553 (12, TagClass::Application, 4),
554 (0, TagClass::Context, 1),
555 (3, TagClass::Context, 4),
556 (15, TagClass::Application, 1),
557 (20, TagClass::Context, 2),
558 (2, TagClass::Application, 100),
559 (2, TagClass::Application, 1000),
560 (20, TagClass::Context, 100),
561 ];
562
563 for (tag_num, class, length) in cases {
564 let encoded = encode_to_vec(tag_num, class, length);
565 let (decoded, _) = decode_tag(&encoded, 0).unwrap();
566 assert_eq!(
567 decoded.number, tag_num,
568 "tag number mismatch for ({tag_num}, {class:?}, {length})"
569 );
570 assert_eq!(
571 decoded.class, class,
572 "class mismatch for ({tag_num}, {class:?}, {length})"
573 );
574 assert_eq!(
575 decoded.length, length,
576 "length mismatch for ({tag_num}, {class:?}, {length})"
577 );
578 }
579 }
580
581 #[test]
582 fn decode_tag_empty_buffer() {
583 assert!(decode_tag(&[], 0).is_err());
584 }
585
586 #[test]
587 fn decode_tag_truncated_extended() {
588 assert!(decode_tag(&[0xF1], 0).is_err());
590 }
591
592 #[test]
593 fn decode_tag_excessive_length() {
594 let data = [0x25, 255, 0x10, 0x00, 0x00, 0x00]; assert!(decode_tag(&data, 0).is_err());
597 }
598
599 #[test]
600 fn boolean_tag_detection() {
601 let (tag, _) = decode_tag(&[0x11], 0).unwrap();
603 assert!(tag.is_boolean_true());
604
605 let (tag, _) = decode_tag(&[0x10], 0).unwrap();
607 assert!(!tag.is_boolean_true());
608
609 let (tag, _) = decode_tag(&[0x21], 0).unwrap();
611 assert!(!tag.is_boolean_true());
612 }
613
614 #[test]
615 fn extract_context_value_simple() {
616 let data = [0x0E, 0x21, 42, 0x0F];
618 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
619 assert_eq!(value, &[0x21, 42]);
620 assert_eq!(pos, 4);
621 }
622
623 #[test]
624 fn extract_context_value_nested() {
625 let data = [0x0E, 0x1E, 0x21, 42, 0x1F, 0x0F];
627 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
628 assert_eq!(value, &[0x1E, 0x21, 42, 0x1F]);
629 assert_eq!(pos, 6);
630 }
631
632 #[test]
633 fn extract_context_value_missing_close() {
634 let data = [0x0E, 0x21, 42]; assert!(extract_context_value(&data, 1, 0).is_err());
636 }
637
638 #[test]
639 fn tag_is_context_helper() {
640 let (tag, _) = decode_tag(&[0x09], 0).unwrap(); assert!(tag.is_context(0));
642 assert!(!tag.is_context(1));
643 }
644
645 #[test]
646 fn decode_optional_context_present() {
647 let data = [0x09, 42];
649 let (value, pos) = decode_optional_context(&data, 0, 0).unwrap();
650 assert_eq!(value, Some(&[42u8][..]));
651 assert_eq!(pos, 2);
652 }
653
654 #[test]
655 fn decode_optional_context_absent() {
656 let data = [0x19, 42];
658 let (value, pos) = decode_optional_context(&data, 0, 0).unwrap();
659 assert!(value.is_none());
660 assert_eq!(pos, 0); }
662
663 #[test]
664 fn decode_optional_context_empty_buffer() {
665 let (value, pos) = decode_optional_context(&[], 0, 0).unwrap();
666 assert!(value.is_none());
667 assert_eq!(pos, 0);
668 }
669
670 #[test]
673 fn extract_context_value_mismatched_closing_tag() {
674 let data = [0x0E, 0x21, 42, 0x1F]; assert!(extract_context_value(&data, 1, 0).is_err());
677 }
678
679 #[test]
680 fn extract_context_value_deeply_nested() {
681 let data = [
684 0x0E, 0x1E, 0x2E, 0x21, 42, 0x2F, 0x1F, 0x0F, ];
692 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
693 assert_eq!(value, &data[1..7]); assert_eq!(pos, 8);
695 }
696
697 #[test]
698 fn extract_context_value_nesting_depth_exceeded() {
699 let mut data = Vec::new();
701 for i in 0..MAX_CONTEXT_NESTING_DEPTH {
704 let tag = (i % 15) as u8; data.push((tag << 4) | 0x0E); }
707 let result = extract_context_value(&data, 0, 0);
709 assert!(result.is_err());
710 }
711
712 #[test]
713 fn decode_tag_at_nonzero_offset() {
714 let data = [0xFF, 0xFF, 0x21, 0x00]; let (tag, pos) = decode_tag(&data, 2).unwrap();
717 assert_eq!(tag.number, 2);
718 assert_eq!(tag.class, TagClass::Application);
719 assert_eq!(tag.length, 1);
720 assert_eq!(pos, 3);
721 }
722
723 #[test]
724 fn decode_tag_offset_beyond_buffer() {
725 let data = [0x21];
726 assert!(decode_tag(&data, 5).is_err());
727 }
728
729 #[test]
730 fn decode_tag_truncated_2byte_extended_length() {
731 let data = [0x25, 254, 0x03]; assert!(decode_tag(&data, 0).is_err());
734 }
735
736 #[test]
737 fn decode_tag_truncated_4byte_extended_length() {
738 let data = [0x25, 255, 0x00, 0x01];
740 assert!(decode_tag(&data, 0).is_err());
741 }
742
743 #[test]
744 fn extract_context_value_with_boolean_inside() {
745 let data = [0x0E, 0x11, 0x0F]; let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
749 assert_eq!(value, &[0x11]); assert_eq!(pos, 3);
751 }
752
753 #[test]
754 fn extract_context_value_tag_data_overflows_buffer() {
755 let data = [0x0E, 0x25, 100, 0x01, 0x02, 0x0F];
757 let result = extract_context_value(&data, 1, 0);
758 assert!(result.is_err());
759 }
760
761 #[test]
762 fn decode_optional_context_content_overflows() {
763 let data = [0x0C, 0x01, 0x02]; assert!(decode_optional_context(&data, 0, 0).is_err());
766 }
767
768 #[test]
769 fn opening_closing_tag_extended_round_trip() {
770 let mut buf = BytesMut::new();
771 for tag_num in [15u8, 20, 100, 254] {
773 buf.clear();
774 encode_opening_tag(&mut buf, tag_num);
775 let (tag, pos1) = decode_tag(&buf, 0).unwrap();
776 assert!(tag.is_opening);
777 assert_eq!(tag.number, tag_num);
778 assert_eq!(pos1, 2); buf.clear();
781 encode_closing_tag(&mut buf, tag_num);
782 let (tag, pos2) = decode_tag(&buf, 0).unwrap();
783 assert!(tag.is_closing);
784 assert_eq!(tag.number, tag_num);
785 assert_eq!(pos2, 2);
786 }
787 }
788}