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