Skip to main content

bacnet_network/
response_route.rs

1//! Original-ingress authority for an application response.
2use crate::layer::ReceivedApdu;
3use bacnet_encoding::npdu::{Npdu, NpduAddress, encode_npdu};
4use bacnet_npdu::NetworkPriority;
5use bacnet_transport::port::{DirectResponse, TransportProvenance};
6use bacnet_types::error::Error;
7use bytes::{Bytes, BytesMut};
8
9#[cfg(test)]
10#[path = "response_encode_tests.rs"]
11mod encode_tests;
12
13#[cfg(test)]
14#[path = "response_budget_tests.rs"]
15mod budget_tests;
16
17#[cfg(test)]
18mod size_tests {
19    use super::*;
20    use bacnet_transport::port::ReceivedNpdu;
21
22    /// Every received APDU is moved through two channels and into a
23    /// handler, and its response route is saved with each confirmed
24    /// request. The direct and router capabilities are one pointer each
25    /// (#1713); inline they were 88 bytes apiece. Shrink a type that grows
26    /// past its limit rather than raise the limit.
27    #[test]
28    fn received_envelopes_and_routes_stay_small() {
29        let sizes = [
30            ("ReceivedApdu", std::mem::size_of::<ReceivedApdu>(), 208),
31            ("ReceivedNpdu", std::mem::size_of::<ReceivedNpdu>(), 160),
32            ("ResponseRoute", std::mem::size_of::<ResponseRoute>(), 64),
33        ];
34        for (name, size, limit) in sizes {
35            assert!(size <= limit, "{name} is {size} bytes, over {limit}");
36        }
37    }
38}
39
40/// Saved transport authority and addressing for a response.
41///
42/// Verified direct ingress requires a matching original-socket capability.
43/// Missing or mismatched capability fails closed. Router delivery also retains
44/// its addressed endpoint and original return port/peer. Clones own neither the
45/// router nor a socket and do not extend either lifetime.
46#[derive(Clone, Debug)]
47pub struct ResponseRoute {
48    provenance: TransportProvenance,
49    direct: Option<DirectResponse>,
50    router: Option<crate::router::RouterResponse>,
51}
52
53impl ResponseRoute {
54    /// Capture immutable ingress provenance and its optional direct capability.
55    /// A supplied capability is checked against provenance at every send.
56    pub fn new(provenance: TransportProvenance, direct: Option<DirectResponse>) -> Self {
57        Self {
58            provenance,
59            direct,
60            router: None,
61        }
62    }
63
64    /// Plain, non-direct addressing for locally constructed legacy envelopes.
65    pub fn unverified() -> Self {
66        Self::new(TransportProvenance::unverified(), None)
67    }
68
69    /// Original authentication scope; this never follows a replacement peer.
70    pub fn provenance(&self) -> TransportProvenance {
71        self.provenance.clone()
72    }
73
74    /// Cap an APDU by the captured router port and direct peer NPDU/BVLC limits,
75    /// including both source and destination headers actually used by a reply.
76    /// Ordinary ingress keeps the supplied APDU cap. Invalid authority/addressing
77    /// is an error; retirement does not invalidate this immutable sizing snapshot.
78    pub fn max_apdu_length(
79        &self,
80        apdu_limit: u16,
81        destination: Option<&NpduAddress>,
82    ) -> Result<u16, Error> {
83        let direct = self.direct()?;
84        let header = self.header_len(destination)?;
85        let mut npdu_limit = usize::MAX;
86        if let Some(router) = &self.router {
87            npdu_limit = usize::from(router.max_npdu());
88        }
89        if let Some(direct) = direct {
90            npdu_limit = npdu_limit.min(direct.max_npdu_length());
91        }
92        Ok(usize::from(apdu_limit).min(npdu_limit.saturating_sub(header)) as u16)
93    }
94
95    /// The NPCI octets [`Self::encode`] puts before a reply's APDU, found
96    /// without allocating (#1713). A header the encoder refuses, or one past
97    /// the scratch buffer, takes the owned path for its error or length.
98    fn header_len(&self, destination: Option<&NpduAddress>) -> Result<usize, Error> {
99        let mut scratch = [0u8; 64];
100        let written = match &self.router {
101            Some(router) => router
102                .plan()
103                .npdu(&[], false, NetworkPriority::Normal)
104                .encode_into(&mut scratch),
105            None => {
106                check_response_destination(destination)?;
107                response_npdu(&[], destination, false, NetworkPriority::Normal)
108                    .encode_into(&mut scratch)
109            }
110        };
111        match written {
112            Ok(len) => Ok(len),
113            Err(_) => self
114                .encode(&[], destination, false, NetworkPriority::Normal)
115                .map(|header| header.len()),
116        }
117    }
118
119    /// Whether direct-socket authority must be checked, including a malformed
120    /// context with a missing or mismatched capability.
121    pub fn requires_direct_response(&self) -> bool {
122        self.provenance.is_direct_peer() || self.direct.is_some()
123    }
124
125    /// Whether response issuance requires captured router or direct authority.
126    pub fn requires_original_route(&self) -> bool {
127        self.router.is_some() || self.provenance.is_direct_peer() || self.direct.is_some()
128    }
129
130    /// Compare the opening transport origin and router endpoint of a request.
131    /// A later segment cannot switch the addressed endpoint or router instance.
132    /// Direct capability presence is checked separately at each immediate send;
133    /// reassembly retains the opening capability for its completed request.
134    pub fn same_origin(&self, other: &Self) -> bool {
135        self.provenance == other.provenance
136            && match (&self.router, &other.router) {
137                (Some(a), Some(b)) => a.same_origin(b),
138                (None, None) => true,
139                _ => false,
140            }
141    }
142
143    /// Encode a response for an immediate MS/TP handoff. This validates all
144    /// captured authority and router lifetime before returning any bytes.
145    /// Direct SC responses must use network issuance, never the DER channel.
146    pub fn encode_reply_npdu(
147        &self,
148        apdu: &[u8],
149        destination: Option<&NpduAddress>,
150        expecting_reply: bool,
151        priority: NetworkPriority,
152    ) -> Result<BytesMut, Error> {
153        if self.direct()?.is_some() {
154            return Err(Error::Encoding(
155                "a direct response requires its original socket".into(),
156            ));
157        }
158        if let Some(router) = &self.router {
159            router.check()?;
160        }
161        self.encode(apdu, destination, expecting_reply, priority)
162    }
163
164    pub(crate) fn router(&self) -> Option<&crate::router::RouterResponse> {
165        self.router.as_ref()
166    }
167
168    /// Shared immutable plan, also used without liveness for sizing snapshots.
169    pub(crate) fn encode(
170        &self,
171        apdu: &[u8],
172        destination: Option<&NpduAddress>,
173        expecting_reply: bool,
174        priority: NetworkPriority,
175    ) -> Result<BytesMut, Error> {
176        if let Some(router) = &self.router {
177            let npdu = router.plan().npdu(apdu, expecting_reply, priority);
178            let mut encoded = BytesMut::zeroed(npdu.encoded_len());
179            npdu.encode_into(&mut encoded)
180                .map_err(|error| Error::Encoding(error.to_string()))?;
181            return Ok(encoded);
182        }
183        encode_response_npdu(apdu, destination, expecting_reply, priority)
184    }
185
186    pub(crate) fn direct(&self) -> Result<Option<&DirectResponse>, bacnet_types::error::Error> {
187        match (self.provenance.direct_sc_identity(), self.direct.as_ref()) {
188            (Some(identity), Some(route)) if identity == route.identity() => Ok(Some(route)),
189            (None, None) => Ok(None),
190            _ => Err(bacnet_types::error::Error::Transport(std::io::Error::new(
191                std::io::ErrorKind::PermissionDenied,
192                "missing or mismatched original direct response capability",
193            ))),
194        }
195    }
196}
197
198impl ReceivedApdu {
199    /// Restore the complete opening response context after reassembly.
200    pub fn set_response_route(&mut self, route: ResponseRoute) {
201        self.provenance = route.provenance;
202        self.direct_response = route.direct;
203        self.router_response = route.router;
204    }
205
206    /// Save this ingress's response route before moving it into queued work.
207    pub fn response_route(&self) -> ResponseRoute {
208        ResponseRoute {
209            provenance: self.provenance.clone(),
210            direct: self.direct_response.clone(),
211            router: self.router_response.clone(),
212        }
213    }
214}
215
216/// Shared framing for budget calculation and actual response issuance.
217pub(crate) fn encode_response_npdu(
218    apdu: &[u8],
219    destination: Option<&NpduAddress>,
220    expecting_reply: bool,
221    priority: NetworkPriority,
222) -> Result<BytesMut, Error> {
223    check_response_destination(destination)?;
224    encode_checked_response_npdu(apdu, destination, expecting_reply, priority)
225}
226
227/// Refuse a response `destination` on network 0 or 0xFFFF.
228pub(crate) fn check_response_destination(destination: Option<&NpduAddress>) -> Result<(), Error> {
229    match destination {
230        Some(destination) => crate::layer::check_destination(
231            destination.network,
232            "pass no destination (no DNET) for a local peer",
233        ),
234        None => Ok(()),
235    }
236}
237
238/// [`encode_response_npdu`] once [`check_response_destination`] has passed.
239pub(crate) fn encode_checked_response_npdu(
240    apdu: &[u8],
241    destination: Option<&NpduAddress>,
242    expecting_reply: bool,
243    priority: NetworkPriority,
244) -> Result<BytesMut, Error> {
245    // Encoded straight from the borrowed APDU into a buffer of the NPDU's
246    // size: no copy of the APDU first, no regrowth (#1713).
247    let npdu = response_npdu(apdu, destination, expecting_reply, priority);
248    let mut encoded = BytesMut::zeroed(npdu.encoded_len());
249    if npdu.encode_into(&mut encoded).is_ok() {
250        return Ok(encoded);
251    }
252    // A refused NPDU reports the owned encoder's error.
253    let npdu = Npdu {
254        destination: destination.cloned(),
255        expecting_reply,
256        priority,
257        payload: Bytes::copy_from_slice(apdu),
258        ..Npdu::default()
259    };
260    let mut encoded = BytesMut::new();
261    encode_npdu(&mut encoded, &npdu)?;
262    Ok(encoded)
263}
264
265/// The NPDU a reply carrying `apdu` to `destination` is.
266fn response_npdu<'a>(
267    apdu: &'a [u8],
268    destination: Option<&'a NpduAddress>,
269    expecting_reply: bool,
270    priority: NetworkPriority,
271) -> bacnet_npdu::Npdu<'a> {
272    let mut npdu = bacnet_npdu::Npdu::apdu(apdu);
273    npdu.destination = destination.map(|destination| bacnet_npdu::Address {
274        network: destination.network,
275        mac: &destination.mac_address,
276    });
277    npdu.expecting_reply = expecting_reply;
278    npdu.priority = priority;
279    npdu
280}