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));
96 return;
97 }
98
99 let tag_nibble = if tag_number <= 14 {
100 tag_number << 4
101 } else {
102 0xF0
103 };
104
105 if length <= 4 {
106 buf.put_u8(tag_nibble | cls_bit | (length as u8));
107 if tag_number > 14 {
108 buf.put_u8(tag_number);
109 }
110 return;
111 }
112
113 buf.put_u8(tag_nibble | cls_bit | 5);
114 if tag_number > 14 {
115 buf.put_u8(tag_number);
116 }
117
118 if length <= 253 {
119 buf.put_u8(length as u8);
120 } else if length <= 65535 {
121 buf.put_u8(254);
122 buf.put_u16(length as u16);
123 } else {
124 buf.put_u8(255);
125 buf.put_u32(length);
126 }
127}
128
129pub fn encode_opening_tag(buf: &mut BytesMut, tag_number: u8) {
131 if tag_number <= 14 {
132 buf.put_u8((tag_number << 4) | 0x0E);
133 } else {
134 buf.put_u8(0xFE);
135 buf.put_u8(tag_number);
136 }
137}
138
139pub fn encode_closing_tag(buf: &mut BytesMut, tag_number: u8) {
141 if tag_number <= 14 {
142 buf.put_u8((tag_number << 4) | 0x0F);
143 } else {
144 buf.put_u8(0xFF);
145 buf.put_u8(tag_number);
146 }
147}
148
149const MAX_TAG_LENGTH: u32 = 1_048_576;
156
157pub const MAX_CONTEXT_NESTING_DEPTH: usize = 32;
159
160pub fn decode_tag(data: &[u8], offset: usize) -> Result<(Tag, usize), Error> {
164 if offset >= data.len() {
165 return Err(Error::decoding(
166 offset,
167 "tag decode: offset beyond buffer length",
168 ));
169 }
170
171 let initial = data[offset];
172 let mut pos = offset + 1;
173
174 let mut tag_number = (initial >> 4) & 0x0F;
175 let class = if (initial >> 3) & 0x01 == 1 {
176 TagClass::Context
177 } else {
178 TagClass::Application
179 };
180 let lvt = initial & 0x07;
181
182 if tag_number == 0x0F {
183 if pos >= data.len() {
184 return Err(Error::decoding(pos, "truncated extended tag number"));
185 }
186 tag_number = data[pos];
187 pos += 1;
188 }
189
190 if class == TagClass::Context {
191 if lvt == 6 {
192 return Ok((
193 Tag {
194 number: tag_number,
195 class,
196 length: 0,
197 is_opening: true,
198 is_closing: false,
199 },
200 pos,
201 ));
202 }
203 if lvt == 7 {
204 return Ok((
205 Tag {
206 number: tag_number,
207 class,
208 length: 0,
209 is_opening: false,
210 is_closing: true,
211 },
212 pos,
213 ));
214 }
215 }
216
217 let length = if lvt < 5 {
218 lvt as u32
219 } else {
220 if pos >= data.len() {
221 return Err(Error::decoding(pos, "truncated extended length"));
222 }
223 let ext = data[pos];
224 pos += 1;
225
226 match ext {
227 0..=253 => ext as u32,
228 254 => {
229 if pos + 2 > data.len() {
230 return Err(Error::decoding(pos, "truncated 2-byte extended length"));
231 }
232 let len = u16::from_be_bytes([data[pos], data[pos + 1]]) as u32;
233 pos += 2;
234 len
235 }
236 255 => {
237 if pos + 4 > data.len() {
238 return Err(Error::decoding(pos, "truncated 4-byte extended length"));
239 }
240 let len =
241 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
242 pos += 4;
243 len
244 }
245 }
246 };
247
248 if length > MAX_TAG_LENGTH {
249 return Err(Error::decoding(
250 offset,
251 format!("tag length ({length}) exceeds sanity limit ({MAX_TAG_LENGTH})"),
252 ));
253 }
254
255 Ok((
256 Tag {
257 number: tag_number,
258 class,
259 length,
260 is_opening: false,
261 is_closing: false,
262 },
263 pos,
264 ))
265}
266
267pub fn extract_context_value(
274 data: &[u8],
275 offset: usize,
276 tag_number: u8,
277) -> Result<(&[u8], usize), Error> {
278 let value_start = offset;
279 let mut pos = offset;
280 let mut depth: usize = 1;
281
282 while depth > 0 && pos < data.len() {
283 let (tag, new_pos) = decode_tag(data, pos)?;
284
285 if tag.is_opening {
286 depth += 1;
287 if depth > MAX_CONTEXT_NESTING_DEPTH {
288 return Err(Error::decoding(
289 pos,
290 format!(
291 "context tag nesting depth exceeds maximum ({MAX_CONTEXT_NESTING_DEPTH})"
292 ),
293 ));
294 }
295 pos = new_pos;
296 } else if tag.is_closing {
297 depth -= 1;
298 if depth == 0 {
299 if tag.number != tag_number {
300 return Err(Error::decoding(
301 pos,
302 format!(
303 "closing tag {} does not match opening tag {tag_number}",
304 tag.number
305 ),
306 ));
307 }
308 let value_end = pos;
309 return Ok((&data[value_start..value_end], new_pos));
310 }
311 pos = new_pos;
312 } else if tag.class == TagClass::Application && tag.number == app_tag::BOOLEAN {
313 pos = new_pos;
314 } else {
315 let content_end = new_pos
316 .checked_add(tag.length as usize)
317 .ok_or_else(|| Error::decoding(new_pos, "tag length overflow"))?;
318 if content_end > data.len() {
319 return Err(Error::decoding(
320 new_pos,
321 format!(
322 "tag data overflows buffer: need {} bytes at offset {new_pos}",
323 tag.length
324 ),
325 ));
326 }
327 pos = content_end;
328 }
329 }
330
331 Err(Error::decoding(
332 offset,
333 format!("missing closing tag {tag_number}"),
334 ))
335}
336
337pub fn decode_optional_context(
343 data: &[u8],
344 offset: usize,
345 tag_number: u8,
346) -> Result<(Option<&[u8]>, usize), Error> {
347 if offset >= data.len() {
348 return Ok((None, offset));
349 }
350
351 let (tag, new_pos) = decode_tag(data, offset)?;
352 if tag.is_context(tag_number) {
353 let end = new_pos
354 .checked_add(tag.length as usize)
355 .ok_or_else(|| Error::decoding(new_pos, "tag length overflow"))?;
356 if end > data.len() {
357 return Err(Error::buffer_too_short(end, data.len()));
358 }
359 Ok((Some(&data[new_pos..end]), end))
360 } else {
361 Ok((None, offset))
362 }
363}
364
365#[cfg(test)]
370mod tests {
371 use super::*;
372
373 fn encode_to_vec(tag_number: u8, class: TagClass, length: u32) -> Vec<u8> {
374 let mut buf = BytesMut::with_capacity(8);
375 encode_tag(&mut buf, tag_number, class, length);
376 buf.to_vec()
377 }
378
379 #[test]
380 fn encode_single_byte_application_tag() {
381 assert_eq!(encode_to_vec(0, TagClass::Application, 0), vec![0x00]);
383 assert_eq!(encode_to_vec(2, TagClass::Application, 1), vec![0x21]);
385 assert_eq!(encode_to_vec(4, TagClass::Application, 4), vec![0x44]);
387 assert_eq!(encode_to_vec(12, TagClass::Application, 4), vec![0xC4]);
389 }
390
391 #[test]
392 fn encode_single_byte_context_tag() {
393 assert_eq!(encode_to_vec(0, TagClass::Context, 1), vec![0x09]);
395 assert_eq!(encode_to_vec(1, TagClass::Context, 4), vec![0x1C]);
397 assert_eq!(encode_to_vec(2, TagClass::Context, 0), vec![0x28]);
399 }
400
401 #[test]
402 fn encode_extended_tag_number() {
403 let encoded = encode_to_vec(15, TagClass::Application, 1);
405 assert_eq!(encoded, vec![0xF1, 15]);
406
407 let encoded = encode_to_vec(20, TagClass::Context, 2);
409 assert_eq!(encoded, vec![0xFA, 20]);
410 }
411
412 #[test]
413 fn encode_extended_length() {
414 let encoded = encode_to_vec(2, TagClass::Application, 100);
416 assert_eq!(encoded, vec![0x25, 100]);
417
418 let encoded = encode_to_vec(2, TagClass::Application, 1000);
420 assert_eq!(encoded, vec![0x25, 254, 0x03, 0xE8]);
421
422 let encoded = encode_to_vec(2, TagClass::Application, 100000);
424 assert_eq!(encoded, vec![0x25, 255, 0x00, 0x01, 0x86, 0xA0]);
425 }
426
427 #[test]
428 fn encode_extended_tag_and_length() {
429 let encoded = encode_to_vec(20, TagClass::Context, 100);
431 assert_eq!(encoded, vec![0xFD, 20, 100]);
432 }
433
434 #[test]
435 fn encode_opening_closing_tags() {
436 let mut buf = BytesMut::new();
437
438 encode_opening_tag(&mut buf, 0);
440 assert_eq!(&buf[..], &[0x0E]);
441
442 buf.clear();
443 encode_closing_tag(&mut buf, 0);
445 assert_eq!(&buf[..], &[0x0F]);
446
447 buf.clear();
448 encode_opening_tag(&mut buf, 3);
450 assert_eq!(&buf[..], &[0x3E]);
451
452 buf.clear();
453 encode_opening_tag(&mut buf, 20);
455 assert_eq!(&buf[..], &[0xFE, 20]);
456
457 buf.clear();
458 encode_closing_tag(&mut buf, 20);
460 assert_eq!(&buf[..], &[0xFF, 20]);
461 }
462
463 #[test]
464 fn decode_single_byte_tag() {
465 let (tag, pos) = decode_tag(&[0x21], 0).unwrap();
467 assert_eq!(tag.number, 2);
468 assert_eq!(tag.class, TagClass::Application);
469 assert_eq!(tag.length, 1);
470 assert!(!tag.is_opening);
471 assert!(!tag.is_closing);
472 assert_eq!(pos, 1);
473 }
474
475 #[test]
476 fn decode_context_tag() {
477 let (tag, pos) = decode_tag(&[0x1C], 0).unwrap();
479 assert_eq!(tag.number, 1);
480 assert_eq!(tag.class, TagClass::Context);
481 assert_eq!(tag.length, 4);
482 assert_eq!(pos, 1);
483 }
484
485 #[test]
486 fn decode_opening_closing_tags() {
487 let (tag, _) = decode_tag(&[0x0E], 0).unwrap();
488 assert!(tag.is_opening);
489 assert_eq!(tag.number, 0);
490
491 let (tag, _) = decode_tag(&[0x0F], 0).unwrap();
492 assert!(tag.is_closing);
493 assert_eq!(tag.number, 0);
494
495 let (tag, _) = decode_tag(&[0x3E], 0).unwrap();
496 assert!(tag.is_opening);
497 assert_eq!(tag.number, 3);
498 }
499
500 #[test]
501 fn decode_extended_tag_number() {
502 let (tag, pos) = decode_tag(&[0xF1, 20], 0).unwrap();
504 assert_eq!(tag.number, 20);
505 assert_eq!(tag.class, TagClass::Application);
506 assert_eq!(tag.length, 1);
507 assert_eq!(pos, 2);
508 }
509
510 #[test]
511 fn decode_extended_length() {
512 let (tag, pos) = decode_tag(&[0x25, 100], 0).unwrap();
514 assert_eq!(tag.number, 2);
515 assert_eq!(tag.length, 100);
516 assert_eq!(pos, 2);
517
518 let (tag, pos) = decode_tag(&[0x25, 254, 0x03, 0xE8], 0).unwrap();
520 assert_eq!(tag.number, 2);
521 assert_eq!(tag.length, 1000);
522 assert_eq!(pos, 4);
523
524 let (tag, pos) = decode_tag(&[0x25, 255, 0x00, 0x01, 0x86, 0xA0], 0).unwrap();
526 assert_eq!(tag.number, 2);
527 assert_eq!(tag.length, 100000);
528 assert_eq!(pos, 6);
529 }
530
531 #[test]
532 fn decode_tag_round_trip() {
533 let cases = [
535 (0u8, TagClass::Application, 0u32),
536 (1, TagClass::Application, 1),
537 (4, TagClass::Application, 4),
538 (12, TagClass::Application, 4),
539 (0, TagClass::Context, 1),
540 (3, TagClass::Context, 4),
541 (15, TagClass::Application, 1),
542 (20, TagClass::Context, 2),
543 (2, TagClass::Application, 100),
544 (2, TagClass::Application, 1000),
545 (20, TagClass::Context, 100),
546 ];
547
548 for (tag_num, class, length) in cases {
549 let encoded = encode_to_vec(tag_num, class, length);
550 let (decoded, _) = decode_tag(&encoded, 0).unwrap();
551 assert_eq!(
552 decoded.number, tag_num,
553 "tag number mismatch for ({tag_num}, {class:?}, {length})"
554 );
555 assert_eq!(
556 decoded.class, class,
557 "class mismatch for ({tag_num}, {class:?}, {length})"
558 );
559 assert_eq!(
560 decoded.length, length,
561 "length mismatch for ({tag_num}, {class:?}, {length})"
562 );
563 }
564 }
565
566 #[test]
567 fn decode_tag_empty_buffer() {
568 assert!(decode_tag(&[], 0).is_err());
569 }
570
571 #[test]
572 fn decode_tag_truncated_extended() {
573 assert!(decode_tag(&[0xF1], 0).is_err());
575 }
576
577 #[test]
578 fn decode_tag_excessive_length() {
579 let data = [0x25, 255, 0x10, 0x00, 0x00, 0x00]; assert!(decode_tag(&data, 0).is_err());
582 }
583
584 #[test]
585 fn boolean_tag_detection() {
586 let (tag, _) = decode_tag(&[0x11], 0).unwrap();
588 assert!(tag.is_boolean_true());
589
590 let (tag, _) = decode_tag(&[0x10], 0).unwrap();
592 assert!(!tag.is_boolean_true());
593
594 let (tag, _) = decode_tag(&[0x21], 0).unwrap();
596 assert!(!tag.is_boolean_true());
597 }
598
599 #[test]
600 fn extract_context_value_simple() {
601 let data = [0x0E, 0x21, 42, 0x0F];
603 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
604 assert_eq!(value, &[0x21, 42]);
605 assert_eq!(pos, 4);
606 }
607
608 #[test]
609 fn extract_context_value_nested() {
610 let data = [0x0E, 0x1E, 0x21, 42, 0x1F, 0x0F];
612 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
613 assert_eq!(value, &[0x1E, 0x21, 42, 0x1F]);
614 assert_eq!(pos, 6);
615 }
616
617 #[test]
618 fn extract_context_value_missing_close() {
619 let data = [0x0E, 0x21, 42]; assert!(extract_context_value(&data, 1, 0).is_err());
621 }
622
623 #[test]
624 fn tag_is_context_helper() {
625 let (tag, _) = decode_tag(&[0x09], 0).unwrap(); assert!(tag.is_context(0));
627 assert!(!tag.is_context(1));
628 }
629
630 #[test]
631 fn decode_optional_context_present() {
632 let data = [0x09, 42];
634 let (value, pos) = decode_optional_context(&data, 0, 0).unwrap();
635 assert_eq!(value, Some(&[42u8][..]));
636 assert_eq!(pos, 2);
637 }
638
639 #[test]
640 fn decode_optional_context_absent() {
641 let data = [0x19, 42];
643 let (value, pos) = decode_optional_context(&data, 0, 0).unwrap();
644 assert!(value.is_none());
645 assert_eq!(pos, 0); }
647
648 #[test]
649 fn decode_optional_context_empty_buffer() {
650 let (value, pos) = decode_optional_context(&[], 0, 0).unwrap();
651 assert!(value.is_none());
652 assert_eq!(pos, 0);
653 }
654
655 #[test]
658 fn extract_context_value_mismatched_closing_tag() {
659 let data = [0x0E, 0x21, 42, 0x1F]; assert!(extract_context_value(&data, 1, 0).is_err());
662 }
663
664 #[test]
665 fn extract_context_value_deeply_nested() {
666 let data = [
669 0x0E, 0x1E, 0x2E, 0x21, 42, 0x2F, 0x1F, 0x0F, ];
677 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
678 assert_eq!(value, &data[1..7]); assert_eq!(pos, 8);
680 }
681
682 #[test]
683 fn extract_context_value_nesting_depth_exceeded() {
684 let mut data = Vec::new();
686 for i in 0..MAX_CONTEXT_NESTING_DEPTH {
689 let tag = (i % 15) as u8; data.push((tag << 4) | 0x0E); }
692 let result = extract_context_value(&data, 0, 0);
694 assert!(result.is_err());
695 }
696
697 #[test]
698 fn decode_tag_at_nonzero_offset() {
699 let data = [0xFF, 0xFF, 0x21, 0x00]; let (tag, pos) = decode_tag(&data, 2).unwrap();
702 assert_eq!(tag.number, 2);
703 assert_eq!(tag.class, TagClass::Application);
704 assert_eq!(tag.length, 1);
705 assert_eq!(pos, 3);
706 }
707
708 #[test]
709 fn decode_tag_offset_beyond_buffer() {
710 let data = [0x21];
711 assert!(decode_tag(&data, 5).is_err());
712 }
713
714 #[test]
715 fn decode_tag_truncated_2byte_extended_length() {
716 let data = [0x25, 254, 0x03]; assert!(decode_tag(&data, 0).is_err());
719 }
720
721 #[test]
722 fn decode_tag_truncated_4byte_extended_length() {
723 let data = [0x25, 255, 0x00, 0x01];
725 assert!(decode_tag(&data, 0).is_err());
726 }
727
728 #[test]
729 fn extract_context_value_with_boolean_inside() {
730 let data = [0x0E, 0x11, 0x0F]; let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
734 assert_eq!(value, &[0x11]); assert_eq!(pos, 3);
736 }
737
738 #[test]
739 fn extract_context_value_tag_data_overflows_buffer() {
740 let data = [0x0E, 0x25, 100, 0x01, 0x02, 0x0F];
742 let result = extract_context_value(&data, 1, 0);
743 assert!(result.is_err());
744 }
745
746 #[test]
747 fn decode_optional_context_content_overflows() {
748 let data = [0x0C, 0x01, 0x02]; assert!(decode_optional_context(&data, 0, 0).is_err());
751 }
752
753 #[test]
754 fn opening_closing_tag_extended_round_trip() {
755 let mut buf = BytesMut::new();
756 for tag_num in [15u8, 20, 100, 254] {
758 buf.clear();
759 encode_opening_tag(&mut buf, tag_num);
760 let (tag, pos1) = decode_tag(&buf, 0).unwrap();
761 assert!(tag.is_opening);
762 assert_eq!(tag.number, tag_num);
763 assert_eq!(pos1, 2); buf.clear();
766 encode_closing_tag(&mut buf, tag_num);
767 let (tag, pos2) = decode_tag(&buf, 0).unwrap();
768 assert!(tag.is_closing);
769 assert_eq!(tag.number, tag_num);
770 assert_eq!(pos2, 2);
771 }
772 }
773}