use super::*;
use crate::network_port_registration as registration;
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>,
device_instance: u32,
) -> Result<(), Error> {
let Some(oid) = oid else {
return Ok(());
};
let address = match transport.bip_port() {
Some(port) => registration::bip_address(&port, transport.bip_broadcast_endpoint())?,
None if transport.bip6_port().is_some() => {
return prepare_bip6(db, transport, oid, device_instance);
}
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 (_, lease) = db.reserve_bip_port_internal(oid, address.ip().octets(), address.port())?;
transport.retain_network_port_lease_internal(lease)
}
fn prepare_bip6<T: TransportPort>(
db: &mut ObjectDatabase,
transport: &mut T,
oid: ObjectIdentifier,
device_instance: u32,
) -> Result<(), Error> {
let port = transport
.bip6_port()
.ok_or_else(|| Error::Encoding("registered port requires B/IP or B/IPv6".into()))?;
let address = registration::bip6_address(&port, Some(device_instance))?;
let (_, lease) = db.reserve_bip6_port_internal(oid, address.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;
match transport.bip_port() {
Some(port) => {
registration::publish_bip(db, oid, port, capacity)?;
}
_ => {
let port = transport.bip6_port().ok_or_else(|| {
Error::Encoding("registered B/IP mode changed during bind".into())
})?;
registration::publish_bip6(db, oid, port, capacity)?;
}
}
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,
config.device.instance(),
)?;
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
&& let Some(audit) = audit.as_ref().and_then(std::sync::Weak::upgrade)
{
audit.number_changed();
}
if let Some(npdu) = reply
&& 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(())
}