1use bacnet_types::constructed::{BACnetAddress, BACnetDestination, BACnetRecipient};
31use bacnet_types::error::Error;
32use bacnet_types::primitives::{ObjectIdentifier, Time};
33use bacnet_types::MacAddr;
34use bytes::BytesMut;
35
36use crate::primitives;
37use crate::tags::{self, TagClass};
38
39use super::MAX_FRAMED_ITEMS;
40
41use bacnet_types::bitstring::{pack_octet, unpack_octet};
45
46pub fn encode_destination(buf: &mut BytesMut, dest: &BACnetDestination) {
48 primitives::encode_app_bit_string(buf, 1, &[pack_octet(dest.valid_days)]);
49 primitives::encode_app_time(buf, &dest.from_time);
50 primitives::encode_app_time(buf, &dest.to_time);
51 encode_recipient(buf, &dest.recipient);
52 primitives::encode_app_unsigned(buf, dest.process_identifier as u64);
53 primitives::encode_app_boolean(buf, dest.issue_confirmed_notifications);
54 primitives::encode_app_bit_string(buf, 5, &[pack_octet(dest.transitions)]);
55}
56
57pub fn encode_destination_list(buf: &mut BytesMut, destinations: &[BACnetDestination]) {
60 for dest in destinations {
61 encode_destination(buf, dest);
62 }
63}
64
65pub fn encode_recipient(buf: &mut BytesMut, recipient: &BACnetRecipient) {
67 match recipient {
68 BACnetRecipient::Device(oid) => {
69 tags::encode_tag(buf, 0, TagClass::Context, 4);
71 buf.extend_from_slice(&oid.encode());
72 }
73 BACnetRecipient::Address(address) => {
74 tags::encode_opening_tag(buf, 1);
76 primitives::encode_app_unsigned(buf, address.network_number as u64);
77 primitives::encode_app_octet_string(buf, &address.mac_address);
78 tags::encode_closing_tag(buf, 1);
79 }
80 }
81}
82
83pub fn decode_recipient(data: &[u8], offset: usize) -> Result<(BACnetRecipient, usize), Error> {
85 let what = "BACnetRecipient";
86 let (tag, pos) = tags::decode_tag(data, offset)?;
87 if tag.is_context(0) {
88 if tag.length != 4 {
90 return Err(Error::decoding(
91 offset,
92 format!(
93 "{what}: device [0] expects 4 contents octets, got {}",
94 tag.length
95 ),
96 ));
97 }
98 let end = pos + 4;
99 if end > data.len() {
100 return Err(Error::buffer_too_short(end, data.len()));
101 }
102 return Ok((
103 BACnetRecipient::Device(ObjectIdentifier::decode(&data[pos..end])?),
104 end,
105 ));
106 }
107 if tag.is_opening_tag(1) {
108 let (network_number, pos) = decode_app_unsigned(data, pos, what)?;
110 let network_number = u16::try_from(network_number).map_err(|_| {
111 Error::decoding(pos, format!("{what}: network-number exceeds Unsigned16"))
112 })?;
113 let (mac_address, pos) = decode_app_octet_string(data, pos, what)?;
115 let (close, close_pos) = tags::decode_tag(data, pos)?;
116 if !close.is_closing_tag(1) {
117 return Err(Error::decoding(
118 pos,
119 format!("{what}: missing closing tag [1] for BACnetAddress"),
120 ));
121 }
122 return Ok((
123 BACnetRecipient::Address(BACnetAddress {
124 network_number,
125 mac_address,
126 }),
127 close_pos,
128 ));
129 }
130 Err(Error::decoding(
131 offset,
132 format!(
133 "{what}: expected [0] (device) or [1] (address), got {}",
134 if tag.is_opening {
135 format!("opening tag [{}]", tag.number)
136 } else if tag.is_closing {
137 format!("closing tag [{}]", tag.number)
138 } else {
139 match tag.class {
140 TagClass::Context => format!("primitive context tag [{}]", tag.number),
141 TagClass::Application => format!("application tag {}", tag.number),
142 }
143 }
144 ),
145 ))
146}
147
148pub fn decode_destination(data: &[u8], offset: usize) -> Result<(BACnetDestination, usize), Error> {
151 let what = "BACnetDestination";
152 let ((days_unused, days_data), pos) = decode_app_bit_string(data, offset, what)?;
154 check_fixed_bit_string(
155 days_unused,
156 &days_data,
157 1,
158 "valid-days (BACnetDaysOfWeek: 7 bits)",
159 )?;
160 let valid_days = unpack_octet(&days_data, 7);
161 let (from_time, pos) = decode_app_time(data, pos, what)?;
163 let (to_time, pos) = decode_app_time(data, pos, what)?;
164 let (recipient, pos) = decode_recipient(data, pos)?;
166 let (process_identifier, pos) = decode_app_unsigned(data, pos, what)?;
168 let process_identifier = u32::try_from(process_identifier).map_err(|_| {
169 Error::decoding(
170 pos,
171 format!("{what}: process-identifier exceeds Unsigned32"),
172 )
173 })?;
174 let (tag, pos) = tags::decode_tag(data, pos)?;
177 if tag.class != TagClass::Application || tag.number != tags::app_tag::BOOLEAN {
178 return Err(Error::decoding(
179 pos,
180 format!("{what}: expected application-tagged BOOLEAN"),
181 ));
182 }
183 let issue_confirmed_notifications = tag.length != 0;
184 let ((transitions_unused, transitions_data), pos) = decode_app_bit_string(data, pos, what)?;
186 check_fixed_bit_string(
187 transitions_unused,
188 &transitions_data,
189 5,
190 "transitions (BACnetEventTransitionBits: 3 bits)",
191 )?;
192 let transitions = unpack_octet(&transitions_data, 3);
193
194 Ok((
195 BACnetDestination {
196 valid_days,
197 from_time,
198 to_time,
199 recipient,
200 process_identifier,
201 issue_confirmed_notifications,
202 transitions,
203 },
204 pos,
205 ))
206}
207
208pub fn decode_destination_list(data: &[u8]) -> Result<Vec<BACnetDestination>, Error> {
211 let mut destinations = Vec::new();
212 let mut pos = 0;
213 while pos < data.len() {
214 if destinations.len() >= MAX_FRAMED_ITEMS {
215 return Err(Error::decoding(
216 pos,
217 "Recipient_List: destination count exceeds limit",
218 ));
219 }
220 let (dest, new_pos) = decode_destination(data, pos)?;
221 destinations.push(dest);
222 pos = new_pos;
223 }
224 Ok(destinations)
225}
226
227fn decode_app_unsigned(data: &[u8], offset: usize, what: &str) -> Result<(u64, usize), Error> {
233 let (tag, pos) = tags::decode_tag(data, offset)?;
234 if tag.class != TagClass::Application || tag.number != tags::app_tag::UNSIGNED {
235 return Err(Error::decoding(
236 offset,
237 format!("{what}: expected application-tagged Unsigned"),
238 ));
239 }
240 let end = pos
241 .checked_add(tag.length as usize)
242 .ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
243 if end > data.len() {
244 return Err(Error::buffer_too_short(end, data.len()));
245 }
246 Ok((primitives::decode_unsigned(&data[pos..end])?, end))
247}
248
249fn decode_app_octet_string(
251 data: &[u8],
252 offset: usize,
253 what: &str,
254) -> Result<(MacAddr, usize), Error> {
255 let (tag, pos) = tags::decode_tag(data, offset)?;
256 if tag.class != TagClass::Application || tag.number != tags::app_tag::OCTET_STRING {
257 return Err(Error::decoding(
258 offset,
259 format!("{what}: expected application-tagged OCTET STRING"),
260 ));
261 }
262 let end = pos
263 .checked_add(tag.length as usize)
264 .ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
265 if end > data.len() {
266 return Err(Error::buffer_too_short(end, data.len()));
267 }
268 Ok((MacAddr::from_slice(&data[pos..end]), end))
269}
270
271fn decode_app_time(data: &[u8], offset: usize, what: &str) -> Result<(Time, usize), Error> {
273 let (tag, pos) = tags::decode_tag(data, offset)?;
274 if tag.class != TagClass::Application || tag.number != tags::app_tag::TIME || tag.length != 4 {
275 return Err(Error::decoding(
276 offset,
277 format!("{what}: expected application-tagged Time (4 octets)"),
278 ));
279 }
280 let end = pos + 4;
281 if end > data.len() {
282 return Err(Error::buffer_too_short(end, data.len()));
283 }
284 Ok((Time::decode(&data[pos..end])?, end))
285}
286
287fn check_fixed_bit_string(
296 unused_bits: u8,
297 data: &[u8],
298 expected_unused: u8,
299 name: &str,
300) -> Result<(), Error> {
301 if data.len() != 1 || unused_bits != expected_unused {
302 return Err(Error::decoding(
303 0,
304 format!(
305 "{name}: expected 1 content octet with unused_bits {expected_unused} \
306 (got {} content octet(s), unused_bits {unused_bits})",
307 data.len()
308 ),
309 ));
310 }
311 Ok(())
312}
313
314fn decode_app_bit_string(
317 data: &[u8],
318 offset: usize,
319 what: &str,
320) -> Result<((u8, Vec<u8>), usize), Error> {
321 let (tag, pos) = tags::decode_tag(data, offset)?;
322 if tag.class != TagClass::Application || tag.number != tags::app_tag::BIT_STRING {
323 return Err(Error::decoding(
324 offset,
325 format!("{what}: expected application-tagged BIT STRING"),
326 ));
327 }
328 let end = pos
329 .checked_add(tag.length as usize)
330 .ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
331 if end > data.len() {
332 return Err(Error::buffer_too_short(end, data.len()));
333 }
334 let (unused_bits, bits) = primitives::decode_bit_string(&data[pos..end])?;
335 Ok(((unused_bits, bits), end))
336}