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