Skip to main content

bacnet_server/
device_communication_control.rs

1//! Server policy types for DeviceCommunicationControl (Clause 16.1, #1964).
2
3use crate::server::DccState;
4use bacnet_encoding::npdu::NpduAddress;
5use bacnet_transport::port::TransportProvenance;
6use bacnet_types::MacAddr;
7
8/// Network identity and the requested change supplied to
9/// [`ServerPolicy::authorize_device_communication_control`](crate::ServerPolicy::authorize_device_communication_control).
10///
11/// The policy is asked only once the password, the static
12/// [`DccPolicy`](crate::server::DccPolicy) and the source restriction pass,
13/// and before the disable rate limit is charged. The password is never part of
14/// the context. `Debug` is redacted: address lengths and whether a principal
15/// is present, never addresses or certificate data.
16#[derive(Clone, PartialEq, Eq)]
17pub struct DeviceCommunicationControlAuthorizationContext {
18    /// Immediate data-link peer (or router) address.
19    pub source_mac: MacAddr,
20    /// Originating NPDU source when the request was routed.
21    pub source_network: Option<NpduAddress>,
22    /// Immutable ingress snapshot; the source of the principal, if any.
23    pub provenance: TransportProvenance,
24    /// Confirmed-request invoke identifier.
25    pub invoke_id: u8,
26    /// The state the request asks for. The deprecated DISABLE never gets
27    /// this far: the server refuses it first.
28    pub requested_state: DccState,
29    /// The requested duration in minutes; `None` asks for no time limit.
30    pub duration_minutes: Option<u16>,
31}
32
33impl DeviceCommunicationControlAuthorizationContext {
34    /// The principal of the peer that originated the request: the verified
35    /// leaf certificate of a direct BACnet/SC connection, when the request
36    /// carries no routed source. `None` for other ingress, hub-relayed and
37    /// routed requests included (see [`crate::policy`]).
38    pub fn principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
39        crate::policy::originating_principal(&self.provenance, self.source_network.as_ref())
40    }
41
42    /// The principal of the direct BACnet/SC peer that handed this request
43    /// over, a router included when the request was routed; `None` off a
44    /// verified direct SC connection. Trusting it trusts everything that
45    /// peer forwards.
46    pub fn forwarding_peer_principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
47        self.provenance.principal()
48    }
49
50    crate::policy::certificate_accessors!();
51
52    /// The immediate direct-SC hop's verified leaf and connection incarnation:
53    /// for a routed request, the router that forwarded it. Absent off a
54    /// verified direct SC connection. Separate from claimed addresses.
55    pub fn direct_sc_identity(&self) -> Option<bacnet_transport::port::DirectScIdentity> {
56        self.provenance.direct_sc_identity()
57    }
58}
59
60impl std::fmt::Debug for DeviceCommunicationControlAuthorizationContext {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        f.debug_struct("DeviceCommunicationControlAuthorizationContext")
63            .field("source_mac_len", &self.source_mac.len())
64            .field(
65                "source_network",
66                &self
67                    .source_network
68                    .as_ref()
69                    .map(|s| (s.network, s.mac_address.len())),
70            )
71            .field("provenance", &self.provenance)
72            .field("principal", &self.principal().is_some())
73            .field("invoke_id", &self.invoke_id)
74            .field("requested_state", &self.requested_state)
75            .field("duration_minutes", &self.duration_minutes)
76            .finish()
77    }
78}