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::MacAddr;
7use bacnet_types::primitives::ObjectIdentifier;
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 crate::policy::certificate_accessors!();
57}
58
59/// Transport provenance and decoded content supplied to
60/// [`ServerPolicy::authorize_unconfirmed_audit_notification`](crate::ServerPolicy::authorize_unconfirmed_audit_notification).
61///
62/// The source/target identities inside `request` are peer-reported audit data,
63/// not authenticated transport provenance. Policy should use `source_network`
64/// for a usable routed origin and `source_mac` for the immediate data-link peer.
65/// `Debug` is redacted: address lengths, the sink, whether a principal is
66/// present and the notification count, never the notifications themselves.
67#[derive(Clone, PartialEq)]
68pub struct UnconfirmedAuditNotificationAuthorizationContext {
69 /// Immediate data-link peer (normally a router for routed traffic).
70 pub source_mac: MacAddr,
71 /// Originating NPDU source when one was present.
72 pub source_network: Option<NpduAddress>,
73 /// Honest transport + origin provenance for this ingress; the source of
74 /// the principal, if any.
75 pub provenance: TransportProvenance,
76 /// Explicitly configured local Audit Log sink.
77 pub audit_log_sink: ObjectIdentifier,
78 /// Decoded peer-reported notification list, preserved verbatim.
79 pub request: AuditNotificationRequest,
80}
81
82impl UnconfirmedAuditNotificationAuthorizationContext {
83 /// The principal of the peer that originated the request: the verified
84 /// leaf certificate of a direct BACnet/SC connection, when the request
85 /// carries no routed source. `None` for other ingress, hub-relayed and
86 /// routed requests included (see [`crate::policy`]).
87 pub fn principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
88 crate::policy::originating_principal(&self.provenance, self.source_network.as_ref())
89 }
90
91 /// The principal of the direct BACnet/SC peer that handed this request
92 /// over, a router included when the request was routed; `None` off a
93 /// verified direct SC connection. Trusting it trusts everything that
94 /// peer forwards.
95 pub fn forwarding_peer_principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
96 self.provenance.principal()
97 }
98
99 crate::policy::certificate_accessors!();
100}
101
102impl std::fmt::Debug for AuditNotificationAuthorizationContext {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("AuditNotificationAuthorizationContext")
105 .field("source_mac_len", &self.source_mac.len())
106 .field(
107 "source_network",
108 &self
109 .source_network
110 .as_ref()
111 .map(|source| (source.network, source.mac_address.len())),
112 )
113 .field("provenance", &self.provenance)
114 .field("principal", &self.principal().is_some())
115 .field("invoke_id", &self.invoke_id)
116 .field("audit_log_sink", &self.audit_log_sink)
117 .field("notifications", &self.request.notifications.len())
118 .finish_non_exhaustive()
119 }
120}
121
122impl std::fmt::Debug for UnconfirmedAuditNotificationAuthorizationContext {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 f.debug_struct("UnconfirmedAuditNotificationAuthorizationContext")
125 .field("source_mac_len", &self.source_mac.len())
126 .field(
127 "source_network",
128 &self
129 .source_network
130 .as_ref()
131 .map(|source| (source.network, source.mac_address.len())),
132 )
133 .field("provenance", &self.provenance)
134 .field("principal", &self.principal().is_some())
135 .field("audit_log_sink", &self.audit_log_sink)
136 .field("notifications", &self.request.notifications.len())
137 .finish_non_exhaustive()
138 }
139}