Skip to main content

bacnet_encoding/primitives/
priority_value.rs

1//! The date-time choice of `BACnetPriorityValue` (Clause 21), the element a
2//! DateTime Value or DateTime Pattern Value object keeps in each set slot of
3//! its Priority_Array.
4//!
5//! Every other choice of that production is one application-tagged
6//! primitive, which the generic value codecs already carry. The date-time
7//! choice is constructed: context tag 1 opens it, the application-tagged
8//! Date and Time follow, and a closing tag 1 ends it (#2098). Sending the
9//! Date and Time bare would make a whole-array read two values per set slot,
10//! so a reader could no longer count sixteen slots.
11
12use bacnet_types::error::{DecodeError, Error};
13use bacnet_types::primitives::{Date, Time};
14#[cfg(feature = "alloc")]
15use bytes::BytesMut;
16
17use crate::constructed::tagged::{self, misplaced_kind};
18use crate::tags::{self, app_tag};
19
20/// The context tag of the date-time choice of `BACnetPriorityValue`.
21pub const PRIORITY_DATE_TIME_TAG: u8 = 1;
22
23/// Append one Priority_Array element holding a date-time: opening tag 1, the
24/// application-tagged Date and Time, closing tag 1 (twelve octets).
25///
26/// ```
27/// use bacnet_encoding::primitives::encode_priority_date_time;
28/// use bacnet_types::primitives::{Date, Time};
29/// use bytes::BytesMut;
30///
31/// let date = Date { year: 126, month: 10, day: 25, day_of_week: 7 };
32/// let time = Time { hour: 7, minute: 30, second: 0, hundredths: 0 };
33/// let mut buf = BytesMut::new();
34/// encode_priority_date_time(&mut buf, &date, &time);
35/// assert_eq!(buf.len(), 12);
36/// assert_eq!((buf[0], buf[11]), (0x1E, 0x1F));
37/// ```
38#[cfg(feature = "alloc")]
39pub fn encode_priority_date_time(buf: &mut BytesMut, date: &Date, time: &Time) {
40    tags::encode_opening_tag(buf, PRIORITY_DATE_TIME_TAG);
41    super::encode_app_date(buf, date);
42    super::encode_app_time(buf, time);
43    tags::encode_closing_tag(buf, PRIORITY_DATE_TIME_TAG);
44}
45
46/// Decode the date-time choice of `BACnetPriorityValue` at `offset`,
47/// returning the Date, the Time and the offset past the closing tag.
48///
49/// Anything else at `offset` (another choice, a bare Date) is an error, so a
50/// caller can try this first and fall back to the application-tagged
51/// choices. Errors follow [`constructed::tagged`](crate::constructed::tagged).
52pub fn decode_priority_date_time(
53    data: &[u8],
54    offset: usize,
55) -> Result<((Date, Time), usize), Error> {
56    const WHAT: &str = "BACnetPriorityValue datetime";
57    let (tag, after) = tags::decode_tag(data, offset)?;
58    if !tag.is_opening_tag(PRIORITY_DATE_TIME_TAG) {
59        return Err(DecodeError::new(
60            misplaced_kind(data, offset, &tag, Some(PRIORITY_DATE_TIME_TAG)),
61            offset,
62            "expected opening tag 1 for a BACnetPriorityValue datetime",
63        )
64        .into());
65    }
66    let (date, date_end) = tagged::decode_app_fixed(data, after, app_tag::DATE, 4, WHAT)?;
67    let (time, time_end) = tagged::decode_app_fixed(data, date_end, app_tag::TIME, 4, WHAT)?;
68    let end = tagged::expect_closing(data, time_end, PRIORITY_DATE_TIME_TAG, WHAT)?;
69    Ok(((Date::decode(date)?, Time::decode(time)?), end))
70}
71
72#[cfg(all(test, feature = "alloc"))]
73mod tests {
74    use super::*;
75
76    fn sample() -> (Date, Time) {
77        (
78            Date {
79                year: 126,
80                month: 10,
81                day: 25,
82                day_of_week: 7,
83            },
84            Time {
85                hour: 7,
86                minute: 30,
87                second: 15,
88                hundredths: 0,
89            },
90        )
91    }
92
93    #[test]
94    fn the_datetime_choice_is_wrapped_in_context_tag_one() {
95        let (date, time) = sample();
96        let mut buf = BytesMut::new();
97        encode_priority_date_time(&mut buf, &date, &time);
98        assert_eq!(
99            &buf[..],
100            &[
101                0x1E, // opening tag 1
102                0xA4, 126, 10, 25, 7, // application Date
103                0xB4, 7, 30, 15, 0,    // application Time
104                0x1F, // closing tag 1
105            ][..]
106        );
107    }
108
109    #[test]
110    fn the_datetime_choice_round_trips() {
111        let (date, time) = sample();
112        let mut buf = BytesMut::new();
113        encode_priority_date_time(&mut buf, &date, &time);
114        buf.extend_from_slice(&[0x00]); // a following NULL slot
115        let ((d, t), end) = decode_priority_date_time(&buf, 0).unwrap();
116        assert_eq!((d, t), (date, time));
117        assert_eq!(end, 12);
118    }
119
120    #[test]
121    fn a_bare_date_time_or_other_tag_is_not_the_datetime_choice() {
122        let (date, time) = sample();
123        let mut bare = BytesMut::new();
124        super::super::encode_app_date(&mut bare, &date);
125        super::super::encode_app_time(&mut bare, &time);
126        assert!(decode_priority_date_time(&bare, 0).is_err());
127        // Opening tag 0 (constructed-value) is a different choice.
128        assert!(decode_priority_date_time(&[0x0E, 0x0F], 0).is_err());
129        // Unclosed or truncated.
130        let mut cut = BytesMut::new();
131        encode_priority_date_time(&mut cut, &date, &time);
132        assert!(decode_priority_date_time(&cut[..11], 0).is_err());
133        assert!(decode_priority_date_time(&cut[..4], 0).is_err());
134    }
135}