1use bacnet_types::bitstring::{DaysOfWeek, EventTransitionBits};
37use bacnet_types::constructed::{BACnetAddress, BACnetDestination, BACnetRecipient};
38use bacnet_types::error::Error;
39use bacnet_types::primitives::Time;
40use bacnet_types::MacAddr;
41use bytes::BytesMut;
42
43use crate::primitives;
44use crate::tags::{self, TagClass};
45
46use super::tagged::{
47 contents, decode_app_bit_string, decode_app_unsigned, decode_ctx_object_id, expect_closing,
48 misplaced_tag,
49};
50use super::MAX_FRAMED_ITEMS;
51
52fn mac_len_excess(what: &str, length: usize) -> Option<String> {
55 (length > BACnetAddress::MAX_MAC_LEN).then(|| {
56 format!(
57 "{what}: mac-address of {length} octets exceeds the {}-octet limit",
58 BACnetAddress::MAX_MAC_LEN
59 )
60 })
61}
62
63pub fn check_decoded_mac_len(length: usize, offset: usize, what: &str) -> Result<(), Error> {
69 mac_len_excess(what, length).map_or(Ok(()), |message| Err(Error::overflow(offset, message)))
70}
71
72pub fn check_encoded_mac_len(mac: &[u8], what: &str) -> Result<(), Error> {
76 mac_len_excess(what, mac.len()).map_or(Ok(()), |message| Err(Error::Encoding(message)))
77}
78
79pub(super) fn check_encoded_recipient(recipient: &BACnetRecipient) -> Result<(), Error> {
82 match recipient {
83 BACnetRecipient::Device(_) => Ok(()),
84 BACnetRecipient::Address(address) => {
85 check_encoded_mac_len(&address.mac_address, "BACnetRecipient")
86 }
87 }
88}
89
90pub fn encode_destination(buf: &mut BytesMut, dest: &BACnetDestination) -> Result<(), Error> {
99 check_encoded_recipient(&dest.recipient)?;
100 write_destination(buf, dest);
101 Ok(())
102}
103
104fn write_destination(buf: &mut BytesMut, dest: &BACnetDestination) {
106 primitives::encode_app_bit_string(buf, 1, &[dest.valid_days.to_bacnet()]);
107 primitives::encode_app_time(buf, &dest.from_time);
108 primitives::encode_app_time(buf, &dest.to_time);
109 write_recipient(buf, &dest.recipient);
110 primitives::encode_app_unsigned(buf, dest.process_identifier as u64);
111 primitives::encode_app_boolean(buf, dest.issue_confirmed_notifications);
112 primitives::encode_app_bit_string(buf, 5, &[dest.transitions.to_bacnet()]);
113}
114
115pub fn encode_destination_list(
119 buf: &mut BytesMut,
120 destinations: &[BACnetDestination],
121) -> Result<(), Error> {
122 for dest in destinations {
123 check_encoded_recipient(&dest.recipient)?;
124 }
125 for dest in destinations {
126 write_destination(buf, dest);
127 }
128 Ok(())
129}
130
131pub fn encode_recipient(buf: &mut BytesMut, recipient: &BACnetRecipient) -> Result<(), Error> {
135 check_encoded_recipient(recipient)?;
136 write_recipient(buf, recipient);
137 Ok(())
138}
139
140pub(super) fn write_recipient(buf: &mut BytesMut, recipient: &BACnetRecipient) {
142 match recipient {
143 BACnetRecipient::Device(oid) => {
144 tags::encode_tag(buf, 0, TagClass::Context, 4);
146 buf.extend_from_slice(&oid.encode());
147 }
148 BACnetRecipient::Address(address) => {
149 tags::encode_opening_tag(buf, 1);
151 primitives::encode_app_unsigned(buf, address.network_number as u64);
152 primitives::encode_app_octet_string(buf, &address.mac_address);
153 tags::encode_closing_tag(buf, 1);
154 }
155 }
156}
157
158pub fn decode_recipient(data: &[u8], offset: usize) -> Result<(BACnetRecipient, usize), Error> {
167 let what = "BACnetRecipient";
168 let (tag, pos) = tags::decode_tag(data, offset)?;
169 if tag.is_context(0) {
170 let (device, end) = decode_ctx_object_id(data, offset, 0, "BACnetRecipient device")?;
172 return Ok((BACnetRecipient::Device(device), end));
173 }
174 if tag.is_opening_tag(1) {
175 let (network_number, pos) = decode_app_unsigned::<u64>(data, pos, what)?;
177 let network_number = u16::try_from(network_number).map_err(|_| {
178 Error::out_of_range(pos, format!("{what}: network-number exceeds Unsigned16"))
179 })?;
180 let (mac_address, pos) = decode_app_mac_address(data, pos, what)?;
182 let end = expect_closing(data, pos, 1, "BACnetRecipient address")?;
183 return Ok((
184 BACnetRecipient::Address(BACnetAddress {
185 network_number,
186 mac_address,
187 }),
188 end,
189 ));
190 }
191 Err(misplaced_tag(
192 data,
193 &tag,
194 None,
195 offset,
196 format!(
197 "{what}: expected [0] (device) or [1] (address), got {}",
198 if tag.is_opening {
199 format!("opening tag [{}]", tag.number)
200 } else if tag.is_closing {
201 format!("closing tag [{}]", tag.number)
202 } else {
203 match tag.class {
204 TagClass::Context => format!("primitive context tag [{}]", tag.number),
205 TagClass::Application => format!("application tag {}", tag.number),
206 }
207 }
208 ),
209 ))
210}
211
212pub fn decode_destination(data: &[u8], offset: usize) -> Result<(BACnetDestination, usize), Error> {
217 let what = "BACnetDestination";
218 let ((days_unused, days_data), pos) = decode_app_bit_string(data, offset, what)?;
220 check_fixed_bit_string(
221 days_unused,
222 &days_data,
223 1,
224 "valid-days (BACnetDaysOfWeek: 7 bits)",
225 )?;
226 let valid_days = DaysOfWeek::from_bacnet(&days_data);
227 let (from_time, pos) = decode_app_time(data, pos, what)?;
229 let (to_time, pos) = decode_app_time(data, pos, what)?;
230 let (recipient, pos) = decode_recipient(data, pos)?;
232 let (process_identifier, pos) = decode_app_unsigned::<u64>(data, pos, what)?;
234 let process_identifier = u32::try_from(process_identifier).map_err(|_| {
235 Error::out_of_range(
236 pos,
237 format!("{what}: process-identifier exceeds Unsigned32"),
238 )
239 })?;
240 let (tag, pos) = tags::decode_tag(data, pos)?;
243 if tag.class != TagClass::Application || tag.number != tags::app_tag::BOOLEAN {
244 return Err(misplaced_tag(
245 data,
246 &tag,
247 None,
248 pos,
249 format!("{what}: expected application-tagged BOOLEAN"),
250 ));
251 }
252 let issue_confirmed_notifications = tag.length != 0;
253 let ((transitions_unused, transitions_data), pos) = decode_app_bit_string(data, pos, what)?;
255 check_fixed_bit_string(
256 transitions_unused,
257 &transitions_data,
258 5,
259 "transitions (BACnetEventTransitionBits: 3 bits)",
260 )?;
261 let transitions = EventTransitionBits::from_bacnet(&transitions_data);
262
263 Ok((
264 BACnetDestination {
265 valid_days,
266 from_time,
267 to_time,
268 recipient,
269 process_identifier,
270 issue_confirmed_notifications,
271 transitions,
272 },
273 pos,
274 ))
275}
276
277pub fn decode_destination_list(data: &[u8]) -> Result<Vec<BACnetDestination>, Error> {
280 let mut destinations = Vec::new();
281 let mut pos = 0;
282 while pos < data.len() {
283 if destinations.len() >= MAX_FRAMED_ITEMS {
284 return Err(Error::overflow(
285 pos,
286 "Recipient_List: destination count exceeds limit",
287 ));
288 }
289 let (dest, new_pos) = decode_destination(data, pos)?;
290 destinations.push(dest);
291 pos = new_pos;
292 }
293 Ok(destinations)
294}
295
296pub(super) fn decode_app_mac_address(
304 data: &[u8],
305 offset: usize,
306 what: &str,
307) -> Result<(MacAddr, usize), Error> {
308 let (tag, pos) = tags::decode_tag(data, offset)?;
309 if tag.class != TagClass::Application || tag.number != tags::app_tag::OCTET_STRING {
310 return Err(misplaced_tag(
311 data,
312 &tag,
313 None,
314 offset,
315 format!("{what}: expected application-tagged OCTET STRING"),
316 ));
317 }
318 check_decoded_mac_len(tag.length as usize, offset, what)?;
319 let (mac, end) = contents(data, pos, tag.length)?;
320 Ok((MacAddr::from_slice(mac), end))
321}
322
323fn decode_app_time(data: &[u8], offset: usize, what: &str) -> Result<(Time, usize), Error> {
325 let (tag, pos) = tags::decode_tag(data, offset)?;
326 if tag.class != TagClass::Application || tag.number != tags::app_tag::TIME {
327 return Err(misplaced_tag(
328 data,
329 &tag,
330 None,
331 offset,
332 format!("{what}: expected application-tagged Time"),
333 ));
334 }
335 if tag.length != 4 {
336 return Err(Error::decoding(
337 offset,
338 format!(
339 "{what}: Time has {} contents octets, expected 4",
340 tag.length
341 ),
342 ));
343 }
344 let (octets, end) = contents(data, pos, 4)?;
345 Ok((Time::decode(octets)?, end))
346}
347
348fn check_fixed_bit_string(
357 unused_bits: u8,
358 data: &[u8],
359 expected_unused: u8,
360 name: &str,
361) -> Result<(), Error> {
362 if data.len() != 1 || unused_bits != expected_unused {
363 return Err(Error::decoding(
364 0,
365 format!(
366 "{name}: expected 1 content octet with unused_bits {expected_unused} \
367 (got {} content octet(s), unused_bits {unused_bits})",
368 data.len()
369 ),
370 ));
371 }
372 Ok(())
373}