Skip to main content

bacnet_objects/database/
network_port.rs

1//! Explicit receiving-port reservation and publication for one owned B/IP
2//! or B/IPv6 link. Both IP versions share one registration path; only the
3//! address types differ.
4use super::*;
5use crate::network_port::{
6    Bip6Bind, Bip6PortConfig, BipPortConfig, NetworkPortObject, PortAddress, PortBind,
7};
8use bacnet_types::bip_port::BipPortMode;
9use std::sync::Weak;
10
11pub(super) struct NetworkPortRegistration {
12    oid: ObjectIdentifier,
13    lease: Weak<()>,
14    /// The configured address and UDP port the reservation checked.
15    configured: PortAddress,
16    published: bool,
17}
18
19impl ObjectDatabase {
20    fn builtin_network_port_mut(
21        &mut self,
22        oid: &ObjectIdentifier,
23    ) -> Option<&mut NetworkPortObject> {
24        // Reflect the stored dyn object, never the Box and never an adapter hook.
25        self.objects
26            .get_mut(oid)?
27            .as_mut()
28            .as_stored_any_mut(crate::traits::ObjectStorageAccess(()))
29            .downcast_mut::<NetworkPortObject>()
30            .filter(|port| port.object_identifier() == *oid)
31    }
32
33    /// Snapshot only the concrete built-in object; adapters cannot impersonate it.
34    #[doc(hidden)]
35    pub fn configured_bip_port_internal(
36        &mut self,
37        oid: &ObjectIdentifier,
38    ) -> Option<BipPortConfig> {
39        self.builtin_network_port_mut(oid)?
40            .configuration_internal()
41            .cloned()
42    }
43
44    /// Snapshot only the concrete built-in B/IPv6 object.
45    fn configured_bip6_port_internal(&mut self, oid: &ObjectIdentifier) -> Option<Bip6PortConfig> {
46        self.builtin_network_port_mut(oid)?
47            .bip6_configuration_internal()
48            .cloned()
49    }
50
51    /// Reserve exactly one concrete built-in B/IP object before bind, in any
52    /// B/IP mode. The returned token must move into the transport before it
53    /// can start.
54    #[doc(hidden)]
55    pub fn reserve_bip_port_internal(
56        &mut self,
57        oid: ObjectIdentifier,
58        ip: [u8; 4],
59        udp: u16,
60    ) -> Result<(BipPortConfig, Arc<()>), Error> {
61        let lease = self.reserve_port(oid, PortAddress::V4(ip, udp))?;
62        let configured = self
63            .configured_bip_port_internal(&oid)
64            .expect("reserved B/IP port");
65        Ok((configured, lease))
66    }
67
68    /// Reserve exactly one concrete built-in B/IPv6 object before bind, in
69    /// any mode, as [`reserve_bip_port_internal`](Self::reserve_bip_port_internal)
70    /// does for B/IP.
71    #[doc(hidden)]
72    pub fn reserve_bip6_port_internal(
73        &mut self,
74        oid: ObjectIdentifier,
75        ip: [u8; 16],
76        udp: u16,
77    ) -> Result<(Bip6PortConfig, Arc<()>), Error> {
78        let lease = self.reserve_port(oid, PortAddress::V6(ip, udp))?;
79        let configured = self
80            .configured_bip6_port_internal(&oid)
81            .expect("reserved B/IPv6 port");
82        Ok((configured, lease))
83    }
84
85    fn reserve_port(
86        &mut self,
87        oid: ObjectIdentifier,
88        address: PortAddress,
89    ) -> Result<Arc<()>, Error> {
90        if oid.object_type() != ObjectType::NETWORK_PORT
91            || !(1..=255).contains(&oid.instance_number())
92        {
93            return Err(invalid("registered Network Port instance must be 1..255"));
94        }
95        if self
96            .network_port
97            .as_ref()
98            .is_some_and(|port| port.lease.strong_count() != 0)
99        {
100            return Err(invalid("database already has a live registered port owner"));
101        }
102        let authority = self
103            .builtin_network_port_mut(&oid)
104            .ok_or_else(|| invalid("registration requires the selected built-in Network Port"))?;
105        let configured = match authority.configured_address_internal() {
106            Some(configured) if configured == address => configured,
107            Some(configured) if configured.same_family(address) => {
108                return Err(invalid(
109                    "Network Port configuration differs from transport interface/UDP",
110                ))
111            }
112            _ => {
113                return Err(invalid(match address {
114                    PortAddress::V4(..) => "registration requires an IPV4 port",
115                    PortAddress::V6(..) => "registration requires an IPV6 port",
116                }))
117            }
118        };
119        let lease = Arc::new(());
120        authority.reserve_internal(&lease)?;
121        self.network_port = Some(NetworkPortRegistration {
122            oid,
123            lease: Arc::downgrade(&lease),
124            configured,
125            published: false,
126        });
127        Ok(lease)
128    }
129
130    /// Reconcile one actual bind and its B/IP mode under the database write
131    /// lock before publication. In BBMD mode `mode` must carry the started
132    /// transport's tables, which the object then reads live.
133    #[doc(hidden)]
134    pub fn publish_bip_port_internal(
135        &mut self,
136        oid: ObjectIdentifier,
137        ip: [u8; 4],
138        udp: u16,
139        capacity: u32,
140        mode: BipPortMode,
141    ) -> Result<(), Error> {
142        self.publish_port(oid, PortBind::V4(ip, udp), capacity, mode)
143    }
144
145    /// Reconcile one actual B/IPv6 bind (address, UDP port), the VMAC it
146    /// uses, the multicast group it broadcasts to and its mode, as
147    /// [`publish_bip_port_internal`](Self::publish_bip_port_internal) does
148    /// for B/IP.
149    #[doc(hidden)]
150    #[allow(clippy::too_many_arguments)] // one bind, as the B/IP call takes it
151    pub fn publish_bip6_port_internal(
152        &mut self,
153        oid: ObjectIdentifier,
154        ip: [u8; 16],
155        udp: u16,
156        vmac: [u8; 3],
157        multicast: [u8; 16],
158        capacity: u32,
159        mode: BipPortMode,
160    ) -> Result<(), Error> {
161        let bind = Bip6Bind {
162            ip,
163            udp,
164            vmac,
165            multicast,
166        };
167        self.publish_port(oid, PortBind::V6(bind), capacity, mode)
168    }
169
170    fn publish_port(
171        &mut self,
172        oid: ObjectIdentifier,
173        bind: PortBind,
174        capacity: u32,
175        mode: BipPortMode,
176    ) -> Result<(), Error> {
177        let registration = self
178            .network_port
179            .as_ref()
180            .filter(|port| port.oid == oid)
181            .ok_or_else(|| invalid("missing Network Port reservation"))?;
182        let lease = registration
183            .lease
184            .upgrade()
185            .ok_or_else(|| invalid("Network Port owner expired"))?;
186        let actual = bind.address();
187        let configured = registration.configured;
188        if registration.published
189            || !configured.same_ip(actual)
190            || actual.udp() == 0
191            || (configured.udp() != 0 && configured.udp() != actual.udp())
192        {
193            return Err(invalid("post-bind Network Port identity mismatch"));
194        }
195        self.builtin_network_port_mut(&oid)
196            .ok_or_else(|| invalid("selected Network Port authority disappeared"))?
197            .reconcile_internal(&lease, bind, capacity, mode)?;
198        self.network_port
199            .as_mut()
200            .expect("validated registration")
201            .published = true;
202        Ok(())
203    }
204
205    /// Explicit published receiving port, never a selection by database order.
206    #[doc(hidden)]
207    pub fn registered_port_internal(&self) -> Option<ObjectIdentifier> {
208        self.network_port
209            .as_ref()
210            .filter(|port| port.published && port.lease.strong_count() != 0)
211            .map(|port| port.oid)
212    }
213
214    /// Read or learn through the explicitly published receiving owner only.
215    /// Callers hold the database write lock, so RP/RPM observe coherent pairs.
216    #[doc(hidden)]
217    pub fn network_number_internal(
218        &mut self,
219        oid: ObjectIdentifier,
220        announcement: Option<(u16, u8)>,
221    ) -> Option<bacnet_types::network_number::NetworkNumber> {
222        if self.registered_port_internal() != Some(oid) {
223            return None;
224        }
225        Some(
226            self.builtin_network_port_mut(&oid)?
227                .network_number_internal(announcement),
228        )
229    }
230
231    /// Say whether the registered port's owner activates written values,
232    /// answering ReinitializeDevice ACTIVATE_CHANGES and WARMSTART. Until it
233    /// does, the pending-activation rows refuse writes.
234    #[doc(hidden)]
235    pub fn set_network_port_activation_internal(
236        &mut self,
237        oid: ObjectIdentifier,
238        activation: bool,
239    ) -> Result<(), Error> {
240        self.builtin_network_port_mut(&oid)
241            .ok_or_else(|| invalid("selected Network Port authority disappeared"))?
242            .set_activation_internal(activation);
243        Ok(())
244    }
245
246    /// Lend the registered B/IP port the multicast group of its owner, as
247    /// the owner reports it after start ([`IpMulticast`]), or none for an
248    /// owner that can't use multicast. The port then serves
249    /// BACnet_IP_Multicast_Address (#941).
250    ///
251    /// [`IpMulticast`]: bacnet_types::bip_port::IpMulticast
252    #[doc(hidden)]
253    pub fn set_network_port_multicast_internal(
254        &mut self,
255        oid: ObjectIdentifier,
256        multicast: Option<std::sync::Arc<dyn bacnet_types::bip_port::IpMulticast>>,
257    ) -> Result<(), Error> {
258        self.builtin_network_port_mut(&oid)
259            .ok_or_else(|| invalid("selected Network Port authority disappeared"))?
260            .set_multicast_internal(multicast);
261        Ok(())
262    }
263
264    /// The Network Port objects, built-in ones only.
265    fn network_port_oids(&self) -> Vec<ObjectIdentifier> {
266        self.objects
267            .keys()
268            .filter(|oid| oid.object_type() == ObjectType::NETWORK_PORT)
269            .copied()
270            .collect()
271    }
272
273    /// Check, changing nothing, that every built-in Network Port's pending
274    /// values would still be taken: what a ReinitializeDevice ACTIVATE_CHANGES
275    /// or WARMSTART checks before its handler runs.
276    pub fn check_network_port_changes(&mut self) -> Result<(), Error> {
277        for oid in self.network_port_oids() {
278            if let Some(port) = self.builtin_network_port_mut(&oid) {
279                port.check_pending_changes()?;
280            }
281        }
282        Ok(())
283    }
284
285    /// Put the values written to every built-in Network Port into effect and
286    /// clear its Changes_Pending, as a ReinitializeDevice ACTIVATE_CHANGES
287    /// or WARMSTART does (Clause 12.56.13). How many ports had changes.
288    ///
289    /// Each port's changes go into effect all or none, but not across
290    /// ports: a port whose transport refuses its changes keeps them pending
291    /// while the others take theirs, and the first such error is returned
292    /// once every port has been tried.
293    pub fn activate_network_port_changes(&mut self) -> Result<usize, Error> {
294        let ports = self.network_port_oids();
295        let mut activated = 0;
296        let mut first_error = None;
297        for oid in ports {
298            let Some(port) = self.builtin_network_port_mut(&oid) else {
299                continue;
300            };
301            match port.activate_changes() {
302                Ok(true) => activated += 1,
303                Ok(false) => {}
304                Err(error) => {
305                    first_error.get_or_insert(error);
306                }
307            }
308        }
309        first_error.map_or(Ok(activated), Err)
310    }
311
312    pub(super) fn check_network_port_membership(
313        &self,
314        oid: &ObjectIdentifier,
315    ) -> Result<(), Error> {
316        if self
317            .network_port
318            .as_ref()
319            .is_some_and(|port| port.oid == *oid && port.lease.strong_count() != 0)
320        {
321            return Err(Error::Protocol {
322                class: ErrorClass::OBJECT,
323                code: ErrorCode::OBJECT_DELETION_NOT_PERMITTED,
324            });
325        }
326        Ok(())
327    }
328}
329fn invalid(message: &str) -> Error {
330    Error::Encoding(message.into())
331}