use super::*;
impl<T: TransportPort + 'static> ServerBuilder<T> {
pub fn registered_network_port(mut self, oid: ObjectIdentifier) -> Self {
self.config.registered_network_port = Some(oid);
self
}
}
impl BipServerBuilder {
pub fn registered_network_port(mut self, oid: ObjectIdentifier) -> Self {
self.config.registered_network_port = Some(oid);
self
}
}
pub(super) fn prepare<T: TransportPort>(
db: &mut ObjectDatabase,
transport: &mut T,
oid: Option<ObjectIdentifier>,
) -> Result<(), Error> {
let Some(oid) = oid else {
return Ok(());
};
let address = match transport.bip_port() {
Some(port) => port.endpoint,
None if transport.bip6_port().is_some() => return prepare_bip6(db, transport, oid),
None if transport.bip_broadcast_endpoint().is_some() => {
return Err(Error::Encoding(
"registered Network Port refused: a BBMD that also registers as a foreign \
device has no single B/IP mode"
.into(),
))
}
None => {
return Err(Error::Encoding(
"registered port requires B/IP or B/IPv6".into(),
))
}
};
let ip = *address.ip();
if ip.is_unspecified()
|| ip.is_multicast()
|| ip.is_broadcast()
|| transport
.bip_broadcast_endpoint()
.is_some_and(|broadcast| *broadcast.ip() == ip)
{
return Err(Error::Encoding(
"registered B/IP requires a concrete unicast interface".into(),
));
}
let (_, lease) = db.reserve_bip_port_internal(oid, ip.octets(), address.port())?;
transport.retain_network_port_lease_internal(lease)
}
fn prepare_bip6<T: TransportPort>(
db: &mut ObjectDatabase,
transport: &mut T,
oid: ObjectIdentifier,
) -> Result<(), Error> {
let address = transport
.bip6_port()
.ok_or_else(|| Error::Encoding("registered port requires B/IP or B/IPv6".into()))?
.endpoint;
let ip = *address.ip();
let refused = if ip.is_unspecified() || ip.is_multicast() {
Some("registered B/IPv6 requires a concrete unicast interface")
} else if ip.to_ipv4_mapped().is_some() {
Some("registered B/IPv6 refuses an IPv4-mapped address: register IPv4 as B/IP")
} else if ip.is_unicast_link_local() {
Some("registered B/IPv6 refuses a link-local address: no IPv6_Zone_Index is modeled")
} else {
None
};
if let Some(message) = refused {
return Err(Error::Encoding(message.into()));
}
let (_, lease) = db.reserve_bip6_port_internal(oid, ip.octets(), address.port())?;
transport.retain_network_port_lease_internal(lease)
}
pub(super) fn publish<T: TransportPort + 'static>(
db: &mut ObjectDatabase,
network: &NetworkLayer<T>,
oid: Option<ObjectIdentifier>,
activation: bool,
) -> Result<(), Error> {
let Some(oid) = oid else {
return Ok(());
};
let transport = network.transport();
let capacity = transport.local_receive_apdu_capacity() as u32;
if let Some(port) = transport.bip_port() {
db.publish_bip_port_internal(
oid,
port.endpoint.ip().octets(),
port.endpoint.port(),
capacity,
port.mode,
)?;
db.set_network_port_multicast_internal(oid, port.multicast)?;
} else {
let port = transport
.bip6_port()
.ok_or_else(|| Error::Encoding("registered B/IP mode changed during bind".into()))?;
let vmac = port
.vmac
.ok_or_else(|| Error::Encoding("started B/IPv6 port reports no VMAC".into()))?;
db.publish_bip6_port_internal(
oid,
port.endpoint.ip().octets(),
port.endpoint.port(),
vmac,
port.multicast_group.octets(),
capacity,
port.mode,
)?;
}
db.set_network_port_activation_internal(oid, activation)?;
if let Some(state) = db.network_number_internal(oid, None) {
network.local_network_number().publish(state);
}
Ok(())
}
pub(super) struct StartingNetwork<T: TransportPort + 'static> {
network: Option<NetworkLayer<T>>,
runtime: tokio::runtime::Handle,
}
impl<T: TransportPort + 'static> StartingNetwork<T> {
pub(super) fn new(transport: T) -> Self {
Self {
network: Some(NetworkLayer::new(transport)),
runtime: tokio::runtime::Handle::current(),
}
}
pub(super) fn from_network(network: NetworkLayer<T>) -> Self {
Self {
network: Some(network),
runtime: tokio::runtime::Handle::current(),
}
}
pub(super) fn network(&mut self) -> &mut NetworkLayer<T> {
self.network.as_mut().expect("startup network owned")
}
pub(super) fn finish(mut self) -> NetworkLayer<T> {
self.network.take().expect("startup network owned")
}
pub(super) async fn cleanup(mut self) -> Result<(), Error> {
let mut network = self.network.take().expect("startup network owned");
self.runtime
.spawn(async move { network.stop().await })
.await
.map_err(|error| Error::Encoding(format!("startup cleanup failed: {error}")))?
}
}
impl<T: TransportPort + 'static> Drop for StartingNetwork<T> {
fn drop(&mut self) {
if let Some(mut network) = self.network.take() {
self.runtime.spawn(async move {
if let Err(error) = network.stop().await {
tracing::warn!(%error, "cancelled startup cleanup failed");
}
});
}
}
}
pub(super) async fn start<T: TransportPort + 'static>(
db: &mut ObjectDatabase,
config: &ServerConfig,
mut transport: T,
audit_routes: super::audit_recipient_routes::AuditRoutes,
) -> Result<
(
NetworkLayer<T>,
bacnet_network::layer::ApduReceiver,
Arc<super::audit_recipient_routes::AuditRoutes>,
Option<mpsc::Receiver<bacnet_network::layer::ReceivedNetworkControl>>,
),
Error,
> {
prepare(db, &mut transport, config.registered_network_port)?;
let mut starting = StartingNetwork::new(transport);
let started = async {
let controls = if starting
.network()
.transport()
.supports_local_nonrouter_number_controls()
{
Some(starting.network().enable_network_control_receiver()?)
} else {
None
};
let apdu_rx = starting.network().start().await?;
publish(
db,
starting.network(),
config.registered_network_port,
config.on_reinitialize.is_some(),
)?;
let routes = audit_routes.finish(db, config, starting.network()).await?;
let controls = starting
.network()
.transport()
.supports_local_nonrouter_number_controls()
.then_some(controls)
.flatten();
Ok::<_, Error>((apdu_rx, routes, controls))
}
.await;
match started {
Ok((apdu_rx, routes, controls)) => Ok((starting.finish(), apdu_rx, routes, controls)),
Err(error) => {
starting.cleanup().await?;
Err(error)
}
}
}
pub(super) fn spawn_number_worker<T: TransportPort + 'static>(
network: &Arc<NetworkLayer<T>>,
db: &Arc<RwLock<ObjectDatabase>>,
selected: Option<ObjectIdentifier>,
mut controls: mpsc::Receiver<bacnet_network::layer::ReceivedNetworkControl>,
audit: Option<std::sync::Weak<super::audit_recipient::TargetAudit<T>>>,
) -> JoinHandle<()> {
let network = Arc::clone(network);
let mut owner =
crate::network_number::NetworkNumberOwner::new(selected.map(|oid| (Arc::clone(db), oid)))
.publishing_to(network.local_network_number().clone());
super::heap_futures::spawn_boxed(move || async move {
while let Some(control) = controls.recv().await {
let before = network.local_network_number().get();
let reply = owner.handle(control).await;
if network.local_network_number().get() != before {
if let Some(audit) = audit.as_ref().and_then(std::sync::Weak::upgrade) {
audit.number_changed();
}
}
if let Some(npdu) = reply {
if let Err(error) = network.transport().send_broadcast(&npdu).await {
tracing::debug!(%error, "Network-Number-Is broadcast failed");
}
}
}
})
}
pub(super) fn validate_apdu_capacity<T: TransportPort>(
config: &mut ServerConfig,
transport: &T,
) -> Result<(), Error> {
config.device = config
.device
.resolve(transport.local_receive_apdu_capacity())?;
Ok(())
}