bacnet_objects/notification_forwarder/
selection.rs1use 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#[derive(Debug, Clone, Copy)]
20pub struct ForwardingInput<'a> {
21 pub process_identifier: u32,
23 pub to_state: EventState,
26 pub locally_initiated: bool,
28 pub receiving_port: Option<u8>,
31 pub today: DaysOfWeek,
33 pub current_time: &'a Time,
35}
36
37#[derive(Debug, Clone, Default, PartialEq, Eq)]
39pub struct ForwardingTargets {
40 pub forwarders: Vec<ObjectIdentifier>,
42 pub recipients: Vec<(BACnetRecipient, u32, bool)>,
45}
46
47pub 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
127fn 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
168fn 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}