bacnet_server/audit_notification.rs
1//! Receiver policy for confirmed and unconfirmed AuditNotification services.
2
3use bacnet_encoding::npdu::NpduAddress;
4use bacnet_services::audit::AuditNotificationRequest;
5use bacnet_transport::port::TransportProvenance;
6use bacnet_types::primitives::ObjectIdentifier;
7use bacnet_types::MacAddr;
8
9/// Local maximum accepted AuditNotification service payload.
10pub const MAX_AUDIT_NOTIFICATION_BYTES: usize = 64 * 1024;
11/// Local maximum number of notifications in one accepted request.
12pub const MAX_AUDIT_NOTIFICATIONS: usize = 256;
13
14/// Transport provenance and decoded content supplied to
15/// [`ServerPolicy::authorize_audit_notification`](crate::ServerPolicy::authorize_audit_notification).
16///
17/// The source/target identities inside `request` are peer-reported audit data,
18/// not authenticated transport provenance. Policy should use `source_network`
19/// for a usable routed origin and `source_mac` for the immediate data-link peer.
20/// `Debug` is redacted: address lengths, the sink, whether a principal is
21/// present and the notification count, never the notifications themselves.
22#[derive(Clone, PartialEq)]
23pub struct AuditNotificationAuthorizationContext {
24 /// Immediate data-link peer (normally a router for routed traffic).
25 pub source_mac: MacAddr,
26 /// Originating NPDU source when one was present.
27 pub source_network: Option<NpduAddress>,
28 /// Honest transport + origin provenance for this ingress; the source of
29 /// the principal, if any.
30 pub provenance: TransportProvenance,
31 /// Outer Confirmed-Request invoke identifier.
32 pub invoke_id: u8,
33 /// Explicitly configured local Audit Log sink.
34 pub audit_log_sink: ObjectIdentifier,
35 /// Decoded peer-reported notification list, preserved verbatim.
36 pub request: AuditNotificationRequest,
37}
38
39impl AuditNotificationAuthorizationContext {
40 /// The principal of the peer that originated the request: the verified
41 /// leaf certificate of a direct BACnet/SC connection, when the request
42 /// carries no routed source. `None` for other ingress, hub-relayed and
43 /// routed requests included (see [`crate::policy`]).
44 pub fn principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
45 crate::policy::originating_principal(self.provenance, self.source_network.as_ref())
46 }
47
48 /// The principal of the direct BACnet/SC peer that handed this request
49 /// over, a router included when the request was routed; `None` off a
50 /// verified direct SC connection. Trusting it trusts everything that
51 /// peer forwards.
52 pub fn forwarding_peer_principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
53 self.provenance.principal()
54 }
55}
56
57/// Transport provenance and decoded content supplied to
58/// [`ServerPolicy::authorize_unconfirmed_audit_notification`](crate::ServerPolicy::authorize_unconfirmed_audit_notification).
59///
60/// The source/target identities inside `request` are peer-reported audit data,
61/// not authenticated transport provenance. Policy should use `source_network`
62/// for a usable routed origin and `source_mac` for the immediate data-link peer.
63/// `Debug` is redacted: address lengths, the sink, whether a principal is
64/// present and the notification count, never the notifications themselves.
65#[derive(Clone, PartialEq)]
66pub struct UnconfirmedAuditNotificationAuthorizationContext {
67 /// Immediate data-link peer (normally a router for routed traffic).
68 pub source_mac: MacAddr,
69 /// Originating NPDU source when one was present.
70 pub source_network: Option<NpduAddress>,
71 /// Honest transport + origin provenance for this ingress; the source of
72 /// the principal, if any.
73 pub provenance: TransportProvenance,
74 /// Explicitly configured local Audit Log sink.
75 pub audit_log_sink: ObjectIdentifier,
76 /// Decoded peer-reported notification list, preserved verbatim.
77 pub request: AuditNotificationRequest,
78}
79
80impl UnconfirmedAuditNotificationAuthorizationContext {
81 /// The principal of the peer that originated the request: the verified
82 /// leaf certificate of a direct BACnet/SC connection, when the request
83 /// carries no routed source. `None` for other ingress, hub-relayed and
84 /// routed requests included (see [`crate::policy`]).
85 pub fn principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
86 crate::policy::originating_principal(self.provenance, self.source_network.as_ref())
87 }
88
89 /// The principal of the direct BACnet/SC peer that handed this request
90 /// over, a router included when the request was routed; `None` off a
91 /// verified direct SC connection. Trusting it trusts everything that
92 /// peer forwards.
93 pub fn forwarding_peer_principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
94 self.provenance.principal()
95 }
96}
97
98impl std::fmt::Debug for AuditNotificationAuthorizationContext {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 f.debug_struct("AuditNotificationAuthorizationContext")
101 .field("source_mac_len", &self.source_mac.len())
102 .field(
103 "source_network",
104 &self
105 .source_network
106 .as_ref()
107 .map(|source| (source.network, source.mac_address.len())),
108 )
109 .field("provenance", &self.provenance)
110 .field("principal", &self.principal().is_some())
111 .field("invoke_id", &self.invoke_id)
112 .field("audit_log_sink", &self.audit_log_sink)
113 .field("notifications", &self.request.notifications.len())
114 .finish_non_exhaustive()
115 }
116}
117
118impl std::fmt::Debug for UnconfirmedAuditNotificationAuthorizationContext {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 f.debug_struct("UnconfirmedAuditNotificationAuthorizationContext")
121 .field("source_mac_len", &self.source_mac.len())
122 .field(
123 "source_network",
124 &self
125 .source_network
126 .as_ref()
127 .map(|source| (source.network, source.mac_address.len())),
128 )
129 .field("provenance", &self.provenance)
130 .field("principal", &self.principal().is_some())
131 .field("audit_log_sink", &self.audit_log_sink)
132 .field("notifications", &self.request.notifications.len())
133 .finish_non_exhaustive()
134 }
135}