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}