1use bacnet_types::enums::NetworkPriority;
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
57impl Default for Npdu {
58 fn default() -> Self {
59 Self {
60 is_network_message: false,
61 expecting_reply: false,
62 priority: NetworkPriority::NORMAL,
63 destination: None,
64 source: None,
65 hop_count: 255,
66 message_type: None,
67 vendor_id: None,
68 payload: Bytes::new(),
69 }
70 }
71}
72
73pub fn encode_npdu(buf: &mut BytesMut, npdu: &Npdu) -> Result<(), Error> {
79 buf.put_u8(BACNET_PROTOCOL_VERSION);
81
82 let mut control: u8 = npdu.priority.to_raw() & 0x03;
84 if npdu.is_network_message {
85 control |= 0x80;
86 }
87 if npdu.destination.is_some() {
88 control |= 0x20;
89 }
90 if npdu.source.is_some() {
91 control |= 0x08;
92 }
93 if npdu.expecting_reply {
94 control |= 0x04;
95 }
96 buf.put_u8(control);
97
98 if let Some(dest) = &npdu.destination {
100 buf.put_u16(dest.network);
101 if dest.mac_address.len() > 255 {
102 return Err(Error::Encoding(
103 "NPDU destination MAC address exceeds 255 bytes".into(),
104 ));
105 }
106 buf.put_u8(dest.mac_address.len() as u8);
107 buf.put_slice(&dest.mac_address);
108 }
109
110 if let Some(src) = &npdu.source {
112 buf.put_u16(src.network);
113 if src.mac_address.len() > 255 {
114 return Err(Error::Encoding(
115 "NPDU source MAC address exceeds 255 bytes".into(),
116 ));
117 }
118 buf.put_u8(src.mac_address.len() as u8);
119 buf.put_slice(&src.mac_address);
120 }
121
122 if npdu.destination.is_some() {
124 buf.put_u8(npdu.hop_count);
125 }
126
127 if npdu.is_network_message {
129 if let Some(msg_type) = npdu.message_type {
130 buf.put_u8(msg_type);
131 if msg_type >= 0x80 {
133 buf.put_u16(npdu.vendor_id.unwrap_or(0));
134 }
135 }
136 }
137
138 buf.put_slice(&npdu.payload);
139
140 Ok(())
141}
142
143pub fn decode_npdu(data: Bytes) -> Result<Npdu, Error> {
152 if data.len() < 2 {
153 return Err(Error::buffer_too_short(2, data.len()));
154 }
155
156 let version = data[0];
157 if version != BACNET_PROTOCOL_VERSION {
158 return Err(Error::decoding(
159 0,
160 format!("unsupported BACnet protocol version: {version}"),
161 ));
162 }
163
164 let control = data[1];
165 let is_network_message = control & 0x80 != 0;
166 let has_destination = control & 0x20 != 0;
167 let has_source = control & 0x08 != 0;
168 let expecting_reply = control & 0x04 != 0;
169 let priority = NetworkPriority::from_raw(control & 0x03);
170
171 if control & 0x50 != 0 {
172 tracing::warn!(
174 control_byte = control,
175 "NPDU control byte has reserved bits set (bits 4 or 6)"
176 );
177 }
178
179 let mut offset = 2;
180 let mut destination = None;
181 let mut source = None;
182 let mut hop_count: u8 = 255;
183
184 if has_destination {
186 if offset + 3 > data.len() {
187 return Err(Error::decoding(
188 offset,
189 "NPDU too short for destination fields",
190 ));
191 }
192 let dnet = u16::from_be_bytes([data[offset], data[offset + 1]]);
193 offset += 2;
194 let dlen = data[offset] as usize;
195 offset += 1;
196
197 if dlen > 0 && offset + dlen > data.len() {
198 return Err(Error::decoding(
199 offset,
200 format!("NPDU destination address truncated: DLEN={dlen}"),
201 ));
202 }
203 let dadr = MacAddr::from_slice(&data[offset..offset + dlen]);
204 offset += dlen;
205
206 if dnet == 0 {
207 return Err(Error::decoding(
208 offset - dlen - 3, "NPDU destination network 0 is invalid",
210 ));
211 }
212
213 destination = Some(NpduAddress {
214 network: dnet,
215 mac_address: dadr,
216 });
217 }
218
219 if has_source {
221 if offset + 3 > data.len() {
222 return Err(Error::decoding(offset, "NPDU too short for source fields"));
223 }
224 let snet = u16::from_be_bytes([data[offset], data[offset + 1]]);
225 offset += 2;
226 let slen = data[offset] as usize;
227 offset += 1;
228
229 if slen == 0 {
232 return Err(Error::decoding(
233 offset - 1,
234 "NPDU source SLEN=0 is invalid (Clause 6.2.2)",
235 ));
236 }
237
238 if slen > 0 && offset + slen > data.len() {
239 return Err(Error::decoding(
240 offset,
241 format!("NPDU source address truncated: SLEN={slen}"),
242 ));
243 }
244 let sadr = MacAddr::from_slice(&data[offset..offset + slen]);
245 offset += slen;
246
247 source = Some(NpduAddress {
248 network: snet,
249 mac_address: sadr,
250 });
251
252 if snet == 0 {
253 return Err(Error::decoding(
254 offset - slen - 3, "NPDU source network 0 is invalid",
256 ));
257 }
258 }
259
260 if has_destination {
262 if offset >= data.len() {
263 return Err(Error::decoding(offset, "NPDU too short for hop count"));
264 }
265 hop_count = data[offset];
266 offset += 1;
267 }
268
269 let mut message_type = None;
271 let mut vendor_id = None;
272
273 if is_network_message {
274 if offset >= data.len() {
275 return Err(Error::decoding(
276 offset,
277 "NPDU too short for network message type",
278 ));
279 }
280 let msg_type = data[offset];
281 offset += 1;
282 message_type = Some(msg_type);
283
284 if msg_type >= 0x80 {
286 if offset + 2 > data.len() {
287 return Err(Error::decoding(
288 offset,
289 "NPDU too short for proprietary vendor ID",
290 ));
291 }
292 vendor_id = Some(u16::from_be_bytes([data[offset], data[offset + 1]]));
293 offset += 2;
294 }
295 }
296
297 let payload = data.slice(offset..);
298
299 Ok(Npdu {
300 is_network_message,
301 expecting_reply,
302 priority,
303 destination,
304 source,
305 hop_count,
306 message_type,
307 vendor_id,
308 payload,
309 })
310}
311
312#[cfg(test)]
317mod tests {
318 use super::*;
319
320 fn encode_to_vec(npdu: &Npdu) -> Vec<u8> {
321 let mut buf = BytesMut::with_capacity(64);
322 encode_npdu(&mut buf, npdu).unwrap();
323 buf.to_vec()
324 }
325
326 #[test]
327 fn minimal_local_apdu() {
328 let npdu = Npdu {
330 payload: Bytes::from_static(&[0x10, 0x08]), ..Default::default()
332 };
333 let encoded = encode_to_vec(&npdu);
334 assert_eq!(encoded, vec![0x01, 0x00, 0x10, 0x08]);
336
337 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
338 assert_eq!(decoded, npdu);
339 }
340
341 #[test]
342 fn expecting_reply_flag() {
343 let npdu = Npdu {
344 expecting_reply: true,
345 payload: Bytes::from_static(&[0xAA]),
346 ..Default::default()
347 };
348 let encoded = encode_to_vec(&npdu);
349 assert_eq!(encoded[1], 0x04); let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
351 assert!(decoded.expecting_reply);
352 }
353
354 #[test]
355 fn priority_encoding() {
356 for (prio, expected_bits) in [
357 (NetworkPriority::NORMAL, 0x00),
358 (NetworkPriority::URGENT, 0x01),
359 (NetworkPriority::CRITICAL_EQUIPMENT, 0x02),
360 (NetworkPriority::LIFE_SAFETY, 0x03),
361 ] {
362 let npdu = Npdu {
363 priority: prio,
364 payload: Bytes::new(),
365 ..Default::default()
366 };
367 let encoded = encode_to_vec(&npdu);
368 assert_eq!(encoded[1] & 0x03, expected_bits);
369 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
370 assert_eq!(decoded.priority, prio);
371 }
372 }
373
374 #[test]
375 fn destination_only_round_trip() {
376 let npdu = Npdu {
377 destination: Some(NpduAddress {
378 network: 1000,
379 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x01, 0x01, 0xBA, 0xC0]),
380 }),
381 hop_count: 254,
382 payload: Bytes::from_static(&[0x10, 0x08]),
383 ..Default::default()
384 };
385 let encoded = encode_to_vec(&npdu);
386 assert_eq!(encoded[1] & 0x20, 0x20);
388 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
389 assert_eq!(decoded, npdu);
390 }
391
392 #[test]
393 fn destination_broadcast() {
394 let npdu = Npdu {
396 destination: Some(NpduAddress {
397 network: 0xFFFF,
398 mac_address: MacAddr::new(),
399 }),
400 hop_count: 255,
401 payload: Bytes::from_static(&[0x10, 0x08]),
402 ..Default::default()
403 };
404 let encoded = encode_to_vec(&npdu);
405 assert_eq!(encoded.len(), 8);
407 assert_eq!(&encoded[2..4], &[0xFF, 0xFF]);
409 assert_eq!(encoded[4], 0);
411
412 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
413 assert_eq!(decoded, npdu);
414 }
415
416 #[test]
417 fn source_only_round_trip() {
418 let npdu = Npdu {
419 source: Some(NpduAddress {
420 network: 500,
421 mac_address: MacAddr::from_slice(&[0x01]),
422 }),
423 payload: Bytes::from_static(&[0x30, 0x01, 0x0C]),
424 ..Default::default()
425 };
426 let encoded = encode_to_vec(&npdu);
427 assert_eq!(encoded[1] & 0x08, 0x08);
429 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
431 assert_eq!(decoded, npdu);
432 }
433
434 #[test]
435 fn source_and_destination_round_trip() {
436 let npdu = Npdu {
437 expecting_reply: true,
438 destination: Some(NpduAddress {
439 network: 2000,
440 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x02, 0x01, 0xBA, 0xC0]),
441 }),
442 source: Some(NpduAddress {
443 network: 1000,
444 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x01, 0x01, 0xBA, 0xC0]),
445 }),
446 hop_count: 250,
447 payload: Bytes::from_static(&[0x00, 0x05, 0x01, 0x0C]),
448 ..Default::default()
449 };
450 let encoded = encode_to_vec(&npdu);
451 assert_eq!(encoded[1], 0x2C);
453 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
454 assert_eq!(decoded, npdu);
455 }
456
457 #[test]
458 fn network_message_round_trip() {
459 let npdu = Npdu {
460 is_network_message: true,
461 message_type: Some(0x01), payload: Bytes::from_static(&[0x03, 0xE8]), ..Default::default()
464 };
465 let encoded = encode_to_vec(&npdu);
466 assert_eq!(encoded[1] & 0x80, 0x80);
468 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
469 assert_eq!(decoded, npdu);
470 }
471
472 #[test]
473 fn proprietary_network_message_round_trip() {
474 let npdu = Npdu {
475 is_network_message: true,
476 message_type: Some(0x80), vendor_id: Some(999),
478 payload: Bytes::from_static(&[0xDE, 0xAD]),
479 ..Default::default()
480 };
481 let encoded = encode_to_vec(&npdu);
482 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
483 assert_eq!(decoded, npdu);
484 }
485
486 #[test]
487 fn wire_format_who_is_broadcast() {
488 let wire = [0x01, 0x20, 0xFF, 0xFF, 0x00, 0xFF, 0x10, 0x08];
492 let decoded = decode_npdu(Bytes::copy_from_slice(&wire)).unwrap();
493 assert!(!decoded.is_network_message);
494 assert!(!decoded.expecting_reply);
495 assert_eq!(decoded.priority, NetworkPriority::NORMAL);
496 assert_eq!(
497 decoded.destination,
498 Some(NpduAddress {
499 network: 0xFFFF,
500 mac_address: MacAddr::new(),
501 })
502 );
503 assert!(decoded.source.is_none());
504 assert_eq!(decoded.hop_count, 255);
505 assert_eq!(decoded.payload, vec![0x10, 0x08]);
506
507 let reencoded = encode_to_vec(&decoded);
509 assert_eq!(reencoded, wire);
510 }
511
512 #[test]
513 fn decode_too_short() {
514 assert!(decode_npdu(Bytes::new()).is_err());
515 assert!(decode_npdu(Bytes::from_static(&[0x01])).is_err());
516 }
517
518 #[test]
519 fn decode_wrong_version() {
520 assert!(decode_npdu(Bytes::from_static(&[0x02, 0x00])).is_err());
521 }
522
523 #[test]
524 fn decode_truncated_destination() {
525 assert!(decode_npdu(Bytes::from_static(&[0x01, 0x20, 0xFF])).is_err());
527 }
528
529 #[test]
530 fn decode_truncated_source() {
531 assert!(decode_npdu(Bytes::from_static(&[0x01, 0x08, 0x00])).is_err());
533 }
534
535 #[test]
538 fn npdu_network_zero() {
539 let npdu = Npdu {
541 destination: Some(NpduAddress {
542 network: 0,
543 mac_address: MacAddr::from_slice(&[0x01]),
544 }),
545 hop_count: 255,
546 payload: Bytes::from_static(&[0x10, 0x08]),
547 ..Default::default()
548 };
549 let encoded = encode_to_vec(&npdu);
550 let result = decode_npdu(Bytes::from(encoded));
551 assert!(result.is_err());
552 let err = format!("{}", result.unwrap_err());
553 assert!(err.contains("destination network 0"), "got: {err}");
554 }
555
556 #[test]
557 fn npdu_network_fffe() {
558 let npdu = Npdu {
561 destination: Some(NpduAddress {
562 network: 0xFFFE,
563 mac_address: MacAddr::from_slice(&[0x01, 0x02]),
564 }),
565 hop_count: 200,
566 payload: Bytes::from_static(&[0xAA]),
567 ..Default::default()
568 };
569 let encoded = encode_to_vec(&npdu);
570 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
571 assert_eq!(decoded.destination.as_ref().unwrap().network, 0xFFFE);
572 assert_eq!(decoded.hop_count, 200);
573 }
574
575 #[test]
576 fn npdu_hop_count_zero() {
577 let npdu = Npdu {
579 destination: Some(NpduAddress {
580 network: 1000,
581 mac_address: MacAddr::new(),
582 }),
583 hop_count: 0,
584 payload: Bytes::from_static(&[0x10, 0x08]),
585 ..Default::default()
586 };
587 let encoded = encode_to_vec(&npdu);
588 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
589 assert_eq!(decoded.hop_count, 0);
590 }
591
592 #[test]
593 fn npdu_source_with_empty_mac() {
594 let npdu = Npdu {
597 source: Some(NpduAddress {
598 network: 500,
599 mac_address: MacAddr::new(),
600 }),
601 payload: Bytes::from_static(&[0xBB]),
602 ..Default::default()
603 };
604 let encoded = encode_to_vec(&npdu);
605 let result = decode_npdu(Bytes::from(encoded));
606 assert!(result.is_err());
607 let err = format!("{}", result.unwrap_err());
608 assert!(err.contains("SLEN=0"), "got: {err}");
609 }
610
611 #[test]
612 fn npdu_destination_dlen_zero_broadcast_accepted() {
613 let npdu = Npdu {
615 destination: Some(NpduAddress {
616 network: 0xFFFF,
617 mac_address: MacAddr::new(),
618 }),
619 hop_count: 255,
620 payload: Bytes::from_static(&[0x10, 0x08]),
621 ..Default::default()
622 };
623 let encoded = encode_to_vec(&npdu);
624 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
625 assert_eq!(decoded.destination.as_ref().unwrap().network, 0xFFFF);
626 assert!(decoded.destination.as_ref().unwrap().mac_address.is_empty());
627 }
628
629 #[test]
630 fn npdu_destination_truncated_mac() {
631 let data = [0x01, 0x20, 0x03, 0xE8, 0x06, 0x01, 0x02];
634 assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
635 }
636
637 #[test]
638 fn npdu_source_truncated_mac() {
639 let data = [0x01, 0x08, 0x01, 0xF4, 0x04, 0x01]; assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
642 }
643
644 #[test]
645 fn npdu_missing_hop_count() {
646 let data = [0x01, 0x20, 0xFF, 0xFF, 0x00];
649 assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
650 }
651
652 #[test]
653 fn npdu_network_message_truncated_type() {
654 let data = [0x01, 0x80]; assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
657 }
658
659 #[test]
660 fn npdu_proprietary_message_truncated_vendor() {
661 let data = [0x01, 0x80, 0x80]; assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
664 }
665
666 #[test]
667 fn npdu_all_flags_round_trip() {
668 let npdu = Npdu {
670 is_network_message: false,
671 expecting_reply: true,
672 priority: NetworkPriority::LIFE_SAFETY,
673 destination: Some(NpduAddress {
674 network: 2000,
675 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x02, 0x01, 0xBA, 0xC0]),
676 }),
677 source: Some(NpduAddress {
678 network: 1000,
679 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x01, 0x01, 0xBA, 0xC0]),
680 }),
681 hop_count: 127,
682 payload: Bytes::from_static(&[0xDE, 0xAD, 0xBE, 0xEF]),
683 ..Default::default()
684 };
685 let encoded = encode_to_vec(&npdu);
686 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
687 assert_eq!(decoded, npdu);
688 }
689
690 #[test]
691 fn npdu_empty_payload() {
692 let npdu = Npdu {
694 payload: Bytes::new(),
695 ..Default::default()
696 };
697 let encoded = encode_to_vec(&npdu);
698 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
699 assert!(decoded.payload.is_empty());
700 }
701
702 #[test]
703 fn reject_snet_zero() {
704 let npdu = Npdu {
706 source: Some(NpduAddress {
707 network: 0,
708 mac_address: MacAddr::from_slice(&[0x01]),
709 }),
710 payload: Bytes::from_static(&[0x10, 0x08]),
711 ..Default::default()
712 };
713 let encoded = encode_to_vec(&npdu);
714 let result = decode_npdu(Bytes::from(encoded));
715 assert!(result.is_err());
716 let err = format!("{}", result.unwrap_err());
717 assert!(err.contains("source network 0"), "got: {err}");
718 }
719
720 #[test]
721 fn reserved_bits_warning_still_decodes() {
722 let mut data = vec![0x01, 0x40]; data.extend_from_slice(&[0x10, 0x08]);
727
728 let result = decode_npdu(Bytes::copy_from_slice(&data));
730 assert!(
731 result.is_ok(),
732 "reserved bits should not cause decode failure"
733 );
734 }
735}