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}