Skip to main content

bacnet_client/client/
requests.rs

1use super::segmentation_context::{ConfirmedWait, SegmentedRequestLimits};
2use super::*;
3use bacnet_encoding::apdu::MINIMUM_MESSAGE_SIZE;
4
5/// Which term of Clause 5.2.1.2's minimum bound the transmittable length down.
6///
7/// Named separately so the error blames the right one. The checked value is a
8/// minimum over several terms, and the peer is only sometimes the binding one:
9/// BACnet/SC recomputes its own limit from the hub's Connect-Accept, so a
10/// transport can fall below the floor while the peer is perfectly conformant.
11enum LengthBoundedBy {
12    /// A length advertised by a discovered peer, from I-Am.
13    DiscoveredPeer(u16),
14    /// This client's own configured maximum.
15    LocalConfig(u16),
16    /// The data link, after any routed NPDU header.
17    Transport(u16),
18}
19
20impl core::fmt::Display for LengthBoundedBy {
21    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22        match self {
23            Self::DiscoveredPeer(v) => {
24                write!(f, "the peer's advertised Max APDU Length Accepted of {v}")
25            }
26            Self::LocalConfig(v) => write!(f, "this client's configured maximum of {v}"),
27            Self::Transport(v) => write!(f, "the transport's limit of {v}"),
28        }
29    }
30}
31
32/// Reject a maximum transmittable length no conformant device could accept.
33///
34/// Clause 5.2.1.2 derives this length as the smallest of the local capability,
35/// the internetwork limit, and the peer's receive capacity, whose minimum
36/// is 50 octets. Below that floor no
37/// conformant APDU can be formed at all. Clause 20.1.2.5 gives the same number
38/// a name: MinimumMessageSize, the 50-octet capacity represented by the
39/// lowest max-APDU-length-accepted code `B'0000'`.
40///
41/// The check is a floor, not membership of the six values Clause 20.1.2.5
42/// encodes. A discovered peer's length comes from I-Am's `Max APDU Length
43/// Accepted`, an Unsigned octet count rather than the four-bit code, and
44/// Clause 20.1.2.5 allows actual capacity to exceed the encoded value,
45/// so 600 and 1500 are legitimate and must not be rejected.
46///
47/// Failing rather than clamping up to 50 keeps the client from inventing a
48/// capability the peer never claimed: a device advertising less than 50 is
49/// already non-conformant, and a typed error names that where a silent clamp
50/// would send it frames it said it cannot hold.
51fn check_transmittable_length(peer_or_local: LengthBoundedBy, transport: u16) -> Result<(), Error> {
52    let advertised = match peer_or_local {
53        LengthBoundedBy::DiscoveredPeer(v) | LengthBoundedBy::LocalConfig(v) => v,
54        LengthBoundedBy::Transport(v) => v,
55    };
56    let combined = advertised.min(transport);
57    if combined >= MINIMUM_MESSAGE_SIZE {
58        return Ok(());
59    }
60    let binding = if transport < advertised {
61        LengthBoundedBy::Transport(transport)
62    } else {
63        peer_or_local
64    };
65    Err(Error::Encoding(format!(
66        "maximum transmittable length {combined} is below the {MINIMUM_MESSAGE_SIZE}-octet \
67         MinimumMessageSize every BACnet device accepts (Clause 5.2.1.2); the binding limit is \
68         {binding}"
69    )))
70}
71
72impl<T: TransportPort + 'static> BACnetClient<T> {
73    /// Send a confirmed request and wait for the response.
74    ///
75    /// Returns the service response data (empty for SimpleAck). Automatically
76    /// uses segmented transfer when the payload exceeds the remote device's
77    /// max APDU length.
78    ///
79    /// A confirmed request goes to one device, so a `destination_mac` that
80    /// reaches a group of nodes ([`TransportPort::is_group_destination`]:
81    /// the link's broadcast MAC, or on B/IP the limited broadcast, the
82    /// configured broadcast IP or a multicast address at any port), which
83    /// would make it a local broadcast (Clause 6.3), fails with
84    /// [`Error::Encoding`] before any transaction state is taken (#1479). So
85    /// does a routed target that names this client's own network with such a
86    /// MAC, since it goes local.
87    pub async fn confirmed_request(
88        &self,
89        destination_mac: &[u8],
90        service_choice: ConfirmedServiceChoice,
91        service_data: &[u8],
92    ) -> Result<Bytes, Error> {
93        self.confirmed_request_inner(
94            ConfirmedTarget::Local {
95                mac: destination_mac,
96            },
97            service_choice,
98            service_data,
99        )
100        .await
101    }
102
103    /// Send a confirmed request routed through a BACnet router.
104    ///
105    /// The NPDU is sent as a unicast to `router_mac` with DNET/DADR set so
106    /// the router forwards it to `dest_network`/`dest_mac`.
107    ///
108    /// A confirmed request goes to one device, so `dest_network` must be in
109    /// 1..=65534 and `dest_mac` must hold 1 to
110    /// [`MAX_MAC_LEN`](bacnet_npdu::MAX_MAC_LEN) octets. DNET 0 names no network, DNET
111    /// 65535 and an empty DADR address a broadcast, and any of them fails
112    /// with [`Error::Encoding`] before path or transaction state is taken.
113    /// Every routed confirmed entry point, such as the `_to_device` and
114    /// `_from_device` methods, applies the same rule.
115    ///
116    /// Once the client has learned its own network's number, a
117    /// `dest_network` naming it is this network. After those checks the
118    /// request goes to `dest_mac` as a local request, with no DNET and not
119    /// through `router_mac` (#1358). An answer from `dest_mac` with no SNET
120    /// completes it, and so does one a router relays back with `dest_network`
121    /// as its SNET and `dest_mac` as its SADR (#1465), since both name the
122    /// same station.
123    pub async fn confirmed_request_routed(
124        &self,
125        router_mac: &[u8],
126        dest_network: u16,
127        dest_mac: &[u8],
128        service_choice: ConfirmedServiceChoice,
129        service_data: &[u8],
130    ) -> Result<Bytes, Error> {
131        self.confirmed_request_inner(
132            ConfirmedTarget::Routed {
133                router_mac,
134                dest_network,
135                dest_mac,
136            },
137            service_choice,
138            service_data,
139        )
140        .await
141    }
142
143    pub(super) async fn confirmed_request_inner(
144        &self,
145        target: ConfirmedTarget<'_>,
146        service_choice: ConfirmedServiceChoice,
147        service_data: &[u8],
148    ) -> Result<Bytes, Error> {
149        // A destination that is not one device (#1278), or a DADR or local
150        // source MAC that no router could carry (#1267), fails here, before
151        // the path gate is reserved or awaited and before TSM registration.
152        // A routed target on this client's own network is then sent as the
153        // local target it is (#1358); the peer's limits still come from the
154        // device-table row of the target as named.
155        let named = target;
156        if let ConfirmedTarget::Routed {
157            dest_network,
158            dest_mac,
159            ..
160        } = named
161        {
162            check_routed_unicast(dest_network, dest_mac.len())?;
163        }
164        let target = named.localized(self.network.local_network_number().get());
165        // With no DNET, a group destination is a local broadcast, which
166        // carries no confirmed request (Clause 6.3, #1479).
167        if let ConfirmedTarget::Local { mac } = target
168            && self.network.transport().is_group_destination(mac)
169        {
170            return Err(Error::Encoding(
171                "a confirmed request goes to one device, not to a broadcast or group \
172                     address (a local broadcast carries only an UNCONFIRMED_REQUEST APDU, \
173                     Clause 6.3)"
174                    .into(),
175            ));
176        }
177        let routed_forwarded_npci_len = match target {
178            ConfirmedTarget::Local { .. } => None,
179            ConfirmedTarget::Routed { dest_mac, .. } => {
180                Some(forwarded_npci_len(dest_mac.len(), self.local_mac.len())?)
181            }
182        };
183        // Pace before the path lease, so the wait holds up no other request
184        // through the same router. The guard lives until this request
185        // returns, by any path or by being dropped, and then starts the
186        // interval for the next request to this destination.
187        let _pace = self.pacer.wait(pacing::PaceKey::of(target)).await;
188        // The lease serializes one active request per (immediate router, DNET)
189        // and remains live through every return path, including cancellation.
190        let path_lease = match target {
191            ConfirmedTarget::Local { .. } => None,
192            ConfirmedTarget::Routed {
193                router_mac,
194                dest_network,
195                ..
196            } => Some(
197                self.routed_path_limits
198                    .acquire(router_mac, dest_network)
199                    .await?,
200            ),
201        };
202        let routed_path_max_apdu = path_lease
203            .as_ref()
204            .zip(routed_forwarded_npci_len)
205            .map(|(lease, npci_len)| lease.max_apdu(npci_len));
206        let transaction_peer = target.transaction_peer();
207        let tsm_mac = transaction_peer.tsm_mac;
208        let unsegmented_apdu_size = 4 + service_data.len();
209        let target_transport_max_apdu = self.target_transport_max_apdu_length(target);
210
211        let (peer_max_apdu, remote_max_segments, advertised, peer_segmentation) = {
212            let dt = self.device_table.lock().await;
213            // A routed peer is recorded under the router's MAC, so only the
214            // SNET/SADR of the NPDU that carried its I-Am identifies it
215            // (Clause 5.2.1.2 term (c) binds the peer's Max APDU Length
216            // Accepted regardless of how the peer is reached).
217            let (device, peer_segmentation) = match named {
218                ConfirmedTarget::Local { mac } => {
219                    (dt.get_by_mac(mac), dt.local_peer_segmentation(mac))
220                }
221                ConfirmedTarget::Routed {
222                    dest_network,
223                    dest_mac,
224                    ..
225                } => (
226                    dt.get_by_network_address(dest_network, dest_mac),
227                    dt.routed_peer_segmentation(dest_network, dest_mac),
228                ),
229            };
230            let max_apdu = device
231                .map(|d| u16::try_from(d.max_apdu_length).unwrap_or(u16::MAX))
232                .unwrap_or(self.config.max_apdu_length);
233            let max_seg = device.and_then(|d| d.max_segments_accepted);
234            let advertised = if device.is_some() {
235                LengthBoundedBy::DiscoveredPeer(max_apdu)
236            } else {
237                LengthBoundedBy::LocalConfig(max_apdu)
238            };
239            (max_apdu, max_seg, advertised, peer_segmentation)
240        };
241        check_transmittable_length(advertised, target_transport_max_apdu)?;
242        if routed_path_max_apdu.is_some_and(|path_max_apdu| {
243            path_max_apdu < MINIMUM_MESSAGE_SIZE
244                && path_max_apdu <= peer_max_apdu.min(target_transport_max_apdu)
245        }) {
246            let ConfirmedTarget::Routed { dest_network, .. } = target else {
247                unreachable!("a routed path limit exists only for a routed target")
248            };
249            return Err(Error::RoutedPathTooLong { dnet: dest_network });
250        }
251        let remote_max_apdu = peer_max_apdu
252            .min(target_transport_max_apdu)
253            .min(routed_path_max_apdu.unwrap_or(u16::MAX));
254        if unsegmented_apdu_size > remote_max_apdu as usize {
255            // Clause 12.11: a NO_SEGMENTATION or SEGMENTED_TRANSMIT peer
256            // accepts exactly one segment — only unsegmented requests — and
257            // Clause 18's SEGMENTATION_NOT_SUPPORTED abort is the certain
258            // outcome of sending anyway. Refuse locally when the capability
259            // is authoritative (I-Am or explicit configuration); a legacy
260            // placeholder row stays unknown and keeps today's behavior.
261            if let Some(crate::discovery::PeerSegmentation::Authoritative(capability)) =
262                peer_segmentation
263                && matches!(
264                    capability,
265                    bacnet_types::enums::Segmentation::NONE
266                        | bacnet_types::enums::Segmentation::TRANSMIT
267                )
268            {
269                return Err(bacnet_types::error::SegmentationError::PeerCannotReceive {
270                    capability,
271                }
272                .into());
273            }
274            return self
275                .segmented_confirmed_request(
276                    target,
277                    service_choice,
278                    service_data,
279                    SegmentedRequestLimits {
280                        remote_max_apdu,
281                        remote_max_segments,
282                        routed_forwarded_npci_len,
283                        routed_path_lease: path_lease.as_ref(),
284                    },
285                )
286                .await;
287        }
288
289        let advertised_max_apdu = self.advertised_max_apdu_length_for_target(target)?;
290        let (invoke_id, registration) = {
291            let mut tsm = self.tsm.lock().await;
292            tsm.register_coordinated_transaction_with_progress(
293                tsm_mac.clone(),
294                transaction_peer.canonical,
295                service_choice,
296                false,
297            )
298            .map_err(|error| Error::Encoding(error.to_string()))?
299        };
300
301        let owner = registration.owner.clone();
302        let mut guard = TransactionGuard::new(
303            Arc::clone(&self.tsm),
304            self.cleanup_tx.clone(),
305            tsm_mac.clone(),
306            invoke_id,
307            owner.clone(),
308            None,
309        );
310
311        let pdu = Apdu::ConfirmedRequest(ConfirmedRequestPdu {
312            segmented: false,
313            more_follows: false,
314            segmented_response_accepted: self.config.segmented_response_accepted,
315            max_segments: self.config.max_segments,
316            max_apdu_length: advertised_max_apdu,
317            invoke_id,
318            sequence_number: None,
319            proposed_window_size: None,
320            service_choice,
321            service_request: Bytes::copy_from_slice(service_data),
322        });
323
324        let mut buf = BytesMut::with_capacity(6 + service_data.len());
325        encode_apdu(&mut buf, &pdu)?;
326
327        if let Some(forwarded_npci_len) = routed_forwarded_npci_len {
328            self.routed_path_limits.install_active(
329                target,
330                tsm_mac.clone(),
331                invoke_id,
332                owner.clone(),
333                forwarded_npci_len,
334                self.network.network_control_ingress_sequence(),
335            );
336        }
337        if !self.routed_path_limits.authorize_attempt(
338            target,
339            &tsm_mac,
340            invoke_id,
341            &owner,
342            buf.len(),
343        ) {
344            guard.mark_completed();
345            self.tsm
346                .lock()
347                .await
348                .cancel_transaction_for_owner(&tsm_mac, invoke_id, &owner);
349            self.enqueue_transaction_cleanup(&tsm_mac, invoke_id, &owner, false, None);
350            return Err(Error::Encoding(
351                "routed send lost its active path authorization".into(),
352            ));
353        }
354
355        if let Err(e) = self.send_confirmed_target_apdu(target, &buf).await {
356            guard.mark_completed();
357            let mut tsm = self.tsm.lock().await;
358            tsm.cancel_transaction_for_owner(&tsm_mac, invoke_id, &owner);
359            drop(tsm);
360            self.enqueue_transaction_cleanup(&tsm_mac, invoke_id, &owner, false, None);
361            return Err(e);
362        }
363
364        let response = self
365            .wait_for_confirmed_response(
366                ConfirmedWait {
367                    target,
368                    tsm_mac: &tsm_mac,
369                    invoke_id,
370                    owner: &owner,
371                    retry_apdu: Some(&buf),
372                },
373                registration.response,
374                registration.progress,
375            )
376            .await;
377        if response.is_ok()
378            && let Some(lease) = path_lease.as_ref()
379        {
380            lease.mark_terminal_observed();
381        }
382        guard.mark_completed();
383        response.and_then(Self::confirmed_response_result)
384    }
385
386    /// Wait through AWAIT_CONFIRMATION and SEGMENTED_CONF without allowing
387    /// their timers to cancel each other's phase.
388    pub(super) async fn wait_for_confirmed_response(
389        &self,
390        wait_for: ConfirmedWait<'_>,
391        mut response_rx: oneshot::Receiver<TsmResponse>,
392        mut progress_rx: ProgressReceiver,
393    ) -> Result<TsmResponse, Error> {
394        let ConfirmedWait {
395            target,
396            tsm_mac,
397            invoke_id,
398            owner,
399            retry_apdu,
400        } = wait_for;
401        let (request_timeout, segment_timeout, max_retries) = {
402            let tsm = self.tsm.lock().await;
403            let config = tsm.config();
404            (
405                Duration::from_millis(config.apdu_timeout_ms),
406                Duration::from_millis(config.apdu_segment_timeout_ms.saturating_mul(4)),
407                config.apdu_retries,
408            )
409        };
410        let mut retries_sent = 0u16;
411        let mut progress = progress_rx.borrow_and_update();
412
413        loop {
414            let wait = match progress {
415                TransactionProgress::AwaitingResponse => request_timeout,
416                TransactionProgress::SegmentedResponse { .. } => segment_timeout,
417            };
418            let timer = tokio::time::sleep(wait);
419            tokio::pin!(timer);
420
421            tokio::select! {
422                response = &mut response_rx => {
423                    return response.map_err(|_| Error::Encoding("TSM response channel closed".into()));
424                }
425                // Progress is never published after the transaction leaves
426                // the TSM; the response arm above resolves then instead.
427                () = progress_rx.changed() => {
428                    progress = progress_rx.borrow_and_update();
429                }
430                _ = &mut timer => {
431                    match progress {
432                        TransactionProgress::AwaitingResponse => {
433                            let retry = retry_apdu.is_some()
434                                && retries_sent < u16::from(max_retries);
435                            let mut tsm = self.tsm.lock().await;
436                            match tsm.expire_request_timer(tsm_mac, invoke_id, owner, !retry) {
437                                RequestTimerExpiration::Retry => {
438                                    // Authorization and segment admission use the TSM lock,
439                                    // but transport I/O must not. Once authorized, this retry
440                                    // may finish even if segment zero is admitted meanwhile.
441                                    let Some(retry_apdu) = retry_apdu else {
442                                        tsm.cancel_transaction_for_owner(tsm_mac, invoke_id, owner);
443                                        drop(tsm);
444                                        self.enqueue_transaction_cleanup(
445                                            tsm_mac,
446                                            invoke_id,
447                                            owner,
448                                            false,
449                                            None,
450                                        );
451                                        return Err(Error::Timeout(request_timeout));
452                                    };
453                                    retries_sent += 1;
454                                    drop(tsm);
455                                    if !self.routed_path_limits.authorize_attempt(
456                                        target,
457                                        tsm_mac,
458                                        invoke_id,
459                                        owner,
460                                        retry_apdu.len(),
461                                    ) {
462                                        return (&mut response_rx).await.map_err(|_| {
463                                            Error::Encoding("TSM response channel closed".into())
464                                        });
465                                    }
466                                    let send_result = self
467                                        .send_confirmed_target_apdu(target, retry_apdu)
468                                        .await;
469                                    if let Err(error) = send_result {
470                                        let mut tsm = self.tsm.lock().await;
471                                        match tsm.expire_request_timer(tsm_mac, invoke_id, owner, true) {
472                                            RequestTimerExpiration::SegmentedResponse { generation } => {
473                                                progress = TransactionProgress::SegmentedResponse { generation };
474                                                continue;
475                                            }
476                                            RequestTimerExpiration::NoTransaction => {
477                                                drop(tsm);
478                                                return (&mut response_rx)
479                                                    .await
480                                                    .map_err(|_| Error::Encoding("TSM response channel closed".into()));
481                                            }
482                                            RequestTimerExpiration::TimedOut => {
483                                                drop(tsm);
484                                                self.enqueue_transaction_cleanup(
485                                                    tsm_mac,
486                                                    invoke_id,
487                                                    owner,
488                                                    false,
489                                                    None,
490                                                );
491                                                return Err(error);
492                                            }
493                                            RequestTimerExpiration::Retry => unreachable!(
494                                                "final retry-send failure disposition cannot authorize another retry"
495                                            ),
496                                        }
497                                    }
498                                    debug!(
499                                        invoke_id,
500                                        attempt = retries_sent,
501                                        max_retries,
502                                        "APDU timeout, retrying confirmed request"
503                                    );
504                                }
505                                RequestTimerExpiration::SegmentedResponse { generation } => {
506                                    progress = TransactionProgress::SegmentedResponse { generation };
507                                }
508                                RequestTimerExpiration::TimedOut => {
509                                    drop(tsm);
510                                    self.enqueue_transaction_cleanup(
511                                        tsm_mac,
512                                        invoke_id,
513                                        owner,
514                                        false,
515                                        None,
516                                    );
517                                    return Err(Error::abort(bacnet_types::enums::AbortReason::TSM_TIMEOUT));
518                                }
519                                RequestTimerExpiration::NoTransaction => {
520                                    drop(tsm);
521                                    return (&mut response_rx)
522                                        .await
523                                        .map_err(|_| Error::Encoding("TSM response channel closed".into()));
524                                }
525                            }
526                        }
527                        TransactionProgress::SegmentedResponse { generation } => {
528                            let mut tsm = self.tsm.lock().await;
529                            match tsm.expire_segment_timer(tsm_mac, invoke_id, owner, generation) {
530                                SegmentTimerExpiration::Activity { generation } => {
531                                    progress = TransactionProgress::SegmentedResponse { generation };
532                                }
533                                SegmentTimerExpiration::AwaitingResponse => {
534                                    progress = TransactionProgress::AwaitingResponse;
535                                }
536                                SegmentTimerExpiration::TimedOut => {
537                                    drop(tsm);
538                                    #[cfg(test)]
539                                    self.segmented_cleanup.pause_if_enabled().await;
540                                    self.enqueue_transaction_cleanup(
541                                        tsm_mac,
542                                        invoke_id,
543                                        owner,
544                                        false,
545                                        None,
546                                    );
547                                    return Err(Error::abort(bacnet_types::enums::AbortReason::TSM_TIMEOUT));
548                                }
549                                SegmentTimerExpiration::NoTransaction => {
550                                    drop(tsm);
551                                    return (&mut response_rx)
552                                        .await
553                                        .map_err(|_| Error::Encoding("TSM response channel closed".into()));
554                                }
555                            }
556                        }
557                    }
558                }
559            }
560        }
561    }
562
563    pub(super) fn enqueue_transaction_cleanup(
564        &self,
565        mac: &MacAddr,
566        invoke_id: u8,
567        owner: &TransactionOwner,
568        cancel_tsm: bool,
569        seg_ack_sender: Option<mpsc::Sender<SegmentAckPdu>>,
570    ) {
571        let _ = self.cleanup_tx.send(TransactionCleanup {
572            mac: mac.clone(),
573            invoke_id,
574            owner: owner.clone(),
575            cancel_tsm,
576            seg_ack_sender,
577        });
578    }
579
580    pub(super) fn confirmed_response_result(response: TsmResponse) -> Result<Bytes, Error> {
581        super::confirmed_response_result(response)
582    }
583
584    pub(super) async fn send_confirmed_target_apdu(
585        &self,
586        target: ConfirmedTarget<'_>,
587        apdu: &[u8],
588    ) -> Result<(), Error> {
589        match target {
590            ConfirmedTarget::Local { mac } => {
591                self.network
592                    .send_apdu(apdu, mac, true, NetworkPriority::Normal)
593                    .await
594            }
595            ConfirmedTarget::Routed {
596                router_mac,
597                dest_network,
598                dest_mac,
599            } => {
600                self.network
601                    .send_apdu_routed(
602                        apdu,
603                        dest_network,
604                        dest_mac,
605                        router_mac,
606                        true,
607                        NetworkPriority::Normal,
608                    )
609                    .await
610            }
611        }
612    }
613
614    pub(super) fn target_transport_max_apdu_length(&self, target: ConfirmedTarget<'_>) -> u16 {
615        self.network
616            .transport()
617            .egress_apdu_limit()
618            .saturating_sub(target.additional_npdu_header_len())
619    }
620
621    pub(super) fn advertised_max_apdu_length_for_target(
622        &self,
623        target: ConfirmedTarget<'_>,
624    ) -> Result<u16, Error> {
625        cap_max_apdu_to_transport(
626            self.config.max_apdu_length,
627            self.target_transport_max_apdu_length(target),
628        )
629    }
630
631    /// Send an unconfirmed request (fire-and-forget) to a specific destination.
632    pub async fn unconfirmed_request(
633        &self,
634        destination_mac: &[u8],
635        service_choice: UnconfirmedServiceChoice,
636        service_data: &[u8],
637    ) -> Result<(), Error> {
638        let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
639            service_choice,
640            service_request: Bytes::copy_from_slice(service_data),
641        });
642
643        let mut buf = BytesMut::with_capacity(2 + service_data.len());
644        encode_apdu(&mut buf, &pdu)?;
645
646        self.network
647            .send_apdu(&buf, destination_mac, false, NetworkPriority::Normal)
648            .await
649    }
650
651    /// Broadcast an unconfirmed request on the local network.
652    pub async fn broadcast_unconfirmed(
653        &self,
654        service_choice: UnconfirmedServiceChoice,
655        service_data: &[u8],
656    ) -> Result<(), Error> {
657        let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
658            service_choice,
659            service_request: Bytes::copy_from_slice(service_data),
660        });
661
662        let mut buf = BytesMut::with_capacity(2 + service_data.len());
663        encode_apdu(&mut buf, &pdu)?;
664
665        self.network
666            .broadcast_apdu(&buf, false, NetworkPriority::Normal)
667            .await
668    }
669
670    /// Broadcast an unconfirmed request globally (DNET=0xFFFF).
671    pub async fn broadcast_global_unconfirmed(
672        &self,
673        service_choice: UnconfirmedServiceChoice,
674        service_data: &[u8],
675    ) -> Result<(), Error> {
676        let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
677            service_choice,
678            service_request: Bytes::copy_from_slice(service_data),
679        });
680
681        let mut buf = BytesMut::with_capacity(2 + service_data.len());
682        encode_apdu(&mut buf, &pdu)?;
683
684        self.network
685            .broadcast_global_apdu(&buf, false, NetworkPriority::Normal)
686            .await
687    }
688
689    /// Broadcast an unconfirmed request to a specific remote network.
690    ///
691    /// Once the client has learned its own network's number, a
692    /// `dest_network` naming it is this network, and the request goes as a
693    /// local broadcast with no DNET (#1358).
694    pub async fn broadcast_network_unconfirmed(
695        &self,
696        service_choice: UnconfirmedServiceChoice,
697        service_data: &[u8],
698        dest_network: u16,
699    ) -> Result<(), Error> {
700        let pdu = Apdu::UnconfirmedRequest(bacnet_encoding::apdu::UnconfirmedRequest {
701            service_choice,
702            service_request: Bytes::copy_from_slice(service_data),
703        });
704
705        let mut buf = BytesMut::with_capacity(2 + service_data.len());
706        encode_apdu(&mut buf, &pdu)?;
707
708        if self.network.local_network_number().get() == Some(dest_network) {
709            return self
710                .network
711                .broadcast_apdu(&buf, false, NetworkPriority::Normal)
712                .await;
713        }
714        self.network
715            .broadcast_to_network(&buf, dest_network, false, NetworkPriority::Normal)
716            .await
717    }
718}
719
720#[cfg(test)]
721#[path = "requests_tests.rs"]
722mod transmittable_length_tests;