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    /// 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 send takes `next_hop` as given: unlike
44    /// [`NetworkLayer::send_apdu`], it doesn't send an Unconfirmed-Request
45    /// for one as the link's broadcast, and nothing checks it, since a
46    /// response goes back to the MAC its request came from (B/IP refuses a
47    /// group address there, #1677).
48    ///
49    /// Used by server transaction owners whose lifetime ends at local issuance,
50    /// independently of the transport future's eventual Result.
51    pub async fn send_apdu_on_issuance(
52        &self,
53        apdu: &[u8],
54        next_hop: &[u8],
55        destination: Option<&NpduAddress>,
56        expecting_reply: bool,
57        priority: NetworkPriority,
58        on_issuance: impl FnOnce() + Send,
59    ) -> Result<(), Error> {
60        self.send_response_apdu_on_issuance(
61            IssuedApdu {
62                apdu,
63                next_hop,
64                destination,
65                expecting_reply,
66                priority,
67            },
68            &crate::response_route::ResponseRoute::unverified(),
69            on_issuance,
70        )
71        .await
72    }
73
74    /// Encode and issue a response using its immutable ingress route.
75    ///
76    /// Direct responses use only the matching original direct socket. A
77    /// missing, invalid or stale capability cannot fall back to ordinary unicast.
78    /// The callback has the same local issuance contract as
79    /// [`Self::send_apdu_on_issuance`]; neither issuance nor success proves receipt.
80    pub async fn send_response_apdu_on_issuance(
81        &self,
82        issued: IssuedApdu<'_>,
83        route: &crate::response_route::ResponseRoute,
84        on_issuance: impl FnOnce() + Send,
85    ) -> Result<(), Error> {
86        let IssuedApdu {
87            apdu,
88            next_hop,
89            destination,
90            expecting_reply,
91            priority,
92        } = issued;
93        crate::response_route::check_response_destination(destination)?;
94        if destination.is_some_and(|destination| destination.mac_address.is_empty()) {
95            check_broadcast_apdu(apdu, "a destination with no DADR (a remote broadcast)")?;
96        }
97        let buf = crate::response_route::encode_checked_response_npdu(
98            apdu,
99            destination,
100            expecting_reply,
101            priority,
102        )?;
103        let direct = route.direct()?;
104        on_issuance();
105        if let Some(direct) = direct {
106            return direct.send(&buf, &self.response_scope).await;
107        }
108        self.transport
109            .send_unicast_with_data_attributes(&buf, next_hop, &[])
110            .await
111    }
112}