use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::sync::Arc;
#[cfg(test)]
pub(crate) use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Instant;
use bytes::{Bytes, BytesMut};
use tokio::sync::{Mutex, RwLock, Semaphore, mpsc, oneshot, watch};
use tokio::task::JoinHandle;
use tokio::time::Duration;
use tracing::{debug, warn};
use event_delivery::EventDelivery;
use bacnet_encoding::apdu::{
self, AbortPdu, Apdu, ComplexAck, ConfirmedRequest as ConfirmedRequestPdu, ErrorPdu, RejectPdu,
SegmentAck as SegmentAckPdu, SimpleAck, UnconfirmedRequest as UnconfirmedRequestPdu,
encode_apdu,
};
use bacnet_encoding::npdu::NpduAddress;
use bacnet_encoding::primitives::encode_property_value;
use bacnet_encoding::segmentation::{
SegmentReceiver, SegmentedPduType, duplicate_in_window, max_segment_payload, split_payload,
};
use bacnet_network::layer::NetworkLayer;
use bacnet_npdu::NetworkPriority;
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::COVNotificationMultipleRequest;
use bacnet_services::who_is::{DeviceInstanceRange, IAmRequest, WhoIsRequest};
use bacnet_transport::any::AnyTransport;
use bacnet_transport::bip::{BipMode, BipMulticast, BipTransport};
use bacnet_transport::port::TransportPort;
use bacnet_transport::warn_throttle::WarnThrottle;
use bacnet_transport::warn_throttled;
use bacnet_types::MacAddr;
use bacnet_types::enums::{
AbortReason, ConfirmedServiceChoice, ErrorClass, ErrorCode, LifeSafetyOperation, NotifyType,
ObjectType, PropertyIdentifier, RejectReason, Segmentation, UnconfirmedServiceChoice,
};
use bacnet_types::error::{Error, RemoteError};
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use crate::audit_notification::{
AuditNotificationAuthorizationContext, MAX_AUDIT_NOTIFICATION_BYTES, MAX_AUDIT_NOTIFICATIONS,
UnconfirmedAuditNotificationAuthorizationContext,
};
pub use crate::cov::{CovCounters, CovPolicy};
use crate::cov::{
CovNotificationKind, CovSubscription, CovSubscriptionSnapshot, CovSubscriptionTable,
};
use crate::life_safety::LifeSafetyOperationAuthorizationContext;
use crate::{handlers, runtime_clock};
use confirmed_request_tracker::{
ConfirmedRequestAdmission, ConfirmedRequestTracker, PendingConfirmedRequest,
};
pub use device_bindings::DeviceBinding;
use device_bindings::{DeviceBindingTable, register_configured_binding};
use learned_router_cache::LearnedRouterCache;
use lso_replay::{LsoAdmission, PendingLsoReplay};
use notification_transactions::{
InitiationRestricted, NotificationTransactions, NotificationWorkerResult,
canonical_direct_peer, canonical_routed_peer, run_notification_under_dcc,
run_notification_worker,
};
use request_services::{DispatchContext, RequestOrigin, RequestServices, UnconfirmedServices};
use requests::ConfirmedRequestOwnership;
use requests::confirmed_response::ResponseTarget;
use segmentation::{ComplexAckParams, SegmentedSendResources};
const MAX_SEG_RECEIVERS: usize = 128;
const MAX_REQUEST_SEGMENTS: usize = 256;
const MAX_SEG_SENDERS: usize = 128;
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;
#[derive(Debug, Clone)]
pub struct TimeSyncData {
pub raw_service_data: Bytes,
pub is_utc: bool,
pub source_mac: MacAddr,
pub source_network: Option<NpduAddress>,
pub provenance: bacnet_transport::port::TransportProvenance,
}
mod config;
pub use config::ServerConfig;
mod audit_batch_queue;
mod audit_batch_runtime;
mod audit_context_preparation;
mod audit_forwarder;
#[cfg(test)]
mod audit_forwarder_tests;
mod audit_recipient;
mod audit_recipient_routes;
mod audit_reporter;
mod audit_reporter_changes;
mod audit_send_now;
mod heap_futures;
pub use audit_reporter::{AuditReportersConfig, valid_bip_audit_address};
#[cfg(test)]
mod audit_reporter_tests;
pub struct ServerBuilder<T: TransportPort = AnyTransport> {
config: ServerConfig,
db: Option<ObjectDatabase>,
transport: Option<T>,
configured_device_bindings: Vec<DeviceBinding>,
}
impl<T: TransportPort + 'static> ServerBuilder<T> {
pub fn device(mut self, identity: bacnet_objects::DeviceIdentity) -> Self {
self.config.device = identity;
self
}
pub fn database(mut self, db: ObjectDatabase) -> Self {
self.db = Some(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 audit_notification_sink(mut self, sink: ObjectIdentifier) -> Self {
self.config.audit_notification_sink = Some(sink);
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 discovery_policy(mut self, policy: DiscoveryPolicy) -> Self {
self.config.discovery_policy = policy;
self
}
pub fn cov_policy(mut self, policy: CovPolicy) -> Self {
self.config.cov_policy = policy;
self
}
pub async fn build(mut self) -> Result<BACnetServer<T>, Error> {
let transport = self
.transport
.ok_or_else(|| Error::Encoding("transport not set on ServerBuilder".into()))?;
let db = crate::local_device::prepare_database(
&mut self.config,
self.db,
transport.local_receive_apdu_capacity(),
)?;
BACnetServer::start_with_clock_mode_and_bindings(
self.config,
db,
transport,
Some(ClockConfig::default()),
self.configured_device_bindings,
)
.await
}
}
pub struct BipServerBuilder {
config: ServerConfig,
db: Option<ObjectDatabase>,
configured_device_bindings: Vec<DeviceBinding>,
mode: BipMode,
multicast: Option<BipMulticast>,
}
impl BipServerBuilder {
pub fn device(mut self, identity: bacnet_objects::DeviceIdentity) -> Self {
self.config.device = identity;
self
}
pub fn database(mut self, db: ObjectDatabase) -> Self {
self.db = Some(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 audit_notification_sink(mut self, sink: ObjectIdentifier) -> Self {
self.config.audit_notification_sink = Some(sink);
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 discovery_policy(mut self, policy: DiscoveryPolicy) -> Self {
self.config.discovery_policy = policy;
self
}
pub fn cov_policy(mut self, policy: CovPolicy) -> Self {
self.config.cov_policy = policy;
self
}
pub async fn build(mut self) -> Result<BACnetServer<BipTransport>, Error> {
let config = &self.config;
let mut transport =
BipTransport::new(config.interface, config.port, config.broadcast_address);
transport.set_share_port_by_address(config.share_port_by_address);
transport.set_mode(self.mode);
transport.set_multicast(self.multicast)?;
let db = crate::local_device::prepare_database(
&mut self.config,
self.db,
transport.local_receive_apdu_capacity(),
)?;
BACnetServer::start_with_clock_mode_and_bindings(
self.config,
db,
transport,
Some(ClockConfig::default()),
self.configured_device_bindings,
)
.await
}
}
type TransportCleanup<T> = JoinHandle<(NetworkLayer<T>, Result<(), Error>)>;
pub struct BACnetServer<T: TransportPort = AnyTransport> {
target_audit: Option<Arc<audit_recipient::TargetAudit<T>>>,
config: Arc<ServerConfig>,
discovery_limiter: Arc<DiscoveryLimiter>,
#[allow(dead_code)] time_sync_limiter: Arc<TimeSyncLimiter>,
_clock: Option<Arc<ServerClock>>,
network: Option<Arc<NetworkLayer<T>>>,
broadcaster: Arc<broadcaster::BroadcasterState<T>>,
transport_cleanup: Option<TransportCleanup<T>>,
transport_cleanup_error: Option<String>,
network_number_task: Option<JoinHandle<()>>,
db: Arc<RwLock<ObjectDatabase>>,
cov_table: Arc<RwLock<CovSubscriptionTable>>,
cov_counters: Arc<crate::cov::AtomicCovCounters>,
#[allow(dead_code)]
seg_ack_senders: Arc<segmented_send::SegmentedSendRegistry>,
#[allow(dead_code)]
seg_send_permits: Arc<Semaphore>,
cov_in_flight: Arc<Semaphore>,
learned_routers: Arc<Mutex<LearnedRouterCache>>,
notification_transactions: Arc<NotificationTransactions>,
confirmed_request_tracker: Arc<ConfirmedRequestTracker>,
device_bindings: Arc<RwLock<DeviceBindingTable>>,
comm_state: Arc<CommState>,
dcc_timer: Arc<Mutex<crate::server::dcc_timer::TimerSlot>>,
dcc_outcomes: Arc<dcc_outcomes::DccOutcomes>,
event_suppressions: Arc<event_suppression::EventSuppressions>,
mutation_decisions: Arc<crate::mutation::MutationDecisions>,
dispatch_task: Option<JoinHandle<()>>,
request_tasks: Arc<request_tasks::RequestTasks>,
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<()>>,
cov_revisit_task: Option<JoinHandle<()>>,
local_mac: MacAddr,
}
pub struct IAmBroadcaster<T: TransportPort> {
state: std::sync::Weak<broadcaster::BroadcasterState<T>>,
}
impl<T: TransportPort> Clone for IAmBroadcaster<T> {
fn clone(&self) -> Self {
Self {
state: self.state.clone(),
}
}
}
impl BACnetServer<BipTransport> {
pub fn bip_builder() -> BipServerBuilder {
BipServerBuilder {
config: ServerConfig::default(),
db: None,
configured_device_bindings: Vec::new(),
mode: BipMode::Normal,
multicast: None,
}
}
}
mod clock;
mod command_runs;
mod reinitialize;
mod time_sync_policy;
#[doc(hidden)]
pub use reinitialize::EndpointReinitializeAudit as __endpoint_ReinitializeAudit;
pub use reinitialize::{ReinitializeContext, ReinitializeHandler};
mod write_group;
pub use clock::ClockConfig;
use clock::ServerClock;
#[cfg(test)]
pub(crate) use clock::clocked_test_database;
use time_sync_policy::{TimeSyncLimiter, request_limiters};
pub use time_sync_policy::{
TimeSyncPolicy, TimeSyncRateLimit, TimeSyncSource, TimeSyncSourceRestriction,
};
mod binary_lighting_lifecycle;
#[cfg(test)]
mod confirmed_issuance_tests;
mod confirmed_request_tracker;
mod cov_clock;
mod cov_encoding;
mod cov_fanout;
mod cov_notifications;
mod cov_notify_context;
mod cov_snapshot;
mod dcc_disable_rate;
pub(crate) mod dcc_outcomes;
mod dcc_policy;
mod lso_replay;
pub use dcc_disable_rate::DccDisableRateLimit;
mod dcc_timer;
pub use dcc_outcomes::DccOutcomeCounters;
pub use dcc_policy::{DccPolicy, DccSource, DccSourceRestriction};
use dcc_timer::CommState;
pub use dcc_timer::DccState;
mod binding_probes;
mod device_bindings;
mod discovery;
#[doc(hidden)]
pub use discovery::iam_request_for as discovery_iam_for_test;
pub use discovery::{DiscoveryCounters, DiscoveryPolicy};
pub(crate) use discovery::{DiscoveryLimiter, PreCheckDecision, WhoHasTarget};
mod audit_log_purge;
mod dispatch;
mod durable_writes;
#[doc(hidden)]
pub use durable_writes::settle_endpoint as __endpoint_settle_durable_writes;
mod event_delivery;
mod event_enrollment_lifecycle;
mod event_forwarding;
pub use event_forwarding::MAX_FORWARDED_DESTINATIONS;
mod event_forwarding_repeats;
mod event_message_policy;
pub(crate) mod event_notification_payload;
mod event_notifications;
mod event_recipient_route;
mod event_send;
mod event_suppression;
pub use event_suppression::EventNotificationCounters;
mod received_event_log;
pub use received_event_log::{RECEIVED_EVENT_LOG_RATE, RECEIVED_EVENT_LOG_SOURCES};
mod confirmed_answer;
pub(crate) mod event_timestamp;
mod handles;
mod learned_router_cache;
mod lifecycle;
mod local_authorization;
pub use local_authorization::LocalAuthorizationResult;
mod local_writes;
mod network_port;
#[cfg(test)]
mod network_port_tests;
#[cfg(test)]
#[path = "../../../bacnet-endpoint/tests/support/port_retry.rs"]
mod port_retry;
pub use confirmed_answer::CovAckResult;
mod remote_writes;
pub(crate) use remote_writes::RemoteRequestError;
mod notification_transactions;
#[doc(hidden)]
pub use notification_transactions::{
AuditFailureContext as __endpoint_AuditFailureContext,
AuditFailureQueue as __endpoint_AuditFailureQueue,
AuditFailureTicket as __endpoint_AuditFailureTicket,
NotificationOperation as __endpoint_NotificationOperation,
NotificationReserveError as __endpoint_NotificationReserveError,
NotificationTransactions as __endpoint_NotificationTransactions,
NotificationWorkerResult as __endpoint_NotificationWorkerResult,
canonical_direct_peer as __endpoint_canonical_direct_peer,
canonical_routed_peer as __endpoint_canonical_routed_peer,
run_notification_worker as __endpoint_run_notification_worker,
};
mod requests;
#[doc(hidden)]
pub use requests::endpoint_responder::EndpointResponder as __endpoint_EndpointResponder;
#[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 segmented_send;
pub(crate) use segmented_send::*;
mod request_admission;
mod rpm_budget;
pub use rpm_budget::ReadPropertyMultipleBudget;
mod alarm_summary_budget;
pub use alarm_summary_budget::GetAlarmSummaryBudget;
mod atomic_read_file_budget;
mod atomic_write_file_budget;
mod read_range_budget;
pub use read_range_budget::ReadRangeBudget;
mod event_information_budget;
pub use event_information_budget::GetEventInformationBudget;
mod enrollment_summary_budget;
pub use atomic_read_file_budget::AtomicReadFileBudget;
pub use atomic_write_file_budget::AtomicWriteFileBudget;
pub use enrollment_summary_budget::GetEnrollmentSummaryBudget;
#[cfg(test)]
mod atomic_read_file_tests;
#[cfg(test)]
mod atomic_write_file_tests;
#[cfg(test)]
mod enrollment_summary_tests;
mod request_peer;
mod request_services;
mod request_tasks;
pub use request_admission::{RequestAdmissionCounters, RequestAdmissionPolicy};
mod shutdown;
pub use shutdown::drop_database_off_runtime;
#[cfg(test)]
mod access_door_event_tests;
#[cfg(test)]
mod access_door_pulse_task_tests;
#[cfg(test)]
mod access_zone_event_tests;
#[cfg(test)]
mod acknowledge_alarm_tests;
#[cfg(test)]
mod active_cov_subscriptions_tests;
#[cfg(test)]
mod audit_log_query_tests;
#[cfg(test)]
mod averaging_reference_write_tests;
#[cfg(test)]
mod averaging_sample_tests;
#[cfg(test)]
mod binary_lighting_task_tests;
#[cfg(test)]
mod channel_member_concurrency_tests;
#[cfg(test)]
mod channel_reliability_tests;
#[cfg(test)]
mod channel_remote_datatype_tests;
#[cfg(test)]
mod channel_remote_write_tests;
#[cfg(test)]
mod channel_run_tests;
#[cfg(test)]
mod channel_wire_tests;
#[cfg(test)]
mod color_channel_member_tests;
#[cfg(test)]
mod color_engine_task_tests;
#[cfg(test)]
mod command_action_run_tests;
#[cfg(test)]
mod command_action_wire_tests;
#[cfg(test)]
mod command_remote_write_tests;
#[cfg(test)]
mod command_run_cancel_tests;
#[cfg(test)]
mod command_run_stop_tests;
#[cfg(test)]
mod confirmed_broadcast_tests;
#[cfg(test)]
mod cov_background_tests;
#[cfg(test)]
mod cov_budget_tests;
#[cfg(test)]
mod cov_confirmed_baseline_tests;
#[cfg(test)]
mod cov_confirmed_context_tests;
#[cfg(test)]
mod cov_multiple_admission_tests;
#[cfg(test)]
mod cov_notifications_tests;
#[cfg(test)]
mod cov_quota_tests;
#[cfg(test)]
mod cov_timed_capture_order_tests;
#[cfg(test)]
mod cov_timed_chunk_tests;
#[cfg(test)]
mod cov_timed_deadline_tests;
#[cfg(test)]
mod cov_timed_multiple_tests;
#[cfg(test)]
mod cov_timed_producer_tests;
#[cfg(test)]
mod cov_timed_value_split_tests;
#[cfg(test)]
mod cov_untimed_split_tests;
#[cfg(test)]
mod cov_wire_test_support;
#[cfg(test)]
mod dcc_event_detection_tests;
#[cfg(test)]
mod device_bindings_tests;
#[cfg(test)]
mod device_recipient_routing_tests;
#[cfg(all(test, feature = "sc-tls"))]
mod direct_principal_tests;
#[cfg(test)]
mod discovery_dcc_tests;
#[cfg(test)]
mod discovery_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_forwarding_bounds_tests;
#[cfg(test)]
mod event_forwarding_cap_tests;
#[cfg(test)]
mod event_forwarding_origin_tests;
#[cfg(test)]
mod event_forwarding_rule_tests;
#[cfg(test)]
mod event_forwarding_tests;
#[cfg(test)]
mod event_log_notification_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_application_tests;
#[cfg(test)]
mod life_safety_cov_tests;
#[cfg(test)]
mod life_safety_operation_replay_tests;
#[cfg(test)]
mod life_safety_operation_tests;
#[cfg(test)]
mod lighting_command_member_tests;
#[cfg(test)]
mod lighting_engine_task_tests;
#[cfg(test)]
mod lighting_present_value_tests;
#[cfg(test)]
mod list_element_event_tests;
#[cfg(test)]
mod local_network_number_tests;
#[cfg(test)]
mod local_network_outbound_tests;
#[cfg(test)]
mod log_reference_resize_tests;
#[cfg(test)]
mod loop_controlled_variable_tests;
#[cfg(test)]
mod loop_cov_tests;
#[cfg(test)]
mod mistagged_request_wire_tests;
#[cfg(test)]
mod noncommandable_null_cov_tests;
#[cfg(test)]
mod notification_dcc_tests;
#[cfg(test)]
mod notification_transactions_tests;
#[cfg(test)]
mod pulse_converter_cov_tests;
#[cfg(test)]
mod pulse_converter_reference_tests;
#[cfg(test)]
mod rb07_provenance_tests;
#[cfg(test)]
mod reference_write_wire_tests;
#[cfg(test)]
mod remote_write_answer_tests;
#[cfg(test)]
mod remote_write_discovery_tests;
#[cfg(test)]
mod run_cycle_tests;
#[cfg(test)]
mod schedule_create_retry_tests;
#[cfg(test)]
mod schedule_reference_list_tests;
#[cfg(test)]
mod schedule_reference_write_tests;
#[cfg(test)]
mod schedule_write_tests;
#[cfg(test)]
mod segmentation_tests;
#[cfg(test)]
mod staging_cov_tests;
#[cfg(test)]
mod staging_reference_write_tests;
#[cfg(test)]
mod subscribed_recipients_expiry_tests;
#[cfg(test)]
mod table_13_1_cov_tests;
#[cfg(test)]
pub(crate) mod test_forwarder;
#[cfg(test)]
pub(crate) mod test_transport;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod trailing_octet_wire_tests;
#[cfg(test)]
mod truncated_request_wire_tests;
#[cfg(test)]
mod value_cov_increment_tests;
#[cfg(test)]
mod value_source_local_tests;
#[cfg(test)]
mod warn_spy;
impl<T: TransportPort + 'static> BACnetServer<T> {
pub fn discovery_counters(&self) -> DiscoveryCounters {
self.discovery_limiter.counters()
}
pub fn cov_counters(&self) -> CovCounters {
self.cov_counters.snapshot()
}
pub fn mutation_decision_counters(&self) -> crate::mutation::MutationDecisionCounters {
self.mutation_decisions.snapshot()
}
pub async fn remove_peer_subscriptions(
&self,
mac: &[u8],
network: Option<&NpduAddress>,
) -> usize {
let mut table = self.cov_table.write().await;
table.remove_peer_subscriptions(mac, network)
}
}
#[cfg(test)]
mod cov_identity_completion_tests;
mod broadcaster;
#[cfg(test)]
mod r7_reply_tests;