1use bacnet_types::enums::{NetworkPriority, RejectMessageReason};
8use bacnet_types::error::Error;
9use bacnet_types::MacAddr;
10use bytes::{BufMut, Bytes, BytesMut};
11
12pub const BACNET_PROTOCOL_VERSION: u8 = 1;
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct NpduAddress {
24 pub network: u16,
26 pub mac_address: MacAddr,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct Npdu {
37 pub is_network_message: bool,
39 pub expecting_reply: bool,
41 pub priority: NetworkPriority,
43 pub destination: Option<NpduAddress>,
45 pub source: Option<NpduAddress>,
47 pub hop_count: u8,
49 pub message_type: Option<u8>,
51 pub vendor_id: Option<u16>,
53 pub payload: Bytes,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct RejectMessageToNetwork {
63 pub reason: RejectMessageReason,
65 pub dnet: u16,
67}
68
69impl Default for Npdu {
70 fn default() -> Self {
71 Self {
72 is_network_message: false,
73 expecting_reply: false,
74 priority: NetworkPriority::NORMAL,
75 destination: None,
76 source: None,
77 hop_count: 255,
78 message_type: None,
79 vendor_id: None,
80 payload: Bytes::new(),
81 }
82 }
83}
84
85pub fn encode_npdu(buf: &mut BytesMut, npdu: &Npdu) -> Result<(), Error> {
91 buf.put_u8(BACNET_PROTOCOL_VERSION);
92
93 let mut control: u8 = npdu.priority.to_raw() & 0x03;
94 if npdu.is_network_message {
95 control |= 0x80;
96 }
97 if npdu.destination.is_some() {
98 control |= 0x20;
99 }
100 if npdu.source.is_some() {
101 control |= 0x08;
102 }
103 if npdu.expecting_reply {
104 control |= 0x04;
105 }
106 buf.put_u8(control);
107
108 if let Some(dest) = &npdu.destination {
109 if dest.network == 0 {
110 return Err(Error::Encoding("NPDU DNET must not be 0".into()));
111 }
112 buf.put_u16(dest.network);
113 if dest.mac_address.len() > 255 {
114 return Err(Error::Encoding(
115 "NPDU destination MAC address exceeds 255 bytes".into(),
116 ));
117 }
118 buf.put_u8(dest.mac_address.len() as u8);
119 buf.put_slice(&dest.mac_address);
120 }
121
122 if let Some(src) = &npdu.source {
123 if src.network == 0 || src.network == 0xFFFF {
124 return Err(Error::Encoding(format!(
125 "NPDU SNET must be 1..65534, got {}",
126 src.network
127 )));
128 }
129 if src.mac_address.is_empty() {
130 return Err(Error::Encoding("NPDU SLEN must not be 0".into()));
131 }
132 buf.put_u16(src.network);
133 if src.mac_address.len() > 255 {
134 return Err(Error::Encoding(
135 "NPDU source MAC address exceeds 255 bytes".into(),
136 ));
137 }
138 buf.put_u8(src.mac_address.len() as u8);
139 buf.put_slice(&src.mac_address);
140 }
141
142 if npdu.destination.is_some() {
143 buf.put_u8(npdu.hop_count);
144 }
145
146 if npdu.is_network_message {
147 if let Some(msg_type) = npdu.message_type {
148 buf.put_u8(msg_type);
149 if msg_type >= 0x80 {
150 buf.put_u16(npdu.vendor_id.unwrap_or(0));
151 }
152 }
153 }
154
155 buf.put_slice(&npdu.payload);
156
157 Ok(())
158}
159
160pub fn decode_npdu(data: Bytes) -> Result<Npdu, Error> {
169 if data.len() < 2 {
170 return Err(Error::buffer_too_short(2, data.len()));
171 }
172
173 let version = data[0];
174 if version != BACNET_PROTOCOL_VERSION {
175 return Err(Error::decoding(
176 0,
177 format!("unsupported BACnet protocol version: {version}"),
178 ));
179 }
180
181 let control = data[1];
182 let is_network_message = control & 0x80 != 0;
183 let has_destination = control & 0x20 != 0;
184 let has_source = control & 0x08 != 0;
185 let expecting_reply = control & 0x04 != 0;
186 let priority = NetworkPriority::from_raw(control & 0x03);
187
188 if control & 0x50 != 0 {
189 tracing::warn!(
190 control_byte = control,
191 "NPDU control byte has reserved bits set (bits 4 or 6)"
192 );
193 }
194
195 let mut offset = 2;
196 let mut destination = None;
197 let mut source = None;
198 let mut hop_count: u8 = 255;
199
200 if has_destination {
201 if offset + 3 > data.len() {
202 return Err(Error::decoding(
203 offset,
204 "NPDU too short for destination fields",
205 ));
206 }
207 let dnet = u16::from_be_bytes([data[offset], data[offset + 1]]);
208 offset += 2;
209 let dlen = data[offset] as usize;
210 offset += 1;
211
212 if dlen > 0 && offset + dlen > data.len() {
213 return Err(Error::decoding(
214 offset,
215 format!("NPDU destination address truncated: DLEN={dlen}"),
216 ));
217 }
218 let dadr = MacAddr::from_slice(&data[offset..offset + dlen]);
219 offset += dlen;
220
221 if dnet == 0 {
222 return Err(Error::decoding(
223 offset - dlen - 3, "NPDU destination network 0 is invalid",
225 ));
226 }
227
228 destination = Some(NpduAddress {
229 network: dnet,
230 mac_address: dadr,
231 });
232 }
233
234 if has_source {
235 if offset + 3 > data.len() {
236 return Err(Error::decoding(offset, "NPDU too short for source fields"));
237 }
238 let snet = u16::from_be_bytes([data[offset], data[offset + 1]]);
239 offset += 2;
240 let slen = data[offset] as usize;
241 offset += 1;
242
243 if slen == 0 {
244 return Err(Error::decoding(offset - 1, "NPDU source SLEN=0 is invalid"));
245 }
246
247 if slen > 0 && offset + slen > data.len() {
248 return Err(Error::decoding(
249 offset,
250 format!("NPDU source address truncated: SLEN={slen}"),
251 ));
252 }
253 let sadr = MacAddr::from_slice(&data[offset..offset + slen]);
254 offset += slen;
255
256 source = Some(NpduAddress {
257 network: snet,
258 mac_address: sadr,
259 });
260
261 if snet == 0 {
262 return Err(Error::decoding(
263 offset - slen - 3, "NPDU source network 0 is invalid",
265 ));
266 }
267 if snet == 0xFFFF {
268 return Err(Error::decoding(
269 offset - slen - 3,
270 "NPDU source network 0xFFFF is invalid",
271 ));
272 }
273 }
274
275 if has_destination {
276 if offset >= data.len() {
277 return Err(Error::decoding(offset, "NPDU too short for hop count"));
278 }
279 hop_count = data[offset];
280 offset += 1;
281 }
282
283 let mut message_type = None;
284 let mut vendor_id = None;
285
286 if is_network_message {
287 if offset >= data.len() {
288 return Err(Error::decoding(
289 offset,
290 "NPDU too short for network message type",
291 ));
292 }
293 let msg_type = data[offset];
294 offset += 1;
295 message_type = Some(msg_type);
296
297 if msg_type >= 0x80 {
298 if offset + 2 > data.len() {
299 return Err(Error::decoding(
300 offset,
301 "NPDU too short for proprietary vendor ID",
302 ));
303 }
304 vendor_id = Some(u16::from_be_bytes([data[offset], data[offset + 1]]));
305 offset += 2;
306 }
307 }
308
309 let payload = data.slice(offset..);
310
311 Ok(Npdu {
312 is_network_message,
313 expecting_reply,
314 priority,
315 destination,
316 source,
317 hop_count,
318 message_type,
319 vendor_id,
320 payload,
321 })
322}
323
324pub fn decode_reject_message_to_network(payload: &[u8]) -> Result<RejectMessageToNetwork, Error> {
330 if payload.len() != 3 {
331 return Err(Error::decoding(
332 0,
333 format!(
334 "Reject-Message-To-Network payload must be exactly 3 octets, got {}",
335 payload.len()
336 ),
337 ));
338 }
339 Ok(RejectMessageToNetwork {
340 reason: RejectMessageReason::from_raw(payload[0]),
341 dnet: u16::from_be_bytes([payload[1], payload[2]]),
342 })
343}
344
345#[cfg(test)]
350mod tests {
351 use super::*;
352
353 fn encode_to_vec(npdu: &Npdu) -> Vec<u8> {
354 let mut buf = BytesMut::with_capacity(64);
355 encode_npdu(&mut buf, npdu).unwrap();
356 buf.to_vec()
357 }
358
359 #[test]
360 fn minimal_local_apdu() {
361 let npdu = Npdu {
363 payload: Bytes::from_static(&[0x10, 0x08]), ..Default::default()
365 };
366 let encoded = encode_to_vec(&npdu);
367 assert_eq!(encoded, vec![0x01, 0x00, 0x10, 0x08]);
369
370 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
371 assert_eq!(decoded, npdu);
372 }
373
374 #[test]
375 fn expecting_reply_flag() {
376 let npdu = Npdu {
377 expecting_reply: true,
378 payload: Bytes::from_static(&[0xAA]),
379 ..Default::default()
380 };
381 let encoded = encode_to_vec(&npdu);
382 assert_eq!(encoded[1], 0x04); let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
384 assert!(decoded.expecting_reply);
385 }
386
387 #[test]
388 fn priority_encoding() {
389 for (prio, expected_bits) in [
390 (NetworkPriority::NORMAL, 0x00),
391 (NetworkPriority::URGENT, 0x01),
392 (NetworkPriority::CRITICAL_EQUIPMENT, 0x02),
393 (NetworkPriority::LIFE_SAFETY, 0x03),
394 ] {
395 let npdu = Npdu {
396 priority: prio,
397 payload: Bytes::new(),
398 ..Default::default()
399 };
400 let encoded = encode_to_vec(&npdu);
401 assert_eq!(encoded[1] & 0x03, expected_bits);
402 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
403 assert_eq!(decoded.priority, prio);
404 }
405 }
406
407 #[test]
408 fn destination_only_round_trip() {
409 let npdu = Npdu {
410 destination: Some(NpduAddress {
411 network: 1000,
412 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x01, 0x01, 0xBA, 0xC0]),
413 }),
414 hop_count: 254,
415 payload: Bytes::from_static(&[0x10, 0x08]),
416 ..Default::default()
417 };
418 let encoded = encode_to_vec(&npdu);
419 assert_eq!(encoded[1] & 0x20, 0x20);
421 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
422 assert_eq!(decoded, npdu);
423 }
424
425 #[test]
426 fn destination_broadcast() {
427 let npdu = Npdu {
429 destination: Some(NpduAddress {
430 network: 0xFFFF,
431 mac_address: MacAddr::new(),
432 }),
433 hop_count: 255,
434 payload: Bytes::from_static(&[0x10, 0x08]),
435 ..Default::default()
436 };
437 let encoded = encode_to_vec(&npdu);
438 assert_eq!(encoded.len(), 8);
440 assert_eq!(&encoded[2..4], &[0xFF, 0xFF]);
442 assert_eq!(encoded[4], 0);
444
445 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
446 assert_eq!(decoded, npdu);
447 }
448
449 #[test]
450 fn source_only_round_trip() {
451 let npdu = Npdu {
452 source: Some(NpduAddress {
453 network: 500,
454 mac_address: MacAddr::from_slice(&[0x01]),
455 }),
456 payload: Bytes::from_static(&[0x30, 0x01, 0x0C]),
457 ..Default::default()
458 };
459 let encoded = encode_to_vec(&npdu);
460 assert_eq!(encoded[1] & 0x08, 0x08);
462 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
464 assert_eq!(decoded, npdu);
465 }
466
467 #[test]
468 fn source_and_destination_round_trip() {
469 let npdu = Npdu {
470 expecting_reply: true,
471 destination: Some(NpduAddress {
472 network: 2000,
473 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x02, 0x01, 0xBA, 0xC0]),
474 }),
475 source: Some(NpduAddress {
476 network: 1000,
477 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x01, 0x01, 0xBA, 0xC0]),
478 }),
479 hop_count: 250,
480 payload: Bytes::from_static(&[0x00, 0x05, 0x01, 0x0C]),
481 ..Default::default()
482 };
483 let encoded = encode_to_vec(&npdu);
484 assert_eq!(encoded[1], 0x2C);
486 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
487 assert_eq!(decoded, npdu);
488 }
489
490 #[test]
491 fn network_message_round_trip() {
492 let npdu = Npdu {
493 is_network_message: true,
494 message_type: Some(0x01), payload: Bytes::from_static(&[0x03, 0xE8]), ..Default::default()
497 };
498 let encoded = encode_to_vec(&npdu);
499 assert_eq!(encoded[1] & 0x80, 0x80);
501 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
502 assert_eq!(decoded, npdu);
503 }
504
505 #[test]
506 fn reject_message_to_network_payload_decodes_typed_reason_and_dnet() {
507 let decoded = decode_reject_message_to_network(&[
508 RejectMessageReason::MESSAGE_TOO_LONG.to_raw(),
509 0x12,
510 0x34,
511 ])
512 .unwrap();
513 assert_eq!(decoded.reason, RejectMessageReason::MESSAGE_TOO_LONG);
514 assert_eq!(decoded.dnet, 0x1234);
515 }
516
517 #[test]
518 fn reject_message_to_network_payload_requires_exact_length() {
519 for payload in [&[][..], &[4, 0][..], &[4, 0, 1, 2][..]] {
520 assert!(decode_reject_message_to_network(payload).is_err());
521 }
522 }
523
524 #[test]
525 fn proprietary_network_message_round_trip() {
526 let npdu = Npdu {
527 is_network_message: true,
528 message_type: Some(0x80), vendor_id: Some(999),
530 payload: Bytes::from_static(&[0xDE, 0xAD]),
531 ..Default::default()
532 };
533 let encoded = encode_to_vec(&npdu);
534 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
535 assert_eq!(decoded, npdu);
536 }
537
538 #[test]
539 fn wire_format_who_is_broadcast() {
540 let wire = [0x01, 0x20, 0xFF, 0xFF, 0x00, 0xFF, 0x10, 0x08];
544 let decoded = decode_npdu(Bytes::copy_from_slice(&wire)).unwrap();
545 assert!(!decoded.is_network_message);
546 assert!(!decoded.expecting_reply);
547 assert_eq!(decoded.priority, NetworkPriority::NORMAL);
548 assert_eq!(
549 decoded.destination,
550 Some(NpduAddress {
551 network: 0xFFFF,
552 mac_address: MacAddr::new(),
553 })
554 );
555 assert!(decoded.source.is_none());
556 assert_eq!(decoded.hop_count, 255);
557 assert_eq!(decoded.payload, vec![0x10, 0x08]);
558
559 let reencoded = encode_to_vec(&decoded);
561 assert_eq!(reencoded, wire);
562 }
563
564 #[test]
565 fn decode_too_short() {
566 assert!(decode_npdu(Bytes::new()).is_err());
567 assert!(decode_npdu(Bytes::from_static(&[0x01])).is_err());
568 }
569
570 #[test]
571 fn decode_wrong_version() {
572 assert!(decode_npdu(Bytes::from_static(&[0x02, 0x00])).is_err());
573 }
574
575 #[test]
576 fn decode_truncated_destination() {
577 assert!(decode_npdu(Bytes::from_static(&[0x01, 0x20, 0xFF])).is_err());
579 }
580
581 #[test]
582 fn decode_truncated_source() {
583 assert!(decode_npdu(Bytes::from_static(&[0x01, 0x08, 0x00])).is_err());
585 }
586
587 #[test]
590 fn npdu_network_zero() {
591 let npdu = Npdu {
593 destination: Some(NpduAddress {
594 network: 0,
595 mac_address: MacAddr::from_slice(&[0x01]),
596 }),
597 hop_count: 255,
598 payload: Bytes::from_static(&[0x10, 0x08]),
599 ..Default::default()
600 };
601 let mut buf = BytesMut::new();
602 let result = encode_npdu(&mut buf, &npdu);
603 assert!(result.is_err());
604 let err = format!("{}", result.unwrap_err());
605 assert!(err.contains("DNET"), "got: {err}");
606 }
607
608 #[test]
609 fn npdu_network_fffe() {
610 let npdu = Npdu {
613 destination: Some(NpduAddress {
614 network: 0xFFFE,
615 mac_address: MacAddr::from_slice(&[0x01, 0x02]),
616 }),
617 hop_count: 200,
618 payload: Bytes::from_static(&[0xAA]),
619 ..Default::default()
620 };
621 let encoded = encode_to_vec(&npdu);
622 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
623 assert_eq!(decoded.destination.as_ref().unwrap().network, 0xFFFE);
624 assert_eq!(decoded.hop_count, 200);
625 }
626
627 #[test]
628 fn npdu_hop_count_zero() {
629 let npdu = Npdu {
631 destination: Some(NpduAddress {
632 network: 1000,
633 mac_address: MacAddr::new(),
634 }),
635 hop_count: 0,
636 payload: Bytes::from_static(&[0x10, 0x08]),
637 ..Default::default()
638 };
639 let encoded = encode_to_vec(&npdu);
640 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
641 assert_eq!(decoded.hop_count, 0);
642 }
643
644 #[test]
645 fn npdu_source_with_empty_mac() {
646 let npdu = Npdu {
648 source: Some(NpduAddress {
649 network: 500,
650 mac_address: MacAddr::new(),
651 }),
652 payload: Bytes::from_static(&[0xBB]),
653 ..Default::default()
654 };
655 let mut buf = BytesMut::new();
656 let result = encode_npdu(&mut buf, &npdu);
657 assert!(result.is_err());
658 let err = format!("{}", result.unwrap_err());
659 assert!(err.contains("SLEN"), "got: {err}");
660 }
661
662 #[test]
663 fn npdu_destination_dlen_zero_broadcast_accepted() {
664 let npdu = Npdu {
666 destination: Some(NpduAddress {
667 network: 0xFFFF,
668 mac_address: MacAddr::new(),
669 }),
670 hop_count: 255,
671 payload: Bytes::from_static(&[0x10, 0x08]),
672 ..Default::default()
673 };
674 let encoded = encode_to_vec(&npdu);
675 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
676 assert_eq!(decoded.destination.as_ref().unwrap().network, 0xFFFF);
677 assert!(decoded.destination.as_ref().unwrap().mac_address.is_empty());
678 }
679
680 #[test]
681 fn npdu_destination_truncated_mac() {
682 let data = [0x01, 0x20, 0x03, 0xE8, 0x06, 0x01, 0x02];
685 assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
686 }
687
688 #[test]
689 fn npdu_source_truncated_mac() {
690 let data = [0x01, 0x08, 0x01, 0xF4, 0x04, 0x01]; assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
693 }
694
695 #[test]
696 fn npdu_missing_hop_count() {
697 let data = [0x01, 0x20, 0xFF, 0xFF, 0x00];
700 assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
701 }
702
703 #[test]
704 fn npdu_network_message_truncated_type() {
705 let data = [0x01, 0x80]; assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
708 }
709
710 #[test]
711 fn npdu_proprietary_message_truncated_vendor() {
712 let data = [0x01, 0x80, 0x80]; assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
715 }
716
717 #[test]
718 fn npdu_all_flags_round_trip() {
719 let npdu = Npdu {
721 is_network_message: false,
722 expecting_reply: true,
723 priority: NetworkPriority::LIFE_SAFETY,
724 destination: Some(NpduAddress {
725 network: 2000,
726 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x02, 0x01, 0xBA, 0xC0]),
727 }),
728 source: Some(NpduAddress {
729 network: 1000,
730 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x01, 0x01, 0xBA, 0xC0]),
731 }),
732 hop_count: 127,
733 payload: Bytes::from_static(&[0xDE, 0xAD, 0xBE, 0xEF]),
734 ..Default::default()
735 };
736 let encoded = encode_to_vec(&npdu);
737 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
738 assert_eq!(decoded, npdu);
739 }
740
741 #[test]
742 fn npdu_empty_payload() {
743 let npdu = Npdu {
745 payload: Bytes::new(),
746 ..Default::default()
747 };
748 let encoded = encode_to_vec(&npdu);
749 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
750 assert!(decoded.payload.is_empty());
751 }
752
753 #[test]
754 fn reject_snet_zero() {
755 let npdu = Npdu {
757 source: Some(NpduAddress {
758 network: 0,
759 mac_address: MacAddr::from_slice(&[0x01]),
760 }),
761 payload: Bytes::from_static(&[0x10, 0x08]),
762 ..Default::default()
763 };
764 let mut buf = BytesMut::new();
765 let result = encode_npdu(&mut buf, &npdu);
766 assert!(result.is_err());
767 let err = format!("{}", result.unwrap_err());
768 assert!(err.contains("SNET"), "got: {err}");
769 }
770
771 #[test]
772 fn reserved_bits_warning_still_decodes() {
773 let mut data = vec![0x01, 0x40]; data.extend_from_slice(&[0x10, 0x08]);
777
778 let result = decode_npdu(Bytes::copy_from_slice(&data));
780 assert!(
781 result.is_ok(),
782 "reserved bits should not cause decode failure"
783 );
784 }
785}