Skip to main content

bacnet_client/client/
requests.rs

1use super::*;
2
3impl<T: TransportPort + 'static> BACnetClient<T> {
4    /// Send a confirmed request and wait for the response.
5    ///
6    /// Returns the service response data (empty for SimpleAck). Automatically
7    /// uses segmented transfer when the payload exceeds the remote device's
8    /// max APDU length.
9    pub async fn confirmed_request(
10        &self,
11        destination_mac: &[u8],
12        service_choice: ConfirmedServiceChoice,
13        service_data: &[u8],
14    ) -> Result<Bytes, Error> {
15        self.confirmed_request_inner(
16            ConfirmedTarget::Local {
17                mac: destination_mac,
18            },
19            service_choice,
20            service_data,
21        )
22        .await
23    }
24
25    /// Send a confirmed request routed through a BACnet router.
26    ///
27    /// The NPDU is sent as a unicast to `router_mac` with DNET/DADR set so
28    /// the router forwards it to `dest_network`/`dest_mac`.
29    pub async fn confirmed_request_routed(
30        &self,
31        router_mac: &[u8],
32        dest_network: u16,
33        dest_mac: &[u8],
34        service_choice: ConfirmedServiceChoice,
35        service_data: &[u8],
36    ) -> Result<Bytes, Error> {
37        self.confirmed_request_inner(
38            ConfirmedTarget::Routed {
39                router_mac,
40                dest_network,
41                dest_mac,
42            },
43            service_choice,
44            service_data,
45        )
46        .await
47    }
48
49    pub(super) async fn confirmed_request_inner(
50        &self,
51        target: ConfirmedTarget<'_>,
52        service_choice: ConfirmedServiceChoice,
53        service_data: &[u8],
54    ) -> Result<Bytes, Error> {
55        let tsm_mac = target.tsm_mac();
56        let unsegmented_apdu_size = 4 + service_data.len();
57
58        match target {
59            ConfirmedTarget::Local { mac } => {
60                let (remote_max_apdu, remote_max_segments) = {
61                    let dt = self.device_table.lock().await;
62                    let device = dt.get_by_mac(mac);
63                    let max_apdu = device
64                        .map(|d| d.max_apdu_length as u16)
65                        .unwrap_or(self.config.max_apdu_length);
66                    let max_seg = device.and_then(|d| d.max_segments_accepted);
67                    (max_apdu, max_seg)
68                };
69                if unsegmented_apdu_size > remote_max_apdu as usize {
70                    return self
71                        .segmented_confirmed_request(
72                            target,
73                            service_choice,
74                            service_data,
75                            remote_max_apdu,
76                            remote_max_segments,
77                        )
78                        .await;
79                }
80            }
81            ConfirmedTarget::Routed { .. } => {
82                let remote_max_apdu = self.config.max_apdu_length;
83                if unsegmented_apdu_size > remote_max_apdu as usize {
84                    return self
85                        .segmented_confirmed_request(
86                            target,
87                            service_choice,
88                            service_data,
89                            remote_max_apdu,
90                            None,
91                        )
92                        .await;
93                }
94            }
95        }
96
97        let (invoke_id, rx) = {
98            let mut tsm = self.tsm.lock().await;
99            let invoke_id = tsm.allocate_invoke_id(&tsm_mac).ok_or_else(|| {
100                Error::Encoding("all invoke IDs exhausted for destination".into())
101            })?;
102            let rx = tsm.register_transaction(tsm_mac.clone(), invoke_id);
103            (invoke_id, rx)
104        };
105
106        // Guard cleans up invoke ID if this task is cancelled/aborted
107        let mut guard =
108            crate::tsm::TsmGuard::new(std::sync::Arc::clone(&self.tsm), tsm_mac.clone(), invoke_id);
109
110        let pdu = Apdu::ConfirmedRequest(ConfirmedRequestPdu {
111            segmented: false,
112            more_follows: false,
113            segmented_response_accepted: self.config.segmented_response_accepted,
114            max_segments: self.config.max_segments,
115            max_apdu_length: self.config.max_apdu_length,
116            invoke_id,
117            sequence_number: None,
118            proposed_window_size: None,
119            service_choice,
120            service_request: Bytes::copy_from_slice(service_data),
121        });
122
123        let mut buf = BytesMut::with_capacity(6 + service_data.len());
124        encode_apdu(&mut buf, &pdu)?;
125
126        let timeout_duration = Duration::from_millis(self.config.apdu_timeout_ms);
127        let max_retries = self.config.apdu_retries;
128        let mut attempts: u8 = 0;
129        let mut rx = rx;
130
131        loop {
132            let send_result = match &target {
133                ConfirmedTarget::Local { mac } => {
134                    self.network
135                        .send_apdu(&buf, mac, true, NetworkPriority::NORMAL)
136                        .await
137                }
138                ConfirmedTarget::Routed {
139                    router_mac,
140                    dest_network,
141                    dest_mac,
142                } => {
143                    self.network
144                        .send_apdu_routed(
145                            &buf,
146                            *dest_network,
147                            dest_mac,
148                            router_mac,
149                            true,
150                            NetworkPriority::NORMAL,
151                        )
152                        .await
153                }
154            };
155            if let Err(e) = send_result {
156                guard.mark_completed();
157                let mut tsm = self.tsm.lock().await;
158                tsm.cancel_transaction(&tsm_mac, invoke_id);
159                return Err(e);
160            }
161
162            match timeout(timeout_duration, &mut rx).await {
163                Ok(Ok(response)) => {
164                    guard.mark_completed();
165                    return match response {
166                        TsmResponse::SimpleAck => Ok(Bytes::new()),
167                        TsmResponse::ComplexAck { service_data } => Ok(service_data),
168                        TsmResponse::Error { class, code } => Err(Error::Protocol { class, code }),
169                        TsmResponse::Reject { reason } => Err(Error::Reject { reason }),
170                        TsmResponse::Abort { reason } => Err(Error::Abort { reason }),
171                    };
172                }
173                Ok(Err(_)) => {
174                    guard.mark_completed();
175                    return Err(Error::Encoding("TSM response channel closed".into()));
176                }
177                Err(_timeout) => {
178                    attempts += 1;
179                    if attempts > max_retries {
180                        guard.mark_completed();
181                        let mut tsm = self.tsm.lock().await;
182                        tsm.cancel_transaction(&tsm_mac, invoke_id);
183                        return Err(Error::Timeout(timeout_duration));
184                    }
185                    debug!(
186                        invoke_id,
187                        attempt = attempts,
188                        max_retries,
189                        "APDU timeout, retrying confirmed request"
190                    );
191                }
192            }
193        }
194    }
195
196    pub(super) async fn send_confirmed_target_apdu(
197        &self,
198        target: ConfirmedTarget<'_>,
199        apdu: &[u8],
200    ) -> Result<(), Error> {
201        match target {
202            ConfirmedTarget::Local { mac } => {
203                self.network
204                    .send_apdu(apdu, mac, true, NetworkPriority::NORMAL)
205                    .await
206            }
207            ConfirmedTarget::Routed {
208                router_mac,
209                dest_network,
210                dest_mac,
211            } => {
212                self.network
213                    .send_apdu_routed(
214                        apdu,
215                        dest_network,
216                        dest_mac,
217                        router_mac,
218                        true,
219                        NetworkPriority::NORMAL,
220                    )
221                    .await
222            }
223        }
224    }
225    /// Send an unconfirmed request (fire-and-forget) to a specific destination.
226    pub async fn unconfirmed_request(
227        &self,
228        destination_mac: &[u8],
229        service_choice: UnconfirmedServiceChoice,
230        service_data: &[u8],
231    ) -> Result<(), Error> {
232        let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
233            service_choice,
234            service_request: Bytes::copy_from_slice(service_data),
235        });
236
237        let mut buf = BytesMut::with_capacity(2 + service_data.len());
238        encode_apdu(&mut buf, &pdu)?;
239
240        self.network
241            .send_apdu(&buf, destination_mac, false, NetworkPriority::NORMAL)
242            .await
243    }
244
245    /// Broadcast an unconfirmed request on the local network.
246    pub async fn broadcast_unconfirmed(
247        &self,
248        service_choice: UnconfirmedServiceChoice,
249        service_data: &[u8],
250    ) -> Result<(), Error> {
251        let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
252            service_choice,
253            service_request: Bytes::copy_from_slice(service_data),
254        });
255
256        let mut buf = BytesMut::with_capacity(2 + service_data.len());
257        encode_apdu(&mut buf, &pdu)?;
258
259        self.network
260            .broadcast_apdu(&buf, false, NetworkPriority::NORMAL)
261            .await
262    }
263
264    /// Broadcast an unconfirmed request globally (DNET=0xFFFF).
265    pub async fn broadcast_global_unconfirmed(
266        &self,
267        service_choice: UnconfirmedServiceChoice,
268        service_data: &[u8],
269    ) -> Result<(), Error> {
270        let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
271            service_choice,
272            service_request: Bytes::copy_from_slice(service_data),
273        });
274
275        let mut buf = BytesMut::with_capacity(2 + service_data.len());
276        encode_apdu(&mut buf, &pdu)?;
277
278        self.network
279            .broadcast_global_apdu(&buf, false, NetworkPriority::NORMAL)
280            .await
281    }
282
283    /// Broadcast an unconfirmed request to a specific remote network.
284    pub async fn broadcast_network_unconfirmed(
285        &self,
286        service_choice: UnconfirmedServiceChoice,
287        service_data: &[u8],
288        dest_network: u16,
289    ) -> Result<(), Error> {
290        let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
291            service_choice,
292            service_request: Bytes::copy_from_slice(service_data),
293        });
294
295        let mut buf = BytesMut::with_capacity(2 + service_data.len());
296        encode_apdu(&mut buf, &pdu)?;
297
298        self.network
299            .broadcast_to_network(&buf, dest_network, false, NetworkPriority::NORMAL)
300            .await
301    }
302}