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