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 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}