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}