bacnet_objects/network_port/
pending.rs1use bacnet_types::bip_port::{BbmdChanges, ForeignDeviceChanges};
30use bacnet_types::enums::PropertyIdentifier as P;
31use bacnet_types::error::Error;
32use bacnet_types::primitives::PropertyValue;
33
34use super::NetworkPortObject;
35use super::bip_mode::{LiveMode, bdt_value, host_n_port_value};
36use crate::common;
37
38#[derive(Debug, Clone, Default, PartialEq, Eq)]
41pub(super) struct Pending {
42 bbmd: BbmdChanges,
44 foreign: ForeignDeviceChanges,
46}
47
48impl Pending {
49 fn is_empty(&self) -> bool {
50 self.bbmd.is_empty() && self.foreign.is_empty()
51 }
52}
53
54impl NetworkPortObject {
55 pub(super) fn pending_value(&self, property: P) -> Option<Result<PropertyValue, Error>> {
57 let pending = &self.pending;
58 match property {
59 P::BBMD_BROADCAST_DISTRIBUTION_TABLE => pending
60 .bbmd
61 .broadcast_distribution_table
62 .as_deref()
63 .map(bdt_value),
64 P::BBMD_ACCEPT_FD_REGISTRATIONS => pending
65 .bbmd
66 .accept_foreign_device_registrations
67 .map(|accept| Ok(PropertyValue::Boolean(accept))),
68 P::BACNET_IP_NAT_TRAVERSAL => pending
69 .bbmd
70 .nat_traversal
71 .map(|nat| Ok(PropertyValue::Boolean(nat))),
72 P::BACNET_IP_GLOBAL_ADDRESS => {
73 pending.bbmd.global_address.as_ref().map(host_n_port_value)
74 }
75 P::FD_BBMD_ADDRESS => pending.foreign.bbmd.as_ref().map(host_n_port_value),
76 P::FD_SUBSCRIPTION_LIFETIME => pending
77 .foreign
78 .subscription_lifetime
79 .map(|ttl| Ok(PropertyValue::Unsigned(ttl.into()))),
80 _ => None,
81 }
82 }
83
84 pub(super) fn write_pending(
87 &mut self,
88 property: P,
89 array_index: Option<u32>,
90 value: &PropertyValue,
91 ) -> Option<Result<(), Error>> {
92 let row = match &self.mode {
93 LiveMode::Bbmd(_) => {
94 matches!(
95 property,
96 P::BBMD_BROADCAST_DISTRIBUTION_TABLE | P::BBMD_ACCEPT_FD_REGISTRATIONS
97 ) || (nat_row(property) && self.serves_nat())
98 }
99 LiveMode::Foreign(_) => {
100 matches!(property, P::FD_BBMD_ADDRESS | P::FD_SUBSCRIPTION_LIFETIME)
101 }
102 LiveMode::Normal => false,
103 };
104 if !row {
105 return None;
106 }
107 if !self.activates_changes() {
112 return Some(Err(common::write_access_denied_error()));
113 }
114 if array_index.is_some() {
115 return Some(Err(common::property_is_not_an_array_error()));
116 }
117 Some(match &self.mode {
118 LiveMode::Bbmd(tables) => {
119 let mut changes = BbmdChanges::default();
121 let decoded = match (property, value) {
122 (
123 P::BBMD_BROADCAST_DISTRIBUTION_TABLE,
124 PropertyValue::ApplicationData(octets),
125 ) => bacnet_encoding::constructed::decode_bdt_entry_list(octets)
126 .map_err(|_| common::invalid_data_encoding_error())
127 .map(|table| changes.broadcast_distribution_table = Some(table)),
128 (P::BBMD_ACCEPT_FD_REGISTRATIONS, PropertyValue::Boolean(accept)) => {
129 changes.accept_foreign_device_registrations = Some(*accept);
130 Ok(())
131 }
132 (P::BACNET_IP_NAT_TRAVERSAL, PropertyValue::Boolean(nat)) => {
133 changes.nat_traversal = Some(*nat);
134 Ok(())
135 }
136 (P::BACNET_IP_GLOBAL_ADDRESS, PropertyValue::ApplicationData(octets)) => {
137 decode_host_n_port(octets)
138 .map(|global| changes.global_address = Some(global))
139 }
140 _ => Err(common::invalid_data_type_error()),
141 };
142 decoded
146 .and_then(|()| tables.check_value(&changes))
147 .map(|()| merge(&mut self.pending.bbmd, changes))
148 }
149 LiveMode::Foreign(registration) => {
150 let mut changes = self.pending.foreign.clone();
151 let decoded = match (property, value) {
152 (P::FD_BBMD_ADDRESS, PropertyValue::ApplicationData(octets)) => {
153 decode_host_n_port(octets).map(|bbmd| changes.bbmd = Some(bbmd))
154 }
155 (P::FD_SUBSCRIPTION_LIFETIME, PropertyValue::Unsigned(ttl)) => {
156 u16::try_from(*ttl)
158 .map(|ttl| changes.subscription_lifetime = Some(ttl))
159 .map_err(|_| common::value_out_of_range_error())
160 }
161 _ => Err(common::invalid_data_type_error()),
162 };
163 decoded
166 .and_then(|()| registration.check(&changes))
167 .map(|()| self.pending.foreign = changes)
168 }
169 LiveMode::Normal => Err(common::write_access_denied_error()),
171 })
172 }
173
174 pub fn check_pending_changes(&self) -> Result<(), Error> {
177 self.check_multicast()?;
178 if self.pending.is_empty() {
179 return Ok(());
180 }
181 match &self.mode {
182 LiveMode::Bbmd(tables) => tables.check(&self.pending.bbmd),
183 LiveMode::Foreign(registration) => registration.check(&self.pending.foreign),
184 LiveMode::Normal => Err(common::write_access_denied_error()),
185 }
186 }
187
188 pub(super) fn pending_group(&self) -> Option<[u8; 4]> {
191 self.pending.bbmd.multicast_group
192 }
193
194 pub(super) fn set_pending_group(&mut self, group: [u8; 4]) {
196 self.pending.bbmd.multicast_group = Some(group);
197 }
198
199 pub(super) fn changes_pending(&self) -> bool {
201 !self.pending.is_empty() || self.multicast.is_pending()
202 }
203
204 pub(super) fn activates_changes(&self) -> bool {
208 self.activation && self.mode.can_change()
209 }
210
211 pub fn activate_changes(&mut self) -> Result<bool, Error> {
220 if !self.changes_pending() {
221 return Ok(false);
222 }
223 self.check_pending_changes()?;
224 debug_assert!(
231 self.pending.is_empty() || !self.multicast.is_pending(),
232 "mode values and a multicast group pending together"
233 );
234 if !self.pending.is_empty() {
235 self.activate_mode_changes()?;
236 }
237 self.activate_multicast()?;
238 Ok(true)
239 }
240
241 fn activate_mode_changes(&mut self) -> Result<(), Error> {
243 match &self.mode {
244 LiveMode::Bbmd(tables) => tables.activate(&self.pending.bbmd)?,
245 LiveMode::Foreign(registration) => registration.activate(&self.pending.foreign)?,
246 LiveMode::Normal => return Err(common::write_access_denied_error()),
248 }
249 self.pending = Pending::default();
250 Ok(())
251 }
252}
253
254fn merge(pending: &mut BbmdChanges, written: BbmdChanges) {
256 if written.broadcast_distribution_table.is_some() {
257 pending.broadcast_distribution_table = written.broadcast_distribution_table;
258 }
259 if written.accept_foreign_device_registrations.is_some() {
260 pending.accept_foreign_device_registrations = written.accept_foreign_device_registrations;
261 }
262 if written.nat_traversal.is_some() {
263 pending.nat_traversal = written.nat_traversal;
264 }
265 if written.global_address.is_some() {
266 pending.global_address = written.global_address;
267 }
268 if written.multicast_group.is_some() {
269 pending.multicast_group = written.multicast_group;
270 }
271}
272
273fn decode_host_n_port(octets: &[u8]) -> Result<bacnet_types::constructed::BACnetHostNPort, Error> {
276 match bacnet_encoding::constructed::decode_host_n_port(octets, 0) {
277 Ok((address, end)) if end == octets.len() => Ok(address),
278 _ => Err(common::invalid_data_encoding_error()),
279 }
280}
281
282pub(super) fn writable_pending_row(property: P) -> bool {
285 matches!(
286 property,
287 P::BBMD_BROADCAST_DISTRIBUTION_TABLE
288 | P::BBMD_ACCEPT_FD_REGISTRATIONS
289 | P::FD_BBMD_ADDRESS
290 | P::FD_SUBSCRIPTION_LIFETIME
291 ) || nat_row(property)
292}
293
294pub(super) fn nat_row(property: P) -> bool {
296 matches!(
297 property,
298 P::BACNET_IP_NAT_TRAVERSAL | P::BACNET_IP_GLOBAL_ADDRESS
299 )
300}
301
302impl NetworkPortObject {
303 pub(super) fn serves_nat(&self) -> bool {
306 self.ip
307 .as_ref()
308 .is_some_and(super::ip_config::IpConfig::is_ipv4)
309 && self.mode.has_nat_rows()
310 }
311}