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 /// Seal original-direct response issuance before joining owned server work.
20 /// Queued writes observe this irreversible seal; writes already started
21 /// remain bounded but cannot be retracted. Network stop/drop also seals.
22 pub fn seal_responses(&self) {
23 self.response_scope.seal();
24 }
25}
26
27impl<T: TransportPort + 'static> NetworkLayer<T> {
28 /// Issue one local unicast operation after encoding its complete NPDU.
29 ///
30 /// `destination` selects routed NPDU addressing; `next_hop` is always the
31 /// local data-link destination. The synchronous callback runs exactly once
32 /// after encoding succeeds, immediately before calling/polling the transport
33 /// send. Constructing this future does not call it. Encoding failure never
34 /// calls it; later transport failure does not undo issuance. This boundary
35 /// does not establish physical emission or remote receipt. A `destination`
36 /// on network 0 or 0xFFFF is an encoding failure: this unicast reaches
37 /// one next hop, and a global broadcast goes out through
38 /// [`NetworkLayer::broadcast_global_apdu`] instead (#1380). A
39 /// `destination` with no DADR is a remote broadcast, which carries only
40 /// an Unconfirmed-Request APDU (Clause 6.3), so any other APDU to it is
41 /// an encoding failure too (#1479). With no `destination`, a `next_hop`
42 /// that is the link's broadcast or another group address is a local
43 /// broadcast as well, but this layer doesn't ask the transport on every
44 /// unicast, so that one isn't checked, as with
45 /// [`NetworkLayer::send_apdu`]: a response goes back to the MAC its
46 /// request came from.
47 ///
48 /// Used by server transaction owners whose lifetime ends at local issuance,
49 /// independently of the transport future's eventual Result.
50 pub async fn send_apdu_on_issuance(
51 &self,
52 apdu: &[u8],
53 next_hop: &[u8],
54 destination: Option<&NpduAddress>,
55 expecting_reply: bool,
56 priority: NetworkPriority,
57 on_issuance: impl FnOnce() + Send,
58 ) -> Result<(), Error> {
59 self.send_response_apdu_on_issuance(
60 IssuedApdu {
61 apdu,
62 next_hop,
63 destination,
64 expecting_reply,
65 priority,
66 },
67 &crate::response_route::ResponseRoute::unverified(),
68 on_issuance,
69 )
70 .await
71 }
72
73 /// Encode and issue a response using its immutable ingress route.
74 ///
75 /// Direct responses use only the matching original direct socket. A
76 /// missing, invalid or stale capability cannot fall back to ordinary unicast.
77 /// The callback has the same local issuance contract as
78 /// [`Self::send_apdu_on_issuance`]; neither issuance nor success proves receipt.
79 pub async fn send_response_apdu_on_issuance(
80 &self,
81 issued: IssuedApdu<'_>,
82 route: &crate::response_route::ResponseRoute,
83 on_issuance: impl FnOnce() + Send,
84 ) -> Result<(), Error> {
85 let IssuedApdu {
86 apdu,
87 next_hop,
88 destination,
89 expecting_reply,
90 priority,
91 } = issued;
92 crate::response_route::check_response_destination(destination)?;
93 if destination.is_some_and(|destination| destination.mac_address.is_empty()) {
94 check_broadcast_apdu(apdu, "a destination with no DADR (a remote broadcast)")?;
95 }
96 let buf = crate::response_route::encode_checked_response_npdu(
97 apdu,
98 destination,
99 expecting_reply,
100 priority,
101 )?;
102 let direct = route.direct()?;
103 on_issuance();
104 if let Some(direct) = direct {
105 return direct.send(&buf, &self.response_scope).await;
106 }
107 self.transport
108 .send_unicast_with_data_attributes(&buf, next_hop, &[])
109 .await
110 }
111}