1use super::*;
2use bacnet_encoding::apdu::MINIMUM_MESSAGE_SIZE;
3
4enum LengthBoundedBy {
11 DiscoveredPeer(u16),
13 LocalConfig(u16),
15 Transport(u16),
17}
18
19impl core::fmt::Display for LengthBoundedBy {
20 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
21 match self {
22 Self::DiscoveredPeer(v) => {
23 write!(f, "the peer's advertised Max APDU Length Accepted of {v}")
24 }
25 Self::LocalConfig(v) => write!(f, "this client's configured maximum of {v}"),
26 Self::Transport(v) => write!(f, "the transport's limit of {v}"),
27 }
28 }
29}
30
31fn check_transmittable_length(peer_or_local: LengthBoundedBy, transport: u16) -> Result<(), Error> {
51 let advertised = match peer_or_local {
52 LengthBoundedBy::DiscoveredPeer(v) | LengthBoundedBy::LocalConfig(v) => v,
53 LengthBoundedBy::Transport(v) => v,
54 };
55 let combined = advertised.min(transport);
56 if combined >= MINIMUM_MESSAGE_SIZE {
57 return Ok(());
58 }
59 let binding = if transport < advertised {
60 LengthBoundedBy::Transport(transport)
61 } else {
62 peer_or_local
63 };
64 Err(Error::Encoding(format!(
65 "maximum transmittable length {combined} is below the {MINIMUM_MESSAGE_SIZE}-octet \
66 MinimumMessageSize every BACnet device accepts (Clause 5.2.1.2); the binding limit is \
67 {binding}"
68 )))
69}
70
71impl<T: TransportPort + 'static> BACnetClient<T> {
72 pub async fn confirmed_request(
78 &self,
79 destination_mac: &[u8],
80 service_choice: ConfirmedServiceChoice,
81 service_data: &[u8],
82 ) -> Result<Bytes, Error> {
83 self.confirmed_request_inner(
84 ConfirmedTarget::Local {
85 mac: destination_mac,
86 },
87 service_choice,
88 service_data,
89 )
90 .await
91 }
92
93 pub async fn confirmed_request_routed(
98 &self,
99 router_mac: &[u8],
100 dest_network: u16,
101 dest_mac: &[u8],
102 service_choice: ConfirmedServiceChoice,
103 service_data: &[u8],
104 ) -> Result<Bytes, Error> {
105 self.confirmed_request_inner(
106 ConfirmedTarget::Routed {
107 router_mac,
108 dest_network,
109 dest_mac,
110 },
111 service_choice,
112 service_data,
113 )
114 .await
115 }
116
117 pub(super) async fn confirmed_request_inner(
118 &self,
119 target: ConfirmedTarget<'_>,
120 service_choice: ConfirmedServiceChoice,
121 service_data: &[u8],
122 ) -> Result<Bytes, Error> {
123 let path_lease = match target {
126 ConfirmedTarget::Local { .. } => None,
127 ConfirmedTarget::Routed {
128 router_mac,
129 dest_network,
130 ..
131 } => Some(
132 self.routed_path_limits
133 .acquire(router_mac, dest_network)
134 .await?,
135 ),
136 };
137 let (routed_path_max_apdu, routed_forwarded_npci_len) = match (path_lease.as_ref(), target)
138 {
139 (
140 Some(lease),
141 ConfirmedTarget::Routed {
142 dest_mac,
143 dest_network: _,
144 router_mac: _,
145 },
146 ) => (
147 Some(lease.max_apdu(dest_mac.len(), self.local_mac.len())?),
148 Some(lease.forwarded_npci_len(dest_mac.len(), self.local_mac.len())?),
149 ),
150 _ => (None, None),
151 };
152 let transaction_peer = target.transaction_peer();
153 let tsm_mac = transaction_peer.tsm_mac;
154 let unsegmented_apdu_size = 4 + service_data.len();
155 let target_transport_max_apdu = self.target_transport_max_apdu_length(target);
156
157 let (peer_max_apdu, remote_max_segments, advertised, peer_segmentation) = {
158 let dt = self.device_table.lock().await;
159 let (device, peer_segmentation) = match target {
164 ConfirmedTarget::Local { mac } => {
165 (dt.get_by_mac(mac), dt.local_peer_segmentation(mac))
166 }
167 ConfirmedTarget::Routed {
168 dest_network,
169 dest_mac,
170 ..
171 } => (
172 dt.get_by_network_address(dest_network, dest_mac),
173 dt.routed_peer_segmentation(dest_network, dest_mac),
174 ),
175 };
176 let max_apdu = device
177 .map(|d| u16::try_from(d.max_apdu_length).unwrap_or(u16::MAX))
178 .unwrap_or(self.config.max_apdu_length);
179 let max_seg = device.and_then(|d| d.max_segments_accepted);
180 let advertised = if device.is_some() {
181 LengthBoundedBy::DiscoveredPeer(max_apdu)
182 } else {
183 LengthBoundedBy::LocalConfig(max_apdu)
184 };
185 (max_apdu, max_seg, advertised, peer_segmentation)
186 };
187 check_transmittable_length(advertised, target_transport_max_apdu)?;
188 if routed_path_max_apdu.is_some_and(|path_max_apdu| {
189 path_max_apdu < MINIMUM_MESSAGE_SIZE
190 && path_max_apdu <= peer_max_apdu.min(target_transport_max_apdu)
191 }) {
192 let ConfirmedTarget::Routed { dest_network, .. } = target else {
193 unreachable!("a routed path limit exists only for a routed target")
194 };
195 return Err(Error::RoutedPathTooLong { dnet: dest_network });
196 }
197 let remote_max_apdu = peer_max_apdu
198 .min(target_transport_max_apdu)
199 .min(routed_path_max_apdu.unwrap_or(u16::MAX));
200 if unsegmented_apdu_size > remote_max_apdu as usize {
201 if let Some(crate::discovery::PeerSegmentation::Authoritative(capability)) =
208 peer_segmentation
209 {
210 if matches!(
211 capability,
212 bacnet_types::enums::Segmentation::NONE
213 | bacnet_types::enums::Segmentation::TRANSMIT
214 ) {
215 return Err(Error::Segmentation(format!(
216 "request requires segmentation but the peer advertised \
217 {capability:?} and cannot receive segmented requests"
218 )));
219 }
220 }
221 return self
222 .segmented_confirmed_request(
223 target,
224 service_choice,
225 service_data,
226 remote_max_apdu,
227 remote_max_segments,
228 routed_forwarded_npci_len,
229 path_lease.as_ref(),
230 )
231 .await;
232 }
233
234 let advertised_max_apdu = self.advertised_max_apdu_length_for_target(target)?;
235 let (invoke_id, registration) = {
236 let mut tsm = self.tsm.lock().await;
237 tsm.register_coordinated_transaction_with_progress(
238 tsm_mac.clone(),
239 transaction_peer.canonical,
240 service_choice,
241 false,
242 )
243 .map_err(|error| Error::Encoding(error.to_string()))?
244 };
245
246 let owner = registration.owner.clone();
247 let mut guard = TransactionGuard::new(
248 Arc::clone(&self.tsm),
249 self.cleanup_tx.clone(),
250 tsm_mac.clone(),
251 invoke_id,
252 owner.clone(),
253 None,
254 );
255
256 let pdu = Apdu::ConfirmedRequest(ConfirmedRequestPdu {
257 segmented: false,
258 more_follows: false,
259 segmented_response_accepted: self.config.segmented_response_accepted,
260 max_segments: self.config.max_segments,
261 max_apdu_length: advertised_max_apdu,
262 invoke_id,
263 sequence_number: None,
264 proposed_window_size: None,
265 service_choice,
266 service_request: Bytes::copy_from_slice(service_data),
267 });
268
269 let mut buf = BytesMut::with_capacity(6 + service_data.len());
270 encode_apdu(&mut buf, &pdu)?;
271
272 if let Some(forwarded_npci_len) = routed_forwarded_npci_len {
273 self.routed_path_limits.install_active(
274 target,
275 tsm_mac.clone(),
276 invoke_id,
277 owner.clone(),
278 forwarded_npci_len,
279 self.network.network_control_ingress_sequence(),
280 );
281 }
282 if !self.routed_path_limits.authorize_attempt(
283 target,
284 &tsm_mac,
285 invoke_id,
286 &owner,
287 buf.len(),
288 ) {
289 guard.mark_completed();
290 self.tsm
291 .lock()
292 .await
293 .cancel_transaction_for_owner(&tsm_mac, invoke_id, &owner);
294 self.enqueue_transaction_cleanup(&tsm_mac, invoke_id, &owner, false, None);
295 return Err(Error::Encoding(
296 "routed send lost its active path authorization".into(),
297 ));
298 }
299
300 if let Err(e) = self.send_confirmed_target_apdu(target, &buf).await {
301 guard.mark_completed();
302 let mut tsm = self.tsm.lock().await;
303 tsm.cancel_transaction_for_owner(&tsm_mac, invoke_id, &owner);
304 drop(tsm);
305 self.enqueue_transaction_cleanup(&tsm_mac, invoke_id, &owner, false, None);
306 return Err(e);
307 }
308
309 let response = self
310 .wait_for_confirmed_response(
311 target,
312 &tsm_mac,
313 invoke_id,
314 &owner,
315 registration.response,
316 registration.progress,
317 Some(&buf),
318 )
319 .await;
320 if response.is_ok() {
321 if let Some(lease) = path_lease.as_ref() {
322 lease.mark_terminal_observed();
323 }
324 }
325 guard.mark_completed();
326 response.and_then(Self::confirmed_response_result)
327 }
328
329 #[allow(clippy::too_many_arguments)]
332 pub(super) async fn wait_for_confirmed_response(
333 &self,
334 target: ConfirmedTarget<'_>,
335 tsm_mac: &MacAddr,
336 invoke_id: u8,
337 owner: &TransactionOwner,
338 mut response_rx: oneshot::Receiver<TsmResponse>,
339 mut progress_rx: tokio::sync::watch::Receiver<TransactionProgress>,
340 retry_apdu: Option<&[u8]>,
341 ) -> Result<TsmResponse, Error> {
342 let (request_timeout, segment_timeout, max_retries) = {
343 let tsm = self.tsm.lock().await;
344 let config = tsm.config();
345 (
346 Duration::from_millis(config.apdu_timeout_ms),
347 Duration::from_millis(config.apdu_segment_timeout_ms.saturating_mul(4)),
348 config.apdu_retries,
349 )
350 };
351 let mut retries_sent = 0u16;
352 let mut progress = *progress_rx.borrow_and_update();
353
354 loop {
355 let wait = match progress {
356 TransactionProgress::AwaitingResponse => request_timeout,
357 TransactionProgress::SegmentedResponse { .. } => segment_timeout,
358 };
359 let timer = tokio::time::sleep(wait);
360 tokio::pin!(timer);
361
362 tokio::select! {
363 response = &mut response_rx => {
364 return response.map_err(|_| Error::Encoding("TSM response channel closed".into()));
365 }
366 changed = progress_rx.changed() => {
367 if changed.is_err() {
368 return (&mut response_rx)
369 .await
370 .map_err(|_| Error::Encoding("TSM response channel closed".into()));
371 }
372 progress = *progress_rx.borrow_and_update();
373 }
374 _ = &mut timer => {
375 match progress {
376 TransactionProgress::AwaitingResponse => {
377 let retry = retry_apdu.is_some()
378 && retries_sent < u16::from(max_retries);
379 let mut tsm = self.tsm.lock().await;
380 match tsm.expire_request_timer(tsm_mac, invoke_id, owner, !retry) {
381 RequestTimerExpiration::Retry => {
382 let Some(retry_apdu) = retry_apdu else {
386 tsm.cancel_transaction_for_owner(tsm_mac, invoke_id, owner);
387 drop(tsm);
388 self.enqueue_transaction_cleanup(
389 tsm_mac,
390 invoke_id,
391 owner,
392 false,
393 None,
394 );
395 return Err(Error::Timeout(request_timeout));
396 };
397 retries_sent += 1;
398 drop(tsm);
399 if !self.routed_path_limits.authorize_attempt(
400 target,
401 tsm_mac,
402 invoke_id,
403 owner,
404 retry_apdu.len(),
405 ) {
406 return (&mut response_rx).await.map_err(|_| {
407 Error::Encoding("TSM response channel closed".into())
408 });
409 }
410 let send_result = self
411 .send_confirmed_target_apdu(target, retry_apdu)
412 .await;
413 if let Err(error) = send_result {
414 let mut tsm = self.tsm.lock().await;
415 match tsm.expire_request_timer(tsm_mac, invoke_id, owner, true) {
416 RequestTimerExpiration::SegmentedResponse { generation } => {
417 progress = TransactionProgress::SegmentedResponse { generation };
418 continue;
419 }
420 RequestTimerExpiration::NoTransaction => {
421 drop(tsm);
422 return (&mut response_rx)
423 .await
424 .map_err(|_| Error::Encoding("TSM response channel closed".into()));
425 }
426 RequestTimerExpiration::TimedOut => {
427 drop(tsm);
428 self.enqueue_transaction_cleanup(
429 tsm_mac,
430 invoke_id,
431 owner,
432 false,
433 None,
434 );
435 return Err(error);
436 }
437 RequestTimerExpiration::Retry => unreachable!(
438 "final retry-send failure disposition cannot authorize another retry"
439 ),
440 }
441 }
442 debug!(
443 invoke_id,
444 attempt = retries_sent,
445 max_retries,
446 "APDU timeout, retrying confirmed request"
447 );
448 }
449 RequestTimerExpiration::SegmentedResponse { generation } => {
450 progress = TransactionProgress::SegmentedResponse { generation };
451 }
452 RequestTimerExpiration::TimedOut => {
453 drop(tsm);
454 self.enqueue_transaction_cleanup(
455 tsm_mac,
456 invoke_id,
457 owner,
458 false,
459 None,
460 );
461 return Err(Error::Abort {
462 reason: bacnet_types::enums::AbortReason::TSM_TIMEOUT.to_raw(),
463 });
464 }
465 RequestTimerExpiration::NoTransaction => {
466 drop(tsm);
467 return (&mut response_rx)
468 .await
469 .map_err(|_| Error::Encoding("TSM response channel closed".into()));
470 }
471 }
472 }
473 TransactionProgress::SegmentedResponse { generation } => {
474 let mut tsm = self.tsm.lock().await;
475 match tsm.expire_segment_timer(tsm_mac, invoke_id, owner, generation) {
476 SegmentTimerExpiration::Activity { generation } => {
477 progress = TransactionProgress::SegmentedResponse { generation };
478 }
479 SegmentTimerExpiration::AwaitingResponse => {
480 progress = TransactionProgress::AwaitingResponse;
481 }
482 SegmentTimerExpiration::TimedOut => {
483 drop(tsm);
484 #[cfg(test)]
485 self.segmented_cleanup.pause_if_enabled().await;
486 self.enqueue_transaction_cleanup(
487 tsm_mac,
488 invoke_id,
489 owner,
490 false,
491 None,
492 );
493 return Err(Error::Abort {
494 reason: bacnet_types::enums::AbortReason::TSM_TIMEOUT.to_raw(),
495 });
496 }
497 SegmentTimerExpiration::NoTransaction => {
498 drop(tsm);
499 return (&mut response_rx)
500 .await
501 .map_err(|_| Error::Encoding("TSM response channel closed".into()));
502 }
503 }
504 }
505 }
506 }
507 }
508 }
509 }
510
511 pub(super) fn enqueue_transaction_cleanup(
512 &self,
513 mac: &MacAddr,
514 invoke_id: u8,
515 owner: &TransactionOwner,
516 cancel_tsm: bool,
517 seg_ack_sender: Option<mpsc::Sender<SegmentAckPdu>>,
518 ) {
519 let _ = self.cleanup_tx.send(TransactionCleanup {
520 mac: mac.clone(),
521 invoke_id,
522 owner: owner.clone(),
523 cancel_tsm,
524 seg_ack_sender,
525 });
526 }
527
528 pub(super) fn confirmed_response_result(response: TsmResponse) -> Result<Bytes, Error> {
529 super::confirmed_response_result(response)
530 }
531
532 pub(super) async fn send_confirmed_target_apdu(
533 &self,
534 target: ConfirmedTarget<'_>,
535 apdu: &[u8],
536 ) -> Result<(), Error> {
537 match target {
538 ConfirmedTarget::Local { mac } => {
539 self.network
540 .send_apdu(apdu, mac, true, NetworkPriority::NORMAL)
541 .await
542 }
543 ConfirmedTarget::Routed {
544 router_mac,
545 dest_network,
546 dest_mac,
547 } => {
548 self.network
549 .send_apdu_routed(
550 apdu,
551 dest_network,
552 dest_mac,
553 router_mac,
554 true,
555 NetworkPriority::NORMAL,
556 )
557 .await
558 }
559 }
560 }
561
562 pub(super) fn target_transport_max_apdu_length(&self, target: ConfirmedTarget<'_>) -> u16 {
563 self.network
564 .transport()
565 .max_apdu_length()
566 .saturating_sub(target.additional_npdu_header_len())
567 }
568
569 pub(super) fn advertised_max_apdu_length_for_target(
570 &self,
571 target: ConfirmedTarget<'_>,
572 ) -> Result<u16, Error> {
573 cap_max_apdu_to_transport(
574 self.config.max_apdu_length,
575 self.target_transport_max_apdu_length(target),
576 )
577 }
578
579 pub async fn unconfirmed_request(
581 &self,
582 destination_mac: &[u8],
583 service_choice: UnconfirmedServiceChoice,
584 service_data: &[u8],
585 ) -> Result<(), Error> {
586 let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
587 service_choice,
588 service_request: Bytes::copy_from_slice(service_data),
589 });
590
591 let mut buf = BytesMut::with_capacity(2 + service_data.len());
592 encode_apdu(&mut buf, &pdu)?;
593
594 self.network
595 .send_apdu(&buf, destination_mac, false, NetworkPriority::NORMAL)
596 .await
597 }
598
599 pub async fn broadcast_unconfirmed(
601 &self,
602 service_choice: UnconfirmedServiceChoice,
603 service_data: &[u8],
604 ) -> Result<(), Error> {
605 let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
606 service_choice,
607 service_request: Bytes::copy_from_slice(service_data),
608 });
609
610 let mut buf = BytesMut::with_capacity(2 + service_data.len());
611 encode_apdu(&mut buf, &pdu)?;
612
613 self.network
614 .broadcast_apdu(&buf, false, NetworkPriority::NORMAL)
615 .await
616 }
617
618 pub async fn broadcast_global_unconfirmed(
620 &self,
621 service_choice: UnconfirmedServiceChoice,
622 service_data: &[u8],
623 ) -> Result<(), Error> {
624 let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
625 service_choice,
626 service_request: Bytes::copy_from_slice(service_data),
627 });
628
629 let mut buf = BytesMut::with_capacity(2 + service_data.len());
630 encode_apdu(&mut buf, &pdu)?;
631
632 self.network
633 .broadcast_global_apdu(&buf, false, NetworkPriority::NORMAL)
634 .await
635 }
636
637 pub async fn broadcast_network_unconfirmed(
639 &self,
640 service_choice: UnconfirmedServiceChoice,
641 service_data: &[u8],
642 dest_network: u16,
643 ) -> Result<(), Error> {
644 let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
645 service_choice,
646 service_request: Bytes::copy_from_slice(service_data),
647 });
648
649 let mut buf = BytesMut::with_capacity(2 + service_data.len());
650 encode_apdu(&mut buf, &pdu)?;
651
652 self.network
653 .broadcast_to_network(&buf, dest_network, false, NetworkPriority::NORMAL)
654 .await
655 }
656}
657
658#[cfg(test)]
659#[path = "requests_tests.rs"]
660mod transmittable_length_tests;