bacnet_encoding/constructed/
calendar.rs1use bacnet_types::constructed::{BACnetCalendarEntry, BACnetDateRange, BACnetWeekNDay};
20use bacnet_types::error::Error;
21use bacnet_types::primitives::Date;
22use bytes::BytesMut;
23
24use super::tagged::{contents, expect_closing};
25use super::MAX_FRAMED_ITEMS;
26use crate::primitives;
27use crate::tags::{self, TagClass};
28
29pub fn encode_date_range(buf: &mut BytesMut, range: &BACnetDateRange) {
31 primitives::encode_app_date(buf, &range.start_date);
32 primitives::encode_app_date(buf, &range.end_date);
33}
34
35pub fn decode_date_range(data: &[u8], offset: usize) -> Result<(BACnetDateRange, usize), Error> {
40 let (start_date, offset) = decode_app_date(data, offset, "date range start-date")?;
41 let (end_date, offset) = decode_app_date(data, offset, "date range end-date")?;
42 Ok((
43 BACnetDateRange {
44 start_date,
45 end_date,
46 },
47 offset,
48 ))
49}
50
51pub fn encode_calendar_entry(buf: &mut BytesMut, entry: &BACnetCalendarEntry) {
53 match entry {
54 BACnetCalendarEntry::Date(date) => primitives::encode_ctx_date(buf, 0, date),
55 BACnetCalendarEntry::DateRange(range) => {
56 tags::encode_opening_tag(buf, 1);
57 encode_date_range(buf, range);
58 tags::encode_closing_tag(buf, 1);
59 }
60 BACnetCalendarEntry::WeekNDay(week_n_day) => {
61 primitives::encode_ctx_octet_string(buf, 2, &week_n_day.encode());
62 }
63 }
64}
65
66pub fn decode_calendar_entry(
75 data: &[u8],
76 offset: usize,
77) -> Result<(BACnetCalendarEntry, usize), Error> {
78 let (tag, content) = tags::decode_tag(data, offset)?;
79 if tag.is_context(0) {
80 let (octets, end) = fixed_content(data, offset, content, tag.length, 4, "date [0]")?;
81 return Ok((BACnetCalendarEntry::Date(Date::decode(octets)?), end));
82 }
83 if tag.is_opening_tag(1) {
84 let (range, after) = decode_date_range(data, content)?;
85 let end = expect_closing(data, after, 1, "calendar entry date-range")?;
86 return Ok((BACnetCalendarEntry::DateRange(range), end));
87 }
88 if tag.is_context(2) {
89 let (octets, end) = fixed_content(data, offset, content, tag.length, 3, "weekNDay [2]")?;
90 return Ok((
91 BACnetCalendarEntry::WeekNDay(BACnetWeekNDay::decode(octets)?),
92 end,
93 ));
94 }
95 Err(Error::decoding(
96 offset,
97 "calendar entry requires date [0], date-range [1] or weekNDay [2]",
98 ))
99}
100
101pub fn encode_calendar_entry_list(buf: &mut BytesMut, entries: &[BACnetCalendarEntry]) {
103 for entry in entries {
104 encode_calendar_entry(buf, entry);
105 }
106}
107
108pub fn decode_calendar_entry_list(data: &[u8]) -> Result<Vec<BACnetCalendarEntry>, Error> {
112 let mut entries = Vec::new();
113 let mut offset = 0;
114 while offset < data.len() {
115 if entries.len() >= MAX_FRAMED_ITEMS {
116 return Err(Error::overflow(
117 offset,
118 "calendar entry list exceeds item limit",
119 ));
120 }
121 let (entry, end) = decode_calendar_entry(data, offset)?;
122 entries.push(entry);
123 offset = end;
124 }
125 Ok(entries)
126}
127
128fn decode_app_date(data: &[u8], offset: usize, what: &str) -> Result<(Date, usize), Error> {
130 let (tag, content) = tags::decode_tag(data, offset)?;
131 if tag.class != TagClass::Application || tag.number != tags::app_tag::DATE {
132 return Err(Error::decoding(
133 offset,
134 format!("{what}: expected an application-tagged Date"),
135 ));
136 }
137 let (octets, end) = fixed_content(data, offset, content, tag.length, 4, what)?;
138 Ok((Date::decode(octets)?, end))
139}
140
141fn fixed_content<'a>(
144 data: &'a [u8],
145 offset: usize,
146 content: usize,
147 length: u32,
148 expected: usize,
149 what: &str,
150) -> Result<(&'a [u8], usize), Error> {
151 if usize::try_from(length).ok() != Some(expected) {
152 return Err(Error::decoding(
153 offset,
154 format!("{what}: expected {expected} content octets, got {length}"),
155 ));
156 }
157 contents(data, content, length)
158}