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);
80
81 let mut control: u8 = npdu.priority.to_raw() & 0x03;
82 if npdu.is_network_message {
83 control |= 0x80;
84 }
85 if npdu.destination.is_some() {
86 control |= 0x20;
87 }
88 if npdu.source.is_some() {
89 control |= 0x08;
90 }
91 if npdu.expecting_reply {
92 control |= 0x04;
93 }
94 buf.put_u8(control);
95
96 if let Some(dest) = &npdu.destination {
97 if dest.network == 0 {
98 return Err(Error::Encoding("NPDU DNET must not be 0".into()));
99 }
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 {
111 if src.network == 0 || src.network == 0xFFFF {
112 return Err(Error::Encoding(format!(
113 "NPDU SNET must be 1..65534, got {}",
114 src.network
115 )));
116 }
117 if src.mac_address.is_empty() {
118 return Err(Error::Encoding("NPDU SLEN must not be 0".into()));
119 }
120 buf.put_u16(src.network);
121 if src.mac_address.len() > 255 {
122 return Err(Error::Encoding(
123 "NPDU source MAC address exceeds 255 bytes".into(),
124 ));
125 }
126 buf.put_u8(src.mac_address.len() as u8);
127 buf.put_slice(&src.mac_address);
128 }
129
130 if npdu.destination.is_some() {
131 buf.put_u8(npdu.hop_count);
132 }
133
134 if npdu.is_network_message {
135 if let Some(msg_type) = npdu.message_type {
136 buf.put_u8(msg_type);
137 if msg_type >= 0x80 {
138 buf.put_u16(npdu.vendor_id.unwrap_or(0));
139 }
140 }
141 }
142
143 buf.put_slice(&npdu.payload);
144
145 Ok(())
146}
147
148pub fn decode_npdu(data: Bytes) -> Result<Npdu, Error> {
157 if data.len() < 2 {
158 return Err(Error::buffer_too_short(2, data.len()));
159 }
160
161 let version = data[0];
162 if version != BACNET_PROTOCOL_VERSION {
163 return Err(Error::decoding(
164 0,
165 format!("unsupported BACnet protocol version: {version}"),
166 ));
167 }
168
169 let control = data[1];
170 let is_network_message = control & 0x80 != 0;
171 let has_destination = control & 0x20 != 0;
172 let has_source = control & 0x08 != 0;
173 let expecting_reply = control & 0x04 != 0;
174 let priority = NetworkPriority::from_raw(control & 0x03);
175
176 if control & 0x50 != 0 {
177 tracing::warn!(
178 control_byte = control,
179 "NPDU control byte has reserved bits set (bits 4 or 6)"
180 );
181 }
182
183 let mut offset = 2;
184 let mut destination = None;
185 let mut source = None;
186 let mut hop_count: u8 = 255;
187
188 if has_destination {
189 if offset + 3 > data.len() {
190 return Err(Error::decoding(
191 offset,
192 "NPDU too short for destination fields",
193 ));
194 }
195 let dnet = u16::from_be_bytes([data[offset], data[offset + 1]]);
196 offset += 2;
197 let dlen = data[offset] as usize;
198 offset += 1;
199
200 if dlen > 0 && offset + dlen > data.len() {
201 return Err(Error::decoding(
202 offset,
203 format!("NPDU destination address truncated: DLEN={dlen}"),
204 ));
205 }
206 let dadr = MacAddr::from_slice(&data[offset..offset + dlen]);
207 offset += dlen;
208
209 if dnet == 0 {
210 return Err(Error::decoding(
211 offset - dlen - 3, "NPDU destination network 0 is invalid",
213 ));
214 }
215
216 destination = Some(NpduAddress {
217 network: dnet,
218 mac_address: dadr,
219 });
220 }
221
222 if has_source {
223 if offset + 3 > data.len() {
224 return Err(Error::decoding(offset, "NPDU too short for source fields"));
225 }
226 let snet = u16::from_be_bytes([data[offset], data[offset + 1]]);
227 offset += 2;
228 let slen = data[offset] as usize;
229 offset += 1;
230
231 if slen == 0 {
232 return Err(Error::decoding(offset - 1, "NPDU source SLEN=0 is invalid"));
233 }
234
235 if slen > 0 && offset + slen > data.len() {
236 return Err(Error::decoding(
237 offset,
238 format!("NPDU source address truncated: SLEN={slen}"),
239 ));
240 }
241 let sadr = MacAddr::from_slice(&data[offset..offset + slen]);
242 offset += slen;
243
244 source = Some(NpduAddress {
245 network: snet,
246 mac_address: sadr,
247 });
248
249 if snet == 0 {
250 return Err(Error::decoding(
251 offset - slen - 3, "NPDU source network 0 is invalid",
253 ));
254 }
255 if snet == 0xFFFF {
256 return Err(Error::decoding(
257 offset - slen - 3,
258 "NPDU source network 0xFFFF is invalid",
259 ));
260 }
261 }
262
263 if has_destination {
264 if offset >= data.len() {
265 return Err(Error::decoding(offset, "NPDU too short for hop count"));
266 }
267 hop_count = data[offset];
268 offset += 1;
269 }
270
271 let mut message_type = None;
272 let mut vendor_id = None;
273
274 if is_network_message {
275 if offset >= data.len() {
276 return Err(Error::decoding(
277 offset,
278 "NPDU too short for network message type",
279 ));
280 }
281 let msg_type = data[offset];
282 offset += 1;
283 message_type = Some(msg_type);
284
285 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 mut buf = BytesMut::new();
550 let result = encode_npdu(&mut buf, &npdu);
551 assert!(result.is_err());
552 let err = format!("{}", result.unwrap_err());
553 assert!(err.contains("DNET"), "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 {
596 source: Some(NpduAddress {
597 network: 500,
598 mac_address: MacAddr::new(),
599 }),
600 payload: Bytes::from_static(&[0xBB]),
601 ..Default::default()
602 };
603 let mut buf = BytesMut::new();
604 let result = encode_npdu(&mut buf, &npdu);
605 assert!(result.is_err());
606 let err = format!("{}", result.unwrap_err());
607 assert!(err.contains("SLEN"), "got: {err}");
608 }
609
610 #[test]
611 fn npdu_destination_dlen_zero_broadcast_accepted() {
612 let npdu = Npdu {
614 destination: Some(NpduAddress {
615 network: 0xFFFF,
616 mac_address: MacAddr::new(),
617 }),
618 hop_count: 255,
619 payload: Bytes::from_static(&[0x10, 0x08]),
620 ..Default::default()
621 };
622 let encoded = encode_to_vec(&npdu);
623 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
624 assert_eq!(decoded.destination.as_ref().unwrap().network, 0xFFFF);
625 assert!(decoded.destination.as_ref().unwrap().mac_address.is_empty());
626 }
627
628 #[test]
629 fn npdu_destination_truncated_mac() {
630 let data = [0x01, 0x20, 0x03, 0xE8, 0x06, 0x01, 0x02];
633 assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
634 }
635
636 #[test]
637 fn npdu_source_truncated_mac() {
638 let data = [0x01, 0x08, 0x01, 0xF4, 0x04, 0x01]; assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
641 }
642
643 #[test]
644 fn npdu_missing_hop_count() {
645 let data = [0x01, 0x20, 0xFF, 0xFF, 0x00];
648 assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
649 }
650
651 #[test]
652 fn npdu_network_message_truncated_type() {
653 let data = [0x01, 0x80]; assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
656 }
657
658 #[test]
659 fn npdu_proprietary_message_truncated_vendor() {
660 let data = [0x01, 0x80, 0x80]; assert!(decode_npdu(Bytes::copy_from_slice(&data)).is_err());
663 }
664
665 #[test]
666 fn npdu_all_flags_round_trip() {
667 let npdu = Npdu {
669 is_network_message: false,
670 expecting_reply: true,
671 priority: NetworkPriority::LIFE_SAFETY,
672 destination: Some(NpduAddress {
673 network: 2000,
674 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x02, 0x01, 0xBA, 0xC0]),
675 }),
676 source: Some(NpduAddress {
677 network: 1000,
678 mac_address: MacAddr::from_slice(&[0x0A, 0x00, 0x01, 0x01, 0xBA, 0xC0]),
679 }),
680 hop_count: 127,
681 payload: Bytes::from_static(&[0xDE, 0xAD, 0xBE, 0xEF]),
682 ..Default::default()
683 };
684 let encoded = encode_to_vec(&npdu);
685 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
686 assert_eq!(decoded, npdu);
687 }
688
689 #[test]
690 fn npdu_empty_payload() {
691 let npdu = Npdu {
693 payload: Bytes::new(),
694 ..Default::default()
695 };
696 let encoded = encode_to_vec(&npdu);
697 let decoded = decode_npdu(Bytes::from(encoded)).unwrap();
698 assert!(decoded.payload.is_empty());
699 }
700
701 #[test]
702 fn reject_snet_zero() {
703 let npdu = Npdu {
705 source: Some(NpduAddress {
706 network: 0,
707 mac_address: MacAddr::from_slice(&[0x01]),
708 }),
709 payload: Bytes::from_static(&[0x10, 0x08]),
710 ..Default::default()
711 };
712 let mut buf = BytesMut::new();
713 let result = encode_npdu(&mut buf, &npdu);
714 assert!(result.is_err());
715 let err = format!("{}", result.unwrap_err());
716 assert!(err.contains("SNET"), "got: {err}");
717 }
718
719 #[test]
720 fn reserved_bits_warning_still_decodes() {
721 let mut data = vec![0x01, 0x40]; data.extend_from_slice(&[0x10, 0x08]);
725
726 let result = decode_npdu(Bytes::copy_from_slice(&data));
728 assert!(
729 result.is_ok(),
730 "reserved bits should not cause decode failure"
731 );
732 }
733}