Skip to main content

bacnet_objects/notification_forwarder/
selection.rs

1//! Which event notifications each forwarder takes, and the destinations it
2//! sends them to (Clause 12.51). See [`forwarding_targets`].
3
4use crate::prelude::*;
5use bacnet_encoding::constructed::{
6    decode_event_notification_subscription, decode_port_permission,
7};
8use bacnet_types::bitstring::DaysOfWeek;
9use bacnet_types::constructed::BACnetRecipient;
10use bacnet_types::enums::{EventState, ObjectType, PropertyIdentifier};
11use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, Time};
12
13use crate::database::ObjectDatabase;
14use crate::event::EventTransition;
15use crate::notification_class::filter_recipient_list;
16use crate::subscribed_recipients::MAX_SUBSCRIBED_RECIPIENTS;
17use crate::traits::BACnetObject;
18
19/// One event notification offered to this device's forwarders.
20#[derive(Debug, Clone, Copy)]
21pub struct ForwardingInput<'a> {
22    /// The process identifier the notification carries.
23    pub process_identifier: u32,
24    /// The event state after the transition. Its transition (to-offnormal,
25    /// to-fault or to-normal) is what Recipient_List entries filter on.
26    pub to_state: EventState,
27    /// Whether one of this device's own objects generated the notification.
28    pub locally_initiated: bool,
29    /// The port the notification arrived through, by its Clause 6 port ID,
30    /// or `None` when this device handed it to its forwarders itself.
31    pub receiving_port: Option<u8>,
32    /// The current local day, for Recipient_List day filters.
33    pub today: DaysOfWeek,
34    /// The current local time, for Recipient_List time windows.
35    pub current_time: &'a Time,
36}
37
38/// Where this device's forwarders send one notification.
39#[derive(Debug, Clone, Default, PartialEq, Eq)]
40pub struct ForwardingTargets {
41    /// The forwarders that took the notification.
42    pub forwarders: Vec<ObjectIdentifier>,
43    /// Each destination once, as recipient, process identifier and whether
44    /// to confirm, in forwarder then list order.
45    pub recipients: Vec<(BACnetRecipient, u32, bool)>,
46}
47
48/// Select the forwarders that take `input` and their destinations, leaving
49/// out the forwarders in `skip` (those that already took this notification
50/// earlier in a chain within this device).
51///
52/// Every NOTIFICATION_FORWARDER object in the database is read through its
53/// properties, so an application's own forwarder type takes part as the
54/// bundled one does. A forwarder takes a notification when:
55///
56/// - it is in service;
57/// - its Process_Identifier_Filter is NULL or equals the notification's
58///   process identifier;
59/// - its Local_Forwarding_Only is FALSE, or the notification came from one of
60///   this device's own objects;
61/// - for a notification that arrived through a network port, its Port_Filter
62///   enables that port. A forwarder without Port_Filter takes from the one
63///   port a device that does not route has.
64///
65/// A forwarder that cannot serve one of these properties as the clause types
66/// it takes nothing. Its destinations are the Recipient_List entries whose
67/// days, times and transitions admit the notification, filtered as a
68/// Notification Class filters its own, and every live Subscribed_Recipients
69/// entry. A list that does not decode, or runs past its cap, contributes
70/// nothing rather than a part of itself.
71///
72/// The network rules that keep forwarded copies from looping (no copies by
73/// global broadcast, none back onto the network a broadcast came from) need
74/// each destination's route, so the server applies them as it sends.
75pub fn forwarding_targets(
76    db: &ObjectDatabase,
77    input: &ForwardingInput<'_>,
78    skip: &[ObjectIdentifier],
79) -> ForwardingTargets {
80    let mut selected = ForwardingTargets::default();
81    for oid in db.find_by_type(ObjectType::NOTIFICATION_FORWARDER) {
82        if skip.contains(&oid) {
83            continue;
84        }
85        let Some(forwarder) = db.get(&oid) else {
86            continue;
87        };
88        if !takes(forwarder, input) {
89            continue;
90        }
91        selected.forwarders.push(oid);
92        for destination in destinations(forwarder, input) {
93            if !selected.recipients.contains(&destination) {
94                selected.recipients.push(destination);
95            }
96        }
97    }
98    selected
99}
100
101fn takes(forwarder: &dyn BACnetObject, input: &ForwardingInput<'_>) -> bool {
102    let read = |property| forwarder.read_property(property, None);
103    if matches!(
104        read(PropertyIdentifier::OUT_OF_SERVICE),
105        Ok(PropertyValue::Boolean(true))
106    ) {
107        return false;
108    }
109    let process_matches = match read(PropertyIdentifier::PROCESS_IDENTIFIER_FILTER) {
110        Ok(PropertyValue::Null) => true,
111        Ok(PropertyValue::Unsigned(filter)) => filter == u64::from(input.process_identifier),
112        _ => false,
113    };
114    let origin_allowed = match read(PropertyIdentifier::LOCAL_FORWARDING_ONLY) {
115        Ok(PropertyValue::Boolean(local_only)) => !local_only || input.locally_initiated,
116        _ => false,
117    };
118    let port_enabled = match input.receiving_port {
119        None => true,
120        Some(port) => match read(PropertyIdentifier::PORT_FILTER) {
121            Ok(value) => port_enabled(&value, port),
122            Err(_) => true,
123        },
124    };
125    process_matches && origin_allowed && port_enabled
126}
127
128/// Whether a served Port_Filter value enables `port`. A port the array does
129/// not name, or an array that does not decode, is not enabled.
130fn port_enabled(value: &PropertyValue, port: u8) -> bool {
131    let elements: &[PropertyValue] = match value {
132        PropertyValue::List(elements) => elements,
133        single => core::slice::from_ref(single),
134    };
135    elements.iter().any(|element| {
136        let PropertyValue::ApplicationData(bytes) = element else {
137            return false;
138        };
139        matches!(
140            decode_port_permission(bytes, 0),
141            Ok((permission, end)) if end == bytes.len()
142                && permission.port_id == port
143                && permission.enabled
144        )
145    })
146}
147
148fn destinations(
149    forwarder: &dyn BACnetObject,
150    input: &ForwardingInput<'_>,
151) -> Vec<(BACnetRecipient, u32, bool)> {
152    let mut destinations = match forwarder.read_property(PropertyIdentifier::RECIPIENT_LIST, None) {
153        Ok(value) => filter_recipient_list(
154            &value,
155            EventTransition::for_target_state(input.to_state),
156            input.today,
157            input.current_time,
158        ),
159        Err(_) => Vec::new(),
160    };
161    if let Ok(PropertyValue::ApplicationData(bytes)) =
162        forwarder.read_property(PropertyIdentifier::SUBSCRIBED_RECIPIENTS, None)
163    {
164        destinations.extend(subscribed(&bytes));
165    }
166    destinations
167}
168
169/// The live entries of a served Subscribed_Recipients, or none when the list
170/// does not decode or holds more than [`MAX_SUBSCRIBED_RECIPIENTS`] entries.
171fn subscribed(bytes: &[u8]) -> Vec<(BACnetRecipient, u32, bool)> {
172    let mut entries = Vec::new();
173    let mut offset = 0;
174    while offset < bytes.len() {
175        if entries.len() == MAX_SUBSCRIBED_RECIPIENTS {
176            return Vec::new();
177        }
178        let Ok((entry, next)) = decode_event_notification_subscription(bytes, offset) else {
179            return Vec::new();
180        };
181        offset = next;
182        if entry.time_remaining > 0 {
183            entries.push((
184                entry.recipient,
185                entry.process_identifier,
186                entry.issue_confirmed_notifications,
187            ));
188        }
189    }
190    entries
191}