bacnet_objects/notification_forwarder/
selection.rs1use 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#[derive(Debug, Clone, Copy)]
21pub struct ForwardingInput<'a> {
22 pub process_identifier: u32,
24 pub to_state: EventState,
27 pub locally_initiated: bool,
29 pub receiving_port: Option<u8>,
32 pub today: DaysOfWeek,
34 pub current_time: &'a Time,
36}
37
38#[derive(Debug, Clone, Default, PartialEq, Eq)]
40pub struct ForwardingTargets {
41 pub forwarders: Vec<ObjectIdentifier>,
43 pub recipients: Vec<(BACnetRecipient, u32, bool)>,
46}
47
48pub 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
128fn 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
169fn 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}