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::bip_mode::{bdt_value, host_n_port_value, LiveMode};
35use super::NetworkPortObject;
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 changes_pending(&self) -> bool {
190 !self.pending.is_empty() || self.multicast.is_pending()
191 }
192
193 pub(super) fn activates_changes(&self) -> bool {
197 self.activation && self.mode.can_change()
198 }
199
200 pub fn activate_changes(&mut self) -> Result<bool, Error> {
209 if !self.changes_pending() {
210 return Ok(false);
211 }
212 self.check_pending_changes()?;
213 debug_assert!(
220 self.pending.is_empty() || !self.multicast.is_pending(),
221 "mode values and a multicast group pending together"
222 );
223 if !self.pending.is_empty() {
224 self.activate_mode_changes()?;
225 }
226 self.activate_multicast()?;
227 Ok(true)
228 }
229
230 fn activate_mode_changes(&mut self) -> Result<(), Error> {
232 match &self.mode {
233 LiveMode::Bbmd(tables) => tables.activate(&self.pending.bbmd)?,
234 LiveMode::Foreign(registration) => registration.activate(&self.pending.foreign)?,
235 LiveMode::Normal => return Err(common::write_access_denied_error()),
237 }
238 self.pending = Pending::default();
239 Ok(())
240 }
241}
242
243fn merge(pending: &mut BbmdChanges, written: BbmdChanges) {
245 if written.broadcast_distribution_table.is_some() {
246 pending.broadcast_distribution_table = written.broadcast_distribution_table;
247 }
248 if written.accept_foreign_device_registrations.is_some() {
249 pending.accept_foreign_device_registrations = written.accept_foreign_device_registrations;
250 }
251 if written.nat_traversal.is_some() {
252 pending.nat_traversal = written.nat_traversal;
253 }
254 if written.global_address.is_some() {
255 pending.global_address = written.global_address;
256 }
257}
258
259fn decode_host_n_port(octets: &[u8]) -> Result<bacnet_types::constructed::BACnetHostNPort, Error> {
262 match bacnet_encoding::constructed::decode_host_n_port(octets, 0) {
263 Ok((address, end)) if end == octets.len() => Ok(address),
264 _ => Err(common::invalid_data_encoding_error()),
265 }
266}
267
268pub(super) fn writable_pending_row(property: P) -> bool {
271 matches!(
272 property,
273 P::BBMD_BROADCAST_DISTRIBUTION_TABLE
274 | P::BBMD_ACCEPT_FD_REGISTRATIONS
275 | P::FD_BBMD_ADDRESS
276 | P::FD_SUBSCRIPTION_LIFETIME
277 ) || nat_row(property)
278}
279
280pub(super) fn nat_row(property: P) -> bool {
282 matches!(
283 property,
284 P::BACNET_IP_NAT_TRAVERSAL | P::BACNET_IP_GLOBAL_ADDRESS
285 )
286}
287
288impl NetworkPortObject {
289 pub(super) fn serves_nat(&self) -> bool {
292 self.ip
293 .as_ref()
294 .is_some_and(super::ip_config::IpConfig::is_ipv4)
295 && self.mode.has_nat_rows()
296 }
297}