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