1use bacnet_types::error::Error;
7use bacnet_types::primitives::{BACnetTimeStamp, Date, ObjectIdentifier, PropertyValue, Time};
8use bytes::{BufMut, BytesMut};
9
10use crate::tags::{self, app_tag, TagClass};
11
12pub fn encode_unsigned(buf: &mut BytesMut, value: u64) {
20 if value <= 0xFF {
21 buf.put_u8(value as u8);
22 } else if value <= 0xFFFF {
23 buf.put_u16(value as u16);
24 } else if value <= 0xFF_FFFF {
25 buf.put_u8((value >> 16) as u8);
26 buf.put_u16(value as u16);
27 } else if value <= 0xFFFF_FFFF {
28 buf.put_u32(value as u32);
29 } else if value <= 0xFF_FFFF_FFFF {
30 buf.put_u8((value >> 32) as u8);
31 buf.put_u32(value as u32);
32 } else if value <= 0xFFFF_FFFF_FFFF {
33 buf.put_u16((value >> 32) as u16);
34 buf.put_u32(value as u32);
35 } else if value <= 0xFF_FFFF_FFFF_FFFF {
36 buf.put_u8((value >> 48) as u8);
37 buf.put_u16((value >> 32) as u16);
38 buf.put_u32(value as u32);
39 } else {
40 buf.put_u64(value);
41 }
42}
43
44pub fn unsigned_len(value: u64) -> u32 {
46 if value <= 0xFF {
47 1
48 } else if value <= 0xFFFF {
49 2
50 } else if value <= 0xFF_FFFF {
51 3
52 } else if value <= 0xFFFF_FFFF {
53 4
54 } else if value <= 0xFF_FFFF_FFFF {
55 5
56 } else if value <= 0xFFFF_FFFF_FFFF {
57 6
58 } else if value <= 0xFF_FFFF_FFFF_FFFF {
59 7
60 } else {
61 8
62 }
63}
64
65pub fn decode_unsigned(data: &[u8]) -> Result<u64, Error> {
67 if data.is_empty() || data.len() > 8 {
68 return Err(Error::Decoding {
69 offset: 0,
70 message: format!("unsigned requires 1-8 bytes, got {}", data.len()),
71 });
72 }
73 let mut value: u64 = 0;
74 for &b in data {
75 value = (value << 8) | b as u64;
76 }
77 Ok(value)
78}
79
80pub fn encode_signed(buf: &mut BytesMut, value: i32) {
84 let n = signed_len(value);
85 let bytes = value.to_be_bytes();
86 buf.put_slice(&bytes[4 - n as usize..]);
87}
88
89pub fn signed_len(value: i32) -> u32 {
91 if (-128..=127).contains(&value) {
92 1
93 } else if (-32768..=32767).contains(&value) {
94 2
95 } else if (-8_388_608..=8_388_607).contains(&value) {
96 3
97 } else {
98 4
99 }
100}
101
102pub fn decode_signed(data: &[u8]) -> Result<i32, Error> {
104 if data.is_empty() || data.len() > 4 {
105 return Err(Error::Decoding {
106 offset: 0,
107 message: format!("signed requires 1-4 bytes, got {}", data.len()),
108 });
109 }
110 let sign_extend = if data[0] & 0x80 != 0 { 0xFF } else { 0x00 };
111 let mut bytes = [sign_extend; 4];
112 bytes[4 - data.len()..].copy_from_slice(data);
113 Ok(i32::from_be_bytes(bytes))
114}
115
116pub(crate) fn decode_signed_canonical(data: &[u8]) -> Result<i32, Error> {
117 if data.len() > 1
118 && ((data[0] == 0 && data[1] & 0x80 == 0) || (data[0] == 0xFF && data[1] & 0x80 != 0))
119 {
120 return Err(Error::decoding(0, "signed contains a redundant sign octet"));
121 }
122 decode_signed(data)
123}
124
125pub fn encode_real(buf: &mut BytesMut, value: f32) {
129 buf.put_f32(value);
130}
131
132pub fn decode_real(data: &[u8]) -> Result<f32, Error> {
134 if data.len() != 4 {
135 return Err(Error::decoding(
136 0,
137 format!("Real expects exactly 4 bytes, got {}", data.len()),
138 ));
139 }
140 Ok(f32::from_be_bytes([data[0], data[1], data[2], data[3]]))
141}
142
143pub fn encode_double(buf: &mut BytesMut, value: f64) {
147 buf.put_f64(value);
148}
149
150pub fn decode_double(data: &[u8]) -> Result<f64, Error> {
152 if data.len() != 8 {
153 return Err(Error::decoding(
154 0,
155 format!("Double expects exactly 8 bytes, got {}", data.len()),
156 ));
157 }
158 let bytes: [u8; 8] = [
159 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
160 ];
161 Ok(f64::from_be_bytes(bytes))
162}
163
164pub mod charset {
168 pub const UTF8: u8 = 0;
170 pub const IBM_MICROSOFT_DBCS: u8 = 1;
172 pub const JIS_X_0208: u8 = 2;
174 pub const UCS4: u8 = 3;
176 pub const UCS2: u8 = 4;
178 pub const ISO_8859_1: u8 = 5;
180}
181
182pub fn encode_character_string(buf: &mut BytesMut, value: &str) {
184 buf.put_u8(charset::UTF8);
185 buf.put_slice(value.as_bytes());
186}
187
188pub fn character_string_len(value: &str) -> Result<u32, Error> {
190 u32::try_from(value.len())
191 .ok()
192 .and_then(|n| n.checked_add(1))
193 .ok_or_else(|| Error::Encoding("CharacterString too long for BACnet encoding".into()))
194}
195
196pub fn decode_character_string(data: &[u8]) -> Result<String, Error> {
205 if data.is_empty() {
206 return Err(Error::Decoding {
207 offset: 0,
208 message: "CharacterString requires at least 1 byte for charset".into(),
209 });
210 }
211 let charset_id = data[0];
212 let payload = &data[1..];
213 match charset_id {
214 charset::UTF8 => String::from_utf8(payload.to_vec()).map_err(|e| Error::Decoding {
215 offset: 1,
216 message: format!("invalid UTF-8: {e}"),
217 }),
218 charset::UCS2 => {
219 if !payload.len().is_multiple_of(2) {
220 return Err(Error::Decoding {
221 offset: 1,
222 message: "UCS-2 data must have even length".into(),
223 });
224 }
225 let mut s = String::new();
226 for (i, chunk) in payload.chunks_exact(2).enumerate() {
227 let code_point = u16::from_be_bytes([chunk[0], chunk[1]]);
228 if let Some(c) = char::from_u32(code_point as u32) {
229 s.push(c);
230 } else {
231 return Err(Error::Decoding {
232 offset: 1 + i * 2,
233 message: "invalid UCS-2 code point".into(),
234 });
235 }
236 }
237 Ok(s)
238 }
239 charset::ISO_8859_1 => Ok(payload.iter().map(|&b| b as char).collect()),
240 charset::IBM_MICROSOFT_DBCS | charset::JIS_X_0208 | charset::UCS4 => Err(Error::Decoding {
241 offset: 0,
242 message: format!("unsupported charset: {charset_id}"),
243 }),
244 other => Err(Error::Decoding {
245 offset: 0,
246 message: format!("unknown charset: {other}"),
247 }),
248 }
249}
250
251pub fn encode_bit_string(buf: &mut BytesMut, unused_bits: u8, data: &[u8]) {
255 buf.put_u8(unused_bits);
256 buf.put_slice(data);
257}
258
259pub fn decode_bit_string(data: &[u8]) -> Result<(u8, Vec<u8>), Error> {
263 if data.is_empty() {
264 return Err(Error::Decoding {
265 offset: 0,
266 message: "BitString requires at least 1 byte for unused-bits count".into(),
267 });
268 }
269 let unused = data[0];
270 if unused > 7 {
271 return Err(Error::Decoding {
272 offset: 0,
273 message: format!("BitString unused_bits must be 0-7, got {unused}"),
274 });
275 }
276 Ok((unused, data[1..].to_vec()))
277}
278
279pub fn encode_app_null(buf: &mut BytesMut) {
285 tags::encode_tag(buf, app_tag::NULL, TagClass::Application, 0);
286}
287
288pub fn encode_app_boolean(buf: &mut BytesMut, value: bool) {
292 tags::encode_tag(
293 buf,
294 app_tag::BOOLEAN,
295 TagClass::Application,
296 if value { 1 } else { 0 },
297 );
298}
299
300pub fn encode_app_unsigned(buf: &mut BytesMut, value: u64) {
302 let len = unsigned_len(value);
303 tags::encode_tag(buf, app_tag::UNSIGNED, TagClass::Application, len);
304 encode_unsigned(buf, value);
305}
306
307pub fn encode_app_signed(buf: &mut BytesMut, value: i32) {
309 let len = signed_len(value);
310 tags::encode_tag(buf, app_tag::SIGNED, TagClass::Application, len);
311 encode_signed(buf, value);
312}
313
314pub fn encode_app_real(buf: &mut BytesMut, value: f32) {
316 tags::encode_tag(buf, app_tag::REAL, TagClass::Application, 4);
317 encode_real(buf, value);
318}
319
320pub fn encode_app_double(buf: &mut BytesMut, value: f64) {
322 tags::encode_tag(buf, app_tag::DOUBLE, TagClass::Application, 8);
323 encode_double(buf, value);
324}
325
326pub fn encode_app_octet_string(buf: &mut BytesMut, data: &[u8]) {
328 let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
329 tags::encode_tag(buf, app_tag::OCTET_STRING, TagClass::Application, data_len);
330 buf.put_slice(data);
331}
332
333pub fn encode_app_character_string(buf: &mut BytesMut, value: &str) -> Result<(), Error> {
335 let len = character_string_len(value)?;
336 tags::encode_tag(buf, app_tag::CHARACTER_STRING, TagClass::Application, len);
337 encode_character_string(buf, value);
338 Ok(())
339}
340
341pub fn encode_app_bit_string(buf: &mut BytesMut, unused_bits: u8, data: &[u8]) {
343 let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
344 let len = data_len.saturating_add(1);
345 tags::encode_tag(buf, app_tag::BIT_STRING, TagClass::Application, len);
346 encode_bit_string(buf, unused_bits, data);
347}
348
349pub fn encode_app_enumerated(buf: &mut BytesMut, value: u32) {
351 let len = unsigned_len(value as u64);
352 tags::encode_tag(buf, app_tag::ENUMERATED, TagClass::Application, len);
353 encode_unsigned(buf, value as u64);
354}
355
356pub fn encode_app_date(buf: &mut BytesMut, date: &Date) {
358 tags::encode_tag(buf, app_tag::DATE, TagClass::Application, 4);
359 buf.put_slice(&date.encode());
360}
361
362pub fn encode_app_time(buf: &mut BytesMut, time: &Time) {
364 tags::encode_tag(buf, app_tag::TIME, TagClass::Application, 4);
365 buf.put_slice(&time.encode());
366}
367
368pub fn encode_app_object_id(buf: &mut BytesMut, oid: &ObjectIdentifier) {
370 tags::encode_tag(buf, app_tag::OBJECT_IDENTIFIER, TagClass::Application, 4);
371 buf.put_slice(&oid.encode());
372}
373
374pub fn encode_ctx_unsigned(buf: &mut BytesMut, tag: u8, value: u64) {
380 let len = unsigned_len(value);
381 tags::encode_tag(buf, tag, TagClass::Context, len);
382 encode_unsigned(buf, value);
383}
384
385pub fn encode_ctx_signed(buf: &mut BytesMut, tag: u8, value: i32) {
387 let len = signed_len(value);
388 tags::encode_tag(buf, tag, TagClass::Context, len);
389 encode_signed(buf, value);
390}
391
392pub fn encode_ctx_real(buf: &mut BytesMut, tag: u8, value: f32) {
394 tags::encode_tag(buf, tag, TagClass::Context, 4);
395 encode_real(buf, value);
396}
397
398pub fn encode_ctx_double(buf: &mut BytesMut, tag: u8, value: f64) {
400 tags::encode_tag(buf, tag, TagClass::Context, 8);
401 encode_double(buf, value);
402}
403
404pub fn encode_ctx_enumerated(buf: &mut BytesMut, tag: u8, value: u32) {
406 let len = unsigned_len(value as u64);
407 tags::encode_tag(buf, tag, TagClass::Context, len);
408 encode_unsigned(buf, value as u64);
409}
410
411pub fn encode_ctx_boolean(buf: &mut BytesMut, tag: u8, value: bool) {
415 tags::encode_tag(buf, tag, TagClass::Context, 1);
416 buf.put_u8(if value { 1 } else { 0 });
417}
418
419pub fn encode_ctx_object_id(buf: &mut BytesMut, tag: u8, oid: &ObjectIdentifier) {
421 tags::encode_tag(buf, tag, TagClass::Context, 4);
422 buf.put_slice(&oid.encode());
423}
424
425pub fn encode_ctx_octet_string(buf: &mut BytesMut, tag: u8, data: &[u8]) {
427 let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
428 tags::encode_tag(buf, tag, TagClass::Context, data_len);
429 buf.put_slice(data);
430}
431
432pub fn encode_ctx_character_string(buf: &mut BytesMut, tag: u8, value: &str) -> Result<(), Error> {
434 let len = character_string_len(value)?;
435 tags::encode_tag(buf, tag, TagClass::Context, len);
436 encode_character_string(buf, value);
437 Ok(())
438}
439
440pub fn encode_ctx_date(buf: &mut BytesMut, tag: u8, date: &Date) {
442 tags::encode_tag(buf, tag, TagClass::Context, 4);
443 buf.put_slice(&date.encode());
444}
445
446pub fn encode_ctx_bit_string(buf: &mut BytesMut, tag: u8, unused_bits: u8, data: &[u8]) {
448 let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
449 let len = data_len.saturating_add(1);
450 tags::encode_tag(buf, tag, TagClass::Context, len);
451 encode_bit_string(buf, unused_bits, data);
452}
453
454pub fn decode_application_value(
470 data: &[u8],
471 offset: usize,
472) -> Result<(PropertyValue, usize), Error> {
473 let (tag, new_offset) = tags::decode_tag(data, offset)?;
474 if tag.class == TagClass::Context {
475 if tag.is_opening {
476 let (_content, after) = tags::extract_context_value(data, new_offset, tag.number)?;
477 return Ok((
478 PropertyValue::ApplicationData(data[offset..after].to_vec()),
479 after,
480 ));
481 }
482 if tag.is_closing {
483 return Err(Error::decoding(offset, "unexpected closing tag"));
484 }
485 let content_end = new_offset
486 .checked_add(tag.length as usize)
487 .ok_or_else(|| Error::decoding(new_offset, "length overflow"))?;
488 if data.len() < content_end {
489 return Err(Error::buffer_too_short(content_end, data.len()));
490 }
491 return Ok((
492 PropertyValue::ApplicationData(data[offset..content_end].to_vec()),
493 content_end,
494 ));
495 }
496 if tag.is_opening || tag.is_closing {
497 return Err(Error::decoding(offset, "unexpected opening/closing tag"));
498 }
499
500 if tag.number == app_tag::NULL && tag.length != 0 {
501 return Err(Error::decoding(
502 offset,
503 "application NULL must have no contents",
504 ));
505 }
506 if tag.number == app_tag::BOOLEAN && tag.length > 1 {
507 return Err(Error::decoding(
508 offset,
509 "application BOOLEAN L/V/T must be 0 or 1",
510 ));
511 }
512
513 let content_start = new_offset;
514 let content_len = tag.length as usize;
515 let content_end = content_start
516 .checked_add(content_len)
517 .ok_or_else(|| Error::decoding(content_start, "length overflow"))?;
518
519 if tag.number == app_tag::BOOLEAN {
520 return Ok((PropertyValue::Boolean(tag.length != 0), content_start));
521 }
522
523 if data.len() < content_end {
524 return Err(Error::buffer_too_short(content_end, data.len()));
525 }
526
527 let content = &data[content_start..content_end];
528
529 let value = match tag.number {
530 app_tag::NULL => PropertyValue::Null,
531 app_tag::UNSIGNED => PropertyValue::Unsigned(decode_unsigned(content)?),
532 app_tag::SIGNED => PropertyValue::Signed(decode_signed(content)?),
533 app_tag::REAL => PropertyValue::Real(decode_real(content)?),
534 app_tag::DOUBLE => PropertyValue::Double(decode_double(content)?),
535 app_tag::OCTET_STRING => PropertyValue::OctetString(content.to_vec()),
536 app_tag::CHARACTER_STRING => {
537 PropertyValue::CharacterString(decode_character_string(content)?)
538 }
539 app_tag::BIT_STRING => {
540 let (unused, bits) = decode_bit_string(content)?;
541 PropertyValue::BitString {
542 unused_bits: unused,
543 data: bits,
544 }
545 }
546 app_tag::ENUMERATED => {
547 let value = u32::try_from(decode_unsigned(content)?)
548 .map_err(|_| Error::decoding(content_start, "ENUMERATED exceeds u32"))?;
549 PropertyValue::Enumerated(value)
550 }
551 app_tag::DATE => PropertyValue::Date(Date::decode(content)?),
552 app_tag::TIME => PropertyValue::Time(Time::decode(content)?),
553 app_tag::OBJECT_IDENTIFIER => {
554 PropertyValue::ObjectIdentifier(ObjectIdentifier::decode(content)?)
555 }
556 other => {
557 return Err(Error::decoding(
558 offset,
559 format!("unknown application tag number {other}"),
560 ));
561 }
562 };
563
564 Ok((value, content_end))
565}
566
567pub(crate) fn validate_application_value(data: &[u8], offset: usize) -> Result<usize, Error> {
568 let (tag, content_start) = tags::decode_tag(data, offset)?;
569 if tag.class != TagClass::Application || tag.is_opening || tag.is_closing {
570 return Err(Error::decoding(
571 offset,
572 "expected an application-tagged value",
573 ));
574 }
575 if tag.number != app_tag::CHARACTER_STRING && tag.number != app_tag::SIGNED {
576 return decode_application_value(data, offset).map(|(_, end)| end);
577 }
578
579 let content_end = content_start
580 .checked_add(tag.length as usize)
581 .ok_or_else(|| Error::decoding(content_start, "length overflow"))?;
582 if content_end > data.len() {
583 return Err(Error::buffer_too_short(content_end, data.len()));
584 }
585 let content = &data[content_start..content_end];
586 if tag.number == app_tag::SIGNED {
587 decode_signed_canonical(content)?;
588 return Ok(content_end);
589 }
590 let Some((&charset_id, payload)) = content.split_first() else {
591 return Err(Error::decoding(
592 content_start,
593 "CharacterString requires a character-set octet",
594 ));
595 };
596 match charset_id {
597 charset::UTF8 | charset::UCS2 | charset::ISO_8859_1 => {
598 decode_character_string(content)?;
599 }
600 charset::IBM_MICROSOFT_DBCS if payload.len() < 2 => {
601 return Err(Error::decoding(
602 content_start + 1,
603 "DBCS CharacterString requires a two-octet code page",
604 ));
605 }
606 charset::IBM_MICROSOFT_DBCS | charset::JIS_X_0208 => {}
607 charset::UCS4 => {
608 if !payload.len().is_multiple_of(4) {
609 return Err(Error::decoding(
610 content_start + 1,
611 "UCS-4 CharacterString length must be a multiple of four",
612 ));
613 }
614 for (index, encoded) in payload.chunks_exact(4).enumerate() {
615 let code_point = u32::from_be_bytes(encoded.try_into().unwrap());
616 if char::from_u32(code_point).is_none() {
617 return Err(Error::decoding(
618 content_start + 1 + index * 4,
619 "invalid UCS-4 code point",
620 ));
621 }
622 }
623 }
624 _ => {
625 return Err(Error::decoding(
626 content_start,
627 format!("reserved character set: {charset_id}"),
628 ));
629 }
630 }
631 Ok(content_end)
632}
633
634pub fn encode_property_value(buf: &mut BytesMut, value: &PropertyValue) -> Result<(), Error> {
636 match value {
637 PropertyValue::Null => encode_app_null(buf),
638 PropertyValue::Boolean(v) => encode_app_boolean(buf, *v),
639 PropertyValue::Unsigned(v) => encode_app_unsigned(buf, *v),
640 PropertyValue::Signed(v) => encode_app_signed(buf, *v),
641 PropertyValue::Real(v) => encode_app_real(buf, *v),
642 PropertyValue::Double(v) => encode_app_double(buf, *v),
643 PropertyValue::OctetString(v) => encode_app_octet_string(buf, v),
644 PropertyValue::CharacterString(v) => encode_app_character_string(buf, v)?,
645 PropertyValue::BitString { unused_bits, data } => {
646 encode_app_bit_string(buf, *unused_bits, data)
647 }
648 PropertyValue::Enumerated(v) => encode_app_enumerated(buf, *v),
649 PropertyValue::Date(v) => encode_app_date(buf, v),
650 PropertyValue::Time(v) => encode_app_time(buf, v),
651 PropertyValue::ObjectIdentifier(v) => encode_app_object_id(buf, v),
652 PropertyValue::List(values) => {
653 for v in values {
654 encode_property_value(buf, v)?;
655 }
656 }
657 PropertyValue::ApplicationData(bytes) => {
658 buf.put_slice(bytes);
661 }
662 }
663 Ok(())
664}
665
666pub const MAX_TIMESTAMP_SEQUENCE_NUMBER: u64 = 65535;
676
677pub fn encode_timestamp_choice(buf: &mut BytesMut, ts: &BACnetTimeStamp) -> Result<(), Error> {
694 match ts {
695 BACnetTimeStamp::Time(t) => {
696 tags::encode_tag(buf, 0, TagClass::Context, 4);
697 buf.put_slice(&t.encode());
698 }
699 BACnetTimeStamp::SequenceNumber(n) => {
700 encode_ctx_unsigned(buf, 1, u64::from(*n));
701 }
702 BACnetTimeStamp::DateTime { date, time } => {
703 tags::encode_opening_tag(buf, 2);
704 encode_app_date(buf, date);
705 encode_app_time(buf, time);
706 tags::encode_closing_tag(buf, 2);
707 }
708 }
709 Ok(())
710}
711
712pub fn decode_timestamp_choice(
719 data: &[u8],
720 offset: usize,
721) -> Result<(BACnetTimeStamp, usize), Error> {
722 let (inner_tag, inner_pos) = tags::decode_tag(data, offset)?;
723
724 let (ts, after_inner) = if inner_tag.is_context(0) {
725 let end = inner_pos
726 .checked_add(inner_tag.length as usize)
727 .ok_or_else(|| Error::decoding(inner_pos, "BACnetTimeStamp Time length overflow"))?;
728 if end > data.len() {
729 return Err(Error::decoding(inner_pos, "BACnetTimeStamp Time truncated"));
730 }
731 let t = Time::decode(&data[inner_pos..end])?;
732 (BACnetTimeStamp::Time(t), end)
733 } else if inner_tag.is_context(1) {
734 let end = inner_pos
735 .checked_add(inner_tag.length as usize)
736 .ok_or_else(|| {
737 Error::decoding(inner_pos, "BACnetTimeStamp SequenceNumber length overflow")
738 })?;
739 if end > data.len() {
740 return Err(Error::decoding(
741 inner_pos,
742 "BACnetTimeStamp SequenceNumber truncated",
743 ));
744 }
745 let n = decode_unsigned(&data[inner_pos..end])?;
746 if n > MAX_TIMESTAMP_SEQUENCE_NUMBER {
747 return Err(Error::decoding(
748 inner_pos,
749 format!("BACnetTimeStamp sequence-number {n} exceeds 65535"),
750 ));
751 }
752 (
753 BACnetTimeStamp::SequenceNumber(
754 u16::try_from(n).expect("BACnetTimeStamp sequence range checked above"),
755 ),
756 end,
757 )
758 } else if inner_tag.is_opening_tag(2) {
759 let (date_tag, date_pos) = tags::decode_tag(data, inner_pos)?;
760 if date_tag.class != TagClass::Application || date_tag.number != app_tag::DATE {
761 return Err(Error::decoding(
762 inner_pos,
763 "BACnetTimeStamp DateTime expected Date",
764 ));
765 }
766 let date_end = date_pos
767 .checked_add(date_tag.length as usize)
768 .ok_or_else(|| {
769 Error::decoding(date_pos, "BACnetTimeStamp DateTime Date length overflow")
770 })?;
771 if date_end > data.len() {
772 return Err(Error::decoding(
773 date_pos,
774 "BACnetTimeStamp DateTime Date truncated",
775 ));
776 }
777 let date = Date::decode(&data[date_pos..date_end])?;
778
779 let (time_tag, time_pos) = tags::decode_tag(data, date_end)?;
780 if time_tag.class != TagClass::Application || time_tag.number != app_tag::TIME {
781 return Err(Error::decoding(
782 date_end,
783 "BACnetTimeStamp DateTime expected Time",
784 ));
785 }
786 let time_end = time_pos
787 .checked_add(time_tag.length as usize)
788 .ok_or_else(|| {
789 Error::decoding(time_pos, "BACnetTimeStamp DateTime Time length overflow")
790 })?;
791 if time_end > data.len() {
792 return Err(Error::decoding(
793 time_pos,
794 "BACnetTimeStamp DateTime Time truncated",
795 ));
796 }
797 let time = Time::decode(&data[time_pos..time_end])?;
798
799 let (close_tag, close_pos) = tags::decode_tag(data, time_end)?;
800 if !close_tag.is_closing_tag(2) {
801 return Err(Error::decoding(
802 time_end,
803 "BACnetTimeStamp DateTime missing closing tag 2",
804 ));
805 }
806 (BACnetTimeStamp::DateTime { date, time }, close_pos)
807 } else {
808 return Err(Error::decoding(
809 offset,
810 "BACnetTimeStamp: unexpected inner choice tag",
811 ));
812 };
813
814 Ok((ts, after_inner))
815}
816
817pub fn encode_timestamp(
823 buf: &mut BytesMut,
824 tag_number: u8,
825 ts: &BACnetTimeStamp,
826) -> Result<(), Error> {
827 tags::encode_opening_tag(buf, tag_number);
828 encode_timestamp_choice(buf, ts)?;
829 tags::encode_closing_tag(buf, tag_number);
830 Ok(())
831}
832
833pub fn decode_timestamp(
838 data: &[u8],
839 offset: usize,
840 tag_number: u8,
841) -> Result<(BACnetTimeStamp, usize), Error> {
842 let (tag, pos) = tags::decode_tag(data, offset)?;
843 if !tag.is_opening_tag(tag_number) {
844 return Err(Error::decoding(
845 offset,
846 format!("expected opening tag {tag_number} for BACnetTimeStamp"),
847 ));
848 }
849
850 let (ts, after_inner) = decode_timestamp_choice(data, pos)?;
851
852 let (close, final_pos) = tags::decode_tag(data, after_inner)?;
853 if !close.is_closing_tag(tag_number) {
854 return Err(Error::decoding(
855 after_inner,
856 format!("expected closing tag {tag_number} for BACnetTimeStamp"),
857 ));
858 }
859
860 Ok((ts, final_pos))
861}
862
863#[cfg(test)]
868mod tests;