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 if class == TagClass::Application && lvt > 5 {
182 return Err(Error::decoding(offset, "reserved application tag L/V/T"));
183 }
184
185 if tag_number == 0x0F {
186 if pos >= data.len() {
187 return Err(Error::decoding(pos, "truncated extended tag number"));
188 }
189 tag_number = data[pos];
190 if !(15..=254).contains(&tag_number) {
191 return Err(Error::decoding(
192 pos,
193 format!("invalid extended tag number {tag_number}"),
194 ));
195 }
196 pos += 1;
197 }
198
199 if class == TagClass::Context {
200 if lvt == 6 {
201 return Ok((
202 Tag {
203 number: tag_number,
204 class,
205 length: 0,
206 is_opening: true,
207 is_closing: false,
208 },
209 pos,
210 ));
211 }
212 if lvt == 7 {
213 return Ok((
214 Tag {
215 number: tag_number,
216 class,
217 length: 0,
218 is_opening: false,
219 is_closing: true,
220 },
221 pos,
222 ));
223 }
224 }
225
226 let length = if lvt < 5 {
227 lvt as u32
228 } else {
229 if pos >= data.len() {
230 return Err(Error::decoding(pos, "truncated extended length"));
231 }
232 let ext = data[pos];
233 pos += 1;
234
235 match ext {
236 0..=4 => {
237 return Err(Error::decoding(
238 pos - 1,
239 format!("non-canonical extended tag length {ext}"),
240 ));
241 }
242 5..=253 => ext as u32,
243 254 => {
244 if pos + 2 > data.len() {
245 return Err(Error::decoding(pos, "truncated 2-byte extended length"));
246 }
247 let len = u16::from_be_bytes([data[pos], data[pos + 1]]) as u32;
248 if len < 254 {
249 return Err(Error::decoding(
250 pos,
251 format!("non-canonical 2-byte extended tag length {len}"),
252 ));
253 }
254 pos += 2;
255 len
256 }
257 255 => {
258 if pos + 4 > data.len() {
259 return Err(Error::decoding(pos, "truncated 4-byte extended length"));
260 }
261 let len =
262 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
263 if len < 65_536 {
264 return Err(Error::decoding(
265 pos,
266 format!("non-canonical 4-byte extended tag length {len}"),
267 ));
268 }
269 pos += 4;
270 len
271 }
272 }
273 };
274
275 if length > MAX_TAG_LENGTH {
276 return Err(Error::decoding(
277 offset,
278 format!("tag length ({length}) exceeds sanity limit ({MAX_TAG_LENGTH})"),
279 ));
280 }
281
282 Ok((
283 Tag {
284 number: tag_number,
285 class,
286 length,
287 is_opening: false,
288 is_closing: false,
289 },
290 pos,
291 ))
292}
293
294pub fn extract_context_value(
301 data: &[u8],
302 offset: usize,
303 tag_number: u8,
304) -> Result<(&[u8], usize), Error> {
305 let value_start = offset;
306 let mut pos = offset;
307 let mut depth: usize = 1;
308 let mut open_tags = [0u8; MAX_CONTEXT_NESTING_DEPTH];
309 open_tags[0] = tag_number;
310
311 while depth > 0 && pos < data.len() {
312 let (tag, new_pos) = decode_tag(data, pos)?;
313
314 if tag.is_opening {
315 if depth == MAX_CONTEXT_NESTING_DEPTH {
316 return Err(Error::decoding(
317 pos,
318 format!(
319 "context tag nesting depth exceeds maximum ({MAX_CONTEXT_NESTING_DEPTH})"
320 ),
321 ));
322 }
323 open_tags[depth] = tag.number;
324 depth += 1;
325 pos = new_pos;
326 } else if tag.is_closing {
327 let expected = open_tags[depth - 1];
328 if tag.number != expected {
329 return Err(Error::decoding(
330 pos,
331 format!(
332 "closing tag {} does not match opening tag {expected}",
333 tag.number
334 ),
335 ));
336 }
337 depth -= 1;
338 if depth == 0 {
339 let value_end = pos;
340 return Ok((&data[value_start..value_end], new_pos));
341 }
342 pos = new_pos;
343 } else if tag.class == TagClass::Application && tag.number == app_tag::BOOLEAN {
344 pos = new_pos;
345 } else {
346 let content_end = new_pos
347 .checked_add(tag.length as usize)
348 .ok_or_else(|| Error::decoding(new_pos, "tag length overflow"))?;
349 if content_end > data.len() {
350 return Err(Error::decoding(
351 new_pos,
352 format!(
353 "tag data overflows buffer: need {} bytes at offset {new_pos}",
354 tag.length
355 ),
356 ));
357 }
358 pos = content_end;
359 }
360 }
361
362 Err(Error::decoding(
363 offset,
364 format!("missing closing tag {tag_number}"),
365 ))
366}
367
368pub fn extract_raw_context<'a>(
385 data: &'a [u8],
386 offset: usize,
387 tag_number: u8,
388) -> Result<(&'a [u8], usize), Error> {
389 let wide = tag_number > 14;
390 let open_first = if wide { 0xFE } else { (tag_number << 4) | 0x0E };
391 let close_first = if wide { 0xFF } else { (tag_number << 4) | 0x0F };
392 let width = if wide { 2 } else { 1 };
393
394 let value_start = offset;
395 let mut pos = offset;
396 let mut depth: usize = 1;
397
398 while pos < data.len() {
399 let b = data[pos];
400 if b == open_first && (!wide || data.get(pos + 1) == Some(&tag_number)) {
401 depth += 1;
402 pos += width;
403 continue;
404 }
405 if b == close_first && (!wide || data.get(pos + 1) == Some(&tag_number)) {
406 depth -= 1;
407 if depth == 0 {
408 return Ok((&data[value_start..pos], pos + width));
409 }
410 pos += width;
411 continue;
412 }
413 pos += 1;
414 }
415
416 Err(Error::decoding(
417 value_start,
418 format!("missing closing tag {tag_number}"),
419 ))
420}
421
422pub fn decode_optional_context(
428 data: &[u8],
429 offset: usize,
430 tag_number: u8,
431) -> Result<(Option<&[u8]>, usize), Error> {
432 if offset >= data.len() {
433 return Ok((None, offset));
434 }
435
436 let (tag, new_pos) = decode_tag(data, offset)?;
437 if tag.is_context(tag_number) {
438 let end = new_pos
439 .checked_add(tag.length as usize)
440 .ok_or_else(|| Error::decoding(new_pos, "tag length overflow"))?;
441 if end > data.len() {
442 return Err(Error::buffer_too_short(end, data.len()));
443 }
444 Ok((Some(&data[new_pos..end]), end))
445 } else {
446 Ok((None, offset))
447 }
448}
449
450#[cfg(test)]
455mod tests {
456 use super::*;
457
458 fn encode_to_vec(tag_number: u8, class: TagClass, length: u32) -> Vec<u8> {
459 let mut buf = BytesMut::with_capacity(8);
460 encode_tag(&mut buf, tag_number, class, length);
461 buf.to_vec()
462 }
463
464 #[test]
465 fn encode_single_byte_application_tag() {
466 assert_eq!(encode_to_vec(0, TagClass::Application, 0), vec![0x00]);
468 assert_eq!(encode_to_vec(2, TagClass::Application, 1), vec![0x21]);
470 assert_eq!(encode_to_vec(4, TagClass::Application, 4), vec![0x44]);
472 assert_eq!(encode_to_vec(12, TagClass::Application, 4), vec![0xC4]);
474 }
475
476 #[test]
477 fn encode_single_byte_context_tag() {
478 assert_eq!(encode_to_vec(0, TagClass::Context, 1), vec![0x09]);
480 assert_eq!(encode_to_vec(1, TagClass::Context, 4), vec![0x1C]);
482 assert_eq!(encode_to_vec(2, TagClass::Context, 0), vec![0x28]);
484 }
485
486 #[test]
487 fn encode_extended_tag_number() {
488 let encoded = encode_to_vec(15, TagClass::Application, 1);
490 assert_eq!(encoded, vec![0xF1, 15]);
491
492 let encoded = encode_to_vec(20, TagClass::Context, 2);
494 assert_eq!(encoded, vec![0xFA, 20]);
495 }
496
497 #[test]
498 fn encode_extended_length() {
499 let encoded = encode_to_vec(2, TagClass::Application, 100);
501 assert_eq!(encoded, vec![0x25, 100]);
502
503 let encoded = encode_to_vec(2, TagClass::Application, 1000);
505 assert_eq!(encoded, vec![0x25, 254, 0x03, 0xE8]);
506
507 let encoded = encode_to_vec(2, TagClass::Application, 100000);
509 assert_eq!(encoded, vec![0x25, 255, 0x00, 0x01, 0x86, 0xA0]);
510 }
511
512 #[test]
513 fn encode_extended_tag_and_length() {
514 let encoded = encode_to_vec(20, TagClass::Context, 100);
516 assert_eq!(encoded, vec![0xFD, 20, 100]);
517 }
518
519 #[test]
520 fn encode_opening_closing_tags() {
521 let mut buf = BytesMut::new();
522
523 encode_opening_tag(&mut buf, 0);
525 assert_eq!(&buf[..], &[0x0E]);
526
527 buf.clear();
528 encode_closing_tag(&mut buf, 0);
530 assert_eq!(&buf[..], &[0x0F]);
531
532 buf.clear();
533 encode_opening_tag(&mut buf, 3);
535 assert_eq!(&buf[..], &[0x3E]);
536
537 buf.clear();
538 encode_opening_tag(&mut buf, 20);
540 assert_eq!(&buf[..], &[0xFE, 20]);
541
542 buf.clear();
543 encode_closing_tag(&mut buf, 20);
545 assert_eq!(&buf[..], &[0xFF, 20]);
546 }
547
548 #[test]
549 fn decode_single_byte_tag() {
550 let (tag, pos) = decode_tag(&[0x21], 0).unwrap();
552 assert_eq!(tag.number, 2);
553 assert_eq!(tag.class, TagClass::Application);
554 assert_eq!(tag.length, 1);
555 assert!(!tag.is_opening);
556 assert!(!tag.is_closing);
557 assert_eq!(pos, 1);
558 }
559
560 #[test]
561 fn decode_context_tag() {
562 let (tag, pos) = decode_tag(&[0x1C], 0).unwrap();
564 assert_eq!(tag.number, 1);
565 assert_eq!(tag.class, TagClass::Context);
566 assert_eq!(tag.length, 4);
567 assert_eq!(pos, 1);
568 }
569
570 #[test]
571 fn decode_opening_closing_tags() {
572 let (tag, _) = decode_tag(&[0x0E], 0).unwrap();
573 assert!(tag.is_opening);
574 assert_eq!(tag.number, 0);
575
576 let (tag, _) = decode_tag(&[0x0F], 0).unwrap();
577 assert!(tag.is_closing);
578 assert_eq!(tag.number, 0);
579
580 let (tag, _) = decode_tag(&[0x3E], 0).unwrap();
581 assert!(tag.is_opening);
582 assert_eq!(tag.number, 3);
583 }
584
585 #[test]
586 fn decode_extended_tag_number() {
587 let (tag, pos) = decode_tag(&[0xF1, 20], 0).unwrap();
589 assert_eq!(tag.number, 20);
590 assert_eq!(tag.class, TagClass::Application);
591 assert_eq!(tag.length, 1);
592 assert_eq!(pos, 2);
593 }
594
595 #[test]
596 fn decode_extended_length() {
597 let (tag, pos) = decode_tag(&[0x25, 100], 0).unwrap();
599 assert_eq!(tag.number, 2);
600 assert_eq!(tag.length, 100);
601 assert_eq!(pos, 2);
602
603 let (tag, pos) = decode_tag(&[0x25, 254, 0x03, 0xE8], 0).unwrap();
605 assert_eq!(tag.number, 2);
606 assert_eq!(tag.length, 1000);
607 assert_eq!(pos, 4);
608
609 let (tag, pos) = decode_tag(&[0x25, 255, 0x00, 0x01, 0x86, 0xA0], 0).unwrap();
611 assert_eq!(tag.number, 2);
612 assert_eq!(tag.length, 100000);
613 assert_eq!(pos, 6);
614 }
615
616 #[test]
617 fn decode_tag_round_trip() {
618 let cases = [
620 (0u8, TagClass::Application, 0u32),
621 (1, TagClass::Application, 1),
622 (4, TagClass::Application, 4),
623 (12, TagClass::Application, 4),
624 (0, TagClass::Context, 1),
625 (3, TagClass::Context, 4),
626 (15, TagClass::Application, 1),
627 (20, TagClass::Context, 2),
628 (2, TagClass::Application, 100),
629 (2, TagClass::Application, 1000),
630 (20, TagClass::Context, 100),
631 ];
632
633 for (tag_num, class, length) in cases {
634 let encoded = encode_to_vec(tag_num, class, length);
635 let (decoded, _) = decode_tag(&encoded, 0).unwrap();
636 assert_eq!(
637 decoded.number, tag_num,
638 "tag number mismatch for ({tag_num}, {class:?}, {length})"
639 );
640 assert_eq!(
641 decoded.class, class,
642 "class mismatch for ({tag_num}, {class:?}, {length})"
643 );
644 assert_eq!(
645 decoded.length, length,
646 "length mismatch for ({tag_num}, {class:?}, {length})"
647 );
648 }
649 }
650
651 #[test]
652 fn decode_tag_empty_buffer() {
653 assert!(decode_tag(&[], 0).is_err());
654 }
655
656 #[test]
657 fn decode_tag_truncated_extended() {
658 assert!(decode_tag(&[0xF1], 0).is_err());
660 }
661
662 #[test]
663 fn decode_tag_excessive_length() {
664 let data = [0x25, 255, 0x10, 0x00, 0x00, 0x00]; assert!(decode_tag(&data, 0).is_err());
667 }
668
669 #[test]
670 fn boolean_tag_detection() {
671 let (tag, _) = decode_tag(&[0x11], 0).unwrap();
673 assert!(tag.is_boolean_true());
674
675 let (tag, _) = decode_tag(&[0x10], 0).unwrap();
677 assert!(!tag.is_boolean_true());
678
679 let (tag, _) = decode_tag(&[0x21], 0).unwrap();
681 assert!(!tag.is_boolean_true());
682 }
683
684 #[test]
685 fn extract_context_value_simple() {
686 let data = [0x0E, 0x21, 42, 0x0F];
688 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
689 assert_eq!(value, &[0x21, 42]);
690 assert_eq!(pos, 4);
691 }
692
693 #[test]
696 fn extract_raw_context_non_tlv_payload() {
697 let data = [0x9E, 0xDE, 0xAD, 0xBE, 0xEF, 0x9F];
700 let (value, pos) = extract_raw_context(&data, 1, 9).unwrap();
701 assert_eq!(value, &[0xDE, 0xAD, 0xBE, 0xEF]);
702 assert_eq!(pos, 6);
703 }
704
705 #[test]
706 fn extract_raw_context_nested_same_tag() {
707 let data = [0x2E, 0x2E, 0x01, 0x2F, 0x02, 0x2F];
709 let (value, pos) = extract_raw_context(&data, 1, 2).unwrap();
710 assert_eq!(value, &[0x2E, 0x01, 0x2F, 0x02]);
711 assert_eq!(pos, 6);
712 }
713
714 #[test]
715 fn extract_raw_context_extended_tag() {
716 let data = [0xFE, 200, 0x01, 0x02, 0xFF, 200];
718 let (value, pos) = extract_raw_context(&data, 2, 200).unwrap();
719 assert_eq!(value, &[0x01, 0x02]);
720 assert_eq!(pos, 6);
721 }
722
723 #[test]
724 fn extract_raw_context_missing_closing() {
725 let data = [0x9E, 0xDE, 0xAD];
726 assert!(extract_raw_context(&data, 1, 9).is_err());
727 }
728
729 #[test]
730 fn extract_raw_context_truncated_extended_closing() {
731 let data = [0xFE, 200, 0x01, 0x02, 0xFF];
733 assert!(extract_raw_context(&data, 2, 200).is_err());
734 }
735
736 #[test]
737 fn extract_context_value_nested() {
738 let data = [0x0E, 0x1E, 0x21, 42, 0x1F, 0x0F];
740 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
741 assert_eq!(value, &[0x1E, 0x21, 42, 0x1F]);
742 assert_eq!(pos, 6);
743 }
744
745 #[test]
746 fn extract_context_value_missing_close() {
747 let data = [0x0E, 0x21, 42]; assert!(extract_context_value(&data, 1, 0).is_err());
749 }
750
751 #[test]
752 fn tag_is_context_helper() {
753 let (tag, _) = decode_tag(&[0x09], 0).unwrap(); assert!(tag.is_context(0));
755 assert!(!tag.is_context(1));
756 }
757
758 #[test]
759 fn decode_optional_context_present() {
760 let data = [0x09, 42];
762 let (value, pos) = decode_optional_context(&data, 0, 0).unwrap();
763 assert_eq!(value, Some(&[42u8][..]));
764 assert_eq!(pos, 2);
765 }
766
767 #[test]
768 fn decode_optional_context_absent() {
769 let data = [0x19, 42];
771 let (value, pos) = decode_optional_context(&data, 0, 0).unwrap();
772 assert!(value.is_none());
773 assert_eq!(pos, 0); }
775
776 #[test]
777 fn decode_optional_context_empty_buffer() {
778 let (value, pos) = decode_optional_context(&[], 0, 0).unwrap();
779 assert!(value.is_none());
780 assert_eq!(pos, 0);
781 }
782
783 #[test]
786 fn extract_context_value_mismatched_closing_tag() {
787 let data = [0x0E, 0x21, 42, 0x1F]; assert!(extract_context_value(&data, 1, 0).is_err());
790 }
791
792 #[test]
793 fn extract_context_value_mismatched_nested_closing_tag() {
794 let data = [0x0E, 0x1E, 0x2F, 0x0F];
796 assert!(extract_context_value(&data, 1, 0).is_err());
797 }
798
799 #[test]
800 fn extract_context_value_deeply_nested() {
801 let data = [
804 0x0E, 0x1E, 0x2E, 0x21, 42, 0x2F, 0x1F, 0x0F, ];
812 let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
813 assert_eq!(value, &data[1..7]); assert_eq!(pos, 8);
815 }
816
817 #[test]
818 fn extract_context_value_nesting_depth_exceeded() {
819 let mut data = Vec::new();
821 for i in 0..MAX_CONTEXT_NESTING_DEPTH {
824 let tag = (i % 15) as u8; data.push((tag << 4) | 0x0E); }
827 let result = extract_context_value(&data, 0, 0);
829 assert!(result.is_err());
830 }
831
832 #[test]
833 fn decode_tag_at_nonzero_offset() {
834 let data = [0xFF, 0xFF, 0x21, 0x00]; let (tag, pos) = decode_tag(&data, 2).unwrap();
837 assert_eq!(tag.number, 2);
838 assert_eq!(tag.class, TagClass::Application);
839 assert_eq!(tag.length, 1);
840 assert_eq!(pos, 3);
841 }
842
843 #[test]
844 fn decode_tag_offset_beyond_buffer() {
845 let data = [0x21];
846 assert!(decode_tag(&data, 5).is_err());
847 }
848
849 #[test]
850 fn decode_tag_truncated_2byte_extended_length() {
851 let data = [0x25, 254, 0x03]; assert!(decode_tag(&data, 0).is_err());
854 }
855
856 #[test]
857 fn decode_tag_truncated_4byte_extended_length() {
858 let data = [0x25, 255, 0x00, 0x01];
860 assert!(decode_tag(&data, 0).is_err());
861 }
862
863 #[test]
864 fn extract_context_value_with_boolean_inside() {
865 let data = [0x0E, 0x11, 0x0F]; let (value, pos) = extract_context_value(&data, 1, 0).unwrap();
869 assert_eq!(value, &[0x11]); assert_eq!(pos, 3);
871 }
872
873 #[test]
874 fn extract_context_value_tag_data_overflows_buffer() {
875 let data = [0x0E, 0x25, 100, 0x01, 0x02, 0x0F];
877 let result = extract_context_value(&data, 1, 0);
878 assert!(result.is_err());
879 }
880
881 #[test]
882 fn decode_optional_context_content_overflows() {
883 let data = [0x0C, 0x01, 0x02]; assert!(decode_optional_context(&data, 0, 0).is_err());
886 }
887
888 #[test]
889 fn opening_closing_tag_extended_round_trip() {
890 let mut buf = BytesMut::new();
891 for tag_num in [15u8, 20, 100, 254] {
893 buf.clear();
894 encode_opening_tag(&mut buf, tag_num);
895 let (tag, pos1) = decode_tag(&buf, 0).unwrap();
896 assert!(tag.is_opening);
897 assert_eq!(tag.number, tag_num);
898 assert_eq!(pos1, 2); buf.clear();
901 encode_closing_tag(&mut buf, tag_num);
902 let (tag, pos2) = decode_tag(&buf, 0).unwrap();
903 assert!(tag.is_closing);
904 assert_eq!(tag.number, tag_num);
905 assert_eq!(pos2, 2);
906 }
907 }
908}