Skip to main content

bacnet_endpoint/
registered_port.rs

1//! One explicit registered B/IP or B/IPv6 association; declaration is not
2//! registration. The port may run in NORMAL, FOREIGN or BBMD mode; startup
3//! publishes it. A B/IPv6 port registers through
4//! [`TransportPort::bip6_port`] and publishes its VMAC and multicast group
5//! too, as the full server's does (#1579, #1940).
6use super::*;
7use bacnet_server::network_port_registration as registration;
8use bacnet_transport::port::{Bip6Port, BipPort};
9
10/// What the started link reports for its registered port, with the port
11/// capacity: B/IP's or B/IPv6's.
12pub(super) type BoundPorts = (Option<(BipPort, u16)>, Option<(Bip6Port, u16)>);
13
14impl<T: TransportPort + 'static> EndpointSession<T> {
15    /// Select one existing built-in Network Port for the owned B/IP or
16    /// B/IPv6 link, in whichever mode the transport runs: an IPV4 port for
17    /// B/IP, an IPV6 one for B/IPv6.
18    /// Startup requires a matching concrete interface, identity entry and database
19    /// snapshot, and a B/IPv6 transport built with the served Device's
20    /// instance (the identity's, else the database's Device), which is its
21    /// VMAC (#2057). It publishes the actual address, UDP port and MAC (a
22    /// B/IPv6 link's VMAC) before exposing responder roles.
23    ///
24    /// There is no B/IPv6 endpoint builder, as there is no B/IPv6 server
25    /// builder: compose the session over a `Bip6Transport` (the
26    /// `bacnet-transport/ipv6` feature) and declare the port with
27    /// [`DeviceIdentity::with_bip6_port`](crate::DeviceIdentity::with_bip6_port).
28    ///
29    /// ```no_run
30    /// use std::net::Ipv6Addr;
31    /// use bacnet_endpoint::{DeviceIdentity, EndpointSession, SessionConfig, SessionRole};
32    /// use bacnet_transport::bip6::Bip6Transport;
33    /// use bacnet_types::{enums::ObjectType, primitives::ObjectIdentifier};
34    ///
35    /// # async fn example() -> Result<(), bacnet_types::error::Error> {
36    /// let identity = DeviceIdentity::new(1234, 42)?.with_bip6_port(1, 6, Ipv6Addr::LOCALHOST, 0)?;
37    /// let port = ObjectIdentifier::new(ObjectType::NETWORK_PORT, 1)?;
38    /// // The VMAC comes from the Device instance (Clause H.7.2).
39    /// let transport = Bip6Transport::new(Ipv6Addr::LOCALHOST, 0, Some(identity.instance()));
40    /// let mut session =
41    ///     EndpointSession::new(transport, SessionRole::ServerOnly, SessionConfig::default())?
42    ///         .device(identity)
43    ///         .with_registered_network_port(port);
44    /// session.start().await?; // Port 1 now serves the bound address and VMAC.
45    /// session.stop().await?;
46    /// # Ok(())
47    /// # }
48    /// ```
49    pub fn with_registered_network_port(mut self, oid: ObjectIdentifier) -> Self {
50        self.assert_configurable();
51        self.registered_network_port = Some(oid);
52        self
53    }
54
55    pub(super) async fn prepare_registered_port(&mut self) -> Result<(), Error> {
56        let Some(oid) = self.registered_network_port else {
57            return Ok(());
58        };
59        let ingress = self
60            .ingress
61            .as_mut()
62            .ok_or_else(|| invalid("missing ingress"))?;
63        let address = match ingress.bip_port() {
64            Some(port) => registration::bip_address(&port, ingress.bip_broadcast_endpoint())?,
65            None if ingress.bip6_port().is_some() => return self.prepare_bip6(oid).await,
66            // A B/IP link that reports no mode is configured both ways.
67            None if ingress.bip_broadcast_endpoint().is_some() => {
68                return Err(invalid(
69                    "registered Network Port refused: a BBMD that also registers as a \
70                     foreign device has no single B/IP mode",
71                ));
72            }
73            None => return Err(invalid("registration requires B/IP or B/IPv6")),
74        };
75        let db = self
76            .database
77            .as_ref()
78            .ok_or_else(|| invalid("registered port requires database"))?;
79        let mut db = db.write().await;
80        // Validate identity before reservation so a rejected configuration has no effects.
81        let configured = db
82            .configured_bip_port_internal(&oid)
83            .ok_or_else(|| invalid("selected built-in B/IP port missing"))?;
84        if let Some(identity) = &self.identity {
85            identity.validate_registered_bip_internal(oid, &configured)?;
86        }
87        let (_, lease) =
88            db.reserve_bip_port_internal(oid, address.ip().octets(), address.port())?;
89        self.registered_port_lease = Arc::downgrade(&lease);
90        ingress.retain_network_port_lease_internal(lease)
91    }
92
93    /// [`prepare_registered_port`](Self::prepare_registered_port) for a
94    /// B/IPv6 link: one concrete unicast address, a VMAC from the served
95    /// Device's instance (#2057) and the identity entry, all checked before
96    /// the port is reserved.
97    async fn prepare_bip6(&mut self, oid: ObjectIdentifier) -> Result<(), Error> {
98        let ingress = self
99            .ingress
100            .as_mut()
101            .ok_or_else(|| invalid("missing ingress"))?;
102        let port = ingress
103            .bip6_port()
104            .ok_or_else(|| invalid("registration requires B/IP or B/IPv6"))?;
105        let db = self
106            .database
107            .as_ref()
108            .ok_or_else(|| invalid("registered port requires database"))?;
109        let mut db = db.write().await;
110        // The served Device: the identity's, else the database's own.
111        let served = self
112            .identity
113            .as_ref()
114            .map(|identity| identity.instance())
115            .or_else(|| {
116                db.local_device()
117                    .identifier()
118                    .map(|device| device.instance_number())
119            });
120        let address = registration::bip6_address(&port, served)?;
121        // Validate identity before reservation so a rejected configuration has no effects.
122        let configured = db
123            .configured_bip6_port_internal(&oid)
124            .ok_or_else(|| invalid("selected built-in B/IPv6 port missing"))?;
125        if let Some(identity) = &self.identity {
126            identity.validate_registered_bip6_internal(oid, &configured)?;
127        }
128        let (_, lease) =
129            db.reserve_bip6_port_internal(oid, address.ip().octets(), address.port())?;
130        self.registered_port_lease = Arc::downgrade(&lease);
131        ingress.retain_network_port_lease_internal(lease)
132    }
133
134    pub(super) async fn publish_registered_port(
135        &mut self,
136        actual: BoundPorts,
137        local_network: &bacnet_network::network_number::LocalNetworkNumber,
138    ) -> Result<(), Error> {
139        let Some(oid) = self.registered_network_port else {
140            return Ok(());
141        };
142        let mut db = self
143            .database
144            .as_ref()
145            .expect("validated database")
146            .write()
147            .await;
148        // The identity entry follows only once the database took the bind.
149        let bound = match actual {
150            (Some((port, capacity)), _) => Bound::V4(registration::publish_bip(
151                &mut db,
152                oid,
153                port,
154                capacity.into(),
155            )?),
156            (None, Some((port, capacity))) => {
157                let (address, vmac) =
158                    registration::publish_bip6(&mut db, oid, port, capacity.into())?;
159                Bound::V6(address, vmac)
160            }
161            (None, None) => return Err(invalid("registered B/IP mode changed during bind")),
162        };
163        // Written values activate only through a ReinitializeDevice handler.
164        db.set_network_port_activation_internal(oid, self.reinitialize.is_some())?;
165        // The port's configured number is the local network number from the
166        // start (#1403); the Number owner copies every later change from the
167        // port, as the full server's does.
168        if let Some(state) = db.network_number_internal(oid, None) {
169            local_network.publish(state);
170        }
171        if let Some(identity) = &mut self.identity {
172            match bound {
173                Bound::V4(address) => identity.publish_registered_bip_internal(oid, address),
174                Bound::V6(address, vmac) => {
175                    identity.publish_registered_bip6_internal(oid, address, vmac)
176                }
177            }
178        }
179        Ok(())
180    }
181}
182
183/// The bind a started link published for its registered port.
184enum Bound {
185    V4(std::net::SocketAddrV4),
186    V6(std::net::SocketAddrV6, [u8; 3]),
187}
188fn invalid(message: &str) -> Error {
189    Error::Encoding(message.into())
190}