Skip to main content

bacnet_objects/network_port/
pending.rs

1//! Writes that wait for activation: a BBMD port's
2//! BBMD_Broadcast_Distribution_Table and BBMD_Accept_FD_Registrations
3//! (Clause 12.56.34, 12.56.35, #1578) and, behind a NAT router,
4//! BACnet_IP_NAT_Traversal and BACnet_IP_Global_Address (Clause 12.56.32,
5//! 12.56.33, #940), and a foreign device's
6//! FD_BBMD_Address and FD_Subscription_Lifetime (Clause 12.56.37,
7//! 12.56.38, #1825).
8//!
9//! A write doesn't take effect at once: the value waits, Changes_Pending
10//! reads TRUE, and a read of the property returns the value written, while
11//! the transport keeps working with the old one, so a Read-BDT answers with
12//! the BDT in use and the foreign device keeps registering where it did
13//! (Clause 12.56.13, 12.56.34). A ReinitializeDevice ACTIVATE_CHANGES or
14//! WARMSTART puts the waiting values into effect through the transport
15//! ([`NetworkPortObject::activate_changes`]).
16//!
17//! A written value is checked by the transport when it is written, so one
18//! it would refuse is refused then and changes nothing: a BBMD's value on
19//! its own (a BDT against the row policy, a global address as an IPv4
20//! unicast host), and a BBMD address and time-to-live as the registration
21//! they would make together with the other value waiting. A BBMD's values
22//! are checked together at activation (a NAT BBMD's BDT must list its
23//! global address and be two-hop only), so they can be written in any
24//! order. The rows take
25//! writes only when the port's owner answers ReinitializeDevice
26//! (`set_activation_internal`); otherwise a written value could never take
27//! effect, and the write is WRITE_ACCESS_DENIED.
28
29use 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/// The values written to a port and waiting for activation. Only the
39/// current mode's half is ever set: publishing a mode clears both.
40#[derive(Debug, Clone, Default, PartialEq, Eq)]
41pub(super) struct Pending {
42    /// A BBMD's rows.
43    bbmd: BbmdChanges,
44    /// A foreign device's rows.
45    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    /// The value written to `property` and waiting for activation, if any.
56    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    /// Take a write to a property the port's mode lets clients change;
85    /// `None` for any other property, or a port in another mode.
86    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        // A value nothing could activate would wait for ever: without an
108        // owner that answers ReinitializeDevice, or a transport that takes
109        // changes, the rows stay read-only (Clause 16.4 ties pending changes
110        // to ACTIVATE_CHANGES).
111        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                // The value written, alone.
120                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                // Checked alone; the rules that tie the values together (a
143                // NAT BBMD's BDT lists its global address) wait for
144                // activation, so they can be written in any order.
145                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                        // Unsigned16 (Clause 12.56.38).
157                        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                // The pair is checked as it would register, so a value that
164                // only works with the other one waiting is judged with it.
165                decoded
166                    .and_then(|()| registration.check(&changes))
167                    .map(|()| self.pending.foreign = changes)
168            }
169            // No row is writable in NORMAL mode, so this isn't reached.
170            LiveMode::Normal => Err(common::write_access_denied_error()),
171        })
172    }
173
174    /// Whether the pending values would still be taken, changing nothing:
175    /// the check a ReinitializeDevice makes before its handler runs.
176    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    /// The group written to a BBMD whose tables view takes it, waiting
189    /// with the BBMD rows (#1963).
190    pub(super) fn pending_group(&self) -> Option<[u8; 4]> {
191        self.pending.bbmd.multicast_group
192    }
193
194    /// Leave `group` waiting with the BBMD rows.
195    pub(super) fn set_pending_group(&mut self, group: [u8; 4]) {
196        self.pending.bbmd.multicast_group = Some(group);
197    }
198
199    /// Whether written values wait for activation: Changes_Pending.
200    pub(super) fn changes_pending(&self) -> bool {
201        !self.pending.is_empty() || self.multicast.is_pending()
202    }
203
204    /// Whether written values can be activated, so the pending-activation
205    /// rows take writes: a ReinitializeDevice owner activates them, and the
206    /// transport takes them.
207    pub(super) fn activates_changes(&self) -> bool {
208        self.activation && self.mode.can_change()
209    }
210
211    /// Put the values written to this port into effect, as a
212    /// ReinitializeDevice ACTIVATE_CHANGES or WARMSTART does (Clause
213    /// 12.56.13), and clear Changes_Pending. Whether there were any.
214    ///
215    /// The transport takes all of them or none: when it refuses, the error
216    /// comes back and they stay pending. Every waiting value is checked
217    /// first, so a refused multicast group leaves the mode's values waiting
218    /// too; the mode's values then take effect, and the group last.
219    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        // Only a NORMAL port's group waits apart from the mode's values, and
225        // NORMAL has no mode rows to write, so the two halves never wait
226        // together and the order below can't leave one half applied and the
227        // other refused. A BBMD's group waits with its rows, since its own
228        // BDT row and its group go together (Annex J.8.3), and goes in with
229        // them in one activation (#1963).
230        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    /// Put the mode's waiting values into effect, and clear them.
242    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            // A port whose mode changed dropped its pending values then.
247            LiveMode::Normal => return Err(common::write_access_denied_error()),
248        }
249        self.pending = Pending::default();
250        Ok(())
251    }
252}
253
254/// Add `written`'s values to `pending`.
255fn 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
273/// A written FD_BBMD_Address or BACnet_IP_Global_Address: exactly one
274/// BACnetHostNPort.
275fn 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
282/// The rows a write leaves pending for activation, in whichever mode has
283/// them.
284pub(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
294/// The two NAT rows (Clause 12.56.32, 12.56.33).
295pub(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    /// Whether this port serves the NAT rows: an IPv4 port in BBMD mode
304    /// whose transport can run behind a NAT router.
305    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}