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 fn encode_real(buf: &mut BytesMut, value: f32) {
120 buf.put_f32(value);
121}
122
123pub fn decode_real(data: &[u8]) -> Result<f32, Error> {
125 if data.len() != 4 {
126 return Err(Error::decoding(
127 0,
128 format!("Real expects exactly 4 bytes, got {}", data.len()),
129 ));
130 }
131 Ok(f32::from_be_bytes([data[0], data[1], data[2], data[3]]))
132}
133
134pub fn encode_double(buf: &mut BytesMut, value: f64) {
138 buf.put_f64(value);
139}
140
141pub fn decode_double(data: &[u8]) -> Result<f64, Error> {
143 if data.len() != 8 {
144 return Err(Error::decoding(
145 0,
146 format!("Double expects exactly 8 bytes, got {}", data.len()),
147 ));
148 }
149 let bytes: [u8; 8] = [
150 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
151 ];
152 Ok(f64::from_be_bytes(bytes))
153}
154
155pub mod charset {
159 pub const UTF8: u8 = 0;
161 pub const IBM_MICROSOFT_DBCS: u8 = 1;
163 pub const JIS_X_0208: u8 = 2;
165 pub const UCS4: u8 = 3;
167 pub const UCS2: u8 = 4;
169 pub const ISO_8859_1: u8 = 5;
171}
172
173pub fn encode_character_string(buf: &mut BytesMut, value: &str) {
175 buf.put_u8(charset::UTF8);
176 buf.put_slice(value.as_bytes());
177}
178
179pub fn character_string_len(value: &str) -> Result<u32, Error> {
181 u32::try_from(value.len())
182 .ok()
183 .and_then(|n| n.checked_add(1))
184 .ok_or_else(|| Error::Encoding("CharacterString too long for BACnet encoding".into()))
185}
186
187pub fn decode_character_string(data: &[u8]) -> Result<String, Error> {
196 if data.is_empty() {
197 return Err(Error::Decoding {
198 offset: 0,
199 message: "CharacterString requires at least 1 byte for charset".into(),
200 });
201 }
202 let charset_id = data[0];
203 let payload = &data[1..];
204 match charset_id {
205 charset::UTF8 => String::from_utf8(payload.to_vec()).map_err(|e| Error::Decoding {
206 offset: 1,
207 message: format!("invalid UTF-8: {e}"),
208 }),
209 charset::UCS2 => {
210 if !payload.len().is_multiple_of(2) {
211 return Err(Error::Decoding {
212 offset: 1,
213 message: "UCS-2 data must have even length".into(),
214 });
215 }
216 let mut s = String::new();
217 for (i, chunk) in payload.chunks_exact(2).enumerate() {
218 let code_point = u16::from_be_bytes([chunk[0], chunk[1]]);
219 if let Some(c) = char::from_u32(code_point as u32) {
220 s.push(c);
221 } else {
222 return Err(Error::Decoding {
223 offset: 1 + i * 2,
224 message: "invalid UCS-2 code point".into(),
225 });
226 }
227 }
228 Ok(s)
229 }
230 charset::ISO_8859_1 => Ok(payload.iter().map(|&b| b as char).collect()),
231 charset::IBM_MICROSOFT_DBCS | charset::JIS_X_0208 | charset::UCS4 => Err(Error::Decoding {
232 offset: 0,
233 message: format!("unsupported charset: {charset_id}"),
234 }),
235 other => Err(Error::Decoding {
236 offset: 0,
237 message: format!("unknown charset: {other}"),
238 }),
239 }
240}
241
242pub fn encode_bit_string(buf: &mut BytesMut, unused_bits: u8, data: &[u8]) {
246 buf.put_u8(unused_bits);
247 buf.put_slice(data);
248}
249
250pub fn decode_bit_string(data: &[u8]) -> Result<(u8, Vec<u8>), Error> {
254 if data.is_empty() {
255 return Err(Error::Decoding {
256 offset: 0,
257 message: "BitString requires at least 1 byte for unused-bits count".into(),
258 });
259 }
260 let unused = data[0];
261 if unused > 7 {
262 return Err(Error::Decoding {
263 offset: 0,
264 message: format!("BitString unused_bits must be 0-7, got {unused}"),
265 });
266 }
267 Ok((unused, data[1..].to_vec()))
268}
269
270pub fn encode_app_null(buf: &mut BytesMut) {
276 tags::encode_tag(buf, app_tag::NULL, TagClass::Application, 0);
277}
278
279pub fn encode_app_boolean(buf: &mut BytesMut, value: bool) {
283 tags::encode_tag(
284 buf,
285 app_tag::BOOLEAN,
286 TagClass::Application,
287 if value { 1 } else { 0 },
288 );
289}
290
291pub fn encode_app_unsigned(buf: &mut BytesMut, value: u64) {
293 let len = unsigned_len(value);
294 tags::encode_tag(buf, app_tag::UNSIGNED, TagClass::Application, len);
295 encode_unsigned(buf, value);
296}
297
298pub fn encode_app_signed(buf: &mut BytesMut, value: i32) {
300 let len = signed_len(value);
301 tags::encode_tag(buf, app_tag::SIGNED, TagClass::Application, len);
302 encode_signed(buf, value);
303}
304
305pub fn encode_app_real(buf: &mut BytesMut, value: f32) {
307 tags::encode_tag(buf, app_tag::REAL, TagClass::Application, 4);
308 encode_real(buf, value);
309}
310
311pub fn encode_app_double(buf: &mut BytesMut, value: f64) {
313 tags::encode_tag(buf, app_tag::DOUBLE, TagClass::Application, 8);
314 encode_double(buf, value);
315}
316
317pub fn encode_app_octet_string(buf: &mut BytesMut, data: &[u8]) {
319 let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
320 tags::encode_tag(buf, app_tag::OCTET_STRING, TagClass::Application, data_len);
321 buf.put_slice(data);
322}
323
324pub fn encode_app_character_string(buf: &mut BytesMut, value: &str) -> Result<(), Error> {
326 let len = character_string_len(value)?;
327 tags::encode_tag(buf, app_tag::CHARACTER_STRING, TagClass::Application, len);
328 encode_character_string(buf, value);
329 Ok(())
330}
331
332pub fn encode_app_bit_string(buf: &mut BytesMut, unused_bits: u8, data: &[u8]) {
334 let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
335 let len = data_len.saturating_add(1);
336 tags::encode_tag(buf, app_tag::BIT_STRING, TagClass::Application, len);
337 encode_bit_string(buf, unused_bits, data);
338}
339
340pub fn encode_app_enumerated(buf: &mut BytesMut, value: u32) {
342 let len = unsigned_len(value as u64);
343 tags::encode_tag(buf, app_tag::ENUMERATED, TagClass::Application, len);
344 encode_unsigned(buf, value as u64);
345}
346
347pub fn encode_app_date(buf: &mut BytesMut, date: &Date) {
349 tags::encode_tag(buf, app_tag::DATE, TagClass::Application, 4);
350 buf.put_slice(&date.encode());
351}
352
353pub fn encode_app_time(buf: &mut BytesMut, time: &Time) {
355 tags::encode_tag(buf, app_tag::TIME, TagClass::Application, 4);
356 buf.put_slice(&time.encode());
357}
358
359pub fn encode_app_object_id(buf: &mut BytesMut, oid: &ObjectIdentifier) {
361 tags::encode_tag(buf, app_tag::OBJECT_IDENTIFIER, TagClass::Application, 4);
362 buf.put_slice(&oid.encode());
363}
364
365pub fn encode_ctx_unsigned(buf: &mut BytesMut, tag: u8, value: u64) {
371 let len = unsigned_len(value);
372 tags::encode_tag(buf, tag, TagClass::Context, len);
373 encode_unsigned(buf, value);
374}
375
376pub fn encode_ctx_signed(buf: &mut BytesMut, tag: u8, value: i32) {
378 let len = signed_len(value);
379 tags::encode_tag(buf, tag, TagClass::Context, len);
380 encode_signed(buf, value);
381}
382
383pub fn encode_ctx_real(buf: &mut BytesMut, tag: u8, value: f32) {
385 tags::encode_tag(buf, tag, TagClass::Context, 4);
386 encode_real(buf, value);
387}
388
389pub fn encode_ctx_double(buf: &mut BytesMut, tag: u8, value: f64) {
391 tags::encode_tag(buf, tag, TagClass::Context, 8);
392 encode_double(buf, value);
393}
394
395pub fn encode_ctx_enumerated(buf: &mut BytesMut, tag: u8, value: u32) {
397 let len = unsigned_len(value as u64);
398 tags::encode_tag(buf, tag, TagClass::Context, len);
399 encode_unsigned(buf, value as u64);
400}
401
402pub fn encode_ctx_boolean(buf: &mut BytesMut, tag: u8, value: bool) {
406 tags::encode_tag(buf, tag, TagClass::Context, 1);
407 buf.put_u8(if value { 1 } else { 0 });
408}
409
410pub fn encode_ctx_object_id(buf: &mut BytesMut, tag: u8, oid: &ObjectIdentifier) {
412 tags::encode_tag(buf, tag, TagClass::Context, 4);
413 buf.put_slice(&oid.encode());
414}
415
416pub fn encode_ctx_octet_string(buf: &mut BytesMut, tag: u8, data: &[u8]) {
418 let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
419 tags::encode_tag(buf, tag, TagClass::Context, data_len);
420 buf.put_slice(data);
421}
422
423pub fn encode_ctx_character_string(buf: &mut BytesMut, tag: u8, value: &str) -> Result<(), Error> {
425 let len = character_string_len(value)?;
426 tags::encode_tag(buf, tag, TagClass::Context, len);
427 encode_character_string(buf, value);
428 Ok(())
429}
430
431pub fn encode_ctx_date(buf: &mut BytesMut, tag: u8, date: &Date) {
433 tags::encode_tag(buf, tag, TagClass::Context, 4);
434 buf.put_slice(&date.encode());
435}
436
437pub fn encode_ctx_bit_string(buf: &mut BytesMut, tag: u8, unused_bits: u8, data: &[u8]) {
439 let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
440 let len = data_len.saturating_add(1);
441 tags::encode_tag(buf, tag, TagClass::Context, len);
442 encode_bit_string(buf, unused_bits, data);
443}
444
445pub fn decode_application_value(
453 data: &[u8],
454 offset: usize,
455) -> Result<(PropertyValue, usize), Error> {
456 let (tag, new_offset) = tags::decode_tag(data, offset)?;
457 if tag.class != TagClass::Application {
458 return Err(Error::decoding(
459 offset,
460 format!("expected application tag, got context tag {}", tag.number),
461 ));
462 }
463 if tag.is_opening || tag.is_closing {
464 return Err(Error::decoding(offset, "unexpected opening/closing tag"));
465 }
466
467 let content_start = new_offset;
468 let content_len = tag.length as usize;
469 let content_end = content_start
470 .checked_add(content_len)
471 .ok_or_else(|| Error::decoding(content_start, "length overflow"))?;
472
473 if tag.number == app_tag::BOOLEAN {
474 return Ok((PropertyValue::Boolean(tag.length != 0), content_start));
475 }
476
477 if data.len() < content_end {
478 return Err(Error::buffer_too_short(content_end, data.len()));
479 }
480
481 let content = &data[content_start..content_end];
482
483 let value = match tag.number {
484 app_tag::NULL => PropertyValue::Null,
485 app_tag::UNSIGNED => PropertyValue::Unsigned(decode_unsigned(content)?),
486 app_tag::SIGNED => PropertyValue::Signed(decode_signed(content)?),
487 app_tag::REAL => PropertyValue::Real(decode_real(content)?),
488 app_tag::DOUBLE => PropertyValue::Double(decode_double(content)?),
489 app_tag::OCTET_STRING => PropertyValue::OctetString(content.to_vec()),
490 app_tag::CHARACTER_STRING => {
491 PropertyValue::CharacterString(decode_character_string(content)?)
492 }
493 app_tag::BIT_STRING => {
494 let (unused, bits) = decode_bit_string(content)?;
495 PropertyValue::BitString {
496 unused_bits: unused,
497 data: bits,
498 }
499 }
500 app_tag::ENUMERATED => PropertyValue::Enumerated(decode_unsigned(content)? as u32),
501 app_tag::DATE => PropertyValue::Date(Date::decode(content)?),
502 app_tag::TIME => PropertyValue::Time(Time::decode(content)?),
503 app_tag::OBJECT_IDENTIFIER => {
504 PropertyValue::ObjectIdentifier(ObjectIdentifier::decode(content)?)
505 }
506 other => {
507 return Err(Error::decoding(
508 offset,
509 format!("unknown application tag number {other}"),
510 ));
511 }
512 };
513
514 Ok((value, content_end))
515}
516
517pub fn encode_property_value(buf: &mut BytesMut, value: &PropertyValue) -> Result<(), Error> {
519 match value {
520 PropertyValue::Null => encode_app_null(buf),
521 PropertyValue::Boolean(v) => encode_app_boolean(buf, *v),
522 PropertyValue::Unsigned(v) => encode_app_unsigned(buf, *v),
523 PropertyValue::Signed(v) => encode_app_signed(buf, *v),
524 PropertyValue::Real(v) => encode_app_real(buf, *v),
525 PropertyValue::Double(v) => encode_app_double(buf, *v),
526 PropertyValue::OctetString(v) => encode_app_octet_string(buf, v),
527 PropertyValue::CharacterString(v) => encode_app_character_string(buf, v)?,
528 PropertyValue::BitString { unused_bits, data } => {
529 encode_app_bit_string(buf, *unused_bits, data)
530 }
531 PropertyValue::Enumerated(v) => encode_app_enumerated(buf, *v),
532 PropertyValue::Date(v) => encode_app_date(buf, v),
533 PropertyValue::Time(v) => encode_app_time(buf, v),
534 PropertyValue::ObjectIdentifier(v) => encode_app_object_id(buf, v),
535 PropertyValue::List(values) => {
536 for v in values {
537 encode_property_value(buf, v)?;
538 }
539 }
540 }
541 Ok(())
542}
543
544pub fn encode_timestamp(buf: &mut BytesMut, tag_number: u8, ts: &BACnetTimeStamp) {
554 tags::encode_opening_tag(buf, tag_number);
555 match ts {
556 BACnetTimeStamp::Time(t) => {
557 tags::encode_tag(buf, 0, TagClass::Context, 4);
558 buf.put_slice(&t.encode());
559 }
560 BACnetTimeStamp::SequenceNumber(n) => {
561 encode_ctx_unsigned(buf, 1, *n);
562 }
563 BACnetTimeStamp::DateTime { date, time } => {
564 tags::encode_opening_tag(buf, 2);
565 encode_app_date(buf, date);
566 encode_app_time(buf, time);
567 tags::encode_closing_tag(buf, 2);
568 }
569 }
570 tags::encode_closing_tag(buf, tag_number);
571}
572
573pub fn decode_timestamp(
578 data: &[u8],
579 offset: usize,
580 tag_number: u8,
581) -> Result<(BACnetTimeStamp, usize), Error> {
582 let (tag, pos) = tags::decode_tag(data, offset)?;
583 if !tag.is_opening_tag(tag_number) {
584 return Err(Error::decoding(
585 offset,
586 format!("expected opening tag {tag_number} for BACnetTimeStamp"),
587 ));
588 }
589
590 let (inner_tag, inner_pos) = tags::decode_tag(data, pos)?;
591
592 let (ts, after_inner) = if inner_tag.is_context(0) {
593 let end = inner_pos
594 .checked_add(inner_tag.length as usize)
595 .ok_or_else(|| Error::decoding(inner_pos, "BACnetTimeStamp Time length overflow"))?;
596 if end > data.len() {
597 return Err(Error::decoding(inner_pos, "BACnetTimeStamp Time truncated"));
598 }
599 let t = Time::decode(&data[inner_pos..end])?;
600 (BACnetTimeStamp::Time(t), end)
601 } else if inner_tag.is_context(1) {
602 let end = inner_pos
603 .checked_add(inner_tag.length as usize)
604 .ok_or_else(|| {
605 Error::decoding(inner_pos, "BACnetTimeStamp SequenceNumber length overflow")
606 })?;
607 if end > data.len() {
608 return Err(Error::decoding(
609 inner_pos,
610 "BACnetTimeStamp SequenceNumber truncated",
611 ));
612 }
613 let n = decode_unsigned(&data[inner_pos..end])?;
614 (BACnetTimeStamp::SequenceNumber(n), end)
615 } else if inner_tag.is_opening_tag(2) {
616 let (date_tag, date_pos) = tags::decode_tag(data, inner_pos)?;
617 if date_tag.class != TagClass::Application || date_tag.number != app_tag::DATE {
618 return Err(Error::decoding(
619 inner_pos,
620 "BACnetTimeStamp DateTime expected Date",
621 ));
622 }
623 let date_end = date_pos
624 .checked_add(date_tag.length as usize)
625 .ok_or_else(|| {
626 Error::decoding(date_pos, "BACnetTimeStamp DateTime Date length overflow")
627 })?;
628 if date_end > data.len() {
629 return Err(Error::decoding(
630 date_pos,
631 "BACnetTimeStamp DateTime Date truncated",
632 ));
633 }
634 let date = Date::decode(&data[date_pos..date_end])?;
635
636 let (time_tag, time_pos) = tags::decode_tag(data, date_end)?;
637 if time_tag.class != TagClass::Application || time_tag.number != app_tag::TIME {
638 return Err(Error::decoding(
639 date_end,
640 "BACnetTimeStamp DateTime expected Time",
641 ));
642 }
643 let time_end = time_pos
644 .checked_add(time_tag.length as usize)
645 .ok_or_else(|| {
646 Error::decoding(time_pos, "BACnetTimeStamp DateTime Time length overflow")
647 })?;
648 if time_end > data.len() {
649 return Err(Error::decoding(
650 time_pos,
651 "BACnetTimeStamp DateTime Time truncated",
652 ));
653 }
654 let time = Time::decode(&data[time_pos..time_end])?;
655
656 let (close_tag, close_pos) = tags::decode_tag(data, time_end)?;
657 if !close_tag.is_closing_tag(2) {
658 return Err(Error::decoding(
659 time_end,
660 "BACnetTimeStamp DateTime missing closing tag 2",
661 ));
662 }
663 (BACnetTimeStamp::DateTime { date, time }, close_pos)
664 } else {
665 return Err(Error::decoding(
666 pos,
667 "BACnetTimeStamp: unexpected inner choice tag",
668 ));
669 };
670
671 let (close, final_pos) = tags::decode_tag(data, after_inner)?;
672 if !close.is_closing_tag(tag_number) {
673 return Err(Error::decoding(
674 after_inner,
675 format!("expected closing tag {tag_number} for BACnetTimeStamp"),
676 ));
677 }
678
679 Ok((ts, final_pos))
680}
681
682#[cfg(test)]
687mod tests;