use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::sync::atomic::{AtomicBool, AtomicU16, AtomicU8, Ordering};
use std::sync::Arc;
use std::time::Instant;
use bytes::{Bytes, BytesMut};
use tokio::sync::{mpsc, oneshot, watch, 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::npdu::NpduAddress;
use bacnet_encoding::primitives::encode_property_value;
use bacnet_encoding::segmentation::{
duplicate_in_window, max_segment_payload, split_payload, SegmentReceiver, SegmentedPduType,
};
use bacnet_network::layer::NetworkLayer;
use bacnet_objects::database::ObjectDatabase;
use bacnet_objects::notification_class::{
lookup_notification_recipients, resolve_transition_priority_ack,
};
use bacnet_services::alarm_event::EventNotificationRequest;
use bacnet_services::common::BACnetPropertyValue;
use bacnet_services::cov::COVNotificationRequest;
use bacnet_services::cov_multiple::{
COVNotificationItem, COVNotificationMultipleRequest, COVNotificationValue,
};
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, LifeSafetyOperation,
NetworkPriority, NotifyType, ObjectType, PropertyIdentifier, RejectReason, Segmentation,
UnconfirmedServiceChoice,
};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use bacnet_types::MacAddr;
use crate::audit_notification::{
AuditNotificationAuthorizationContext, AuditNotificationAuthorizer,
UnconfirmedAuditNotificationAuthorizationContext, UnconfirmedAuditNotificationAuthorizer,
MAX_AUDIT_NOTIFICATIONS, MAX_AUDIT_NOTIFICATION_BYTES,
};
use crate::cov::{CovNotificationKind, CovSubscription, CovSubscriptionTable};
use crate::handlers;
use crate::life_safety::{LifeSafetyOperationAuthorizationContext, LifeSafetyOperationAuthorizer};
use confirmed_request_tracker::{ConfirmedRequestAdmission, ConfirmedRequestTracker};
pub use device_bindings::DeviceBinding;
use device_bindings::{register_configured_binding, DeviceBindingTable};
use notification_transactions::{
canonical_direct_peer, canonical_routed_peer, run_notification_worker,
NotificationTransactions, NotificationWorkerResult,
};
const MAX_SEG_RECEIVERS: usize = 128;
const MAX_REQUEST_SEGMENTS: usize = 256;
const MAX_SEG_SENDERS: usize = 128;
const SEG_RECEIVER_TIMEOUT: Duration = Duration::from_secs(4);
const MAX_NEG_SEGMENT_ACK_RETRIES: u8 = 3;
const DEFAULT_APDU_SEGMENT_TIMEOUT: Duration = Duration::from_secs(5);
const DEFAULT_APDU_SEGMENT_RETRIES: u8 = MAX_NEG_SEGMENT_ACK_RETRIES;
const DEFAULT_APDU_RETRIES: u8 = 3;
type TsmPeer = (MacAddr, Option<NpduAddress>);
type TsmKey = (MacAddr, Option<NpduAddress>, u8);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CovAckResult {
Ack,
Error,
}
pub struct ServerTsm {
#[allow(dead_code)]
next_invoke_id: u8,
#[allow(dead_code)]
pending: HashMap<TsmKey, oneshot::Sender<CovAckResult>>,
routers: HashMap<u16, MacAddr>,
}
const MAX_LEARNED_ROUTERS: usize = 64;
impl ServerTsm {
fn new() -> Self {
Self {
next_invoke_id: 0,
pending: HashMap::new(),
routers: HashMap::new(),
}
}
#[allow(dead_code)]
fn allocate(&mut self, peer: TsmPeer) -> Option<(u8, oneshot::Receiver<CovAckResult>)> {
for offset in 0..=u8::MAX {
let id = self.next_invoke_id.wrapping_add(offset);
if !self
.pending
.contains_key(&(peer.0.clone(), peer.1.clone(), id))
{
self.next_invoke_id = id.wrapping_add(1);
let rx = self.register(peer, id);
return Some((id, rx));
}
}
None
}
#[allow(dead_code)]
fn register(&mut self, peer: TsmPeer, invoke_id: u8) -> oneshot::Receiver<CovAckResult> {
let (tx, rx) = oneshot::channel();
self.pending.insert((peer.0, peer.1, invoke_id), tx);
rx
}
#[allow(dead_code)]
fn record_result(
&mut self,
peer: &MacAddr,
network: Option<&NpduAddress>,
invoke_id: u8,
result: CovAckResult,
) -> bool {
if let Some(tx) = self
.pending
.remove(&(peer.clone(), network.cloned(), invoke_id))
{
let _ = tx.send(result);
true
} else {
false
}
}
#[allow(dead_code)]
fn remove(&mut self, peer: &TsmPeer, invoke_id: u8) {
self.pending
.remove(&(peer.0.clone(), peer.1.clone(), invoke_id));
}
#[allow(dead_code)]
fn record_result_correlated(
&mut self,
source_mac: &MacAddr,
source_network: Option<&NpduAddress>,
invoke_id: u8,
result: CovAckResult,
) -> bool {
let hit = self.record_result(source_mac, source_network, invoke_id, result)
|| (source_network.is_some()
&& self.record_result(&MacAddr::new(), source_network, invoke_id, result))
|| self.record_result(&MacAddr::new(), None, invoke_id, result);
if hit {
if let Some(address) = source_network {
self.learn_router(address.network, source_mac);
}
}
hit
}
fn learn_router(&mut self, network: u16, router: &MacAddr) {
if router.is_empty() {
return;
}
if self.routers.len() >= MAX_LEARNED_ROUTERS && !self.routers.contains_key(&network) {
return;
}
self.routers.insert(network, router.clone());
}
fn cached_router(&self, network: u16) -> Option<MacAddr> {
self.routers.get(&network).cloned()
}
}
#[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 life_safety_operation_authorizer: Option<LifeSafetyOperationAuthorizer>,
pub audit_notification_sink: Option<ObjectIdentifier>,
pub audit_notification_authorizer: Option<AuditNotificationAuthorizer>,
pub unconfirmed_audit_notification_authorizer: Option<UnconfirmedAuditNotificationAuthorizer>,
pub dcc_password: Option<String>,
pub reinit_password: Option<String>,
pub enable_fault_detection: bool,
pub enable_event_enrollment: bool,
pub event_enrollment_interval_secs: u64,
}
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(
"life_safety_operation_authorizer",
&self
.life_safety_operation_authorizer
.as_ref()
.map(|_| "<callback>"),
)
.field("audit_notification_sink", &self.audit_notification_sink)
.field(
"audit_notification_authorizer",
&self
.audit_notification_authorizer
.as_ref()
.map(|_| "<callback>"),
)
.field(
"unconfirmed_audit_notification_authorizer",
&self
.unconfirmed_audit_notification_authorizer
.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)
.field("enable_event_enrollment", &self.enable_event_enrollment)
.field(
"event_enrollment_interval_secs",
&self.event_enrollment_interval_secs,
)
.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,
life_safety_operation_authorizer: None,
audit_notification_sink: None,
audit_notification_authorizer: None,
unconfirmed_audit_notification_authorizer: None,
dcc_password: None,
reinit_password: None,
enable_fault_detection: false,
enable_event_enrollment: true,
event_enrollment_interval_secs: 10,
}
}
}
pub struct ServerBuilder<T: TransportPort> {
config: ServerConfig,
db: ObjectDatabase,
transport: Option<T>,
configured_device_bindings: Vec<DeviceBinding>,
}
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 device_binding(mut self, binding: DeviceBinding) -> Result<Self, Error> {
register_configured_binding(&mut self.configured_device_bindings, binding)?;
Ok(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 life_safety_operation_authorizer<F>(mut self, authorizer: F) -> Self
where
F: Fn(&LifeSafetyOperationAuthorizationContext) -> bool + Send + Sync + 'static,
{
self.config.life_safety_operation_authorizer = Some(Arc::new(authorizer));
self
}
pub fn audit_notification_sink(mut self, sink: ObjectIdentifier) -> Self {
self.config.audit_notification_sink = Some(sink);
self
}
pub fn audit_notification_authorizer<F>(mut self, authorizer: F) -> Self
where
F: Fn(&AuditNotificationAuthorizationContext) -> bool + Send + Sync + 'static,
{
self.config.audit_notification_authorizer = Some(Arc::new(authorizer));
self
}
pub fn unconfirmed_audit_notification_authorizer<F>(mut self, authorizer: F) -> Self
where
F: Fn(&UnconfirmedAuditNotificationAuthorizationContext) -> bool + Send + Sync + 'static,
{
self.config.unconfirmed_audit_notification_authorizer = Some(Arc::new(authorizer));
self
}
pub fn enable_fault_detection(mut self, enabled: bool) -> Self {
self.config.enable_fault_detection = enabled;
self
}
pub fn enable_event_enrollment(mut self, enabled: bool) -> Self {
self.config.enable_event_enrollment = enabled;
self
}
pub fn event_enrollment_interval_secs(mut self, secs: u64) -> Self {
self.config.event_enrollment_interval_secs = secs;
self
}
pub fn segmentation_supported(mut self, segmentation: Segmentation) -> Self {
self.config.segmentation_supported = segmentation;
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_with_clock_mode_and_bindings(
self.config,
self.db,
transport,
Some(ClockConfig::default()),
self.configured_device_bindings,
)
.await
}
}
pub struct BipServerBuilder {
config: ServerConfig,
db: ObjectDatabase,
configured_device_bindings: Vec<DeviceBinding>,
}
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 device_binding(mut self, binding: DeviceBinding) -> Result<Self, Error> {
register_configured_binding(&mut self.configured_device_bindings, binding)?;
Ok(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 life_safety_operation_authorizer<F>(mut self, authorizer: F) -> Self
where
F: Fn(&LifeSafetyOperationAuthorizationContext) -> bool + Send + Sync + 'static,
{
self.config.life_safety_operation_authorizer = Some(Arc::new(authorizer));
self
}
pub fn audit_notification_sink(mut self, sink: ObjectIdentifier) -> Self {
self.config.audit_notification_sink = Some(sink);
self
}
pub fn audit_notification_authorizer<F>(mut self, authorizer: F) -> Self
where
F: Fn(&AuditNotificationAuthorizationContext) -> bool + Send + Sync + 'static,
{
self.config.audit_notification_authorizer = Some(Arc::new(authorizer));
self
}
pub fn unconfirmed_audit_notification_authorizer<F>(mut self, authorizer: F) -> Self
where
F: Fn(&UnconfirmedAuditNotificationAuthorizationContext) -> bool + Send + Sync + 'static,
{
self.config.unconfirmed_audit_notification_authorizer = Some(Arc::new(authorizer));
self
}
pub fn enable_fault_detection(mut self, enabled: bool) -> Self {
self.config.enable_fault_detection = enabled;
self
}
pub fn enable_event_enrollment(mut self, enabled: bool) -> Self {
self.config.enable_event_enrollment = enabled;
self
}
pub fn event_enrollment_interval_secs(mut self, secs: u64) -> Self {
self.config.event_enrollment_interval_secs = secs;
self
}
pub fn segmentation_supported(mut self, segmentation: Segmentation) -> Self {
self.config.segmentation_supported = segmentation;
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_with_clock_mode_and_bindings(
self.config,
self.db,
transport,
Some(ClockConfig::default()),
self.configured_device_bindings,
)
.await
}
}
type SegKey = (MacAddr, Option<NpduAddress>, u8);
fn segmented_transaction_key(
source_mac: &[u8],
source_network: Option<&NpduAddress>,
invoke_id: u8,
) -> SegKey {
match source_network {
Some(address)
if (1..=0xFFFE).contains(&address.network) && !address.mac_address.is_empty() =>
{
(MacAddr::new(), Some(address.clone()), invoke_id)
}
_ => (
MacAddr::from_slice(source_mac),
source_network.cloned(),
invoke_id,
),
}
}
#[derive(Debug)]
enum SegmentedSendEvent {
SegmentAck(SegmentAckPdu),
Abort(AbortPdu),
}
#[derive(Debug, Clone)]
enum SegmentedSendControlEvent {
Abort(AbortPdu),
Cancel,
}
struct SegmentedSendHandle {
segment_ack_tx: mpsc::Sender<SegmentAckPdu>,
control_tx: watch::Sender<Option<SegmentedSendControlEvent>>,
closed: AtomicBool,
current_sequence: AtomicU16,
total_segments: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SegmentAckDisposition {
Advance,
Retransmit,
}
fn segment_ack_disposition(
ack: &SegmentAckPdu,
current: usize,
total_segments: usize,
) -> Option<SegmentAckDisposition> {
if current >= total_segments {
return None;
}
let ack_seq = ack.sequence_number as usize;
if ack_seq >= total_segments {
return None;
}
if ack_seq == current {
Some(SegmentAckDisposition::Advance)
} else if ack.negative_ack && current.checked_sub(1) == Some(ack_seq) {
Some(SegmentAckDisposition::Retransmit)
} else {
None
}
}
impl SegmentedSendHandle {
fn new(
segment_ack_tx: mpsc::Sender<SegmentAckPdu>,
control_tx: watch::Sender<Option<SegmentedSendControlEvent>>,
total_segments: usize,
) -> Self {
Self {
segment_ack_tx,
control_tx,
closed: AtomicBool::new(false),
current_sequence: AtomicU16::new(u16::MAX),
total_segments,
}
}
fn accepts_segment_ack(&self, ack: &SegmentAckPdu) -> bool {
if ack.sent_by_server || self.closed.load(Ordering::Acquire) {
return false;
}
let current = self.current_sequence.load(Ordering::Acquire) as usize;
if current >= self.total_segments {
return false;
}
segment_ack_disposition(ack, current, self.total_segments).is_some()
}
fn send_control(&self, event: SegmentedSendControlEvent) {
self.closed.store(true, Ordering::Release);
self.control_tx.send_replace(Some(event));
}
fn same_channel(&self, sender: &mpsc::Sender<SegmentAckPdu>) -> bool {
self.segment_ack_tx.same_channel(sender)
}
}
#[derive(Debug, Clone, Copy)]
struct SegmentedSendOptions {
segment_timeout: Duration,
max_retries: u8,
}
impl Default for SegmentedSendOptions {
fn default() -> Self {
Self {
segment_timeout: DEFAULT_APDU_SEGMENT_TIMEOUT,
max_retries: DEFAULT_APDU_SEGMENT_RETRIES,
}
}
}
struct SegmentedRequestState {
payload: segmented_receive::RequestPayload,
last_activity: Instant,
last_progress: Instant,
expected_seq: u8,
initial_sequence_number: u8,
duplicate_count: u8,
last_acked_seq: u8,
window_pos: u8,
actual_window_size: u8,
accepted_segments: usize,
}
pub struct BACnetServer<T: TransportPort> {
config: ServerConfig,
_clock: Option<Arc<ServerClock>>,
network: Arc<NetworkLayer<T>>,
db: Arc<RwLock<ObjectDatabase>>,
cov_table: Arc<RwLock<CovSubscriptionTable>>,
#[allow(dead_code)]
seg_ack_senders: Arc<Mutex<HashMap<SegKey, Arc<SegmentedSendHandle>>>>,
#[allow(dead_code)]
seg_send_permits: Arc<Semaphore>,
cov_in_flight: Arc<Semaphore>,
server_tsm: Arc<Mutex<ServerTsm>>,
notification_transactions: Arc<NotificationTransactions>,
#[allow(dead_code)]
confirmed_request_tracker: Arc<ConfirmedRequestTracker>,
device_bindings: Arc<RwLock<DeviceBindingTable>>,
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<()>>,
intrinsic_reporting_task: Option<JoinHandle<()>>,
binary_lighting_operation_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(),
configured_device_bindings: Vec::new(),
}
}
pub fn builder() -> BipServerBuilder {
Self::bip_builder()
}
}
mod clock;
#[cfg(test)]
pub(crate) use clock::clocked_test_database;
pub use clock::ClockConfig;
use clock::ServerClock;
mod binary_lighting_lifecycle;
mod confirmed_request_tracker;
mod cov_clock;
mod cov_encoding;
mod cov_notifications;
mod cov_snapshot;
mod device_bindings;
mod dispatch;
mod event_enrollment_lifecycle;
mod event_message_policy;
pub(crate) mod event_notification_payload;
mod event_notifications;
mod event_recipient_route;
pub(crate) mod event_timestamp;
mod lifecycle;
mod local_writes;
mod notification_transactions;
mod requests;
#[cfg(feature = "sc-tls")]
mod sc_builder;
#[cfg(test)]
pub(crate) use requests::{EXECUTED_CONFIRMED, EXECUTED_UNCONFIRMED};
#[cfg(test)]
mod audit_notification_tests;
#[cfg(test)]
mod unconfirmed_audit_notification_tests;
#[cfg(feature = "sc-tls")]
pub use sc_builder::ScServerBuilder;
mod responses;
mod segmentation;
mod segmented_receive;
mod shutdown;
#[cfg(test)]
mod acknowledge_alarm_tests;
#[cfg(test)]
mod audit_log_query_tests;
#[cfg(test)]
mod binary_lighting_task_tests;
#[cfg(test)]
mod cov_notifications_tests;
#[cfg(test)]
mod dcc_event_detection_tests;
#[cfg(test)]
mod device_bindings_tests;
#[cfg(test)]
mod device_recipient_routing_tests;
#[cfg(test)]
mod event_confirmed_routing_tests;
#[cfg(test)]
mod event_enable_distribution_tests;
#[cfg(test)]
mod event_enrollment_task_tests;
#[cfg(test)]
mod event_network_priority_tests;
#[cfg(test)]
mod event_notifications_tests;
#[cfg(test)]
mod event_recipient_routing_tests;
#[cfg(test)]
mod life_safety_cov_tests;
#[cfg(test)]
mod life_safety_operation_tests;
#[cfg(test)]
mod notification_transactions_tests;
#[cfg(test)]
mod segmentation_tests;
#[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,
configured_device_bindings: Vec::new(),
}
}
}