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::{encode_npdu, Npdu, NpduAddress};
4use bacnet_transport::port::{DirectResponse, TransportProvenance};
5use bacnet_types::{enums::NetworkPriority, error::Error};
6use bytes::{Bytes, BytesMut};
7
8/// Saved response route, independent of the request's claimed BACnet addresses.
9///
10/// Verified direct ingress requires a matching original-socket capability.
11/// Missing or mismatched capability fails closed; other ingress retains normal
12/// addressing. Clones do not retain a socket or extend its membership lifetime.
13#[derive(Clone, Debug)]
14pub struct ResponseRoute {
15    provenance: TransportProvenance,
16    direct: Option<DirectResponse>,
17}
18
19impl ResponseRoute {
20    /// Capture immutable ingress provenance and its optional direct capability.
21    /// A supplied capability is checked against provenance at every send.
22    pub fn new(provenance: TransportProvenance, direct: Option<DirectResponse>) -> Self {
23        Self { provenance, direct }
24    }
25
26    /// Plain, non-direct addressing for locally constructed legacy envelopes.
27    pub fn unverified() -> Self {
28        Self::new(TransportProvenance::unverified(), None)
29    }
30
31    /// Original authentication scope; this never follows a replacement peer.
32    pub fn provenance(&self) -> TransportProvenance {
33        self.provenance
34    }
35
36    /// Cap an APDU by the original direct peer's NPDU/BVLC receive limits,
37    /// including the actual local or routed response NPDU header. Non-direct
38    /// ingress keeps the supplied APDU cap. Invalid authority/addressing is an
39    /// error; retirement does not invalidate this immutable sizing snapshot.
40    pub fn max_apdu_length(
41        &self,
42        apdu_limit: u16,
43        destination: Option<&NpduAddress>,
44    ) -> Result<u16, Error> {
45        let Some(direct) = self.direct()? else {
46            return Ok(apdu_limit);
47        };
48        let header = encode_response_npdu(&[], destination, false, NetworkPriority::NORMAL)?;
49        let available = direct.max_npdu_length().saturating_sub(header.len());
50        Ok(usize::from(apdu_limit).min(available) as u16)
51    }
52
53    pub(crate) fn direct(&self) -> Result<Option<&DirectResponse>, bacnet_types::error::Error> {
54        match (self.provenance.direct_sc_identity(), self.direct.as_ref()) {
55            (Some(identity), Some(route)) if identity == route.identity() => Ok(Some(route)),
56            (None, None) => Ok(None),
57            _ => Err(bacnet_types::error::Error::Transport(std::io::Error::new(
58                std::io::ErrorKind::PermissionDenied,
59                "missing or mismatched original direct response capability",
60            ))),
61        }
62    }
63}
64
65impl ReceivedApdu {
66    /// Save this ingress's response route before moving it into queued work.
67    pub fn response_route(&self) -> ResponseRoute {
68        ResponseRoute::new(self.provenance, self.direct_response.clone())
69    }
70}
71
72/// Shared framing for budget calculation and actual response issuance.
73pub(crate) fn encode_response_npdu(
74    apdu: &[u8],
75    destination: Option<&NpduAddress>,
76    expecting_reply: bool,
77    priority: NetworkPriority,
78) -> Result<BytesMut, Error> {
79    check_response_destination(destination)?;
80    encode_checked_response_npdu(apdu, destination, expecting_reply, priority)
81}
82
83/// Refuse a response `destination` on network 0 or 0xFFFF.
84pub(crate) fn check_response_destination(destination: Option<&NpduAddress>) -> Result<(), Error> {
85    match destination {
86        Some(destination) => crate::layer::check_destination(
87            destination.network,
88            "pass no destination (no DNET) for a local peer",
89        ),
90        None => Ok(()),
91    }
92}
93
94/// [`encode_response_npdu`] once [`check_response_destination`] has passed.
95pub(crate) fn encode_checked_response_npdu(
96    apdu: &[u8],
97    destination: Option<&NpduAddress>,
98    expecting_reply: bool,
99    priority: NetworkPriority,
100) -> Result<BytesMut, Error> {
101    let npdu = Npdu {
102        destination: destination.cloned(),
103        expecting_reply,
104        priority,
105        payload: Bytes::copy_from_slice(apdu),
106        ..Npdu::default()
107    };
108    let mut encoded = BytesMut::new();
109    encode_npdu(&mut encoded, &npdu)?;
110    Ok(encoded)
111}