bacnet_server/server/event_forwarding.rs
1//! Event notifications through this device's Notification Forwarder objects
2//! (Clause 12.51).
3//!
4//! Two paths reach the forwarders. A ConfirmedEventNotification or
5//! UnconfirmedEventNotification the server receives is offered to them as it
6//! arrived. A notification one of this device's own objects generates is
7//! offered when its Notification Class names this device's Device object as
8//! a recipient, with that recipient's process identifier. Each forwarder that
9//! takes the notification ([`forwarding_targets`]) sends it on to its
10//! destinations, with only the process identifier changed, through the
11//! server's one send path.
12//!
13//! A destination that names this device again hands the copy to the
14//! forwarders that have not yet taken this notification, so forwarders can
15//! chain within the device without a copy going round twice. Across such a
16//! chain each destination (recipient, process identifier and confirmation)
17//! gets one copy, however many forwarders name it.
18//!
19//! A received notification that no forwarder takes is still acknowledged
20//! when it came confirmed; it counts in
21//! [`EventNotificationCounters::received_not_forwarded`].
22//!
23//! One notification goes to at most [`MAX_FORWARDED_DESTINATIONS`]
24//! destinations across every forwarder in such a chain, counting only the
25//! copies the loop and route rules let through ([`ForwardingBudget`]); the
26//! rest are dropped and counted in
27//! [`EventNotificationCounters::forwarding_cap_dropped`]. A local
28//! notification that names this device's Device object under several
29//! process identifiers is one notification, with one cap.
30//!
31//! The loop rules that need a destination's route are applied as each copy is
32//! sent ([`ForwardOrigin::admits`]). The server is one node on one network, so a
33//! received notification always arrives through Port_ID 0, and its network is
34//! the local one. Once the server knows that network's number, configured on
35//! the registered Network Port or learned from Network-Number-Is with or
36//! without one, the send path reads it from the network layer and turns a
37//! route naming it into a local route ([`RecipientRoute::localize`]). The loop
38//! rules see that local route, and the copy goes with no DNET.
39
40use super::event_recipient_route::{RecipientRoute, system_utc_recipient_filter_time};
41use super::event_send::OutboundNotification;
42use super::event_suppression::EventSuppression;
43use super::*;
44use bacnet_encoding::constructed::encode_event_notification;
45use bacnet_objects::notification_class::MAX_RECIPIENT_LIST_DESTINATIONS;
46use bacnet_objects::notification_forwarder::{ForwardingInput, forwarding_targets};
47use bacnet_objects::subscribed_recipients::MAX_SUBSCRIBED_RECIPIENTS;
48use bacnet_services::alarm_event::ForwardedEventNotification;
49use bacnet_types::constructed::BACnetRecipient;
50use std::sync::atomic::AtomicUsize;
51
52/// The Port_ID of the one network port this server receives through: a node
53/// that does not route numbers its port 0 (Clause 12.51.11).
54const RECEIVING_PORT: u8 = 0;
55
56/// The most destinations one notification is forwarded to, across every
57/// Notification Forwarder in this device that takes it: as many as one
58/// forwarder's full Recipient_List and Subscribed_Recipients name, so no
59/// single forwarder is cut short. It bounds how far one received notification
60/// can multiply. Only copies that would be sent count: a destination the
61/// loop rules refuse, whose route is skipped, or whose copy is too large is
62/// not. Destinations naming this device's own Device object, which hand the
63/// notification on within the device, do not count either. The destinations
64/// past the cap count in [`EventNotificationCounters::forwarding_cap_dropped`].
65pub const MAX_FORWARDED_DESTINATIONS: usize =
66 MAX_RECIPIENT_LIST_DESTINATIONS + MAX_SUBSCRIBED_RECIPIENTS;
67
68/// What is left of one notification's [`MAX_FORWARDED_DESTINATIONS`],
69/// shared by every copy it makes. The send path takes room for a copy only
70/// once the copy passed every rule and is about to go out.
71pub(super) struct ForwardingBudget {
72 remaining: AtomicUsize,
73 dropped: AtomicUsize,
74}
75
76impl ForwardingBudget {
77 fn new() -> Self {
78 Self {
79 remaining: AtomicUsize::new(MAX_FORWARDED_DESTINATIONS),
80 dropped: AtomicUsize::new(0),
81 }
82 }
83
84 /// Take room for one more copy, or, when none is left, note it as
85 /// dropped and return `false`. Each take is one atomic update, and no
86 /// other memory is ordered by it, so relaxed ordering is enough, a copy
87 /// that waited for its device's I-Am taking room from its own task
88 /// included (#1368).
89 pub(super) fn take(&self) -> bool {
90 let taken = self
91 .remaining
92 .try_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_sub(1))
93 .is_ok();
94 if !taken {
95 self.dropped.fetch_add(1, Ordering::Relaxed);
96 }
97 taken
98 }
99
100 fn dropped(&self) -> usize {
101 self.dropped.load(Ordering::Relaxed)
102 }
103}
104
105/// How a notification reached this device's forwarders.
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub(super) enum ForwardOrigin {
108 /// One of this device's own objects generated it.
109 Local,
110 /// It arrived from the network.
111 Received(Reception),
112}
113
114/// How a received notification was addressed.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub(super) struct Reception {
117 /// Sent to a broadcast or multicast address, so every node on the
118 /// receiving network already has it.
119 pub(super) group: bool,
120 /// Sent by global broadcast.
121 pub(super) global: bool,
122}
123
124impl Reception {
125 /// A notification addressed to this device alone.
126 pub(super) const UNICAST: Self = Self {
127 group: false,
128 global: false,
129 };
130
131 /// The addressing of an unconfirmed request as the network layer saw it.
132 pub(super) fn of(received: &bacnet_network::layer::ReceivedApdu) -> Self {
133 Self {
134 group: received.is_group,
135 global: received.global_broadcast,
136 }
137 }
138}
139
140/// Where a route takes a copy, as the loop rules see it.
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142enum Reach {
143 /// Every network, by global broadcast.
144 Everywhere,
145 /// Every node on the local network.
146 LocalBroadcast,
147 /// One node on the local network.
148 LocalNode,
149 /// A remote network, or a node on one.
150 Elsewhere,
151}
152
153impl Reach {
154 /// Classify `route`. The send path has already turned a route naming
155 /// the local network's number into a local one
156 /// ([`RecipientRoute::localize`]), so a route that still names a network
157 /// names a remote one.
158 fn of(route: &RecipientRoute) -> Self {
159 match route {
160 RecipientRoute::GlobalBroadcast => Self::Everywhere,
161 RecipientRoute::LocalBroadcast => Self::LocalBroadcast,
162 RecipientRoute::LocalUnicast(_) | RecipientRoute::BoundLocalUnicast { .. } => {
163 Self::LocalNode
164 }
165 _ => Self::Elsewhere,
166 }
167 }
168}
169
170impl ForwardOrigin {
171 /// Whether a forwarded copy may go where `reach` says (Clause 12.51). No
172 /// copy goes by global broadcast. A received notification is not
173 /// broadcast back onto the network it arrived from, and one that arrived
174 /// by broadcast goes to no node on that network, since each already has
175 /// it.
176 fn admits(self, reach: Reach) -> bool {
177 match (self, reach) {
178 (_, Reach::Everywhere) => false,
179 (ForwardOrigin::Local, _) => true,
180 (ForwardOrigin::Received(_), Reach::LocalBroadcast) => false,
181 (ForwardOrigin::Received(reception), Reach::LocalNode) => !reception.group,
182 (ForwardOrigin::Received(_), Reach::Elsewhere) => true,
183 }
184 }
185
186 fn receiving_port(self) -> Option<u8> {
187 match self {
188 ForwardOrigin::Local => None,
189 ForwardOrigin::Received(_) => Some(RECEIVING_PORT),
190 }
191 }
192}
193
194/// Split off the recipients that name `local_device`, returning their
195/// distinct process identifiers.
196fn take_local(
197 recipients: &mut Vec<(BACnetRecipient, u32, bool)>,
198 local_device: ObjectIdentifier,
199) -> Vec<u32> {
200 let mut local = Vec::new();
201 recipients.retain(|(recipient, process_identifier, _)| {
202 let is_local = *recipient == BACnetRecipient::Device(local_device);
203 if is_local && !local.contains(process_identifier) {
204 local.push(*process_identifier);
205 }
206 !is_local
207 });
208 local
209}
210
211impl<T: TransportPort + 'static> BACnetServer<T> {
212 /// Send a notification this device generated to the recipients its
213 /// Notification Class selected. Recipients naming this device's own
214 /// Device object take it through the local forwarders instead of the
215 /// network.
216 pub(super) async fn deliver_local_notification(
217 ctx: &EventDelivery<'_, T>,
218 notification: EventNotificationRequest,
219 recipients: &[(BACnetRecipient, u32, bool)],
220 ) {
221 let mut remote = recipients.to_vec();
222 let local = take_local(&mut remote, notification.initiating_device_identifier);
223 if !remote.is_empty() {
224 let encode_for = |process_identifier| {
225 let mut targeted = notification.clone();
226 targeted.process_identifier = process_identifier;
227 let mut buf = BytesMut::new();
228 encode_event_notification(&targeted, &mut buf).map(|()| buf.freeze())
229 };
230 let outbound = OutboundNotification {
231 notification_class: notification.notification_class,
232 priority: notification.priority,
233 encode_for: &encode_for,
234 admits: Arc::new(|_| true),
235 budget: None,
236 };
237 Self::send_event_notification(ctx, &outbound, &remote).await;
238 }
239 if local.is_empty() {
240 return;
241 }
242 let forwarded = match ForwardedEventNotification::from_request(¬ification) {
243 Ok(forwarded) => forwarded,
244 Err(error) => {
245 warn!(%error, "Failed to encode EventNotification for local forwarding");
246 return;
247 }
248 };
249 // One notification, whatever number of process identifiers names
250 // this device: one cap, and one copy per destination.
251 let offered = local
252 .into_iter()
253 .map(|process_identifier| forwarded.retargeted(process_identifier))
254 .collect();
255 Self::forward_event_notification(ctx, offered, ForwardOrigin::Local).await;
256 }
257
258 /// Offer one notification, as `offered` gives it to this device's
259 /// forwarders (one entry per process identifier it reaches them under),
260 /// and send each copy they ask for, up to [`MAX_FORWARDED_DESTINATIONS`]
261 /// destinations in all. Returns once every unconfirmed copy is sent and
262 /// every confirmed one is handed to its notification worker; it never
263 /// reports a delivery outcome to the caller.
264 pub(super) async fn forward_event_notification(
265 ctx: &EventDelivery<'_, T>,
266 offered: Vec<ForwardedEventNotification>,
267 origin: ForwardOrigin,
268 ) {
269 if matches!(origin, ForwardOrigin::Received(reception) if reception.global) {
270 debug!("Forwarders ignore an event notification sent by global broadcast");
271 return;
272 }
273 if ctx.comm_state.initiation_restricted() {
274 return;
275 }
276 let mut taken = Vec::new();
277 let mut sent: Vec<(BACnetRecipient, u32, bool)> = Vec::new();
278 // Shared with the copies that wait for their device's I-Am, which
279 // draw on it when they go out (#1368).
280 let budget = Arc::new(ForwardingBudget::new());
281 // A stack, so each entry's hand-offs within the device run before the
282 // next entry; reversed so the entries run in the order given.
283 let mut pending = offered;
284 pending.reverse();
285 while let Some(notification) = pending.pop() {
286 let (local_device, mut recipients) = {
287 let db = ctx.db.read().await;
288 let system_utc = std::time::SystemTime::now()
289 .duration_since(std::time::UNIX_EPOCH)
290 .unwrap_or_default();
291 let (today, current_time) = match db.clock_frame() {
292 Some(frame) if frame.is_valid_actual_datetime() => (
293 frame
294 .day_of_week()
295 .expect("validated ClockFrame has a day of week"),
296 frame.local_time,
297 ),
298 _ => system_utc_recipient_filter_time(system_utc),
299 };
300 let input = ForwardingInput {
301 process_identifier: notification.process_identifier,
302 to_state: notification.to_state,
303 locally_initiated: origin == ForwardOrigin::Local,
304 receiving_port: origin.receiving_port(),
305 today,
306 current_time: ¤t_time,
307 };
308 let targets = forwarding_targets(&db, &input, &taken);
309 if taken.is_empty()
310 && targets.forwarders.is_empty()
311 && matches!(origin, ForwardOrigin::Received(_))
312 {
313 debug!(
314 process_identifier = notification.process_identifier,
315 "No Notification Forwarder takes the received event notification"
316 );
317 ctx.suppressions
318 .record(EventSuppression::ReceivedNotForwarded);
319 }
320 taken.extend(targets.forwarders);
321 (db.selected_device(), targets.recipients)
322 };
323 if let Some(local_device) = local_device {
324 for process_identifier in take_local(&mut recipients, local_device) {
325 pending.push(notification.retargeted(process_identifier));
326 }
327 }
328 recipients.retain(|destination| !sent.contains(destination));
329 if recipients.is_empty() {
330 continue;
331 }
332 sent.extend(recipients.iter().cloned());
333 let encode_for = |process_identifier| Ok(notification.encode_for(process_identifier));
334 let outbound = OutboundNotification {
335 notification_class: notification.notification_class,
336 priority: notification.priority,
337 encode_for: &encode_for,
338 admits: Arc::new(move |route: &RecipientRoute| origin.admits(Reach::of(route))),
339 budget: Some(Arc::clone(&budget)),
340 };
341 Self::send_event_notification(ctx, &outbound, &recipients).await;
342 }
343 // A copy still waiting for its device's I-Am draws on the cap later;
344 // one dropped then is counted but not in this warning.
345 // One warning per interval for this server, with the capped
346 // notifications since the last; the rest log at debug (#1950).
347 let dropped = budget.dropped();
348 if dropped > 0 {
349 warn_throttled!(
350 ctx.suppressions.cap_warning,
351 dropped,
352 cap = MAX_FORWARDED_DESTINATIONS,
353 "Event notification reached the forwarding cap; destinations dropped"
354 );
355 }
356 }
357}