Skip to main content

bacnet_network/
layer_issuance.rs

1use super::*;
2
3/// One APDU handed to a local-issuance send.
4#[derive(Debug, Clone, Copy)]
5pub struct IssuedApdu<'a> {
6    /// Encoded APDU to wrap in an NPDU.
7    pub apdu: &'a [u8],
8    /// Local data-link destination.
9    pub next_hop: &'a [u8],
10    /// Routed NPDU destination (DNET/DADR), when the peer is behind a router.
11    pub destination: Option<&'a NpduAddress>,
12    /// Whether the NPDU sets the data-expecting-reply flag.
13    pub expecting_reply: bool,
14    /// Network priority of the NPDU.
15    pub priority: NetworkPriority,
16}
17
18impl<T: TransportPort> NetworkLayer<T> {
19    /// Encode a prompt data-link response after checking both this network
20    /// owner and the captured router/direct authority. The caller hands these
21    /// bytes to its one-shot DER sender without an intervening await.
22    pub fn encode_response_for_handoff(
23        &self,
24        issued: IssuedApdu<'_>,
25        route: &crate::response_route::ResponseRoute,
26    ) -> Result<BytesMut, Error> {
27        if !self.response_scope.guard().is_open() {
28            return Err(Error::Transport(std::io::Error::new(
29                std::io::ErrorKind::NotConnected,
30                "response network owner unavailable",
31            )));
32        }
33        route.encode_reply_npdu(
34            issued.apdu,
35            issued.destination,
36            issued.expecting_reply,
37            issued.priority,
38        )
39    }
40
41    /// Seal original-route response issuance before joining owned server work.
42    /// Queued writes observe this irreversible seal; writes already started
43    /// remain bounded but cannot be retracted. Network stop/drop also seals.
44    pub fn seal_responses(&self) {
45        self.response_scope.seal();
46    }
47}
48
49impl<T: TransportPort + 'static> NetworkLayer<T> {
50    /// Issue one local unicast operation after encoding its complete NPDU.
51    ///
52    /// `destination` selects routed NPDU addressing; `next_hop` is always the
53    /// local data-link destination. The synchronous callback runs exactly once
54    /// after encoding succeeds, immediately before calling/polling the transport
55    /// send. Constructing this future does not call it. Encoding failure never
56    /// calls it; later transport failure does not undo issuance. This boundary
57    /// does not establish physical emission or remote receipt. A `destination`
58    /// on network 0 or 0xFFFF is an encoding failure: this unicast reaches
59    /// one next hop, and a global broadcast goes out through
60    /// [`NetworkLayer::broadcast_global_apdu`] instead (#1380). A
61    /// `destination` with no DADR is a remote broadcast, which carries only
62    /// an Unconfirmed-Request APDU (Clause 6.3), so any other APDU to it is
63    /// an encoding failure too (#1479). With no `destination`, a `next_hop`
64    /// that is the link's broadcast or another group address is a local
65    /// broadcast as well, but this send takes `next_hop` as given: unlike
66    /// [`NetworkLayer::send_apdu`], it doesn't send an Unconfirmed-Request
67    /// for one as the link's broadcast, and nothing checks it, since a
68    /// response goes back to the MAC its request came from (B/IP refuses a
69    /// group address there, #1677).
70    ///
71    /// Used by server transaction owners whose lifetime ends at local issuance,
72    /// independently of the transport future's eventual Result.
73    pub async fn send_apdu_on_issuance(
74        &self,
75        apdu: &[u8],
76        next_hop: &[u8],
77        destination: Option<&NpduAddress>,
78        expecting_reply: bool,
79        priority: NetworkPriority,
80        on_issuance: impl FnOnce() + Send,
81    ) -> Result<(), Error> {
82        crate::response_route::check_response_destination(destination)?;
83        if destination.is_some_and(|destination| destination.mac_address.is_empty()) {
84            check_broadcast_apdu(apdu, "a destination with no DADR (a remote broadcast)")?;
85        }
86        let bytes = crate::response_route::encode_checked_response_npdu(
87            apdu,
88            destination,
89            expecting_reply,
90            priority,
91        )?;
92        on_issuance();
93        self.transport
94            .send_unicast_with_data_attributes(&bytes, next_hop, &[])
95            .await
96    }
97
98    /// Encode and issue a response using its immutable ingress route.
99    ///
100    /// Router replies use the captured port/peer and addressed source identity;
101    /// direct responses require the matching original socket as well. Missing
102    /// or retired authority never falls back to ordinary unicast. Router replies
103    /// wait for their bounded send queue: cancellation before selection sends
104    /// nothing and drops the callback. The callback is owned by that queue and
105    /// runs when its transport send starts, with the local issuance contract of
106    /// [`Self::send_apdu_on_issuance`]; neither issuance nor success proves receipt.
107    ///
108    /// A server's request handler carries this future for every reply, so it
109    /// is kept small (#1713): not an `async fn`, which would copy `issued`
110    /// and `on_issuance` into locals of their own, and `issued` is read in
111    /// place.
112    #[allow(
113        clippy::manual_async_fn,
114        reason = "an async fn would hold its moved arguments twice (#1713)"
115    )]
116    pub fn send_response_apdu_on_issuance<'a>(
117        &'a self,
118        issued: IssuedApdu<'a>,
119        route: &'a crate::response_route::ResponseRoute,
120        on_issuance: impl FnOnce() + Send + 'static,
121    ) -> impl std::future::Future<Output = Result<(), Error>> + Send + 'a {
122        async move {
123            let destination = issued.destination;
124            crate::response_route::check_response_destination(destination)?;
125            if destination.is_some_and(|destination| destination.mac_address.is_empty()) {
126                check_broadcast_apdu(
127                    issued.apdu,
128                    "a destination with no DADR (a remote broadcast)",
129                )?;
130            }
131            let buf = route.encode(
132                issued.apdu,
133                destination,
134                issued.expecting_reply,
135                issued.priority,
136            )?;
137            let direct = route.direct()?;
138            // The direct (BACnet/SC) sends are boxed: inline, each is larger
139            // than the transport send, and a server's request handler would
140            // carry it for every reply (#1713).
141            if let Some(router) = route.router() {
142                // Retire the router route before either authority path; the
143                // temporary lifetime owner holds no engine, queue or socket.
144                let lifetime = router.lifetime()?;
145                if let Some(direct) = direct {
146                    on_issuance();
147                    return Box::pin(direct.send_with_owner(
148                        &buf,
149                        &self.response_scope,
150                        &lifetime.direct.guard(),
151                    ))
152                    .await;
153                }
154                drop(lifetime);
155                return router
156                    .send(
157                        buf.freeze(),
158                        issued.priority,
159                        self.response_scope.guard(),
160                        on_issuance,
161                    )
162                    .await;
163            }
164            on_issuance();
165            if let Some(direct) = direct {
166                return Box::pin(direct.send(&buf, &self.response_scope)).await;
167            }
168            self.transport
169                .send_unicast_with_data_attributes(&buf, issued.next_hop, &[])
170                .await
171        }
172    }
173}