1use bacnet_encoding::apdu::{self, Apdu};
6use bacnet_encoding::npdu;
7use bacnet_services::who_is::IAmRequest;
8use bacnet_types::enums::UnconfirmedServiceChoice;
9use bytes::Bytes;
10
11#[derive(Debug)]
13pub struct DecodedNpdu {
14 pub is_network_message: bool,
16 pub source_network: Option<u16>,
18 pub dest_network: Option<u16>,
20 pub network_message_type: Option<u8>,
22 pub broadcast: bool,
24 pub apdu: Option<DecodedApdu>,
26}
27
28#[derive(Debug)]
30pub struct DecodedApdu {
31 pub pdu_type: String,
33 pub invoke_id: Option<u8>,
35 pub segmented: bool,
37 pub service_name: String,
40 pub service: Option<String>,
43 pub problem: Option<String>,
46 pub device: Option<u32>,
48}
49
50pub fn decode_npdu_layer(npdu: Bytes) -> Result<DecodedNpdu, String> {
52 let npdu_result = npdu::decode_npdu(npdu).map_err(|e| format!("NPDU decode error: {e}"))?;
53
54 let source_network = npdu_result.source.as_ref().map(|a| a.network);
55 let dest_network = npdu_result.destination.as_ref().map(|a| a.network);
56
57 let apdu = if !npdu_result.is_network_message() && !npdu_result.payload.is_empty() {
58 Some(decode_apdu_layer(&npdu_result)?)
59 } else {
60 None
61 };
62
63 Ok(DecodedNpdu {
64 is_network_message: npdu_result.is_network_message(),
65 source_network,
66 dest_network,
67 network_message_type: npdu_result.message_type.map(|kind| kind.to_raw()),
68 broadcast: npdu_result
69 .destination
70 .as_ref()
71 .is_some_and(|d| d.mac_address.is_empty()),
72 apdu,
73 })
74}
75
76fn decode_apdu_layer(npdu_data: &npdu::Npdu) -> Result<DecodedApdu, String> {
78 let apdu_result = apdu::decode_apdu(npdu_data.payload.clone())
79 .map_err(|e| format!("APDU decode error: {e}"))?;
80
81 let service = |choice: &dyn std::fmt::Display| Some(choice.to_string());
82 let decoded = match apdu_result {
83 Apdu::ConfirmedRequest(ref cr) => DecodedApdu {
84 pdu_type: "Confirmed-Request".to_string(),
85 invoke_id: Some(cr.invoke_id),
86 segmented: cr.segmented,
87 service_name: format!("{}", cr.service_choice),
88 service: service(&cr.service_choice),
89 problem: None,
90 device: None,
91 },
92 Apdu::UnconfirmedRequest(ref ur) => DecodedApdu {
93 pdu_type: "Unconfirmed-Request".to_string(),
94 invoke_id: None,
95 segmented: false,
96 service_name: format!("{}", ur.service_choice),
97 service: service(&ur.service_choice),
98 problem: None,
99 device: (ur.service_choice == UnconfirmedServiceChoice::I_AM)
100 .then(|| IAmRequest::decode(&ur.service_request).ok())
101 .flatten()
102 .map(|i_am| i_am.object_identifier.instance_number()),
103 },
104 Apdu::SimpleAck(ref sa) => DecodedApdu {
105 pdu_type: "Simple-ACK".to_string(),
106 invoke_id: Some(sa.invoke_id),
107 segmented: false,
108 service_name: format!("{}-ACK", sa.service_choice),
109 service: service(&sa.service_choice),
110 problem: None,
111 device: None,
112 },
113 Apdu::ComplexAck(ref ca) => DecodedApdu {
114 pdu_type: "Complex-ACK".to_string(),
115 invoke_id: Some(ca.invoke_id),
116 segmented: ca.segmented,
117 service_name: format!("{}-ACK", ca.service_choice),
118 service: service(&ca.service_choice),
119 problem: None,
120 device: None,
121 },
122 Apdu::SegmentAck(ref sa) => DecodedApdu {
123 pdu_type: "Segment-ACK".to_string(),
124 invoke_id: Some(sa.invoke_id),
125 segmented: false,
126 service_name: "SegmentACK".to_string(),
127 service: None,
128 problem: None,
129 device: None,
130 },
131 Apdu::Error(ref ep) => DecodedApdu {
132 pdu_type: "Error".to_string(),
133 invoke_id: Some(ep.invoke_id),
134 segmented: false,
135 service_name: format!("{}-Error", ep.service_choice),
136 service: service(&ep.service_choice),
137 problem: Some(format!("{}:{}", ep.error_class, ep.error_code)),
138 device: None,
139 },
140 Apdu::Reject(ref rp) => DecodedApdu {
141 pdu_type: "Reject".to_string(),
142 invoke_id: Some(rp.invoke_id),
143 segmented: false,
144 service_name: format!("Reject({})", rp.reject_reason),
145 service: None,
146 problem: Some(format!("reject {}", rp.reject_reason)),
147 device: None,
148 },
149 Apdu::Abort(ref ap) => DecodedApdu {
150 pdu_type: "Abort".to_string(),
151 invoke_id: Some(ap.invoke_id),
152 segmented: false,
153 service_name: format!("Abort({})", ap.abort_reason),
154 service: None,
155 problem: Some(format!("abort {}", ap.abort_reason)),
156 device: None,
157 },
158 };
159
160 Ok(decoded)
161}