Skip to main content

bacnet_encoding/
primitives.rs

1//! Application-tagged primitive encode/decode per ASHRAE 135-2020 Clause 20.2.
2//!
3//! Provides both raw value codecs (no tag header) and application/context-tagged
4//! convenience functions for all BACnet primitive types.
5
6use 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
12// ===========================================================================
13// Raw value codecs (no tag header)
14// ===========================================================================
15
16// --- Unsigned Integer ---
17
18/// Encode an unsigned integer using the minimum number of big-endian octets.
19pub 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
44/// Return the number of bytes needed to encode an unsigned value.
45pub 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
65/// Decode an unsigned integer from big-endian bytes (1-8 bytes).
66pub 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
80// --- Signed Integer ---
81
82/// Encode a signed integer using minimum octets, two's complement, big-endian.
83pub 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
89/// Return the number of bytes needed to encode a signed value.
90pub 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
102/// Decode a signed integer from two's-complement big-endian bytes (1-4 bytes).
103pub 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
116// --- Real ---
117
118/// Encode an IEEE-754 single-precision float (big-endian, 4 bytes).
119pub fn encode_real(buf: &mut BytesMut, value: f32) {
120    buf.put_f32(value);
121}
122
123/// Decode an IEEE-754 single-precision float from 4 big-endian bytes.
124pub fn decode_real(data: &[u8]) -> Result<f32, Error> {
125    if data.len() < 4 {
126        return Err(Error::buffer_too_short(4, data.len()));
127    }
128    Ok(f32::from_be_bytes([data[0], data[1], data[2], data[3]]))
129}
130
131// --- Double ---
132
133/// Encode an IEEE-754 double-precision float (big-endian, 8 bytes).
134pub fn encode_double(buf: &mut BytesMut, value: f64) {
135    buf.put_f64(value);
136}
137
138/// Decode an IEEE-754 double-precision float from 8 big-endian bytes.
139pub fn decode_double(data: &[u8]) -> Result<f64, Error> {
140    if data.len() < 8 {
141        return Err(Error::buffer_too_short(8, data.len()));
142    }
143    let bytes: [u8; 8] = [
144        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
145    ];
146    Ok(f64::from_be_bytes(bytes))
147}
148
149// --- Character String ---
150
151/// Character set identifiers.
152pub mod charset {
153    /// ISO 10646 (UTF-8) — X'00'
154    pub const UTF8: u8 = 0;
155    /// IBM/Microsoft DBCS — X'01'
156    pub const IBM_MICROSOFT_DBCS: u8 = 1;
157    /// JIS X 0208 — X'02'
158    pub const JIS_X_0208: u8 = 2;
159    /// ISO 10646 (UCS-4) — X'03'
160    pub const UCS4: u8 = 3;
161    /// ISO 10646 (UCS-2) — X'04'
162    pub const UCS2: u8 = 4;
163    /// ISO 8859-1 — X'05'
164    pub const ISO_8859_1: u8 = 5;
165}
166
167/// Encode a UTF-8 character string with leading charset byte.
168pub fn encode_character_string(buf: &mut BytesMut, value: &str) {
169    buf.put_u8(charset::UTF8);
170    buf.put_slice(value.as_bytes());
171}
172
173/// Return the encoded length of a character string (charset byte + UTF-8 bytes).
174pub fn character_string_len(value: &str) -> Result<u32, Error> {
175    u32::try_from(value.len())
176        .ok()
177        .and_then(|n| n.checked_add(1))
178        .ok_or_else(|| Error::Encoding("CharacterString too long for BACnet encoding".into()))
179}
180
181/// Decode a character string from content bytes.
182///
183/// The first byte is the charset identifier. Supported charsets:
184/// - 0 (UTF-8)
185/// - 4 (UCS-2, big-endian)
186/// - 5 (ISO-8859-1)
187///
188/// Other charsets return an error.
189pub fn decode_character_string(data: &[u8]) -> Result<String, Error> {
190    if data.is_empty() {
191        return Err(Error::Decoding {
192            offset: 0,
193            message: "CharacterString requires at least 1 byte for charset".into(),
194        });
195    }
196    let charset_id = data[0];
197    let payload = &data[1..];
198    match charset_id {
199        charset::UTF8 => String::from_utf8(payload.to_vec()).map_err(|e| Error::Decoding {
200            offset: 1,
201            message: format!("invalid UTF-8: {e}"),
202        }),
203        charset::UCS2 => {
204            if !payload.len().is_multiple_of(2) {
205                return Err(Error::Decoding {
206                    offset: 1,
207                    message: "UCS-2 data must have even length".into(),
208                });
209            }
210            let mut s = String::new();
211            for (i, chunk) in payload.chunks_exact(2).enumerate() {
212                let code_point = u16::from_be_bytes([chunk[0], chunk[1]]);
213                if let Some(c) = char::from_u32(code_point as u32) {
214                    s.push(c);
215                } else {
216                    return Err(Error::Decoding {
217                        offset: 1 + i * 2,
218                        message: "invalid UCS-2 code point".into(),
219                    });
220                }
221            }
222            Ok(s)
223        }
224        charset::ISO_8859_1 => Ok(payload.iter().map(|&b| b as char).collect()),
225        charset::IBM_MICROSOFT_DBCS | charset::JIS_X_0208 | charset::UCS4 => Err(Error::Decoding {
226            offset: 0,
227            message: format!("unsupported charset: {charset_id}"),
228        }),
229        other => Err(Error::Decoding {
230            offset: 0,
231            message: format!("unknown charset: {other}"),
232        }),
233    }
234}
235
236// --- Bit String ---
237
238/// Encode a bit string: leading unused-bits count followed by data bytes.
239pub fn encode_bit_string(buf: &mut BytesMut, unused_bits: u8, data: &[u8]) {
240    buf.put_u8(unused_bits);
241    buf.put_slice(data);
242}
243
244/// Decode a bit string from content bytes.
245///
246/// Returns `(unused_bits, data)`.
247pub fn decode_bit_string(data: &[u8]) -> Result<(u8, Vec<u8>), Error> {
248    if data.is_empty() {
249        return Err(Error::Decoding {
250            offset: 0,
251            message: "BitString requires at least 1 byte for unused-bits count".into(),
252        });
253    }
254    let unused = data[0];
255    if unused > 7 {
256        return Err(Error::Decoding {
257            offset: 0,
258            message: format!("BitString unused_bits must be 0-7, got {unused}"),
259        });
260    }
261    Ok((unused, data[1..].to_vec()))
262}
263
264// ===========================================================================
265// Application-tagged encode helpers
266// ===========================================================================
267
268/// Encode an application-tagged Null.
269pub fn encode_app_null(buf: &mut BytesMut) {
270    tags::encode_tag(buf, app_tag::NULL, TagClass::Application, 0);
271}
272
273/// Encode an application-tagged Boolean.
274///
275/// The value is encoded in the tag's L/V/T bits with no content octets.
276pub fn encode_app_boolean(buf: &mut BytesMut, value: bool) {
277    tags::encode_tag(
278        buf,
279        app_tag::BOOLEAN,
280        TagClass::Application,
281        if value { 1 } else { 0 },
282    );
283}
284
285/// Encode an application-tagged Unsigned.
286pub fn encode_app_unsigned(buf: &mut BytesMut, value: u64) {
287    let len = unsigned_len(value);
288    tags::encode_tag(buf, app_tag::UNSIGNED, TagClass::Application, len);
289    encode_unsigned(buf, value);
290}
291
292/// Encode an application-tagged Signed.
293pub fn encode_app_signed(buf: &mut BytesMut, value: i32) {
294    let len = signed_len(value);
295    tags::encode_tag(buf, app_tag::SIGNED, TagClass::Application, len);
296    encode_signed(buf, value);
297}
298
299/// Encode an application-tagged Real (f32).
300pub fn encode_app_real(buf: &mut BytesMut, value: f32) {
301    tags::encode_tag(buf, app_tag::REAL, TagClass::Application, 4);
302    encode_real(buf, value);
303}
304
305/// Encode an application-tagged Double (f64).
306pub fn encode_app_double(buf: &mut BytesMut, value: f64) {
307    tags::encode_tag(buf, app_tag::DOUBLE, TagClass::Application, 8);
308    encode_double(buf, value);
309}
310
311/// Encode an application-tagged OctetString.
312pub fn encode_app_octet_string(buf: &mut BytesMut, data: &[u8]) {
313    let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
314    tags::encode_tag(buf, app_tag::OCTET_STRING, TagClass::Application, data_len);
315    buf.put_slice(data);
316}
317
318/// Encode an application-tagged CharacterString (UTF-8).
319pub fn encode_app_character_string(buf: &mut BytesMut, value: &str) -> Result<(), Error> {
320    let len = character_string_len(value)?;
321    tags::encode_tag(buf, app_tag::CHARACTER_STRING, TagClass::Application, len);
322    encode_character_string(buf, value);
323    Ok(())
324}
325
326/// Encode an application-tagged BitString.
327pub fn encode_app_bit_string(buf: &mut BytesMut, unused_bits: u8, data: &[u8]) {
328    let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
329    let len = data_len.saturating_add(1);
330    tags::encode_tag(buf, app_tag::BIT_STRING, TagClass::Application, len);
331    encode_bit_string(buf, unused_bits, data);
332}
333
334/// Encode an application-tagged Enumerated.
335pub fn encode_app_enumerated(buf: &mut BytesMut, value: u32) {
336    let len = unsigned_len(value as u64);
337    tags::encode_tag(buf, app_tag::ENUMERATED, TagClass::Application, len);
338    encode_unsigned(buf, value as u64);
339}
340
341/// Encode an application-tagged Date.
342pub fn encode_app_date(buf: &mut BytesMut, date: &Date) {
343    tags::encode_tag(buf, app_tag::DATE, TagClass::Application, 4);
344    buf.put_slice(&date.encode());
345}
346
347/// Encode an application-tagged Time.
348pub fn encode_app_time(buf: &mut BytesMut, time: &Time) {
349    tags::encode_tag(buf, app_tag::TIME, TagClass::Application, 4);
350    buf.put_slice(&time.encode());
351}
352
353/// Encode an application-tagged ObjectIdentifier.
354pub fn encode_app_object_id(buf: &mut BytesMut, oid: &ObjectIdentifier) {
355    tags::encode_tag(buf, app_tag::OBJECT_IDENTIFIER, TagClass::Application, 4);
356    buf.put_slice(&oid.encode());
357}
358
359// ===========================================================================
360// Context-tagged encode helpers
361// ===========================================================================
362
363/// Encode a context-tagged Unsigned.
364pub fn encode_ctx_unsigned(buf: &mut BytesMut, tag: u8, value: u64) {
365    let len = unsigned_len(value);
366    tags::encode_tag(buf, tag, TagClass::Context, len);
367    encode_unsigned(buf, value);
368}
369
370/// Encode a context-tagged Signed.
371pub fn encode_ctx_signed(buf: &mut BytesMut, tag: u8, value: i32) {
372    let len = signed_len(value);
373    tags::encode_tag(buf, tag, TagClass::Context, len);
374    encode_signed(buf, value);
375}
376
377/// Encode a context-tagged Real (f32).
378pub fn encode_ctx_real(buf: &mut BytesMut, tag: u8, value: f32) {
379    tags::encode_tag(buf, tag, TagClass::Context, 4);
380    encode_real(buf, value);
381}
382
383/// Encode a context-tagged Double (f64).
384pub fn encode_ctx_double(buf: &mut BytesMut, tag: u8, value: f64) {
385    tags::encode_tag(buf, tag, TagClass::Context, 8);
386    encode_double(buf, value);
387}
388
389/// Encode a context-tagged Enumerated.
390pub fn encode_ctx_enumerated(buf: &mut BytesMut, tag: u8, value: u32) {
391    let len = unsigned_len(value as u64);
392    tags::encode_tag(buf, tag, TagClass::Context, len);
393    encode_unsigned(buf, value as u64);
394}
395
396/// Encode a context-tagged Boolean.
397///
398/// Context-tagged booleans use a 1-byte content octet (unlike application-tagged).
399pub fn encode_ctx_boolean(buf: &mut BytesMut, tag: u8, value: bool) {
400    tags::encode_tag(buf, tag, TagClass::Context, 1);
401    buf.put_u8(if value { 1 } else { 0 });
402}
403
404/// Encode a context-tagged ObjectIdentifier.
405pub fn encode_ctx_object_id(buf: &mut BytesMut, tag: u8, oid: &ObjectIdentifier) {
406    tags::encode_tag(buf, tag, TagClass::Context, 4);
407    buf.put_slice(&oid.encode());
408}
409
410/// Encode a context-tagged OctetString.
411pub fn encode_ctx_octet_string(buf: &mut BytesMut, tag: u8, data: &[u8]) {
412    let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
413    tags::encode_tag(buf, tag, TagClass::Context, data_len);
414    buf.put_slice(data);
415}
416
417/// Encode a context-tagged CharacterString (UTF-8).
418pub fn encode_ctx_character_string(buf: &mut BytesMut, tag: u8, value: &str) -> Result<(), Error> {
419    let len = character_string_len(value)?;
420    tags::encode_tag(buf, tag, TagClass::Context, len);
421    encode_character_string(buf, value);
422    Ok(())
423}
424
425/// Encode a context-tagged Date.
426pub fn encode_ctx_date(buf: &mut BytesMut, tag: u8, date: &Date) {
427    tags::encode_tag(buf, tag, TagClass::Context, 4);
428    buf.put_slice(&date.encode());
429}
430
431/// Encode a context-tagged BitString.
432pub fn encode_ctx_bit_string(buf: &mut BytesMut, tag: u8, unused_bits: u8, data: &[u8]) {
433    let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
434    let len = data_len.saturating_add(1);
435    tags::encode_tag(buf, tag, TagClass::Context, len);
436    encode_bit_string(buf, unused_bits, data);
437}
438
439// ===========================================================================
440// Application-tagged decode (dispatches by tag number)
441// ===========================================================================
442
443/// Decode a single application-tagged value from `data` at `offset`.
444///
445/// Returns the decoded `PropertyValue` and the new offset past the consumed bytes.
446pub fn decode_application_value(
447    data: &[u8],
448    offset: usize,
449) -> Result<(PropertyValue, usize), Error> {
450    let (tag, new_offset) = tags::decode_tag(data, offset)?;
451    if tag.class != TagClass::Application {
452        return Err(Error::decoding(
453            offset,
454            format!("expected application tag, got context tag {}", tag.number),
455        ));
456    }
457    if tag.is_opening || tag.is_closing {
458        return Err(Error::decoding(offset, "unexpected opening/closing tag"));
459    }
460
461    let content_start = new_offset;
462    let content_len = tag.length as usize;
463    let content_end = content_start
464        .checked_add(content_len)
465        .ok_or_else(|| Error::decoding(content_start, "length overflow"))?;
466
467    if tag.number == app_tag::BOOLEAN {
468        return Ok((PropertyValue::Boolean(tag.length != 0), content_start));
469    }
470
471    if data.len() < content_end {
472        return Err(Error::buffer_too_short(content_end, data.len()));
473    }
474
475    let content = &data[content_start..content_end];
476
477    let value = match tag.number {
478        app_tag::NULL => PropertyValue::Null,
479        app_tag::UNSIGNED => PropertyValue::Unsigned(decode_unsigned(content)?),
480        app_tag::SIGNED => PropertyValue::Signed(decode_signed(content)?),
481        app_tag::REAL => PropertyValue::Real(decode_real(content)?),
482        app_tag::DOUBLE => PropertyValue::Double(decode_double(content)?),
483        app_tag::OCTET_STRING => PropertyValue::OctetString(content.to_vec()),
484        app_tag::CHARACTER_STRING => {
485            PropertyValue::CharacterString(decode_character_string(content)?)
486        }
487        app_tag::BIT_STRING => {
488            let (unused, bits) = decode_bit_string(content)?;
489            PropertyValue::BitString {
490                unused_bits: unused,
491                data: bits,
492            }
493        }
494        app_tag::ENUMERATED => PropertyValue::Enumerated(decode_unsigned(content)? as u32),
495        app_tag::DATE => PropertyValue::Date(Date::decode(content)?),
496        app_tag::TIME => PropertyValue::Time(Time::decode(content)?),
497        app_tag::OBJECT_IDENTIFIER => {
498            PropertyValue::ObjectIdentifier(ObjectIdentifier::decode(content)?)
499        }
500        other => {
501            return Err(Error::decoding(
502                offset,
503                format!("unknown application tag number {other}"),
504            ));
505        }
506    };
507
508    Ok((value, content_end))
509}
510
511/// Encode a `PropertyValue` as an application-tagged value.
512pub fn encode_property_value(buf: &mut BytesMut, value: &PropertyValue) -> Result<(), Error> {
513    match value {
514        PropertyValue::Null => encode_app_null(buf),
515        PropertyValue::Boolean(v) => encode_app_boolean(buf, *v),
516        PropertyValue::Unsigned(v) => encode_app_unsigned(buf, *v),
517        PropertyValue::Signed(v) => encode_app_signed(buf, *v),
518        PropertyValue::Real(v) => encode_app_real(buf, *v),
519        PropertyValue::Double(v) => encode_app_double(buf, *v),
520        PropertyValue::OctetString(v) => encode_app_octet_string(buf, v),
521        PropertyValue::CharacterString(v) => encode_app_character_string(buf, v)?,
522        PropertyValue::BitString { unused_bits, data } => {
523            encode_app_bit_string(buf, *unused_bits, data)
524        }
525        PropertyValue::Enumerated(v) => encode_app_enumerated(buf, *v),
526        PropertyValue::Date(v) => encode_app_date(buf, v),
527        PropertyValue::Time(v) => encode_app_time(buf, v),
528        PropertyValue::ObjectIdentifier(v) => encode_app_object_id(buf, v),
529        PropertyValue::List(values) => {
530            for v in values {
531                encode_property_value(buf, v)?;
532            }
533        }
534    }
535    Ok(())
536}
537
538// ===========================================================================
539// BACnetTimeStamp encode/decode
540// ===========================================================================
541
542/// Encode a BACnetTimeStamp wrapped in a context opening/closing tag pair.
543///
544/// The outer `tag_number` is the context tag of the enclosing field.
545/// Inside, the CHOICE variant uses its own context tag (0=Time,
546/// 1=SequenceNumber, 2=DateTime).
547pub fn encode_timestamp(buf: &mut BytesMut, tag_number: u8, ts: &BACnetTimeStamp) {
548    tags::encode_opening_tag(buf, tag_number);
549    match ts {
550        BACnetTimeStamp::Time(t) => {
551            tags::encode_tag(buf, 0, TagClass::Context, 4);
552            buf.put_slice(&t.encode());
553        }
554        BACnetTimeStamp::SequenceNumber(n) => {
555            encode_ctx_unsigned(buf, 1, *n);
556        }
557        BACnetTimeStamp::DateTime { date, time } => {
558            tags::encode_opening_tag(buf, 2);
559            encode_app_date(buf, date);
560            encode_app_time(buf, time);
561            tags::encode_closing_tag(buf, 2);
562        }
563    }
564    tags::encode_closing_tag(buf, tag_number);
565}
566
567/// Decode a BACnetTimeStamp from inside a context opening/closing tag pair.
568///
569/// `data` should point to the start of the outer opening tag for `tag_number`.
570/// Returns the decoded timestamp and the new offset past the outer closing tag.
571pub fn decode_timestamp(
572    data: &[u8],
573    offset: usize,
574    tag_number: u8,
575) -> Result<(BACnetTimeStamp, usize), Error> {
576    let (tag, pos) = tags::decode_tag(data, offset)?;
577    if !tag.is_opening_tag(tag_number) {
578        return Err(Error::decoding(
579            offset,
580            format!("expected opening tag {tag_number} for BACnetTimeStamp"),
581        ));
582    }
583
584    let (inner_tag, inner_pos) = tags::decode_tag(data, pos)?;
585
586    let (ts, after_inner) = if inner_tag.is_context(0) {
587        let end = inner_pos
588            .checked_add(inner_tag.length as usize)
589            .ok_or_else(|| Error::decoding(inner_pos, "BACnetTimeStamp Time length overflow"))?;
590        if end > data.len() {
591            return Err(Error::decoding(inner_pos, "BACnetTimeStamp Time truncated"));
592        }
593        let t = Time::decode(&data[inner_pos..end])?;
594        (BACnetTimeStamp::Time(t), end)
595    } else if inner_tag.is_context(1) {
596        let end = inner_pos
597            .checked_add(inner_tag.length as usize)
598            .ok_or_else(|| {
599                Error::decoding(inner_pos, "BACnetTimeStamp SequenceNumber length overflow")
600            })?;
601        if end > data.len() {
602            return Err(Error::decoding(
603                inner_pos,
604                "BACnetTimeStamp SequenceNumber truncated",
605            ));
606        }
607        let n = decode_unsigned(&data[inner_pos..end])?;
608        (BACnetTimeStamp::SequenceNumber(n), end)
609    } else if inner_tag.is_opening_tag(2) {
610        let (date_tag, date_pos) = tags::decode_tag(data, inner_pos)?;
611        if date_tag.class != TagClass::Application || date_tag.number != app_tag::DATE {
612            return Err(Error::decoding(
613                inner_pos,
614                "BACnetTimeStamp DateTime expected Date",
615            ));
616        }
617        let date_end = date_pos
618            .checked_add(date_tag.length as usize)
619            .ok_or_else(|| {
620                Error::decoding(date_pos, "BACnetTimeStamp DateTime Date length overflow")
621            })?;
622        if date_end > data.len() {
623            return Err(Error::decoding(
624                date_pos,
625                "BACnetTimeStamp DateTime Date truncated",
626            ));
627        }
628        let date = Date::decode(&data[date_pos..date_end])?;
629
630        let (time_tag, time_pos) = tags::decode_tag(data, date_end)?;
631        if time_tag.class != TagClass::Application || time_tag.number != app_tag::TIME {
632            return Err(Error::decoding(
633                date_end,
634                "BACnetTimeStamp DateTime expected Time",
635            ));
636        }
637        let time_end = time_pos
638            .checked_add(time_tag.length as usize)
639            .ok_or_else(|| {
640                Error::decoding(time_pos, "BACnetTimeStamp DateTime Time length overflow")
641            })?;
642        if time_end > data.len() {
643            return Err(Error::decoding(
644                time_pos,
645                "BACnetTimeStamp DateTime Time truncated",
646            ));
647        }
648        let time = Time::decode(&data[time_pos..time_end])?;
649
650        let (close_tag, close_pos) = tags::decode_tag(data, time_end)?;
651        if !close_tag.is_closing_tag(2) {
652            return Err(Error::decoding(
653                time_end,
654                "BACnetTimeStamp DateTime missing closing tag 2",
655            ));
656        }
657        (BACnetTimeStamp::DateTime { date, time }, close_pos)
658    } else {
659        return Err(Error::decoding(
660            pos,
661            "BACnetTimeStamp: unexpected inner choice tag",
662        ));
663    };
664
665    let (close, final_pos) = tags::decode_tag(data, after_inner)?;
666    if !close.is_closing_tag(tag_number) {
667        return Err(Error::decoding(
668            after_inner,
669            format!("expected closing tag {tag_number} for BACnetTimeStamp"),
670        ));
671    }
672
673    Ok((ts, final_pos))
674}
675
676// ===========================================================================
677// Tests
678// ===========================================================================
679
680#[cfg(test)]
681mod tests {
682    use super::*;
683    use bacnet_types::enums::ObjectType;
684
685    fn encode_to_vec<F: FnOnce(&mut BytesMut)>(f: F) -> Vec<u8> {
686        let mut buf = BytesMut::new();
687        f(&mut buf);
688        buf.to_vec()
689    }
690
691    // --- Raw unsigned ---
692
693    #[test]
694    fn unsigned_encode_decode_1byte() {
695        let mut buf = BytesMut::new();
696        encode_unsigned(&mut buf, 42);
697        assert_eq!(&buf[..], &[42]);
698        assert_eq!(decode_unsigned(&buf).unwrap(), 42);
699    }
700
701    #[test]
702    fn unsigned_encode_decode_2bytes() {
703        let mut buf = BytesMut::new();
704        encode_unsigned(&mut buf, 0x1234);
705        assert_eq!(&buf[..], &[0x12, 0x34]);
706        assert_eq!(decode_unsigned(&buf).unwrap(), 0x1234);
707    }
708
709    #[test]
710    fn unsigned_encode_decode_3bytes() {
711        let mut buf = BytesMut::new();
712        encode_unsigned(&mut buf, 0x12_3456);
713        assert_eq!(&buf[..], &[0x12, 0x34, 0x56]);
714        assert_eq!(decode_unsigned(&buf).unwrap(), 0x12_3456);
715    }
716
717    #[test]
718    fn unsigned_encode_decode_4bytes() {
719        let mut buf = BytesMut::new();
720        encode_unsigned(&mut buf, 0xDEAD_BEEF);
721        assert_eq!(&buf[..], &[0xDE, 0xAD, 0xBE, 0xEF]);
722        assert_eq!(decode_unsigned(&buf).unwrap(), 0xDEAD_BEEF);
723    }
724
725    #[test]
726    fn unsigned_zero() {
727        let mut buf = BytesMut::new();
728        encode_unsigned(&mut buf, 0);
729        assert_eq!(&buf[..], &[0]);
730        assert_eq!(decode_unsigned(&buf).unwrap(), 0);
731    }
732
733    #[test]
734    fn unsigned_max_u64() {
735        let mut buf = BytesMut::new();
736        encode_unsigned(&mut buf, u64::MAX);
737        assert_eq!(buf.len(), 8);
738        assert_eq!(decode_unsigned(&buf).unwrap(), u64::MAX);
739    }
740
741    // --- Raw signed ---
742
743    #[test]
744    fn signed_encode_decode_positive() {
745        let mut buf = BytesMut::new();
746        encode_signed(&mut buf, 42);
747        assert_eq!(&buf[..], &[42]);
748        assert_eq!(decode_signed(&buf).unwrap(), 42);
749    }
750
751    #[test]
752    fn signed_encode_decode_negative() {
753        let mut buf = BytesMut::new();
754        encode_signed(&mut buf, -1);
755        assert_eq!(&buf[..], &[0xFF]);
756        assert_eq!(decode_signed(&buf).unwrap(), -1);
757    }
758
759    #[test]
760    fn signed_encode_decode_neg128() {
761        let mut buf = BytesMut::new();
762        encode_signed(&mut buf, -128);
763        assert_eq!(&buf[..], &[0x80]);
764        assert_eq!(decode_signed(&buf).unwrap(), -128);
765    }
766
767    #[test]
768    fn signed_encode_decode_neg129() {
769        let mut buf = BytesMut::new();
770        encode_signed(&mut buf, -129);
771        assert_eq!(&buf[..], &[0xFF, 0x7F]);
772        assert_eq!(decode_signed(&buf).unwrap(), -129);
773    }
774
775    #[test]
776    fn signed_encode_decode_min() {
777        let mut buf = BytesMut::new();
778        encode_signed(&mut buf, i32::MIN);
779        assert_eq!(buf.len(), 4);
780        assert_eq!(decode_signed(&buf).unwrap(), i32::MIN);
781    }
782
783    #[test]
784    fn signed_encode_decode_max() {
785        let mut buf = BytesMut::new();
786        encode_signed(&mut buf, i32::MAX);
787        assert_eq!(buf.len(), 4);
788        assert_eq!(decode_signed(&buf).unwrap(), i32::MAX);
789    }
790
791    // --- Real / Double ---
792
793    #[test]
794    fn real_round_trip() {
795        let mut buf = BytesMut::new();
796        encode_real(&mut buf, 72.5);
797        assert_eq!(decode_real(&buf).unwrap(), 72.5);
798    }
799
800    #[test]
801    fn double_round_trip() {
802        let mut buf = BytesMut::new();
803        encode_double(&mut buf, core::f64::consts::PI);
804        assert_eq!(decode_double(&buf).unwrap(), core::f64::consts::PI);
805    }
806
807    // --- Character string ---
808
809    #[test]
810    fn character_string_round_trip() {
811        let mut buf = BytesMut::new();
812        encode_character_string(&mut buf, "hello");
813        let decoded = decode_character_string(&buf).unwrap();
814        assert_eq!(decoded, "hello");
815    }
816
817    #[test]
818    fn character_string_empty() {
819        let mut buf = BytesMut::new();
820        encode_character_string(&mut buf, "");
821        let decoded = decode_character_string(&buf).unwrap();
822        assert_eq!(decoded, "");
823    }
824
825    // --- Bit string ---
826
827    #[test]
828    fn bit_string_round_trip() {
829        let mut buf = BytesMut::new();
830        encode_bit_string(&mut buf, 3, &[0b1010_0000]);
831        let (unused, data) = decode_bit_string(&buf).unwrap();
832        assert_eq!(unused, 3);
833        assert_eq!(data, vec![0b1010_0000]);
834    }
835
836    #[test]
837    fn bit_string_invalid_unused() {
838        assert!(decode_bit_string(&[8]).is_err());
839    }
840
841    // --- Application-tagged encode/decode round trips ---
842
843    #[test]
844    fn app_null_round_trip() {
845        let bytes = encode_to_vec(encode_app_null);
846        let (val, offset) = decode_application_value(&bytes, 0).unwrap();
847        assert_eq!(val, PropertyValue::Null);
848        assert_eq!(offset, bytes.len());
849    }
850
851    #[test]
852    fn app_boolean_true_round_trip() {
853        let bytes = encode_to_vec(|buf| encode_app_boolean(buf, true));
854        let (val, _) = decode_application_value(&bytes, 0).unwrap();
855        assert_eq!(val, PropertyValue::Boolean(true));
856    }
857
858    #[test]
859    fn app_boolean_false_round_trip() {
860        let bytes = encode_to_vec(|buf| encode_app_boolean(buf, false));
861        let (val, _) = decode_application_value(&bytes, 0).unwrap();
862        assert_eq!(val, PropertyValue::Boolean(false));
863    }
864
865    #[test]
866    fn app_unsigned_round_trip() {
867        for &v in &[0u64, 1, 255, 256, 65535, 65536, 0xFFFF_FFFF, u64::MAX] {
868            let bytes = encode_to_vec(|buf| encode_app_unsigned(buf, v));
869            let (val, end) = decode_application_value(&bytes, 0).unwrap();
870            assert_eq!(val, PropertyValue::Unsigned(v), "failed for {v}");
871            assert_eq!(end, bytes.len());
872        }
873    }
874
875    #[test]
876    fn app_signed_round_trip() {
877        for &v in &[0i32, 1, -1, 127, -128, 128, -129, i32::MIN, i32::MAX] {
878            let bytes = encode_to_vec(|buf| encode_app_signed(buf, v));
879            let (val, end) = decode_application_value(&bytes, 0).unwrap();
880            assert_eq!(val, PropertyValue::Signed(v), "failed for {v}");
881            assert_eq!(end, bytes.len());
882        }
883    }
884
885    #[test]
886    fn app_real_round_trip() {
887        let bytes = encode_to_vec(|buf| encode_app_real(buf, 72.5));
888        let (val, end) = decode_application_value(&bytes, 0).unwrap();
889        assert_eq!(val, PropertyValue::Real(72.5));
890        assert_eq!(end, bytes.len());
891    }
892
893    #[test]
894    fn app_double_round_trip() {
895        let bytes = encode_to_vec(|buf| encode_app_double(buf, core::f64::consts::PI));
896        let (val, end) = decode_application_value(&bytes, 0).unwrap();
897        assert_eq!(val, PropertyValue::Double(core::f64::consts::PI));
898        assert_eq!(end, bytes.len());
899    }
900
901    #[test]
902    fn app_octet_string_round_trip() {
903        let data = vec![0xDE, 0xAD, 0xBE, 0xEF];
904        let bytes = encode_to_vec(|buf| encode_app_octet_string(buf, &data));
905        let (val, end) = decode_application_value(&bytes, 0).unwrap();
906        assert_eq!(val, PropertyValue::OctetString(data));
907        assert_eq!(end, bytes.len());
908    }
909
910    #[test]
911    fn app_character_string_round_trip() {
912        let bytes = encode_to_vec(|buf| encode_app_character_string(buf, "BACnet").unwrap());
913        let (val, end) = decode_application_value(&bytes, 0).unwrap();
914        assert_eq!(val, PropertyValue::CharacterString("BACnet".into()));
915        assert_eq!(end, bytes.len());
916    }
917
918    #[test]
919    fn app_enumerated_round_trip() {
920        let bytes = encode_to_vec(|buf| encode_app_enumerated(buf, 8));
921        let (val, end) = decode_application_value(&bytes, 0).unwrap();
922        assert_eq!(val, PropertyValue::Enumerated(8));
923        assert_eq!(end, bytes.len());
924    }
925
926    #[test]
927    fn app_date_round_trip() {
928        let date = Date {
929            year: 124,
930            month: 6,
931            day: 15,
932            day_of_week: 6,
933        };
934        let bytes = encode_to_vec(|buf| encode_app_date(buf, &date));
935        let (val, end) = decode_application_value(&bytes, 0).unwrap();
936        assert_eq!(val, PropertyValue::Date(date));
937        assert_eq!(end, bytes.len());
938    }
939
940    #[test]
941    fn app_time_round_trip() {
942        let time = Time {
943            hour: 14,
944            minute: 30,
945            second: 0,
946            hundredths: 0,
947        };
948        let bytes = encode_to_vec(|buf| encode_app_time(buf, &time));
949        let (val, end) = decode_application_value(&bytes, 0).unwrap();
950        assert_eq!(val, PropertyValue::Time(time));
951        assert_eq!(end, bytes.len());
952    }
953
954    #[test]
955    fn app_object_id_round_trip() {
956        let oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
957        let bytes = encode_to_vec(|buf| encode_app_object_id(buf, &oid));
958        let (val, end) = decode_application_value(&bytes, 0).unwrap();
959        assert_eq!(val, PropertyValue::ObjectIdentifier(oid));
960        assert_eq!(end, bytes.len());
961    }
962
963    #[test]
964    fn app_bit_string_round_trip() {
965        let bytes = encode_to_vec(|buf| encode_app_bit_string(buf, 4, &[0xF0]));
966        let (val, end) = decode_application_value(&bytes, 0).unwrap();
967        assert_eq!(
968            val,
969            PropertyValue::BitString {
970                unused_bits: 4,
971                data: vec![0xF0],
972            }
973        );
974        assert_eq!(end, bytes.len());
975    }
976
977    // --- PropertyValue encode/decode round trip ---
978
979    #[test]
980    fn property_value_encode_decode_all_types() {
981        let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
982        let values = vec![
983            PropertyValue::Null,
984            PropertyValue::Boolean(true),
985            PropertyValue::Boolean(false),
986            PropertyValue::Unsigned(12345),
987            PropertyValue::Signed(-42),
988            PropertyValue::Real(72.5),
989            PropertyValue::Double(3.125),
990            PropertyValue::OctetString(vec![1, 2, 3]),
991            PropertyValue::CharacterString("test".into()),
992            PropertyValue::BitString {
993                unused_bits: 0,
994                data: vec![0xFF],
995            },
996            PropertyValue::Enumerated(8),
997            PropertyValue::Date(Date {
998                year: 124,
999                month: 1,
1000                day: 1,
1001                day_of_week: 1,
1002            }),
1003            PropertyValue::Time(Time {
1004                hour: 12,
1005                minute: 0,
1006                second: 0,
1007                hundredths: 0,
1008            }),
1009            PropertyValue::ObjectIdentifier(oid),
1010        ];
1011
1012        for original in &values {
1013            let bytes = encode_to_vec(|buf| encode_property_value(buf, original).unwrap());
1014            let (decoded, end) = decode_application_value(&bytes, 0).unwrap();
1015            assert_eq!(&decoded, original, "round-trip failed for {original:?}");
1016            assert_eq!(end, bytes.len(), "offset mismatch for {original:?}");
1017        }
1018    }
1019
1020    // --- Context-tagged encode ---
1021
1022    #[test]
1023    fn ctx_unsigned_encoding() {
1024        let bytes = encode_to_vec(|buf| encode_ctx_unsigned(buf, 1, 42));
1025        // Context tag 1, length 1, value 42
1026        assert_eq!(bytes, vec![0x19, 42]);
1027    }
1028
1029    #[test]
1030    fn ctx_boolean_encoding() {
1031        let bytes = encode_to_vec(|buf| encode_ctx_boolean(buf, 0, true));
1032        // Context tag 0, length 1, value 1
1033        assert_eq!(bytes, vec![0x09, 0x01]);
1034    }
1035
1036    #[test]
1037    fn ctx_object_id_encoding() {
1038        let oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
1039        let bytes = encode_to_vec(|buf| encode_ctx_object_id(buf, 2, &oid));
1040        // Context tag 2, length 4: (2<<4) | (1<<3) | 4 = 0x2C
1041        let mut expected = vec![0x2C];
1042        expected.extend_from_slice(&oid.encode());
1043        assert_eq!(bytes, expected);
1044    }
1045
1046    // --- BACnetTimeStamp encode/decode ---
1047
1048    #[test]
1049    fn timestamp_sequence_number_round_trip() {
1050        let ts = BACnetTimeStamp::SequenceNumber(42);
1051        let mut buf = BytesMut::new();
1052        encode_timestamp(&mut buf, 3, &ts);
1053        let (decoded, end) = decode_timestamp(&buf, 0, 3).unwrap();
1054        assert_eq!(decoded, ts);
1055        assert_eq!(end, buf.len());
1056    }
1057
1058    #[test]
1059    fn timestamp_time_round_trip() {
1060        let ts = BACnetTimeStamp::Time(Time {
1061            hour: 14,
1062            minute: 30,
1063            second: 45,
1064            hundredths: 50,
1065        });
1066        let mut buf = BytesMut::new();
1067        encode_timestamp(&mut buf, 3, &ts);
1068        let (decoded, end) = decode_timestamp(&buf, 0, 3).unwrap();
1069        assert_eq!(decoded, ts);
1070        assert_eq!(end, buf.len());
1071    }
1072
1073    #[test]
1074    fn timestamp_datetime_round_trip() {
1075        let ts = BACnetTimeStamp::DateTime {
1076            date: Date {
1077                year: 126,
1078                month: 2,
1079                day: 28,
1080                day_of_week: 6,
1081            },
1082            time: Time {
1083                hour: 10,
1084                minute: 15,
1085                second: 0,
1086                hundredths: 0,
1087            },
1088        };
1089        let mut buf = BytesMut::new();
1090        encode_timestamp(&mut buf, 5, &ts);
1091        let (decoded, end) = decode_timestamp(&buf, 0, 5).unwrap();
1092        assert_eq!(decoded, ts);
1093        assert_eq!(end, buf.len());
1094    }
1095
1096    #[test]
1097    fn ucs2_decode_ascii() {
1098        // UCS-2 BE for "AB": 0x00 0x41 0x00 0x42
1099        let data = [charset::UCS2, 0x00, 0x41, 0x00, 0x42];
1100        let result = decode_character_string(&data).unwrap();
1101        assert_eq!(result, "AB");
1102    }
1103
1104    #[test]
1105    fn ucs2_decode_non_ascii() {
1106        // UCS-2 BE for "é" (U+00E9): 0x00 0xE9
1107        let data = [charset::UCS2, 0x00, 0xE9];
1108        let result = decode_character_string(&data).unwrap();
1109        assert_eq!(result, "é");
1110    }
1111
1112    #[test]
1113    fn unsupported_charset_errors() {
1114        for &cs in &[
1115            charset::IBM_MICROSOFT_DBCS,
1116            charset::JIS_X_0208,
1117            charset::UCS4,
1118        ] {
1119            let data = [cs, 0x41, 0x42];
1120            let result = decode_character_string(&data);
1121            assert!(result.is_err(), "charset {cs} should return an error");
1122        }
1123    }
1124}