use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use std::time::Instant;
use bytes::{Bytes, BytesMut};
use tokio::sync::{mpsc, oneshot, Mutex, RwLock, Semaphore};
use tokio::task::JoinHandle;
use tokio::time::Duration;
use tracing::{debug, warn};
use bacnet_encoding::apdu::{
self, encode_apdu, validate_max_apdu_length, AbortPdu, Apdu, ComplexAck,
ConfirmedRequest as ConfirmedRequestPdu, ErrorPdu, RejectPdu, SegmentAck as SegmentAckPdu,
SimpleAck, UnconfirmedRequest as UnconfirmedRequestPdu,
};
use bacnet_encoding::primitives::encode_property_value;
use bacnet_encoding::segmentation::{
max_segment_payload, split_payload, SegmentReceiver, SegmentedPduType,
};
use bacnet_network::layer::NetworkLayer;
use bacnet_objects::database::ObjectDatabase;
use bacnet_objects::notification_class::get_notification_recipients;
use bacnet_services::alarm_event::EventNotificationRequest;
use bacnet_services::common::BACnetPropertyValue;
use bacnet_services::cov::COVNotificationRequest;
use bacnet_services::who_is::{IAmRequest, WhoIsRequest};
use bacnet_transport::bip::BipTransport;
use bacnet_transport::port::TransportPort;
use bacnet_types::enums::{
AbortReason, ConfirmedServiceChoice, ErrorClass, ErrorCode, NetworkPriority, NotifyType,
ObjectType, PropertyIdentifier, RejectReason, Segmentation, UnconfirmedServiceChoice,
};
use bacnet_types::error::Error;
use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier, PropertyValue, Time};
use bacnet_types::MacAddr;
use crate::cov::CovSubscriptionTable;
use crate::handlers;
const MAX_SEG_RECEIVERS: usize = 128;
const SEG_RECEIVER_TIMEOUT: Duration = Duration::from_secs(4);
const MAX_NEG_SEGMENT_ACK_RETRIES: u8 = 3;
const DEFAULT_APDU_RETRIES: u8 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CovAckResult {
Ack,
Error,
}
pub struct ServerTsm {
next_invoke_id: u8,
pending: HashMap<(MacAddr, u8), oneshot::Sender<CovAckResult>>,
}
impl ServerTsm {
fn new() -> Self {
Self {
next_invoke_id: 0,
pending: HashMap::new(),
}
}
fn allocate(&mut self, peer: MacAddr) -> (u8, oneshot::Receiver<CovAckResult>) {
let id = self.next_invoke_id;
self.next_invoke_id = self.next_invoke_id.wrapping_add(1);
let rx = self.register(peer, id);
(id, rx)
}
fn register(&mut self, peer: MacAddr, invoke_id: u8) -> oneshot::Receiver<CovAckResult> {
let (tx, rx) = oneshot::channel();
self.pending.insert((peer, invoke_id), tx);
rx
}
fn record_result(&mut self, peer: &MacAddr, invoke_id: u8, result: CovAckResult) -> bool {
if let Some(tx) = self.pending.remove(&(peer.clone(), invoke_id)) {
let _ = tx.send(result);
true
} else {
false
}
}
fn remove(&mut self, peer: &MacAddr, invoke_id: u8) {
self.pending.remove(&(peer.clone(), invoke_id));
}
}
#[derive(Debug, Clone)]
pub struct TimeSyncData {
pub raw_service_data: Bytes,
pub is_utc: bool,
}
#[derive(Clone)]
pub struct ServerConfig {
pub interface: Ipv4Addr,
pub port: u16,
pub broadcast_address: Ipv4Addr,
pub max_apdu_length: u32,
pub segmentation_supported: Segmentation,
pub vendor_id: u16,
pub cov_retry_timeout_ms: u64,
pub on_time_sync: Option<Arc<dyn Fn(TimeSyncData) + Send + Sync>>,
pub dcc_password: Option<String>,
pub reinit_password: Option<String>,
pub enable_fault_detection: bool,
}
impl std::fmt::Debug for ServerConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ServerConfig")
.field("interface", &self.interface)
.field("port", &self.port)
.field("broadcast_address", &self.broadcast_address)
.field("max_apdu_length", &self.max_apdu_length)
.field("segmentation_supported", &self.segmentation_supported)
.field("vendor_id", &self.vendor_id)
.field("cov_retry_timeout_ms", &self.cov_retry_timeout_ms)
.field(
"on_time_sync",
&self.on_time_sync.as_ref().map(|_| "<callback>"),
)
.field("dcc_password", &self.dcc_password.as_ref().map(|_| "***"))
.field(
"reinit_password",
&self.reinit_password.as_ref().map(|_| "***"),
)
.field("enable_fault_detection", &self.enable_fault_detection)
.finish()
}
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
interface: Ipv4Addr::UNSPECIFIED,
port: 0xBAC0,
broadcast_address: Ipv4Addr::BROADCAST,
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
vendor_id: 0,
cov_retry_timeout_ms: 3000,
on_time_sync: None,
dcc_password: None,
reinit_password: None,
enable_fault_detection: false,
}
}
}
pub struct ServerBuilder<T: TransportPort> {
config: ServerConfig,
db: ObjectDatabase,
transport: Option<T>,
}
impl<T: TransportPort + 'static> ServerBuilder<T> {
pub fn database(mut self, db: ObjectDatabase) -> Self {
self.db = db;
self
}
pub fn transport(mut self, transport: T) -> Self {
self.transport = Some(transport);
self
}
pub fn dcc_password(mut self, password: impl Into<String>) -> Self {
self.config.dcc_password = Some(password.into());
self
}
pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
self.config.reinit_password = Some(password.into());
self
}
pub fn enable_fault_detection(mut self, enabled: bool) -> Self {
self.config.enable_fault_detection = enabled;
self
}
pub fn vendor_id(mut self, id: u16) -> Self {
self.config.vendor_id = id;
self
}
pub async fn build(self) -> Result<BACnetServer<T>, Error> {
let transport = self
.transport
.ok_or_else(|| Error::Encoding("transport not set on ServerBuilder".into()))?;
BACnetServer::start(self.config, self.db, transport).await
}
}
pub struct BipServerBuilder {
config: ServerConfig,
db: ObjectDatabase,
}
impl BipServerBuilder {
pub fn interface(mut self, ip: Ipv4Addr) -> Self {
self.config.interface = ip;
self
}
pub fn port(mut self, port: u16) -> Self {
self.config.port = port;
self
}
pub fn broadcast_address(mut self, addr: Ipv4Addr) -> Self {
self.config.broadcast_address = addr;
self
}
pub fn database(mut self, db: ObjectDatabase) -> Self {
self.db = db;
self
}
pub fn dcc_password(mut self, password: impl Into<String>) -> Self {
self.config.dcc_password = Some(password.into());
self
}
pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
self.config.reinit_password = Some(password.into());
self
}
pub fn enable_fault_detection(mut self, enabled: bool) -> Self {
self.config.enable_fault_detection = enabled;
self
}
pub fn vendor_id(mut self, id: u16) -> Self {
self.config.vendor_id = id;
self
}
pub async fn build(self) -> Result<BACnetServer<BipTransport>, Error> {
let transport = BipTransport::new(
self.config.interface,
self.config.port,
self.config.broadcast_address,
);
BACnetServer::start(self.config, self.db, transport).await
}
}
type SegKey = (MacAddr, u8);
struct SegmentedRequestState {
receiver: SegmentReceiver,
first_req: bacnet_encoding::apdu::ConfirmedRequest,
last_activity: Instant,
expected_seq: u8,
last_acked_seq: u8,
window_pos: u8,
actual_window_size: u8,
}
pub struct BACnetServer<T: TransportPort> {
#[allow(dead_code)]
config: ServerConfig,
#[allow(dead_code)]
network: Arc<NetworkLayer<T>>,
db: Arc<RwLock<ObjectDatabase>>,
#[allow(dead_code)]
cov_table: Arc<RwLock<CovSubscriptionTable>>,
#[allow(dead_code)]
seg_ack_senders: Arc<Mutex<HashMap<SegKey, mpsc::Sender<SegmentAckPdu>>>>,
#[allow(dead_code)]
cov_in_flight: Arc<Semaphore>,
#[allow(dead_code)]
server_tsm: Arc<Mutex<ServerTsm>>,
comm_state: Arc<AtomicU8>,
#[allow(dead_code)]
dcc_timer: Arc<Mutex<Option<JoinHandle<()>>>>,
dispatch_task: Option<JoinHandle<()>>,
cov_purge_task: Option<JoinHandle<()>>,
fault_detection_task: Option<JoinHandle<()>>,
event_enrollment_task: Option<JoinHandle<()>>,
trend_log_task: Option<JoinHandle<()>>,
schedule_tick_task: Option<JoinHandle<()>>,
local_mac: MacAddr,
}
pub struct IAmBroadcaster<T: TransportPort> {
config: ServerConfig,
network: Arc<NetworkLayer<T>>,
db: Arc<RwLock<ObjectDatabase>>,
}
impl<T: TransportPort> Clone for IAmBroadcaster<T> {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
network: Arc::clone(&self.network),
db: Arc::clone(&self.db),
}
}
}
impl BACnetServer<BipTransport> {
pub fn bip_builder() -> BipServerBuilder {
BipServerBuilder {
config: ServerConfig::default(),
db: ObjectDatabase::new(),
}
}
pub fn builder() -> BipServerBuilder {
Self::bip_builder()
}
}
#[cfg(feature = "sc-tls")]
impl BACnetServer<bacnet_transport::sc::ScTransport<bacnet_transport::sc_tls::TlsWebSocket>> {
pub fn sc_builder() -> ScServerBuilder {
ScServerBuilder {
config: ServerConfig::default(),
db: ObjectDatabase::new(),
hub_url: String::new(),
tls_config: None,
vmac: [0; 6],
heartbeat_interval_ms: 30_000,
heartbeat_timeout_ms: 60_000,
reconnect: None,
}
}
}
#[cfg(feature = "sc-tls")]
pub struct ScServerBuilder {
config: ServerConfig,
db: ObjectDatabase,
hub_url: String,
tls_config: Option<std::sync::Arc<tokio_rustls::rustls::ClientConfig>>,
vmac: bacnet_transport::sc_frame::Vmac,
heartbeat_interval_ms: u64,
heartbeat_timeout_ms: u64,
reconnect: Option<bacnet_transport::sc::ScReconnectConfig>,
}
#[cfg(feature = "sc-tls")]
impl ScServerBuilder {
pub fn hub_url(mut self, url: &str) -> Self {
self.hub_url = url.to_string();
self
}
pub fn tls_config(
mut self,
config: std::sync::Arc<tokio_rustls::rustls::ClientConfig>,
) -> Self {
self.tls_config = Some(config);
self
}
pub fn vmac(mut self, vmac: [u8; 6]) -> Self {
self.vmac = vmac;
self
}
pub fn database(mut self, db: ObjectDatabase) -> Self {
self.db = db;
self
}
pub fn heartbeat_interval_ms(mut self, ms: u64) -> Self {
self.heartbeat_interval_ms = ms;
self
}
pub fn heartbeat_timeout_ms(mut self, ms: u64) -> Self {
self.heartbeat_timeout_ms = ms;
self
}
pub fn reconnect(mut self, config: bacnet_transport::sc::ScReconnectConfig) -> Self {
self.reconnect = Some(config);
self
}
pub fn dcc_password(mut self, password: impl Into<String>) -> Self {
self.config.dcc_password = Some(password.into());
self
}
pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
self.config.reinit_password = Some(password.into());
self
}
pub fn enable_fault_detection(mut self, enabled: bool) -> Self {
self.config.enable_fault_detection = enabled;
self
}
pub async fn build(
self,
) -> Result<
BACnetServer<bacnet_transport::sc::ScTransport<bacnet_transport::sc_tls::TlsWebSocket>>,
Error,
> {
let tls_config = self
.tls_config
.ok_or_else(|| Error::Encoding("SC server builder: tls_config is required".into()))?;
let ws = bacnet_transport::sc_tls::TlsWebSocket::connect(&self.hub_url, tls_config).await?;
let mut transport = bacnet_transport::sc::ScTransport::new(ws, self.vmac)
.with_heartbeat_interval_ms(self.heartbeat_interval_ms)
.with_heartbeat_timeout_ms(self.heartbeat_timeout_ms);
if let Some(rc) = self.reconnect {
#[allow(deprecated)]
{
transport = transport.with_reconnect(rc);
}
}
BACnetServer::start(self.config, self.db, transport).await
}
}
mod cov_notifications;
mod dispatch;
mod event_notifications;
mod lifecycle;
mod requests;
mod segmentation;
#[cfg(test)]
mod tests;
impl<T: TransportPort + 'static> BACnetServer<T> {
pub fn generic_builder() -> ServerBuilder<T> {
ServerBuilder {
config: ServerConfig::default(),
db: ObjectDatabase::new(),
transport: None,
}
}
}