Skip to main content

bacnet_server/server/
mod.rs

1//! BACnetServer: builder, APDU dispatch, and lifecycle management.
2//!
3//! The server wraps a NetworkLayer behind Arc (shared with the dispatch task),
4//! owns an ObjectDatabase via `Arc<RwLock>`, and spawns a dispatch task that
5//! routes incoming APDUs to service handlers.
6
7use std::collections::HashMap;
8use std::net::Ipv4Addr;
9#[cfg(test)]
10pub(crate) use std::sync::atomic::AtomicBool;
11use std::sync::atomic::Ordering;
12use std::sync::Arc;
13use std::time::Instant;
14
15use bytes::{Bytes, BytesMut};
16use tokio::sync::{mpsc, oneshot, watch, Mutex, RwLock, Semaphore};
17use tokio::task::JoinHandle;
18use tokio::time::Duration;
19use tracing::{debug, warn};
20
21use event_delivery::EventDelivery;
22
23use bacnet_encoding::apdu::{
24    self, encode_apdu, AbortPdu, Apdu, ComplexAck, ConfirmedRequest as ConfirmedRequestPdu,
25    ErrorPdu, RejectPdu, SegmentAck as SegmentAckPdu, SimpleAck,
26    UnconfirmedRequest as UnconfirmedRequestPdu,
27};
28use bacnet_encoding::npdu::NpduAddress;
29use bacnet_encoding::primitives::encode_property_value;
30use bacnet_encoding::segmentation::{
31    duplicate_in_window, max_segment_payload, split_payload, SegmentReceiver, SegmentedPduType,
32};
33use bacnet_network::layer::NetworkLayer;
34use bacnet_npdu::NetworkPriority;
35use bacnet_objects::database::ObjectDatabase;
36use bacnet_objects::notification_class::{
37    lookup_notification_recipients, resolve_transition_priority_ack,
38};
39use bacnet_services::alarm_event::EventNotificationRequest;
40use bacnet_services::common::BACnetPropertyValue;
41use bacnet_services::cov::COVNotificationRequest;
42use bacnet_services::cov_multiple::COVNotificationMultipleRequest;
43use bacnet_services::who_is::{DeviceInstanceRange, IAmRequest, WhoIsRequest};
44use bacnet_transport::bip::{BipMode, BipMulticast, BipTransport};
45use bacnet_transport::port::TransportPort;
46use bacnet_transport::warn_throttle::WarnThrottle;
47use bacnet_transport::warn_throttled;
48use bacnet_types::enums::{
49    AbortReason, ConfirmedServiceChoice, ErrorClass, ErrorCode, LifeSafetyOperation, NotifyType,
50    ObjectType, PropertyIdentifier, RejectReason, Segmentation, UnconfirmedServiceChoice,
51};
52use bacnet_types::error::Error;
53use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
54use bacnet_types::MacAddr;
55
56use crate::audit_notification::{
57    AuditNotificationAuthorizationContext, UnconfirmedAuditNotificationAuthorizationContext,
58    MAX_AUDIT_NOTIFICATIONS, MAX_AUDIT_NOTIFICATION_BYTES,
59};
60pub use crate::cov::{CovCounters, CovPolicy};
61use crate::cov::{
62    CovNotificationKind, CovSubscription, CovSubscriptionSnapshot, CovSubscriptionTable,
63};
64use crate::life_safety::LifeSafetyOperationAuthorizationContext;
65use crate::{handlers, runtime_clock};
66use confirmed_request_tracker::{
67    ConfirmedRequestAdmission, ConfirmedRequestTracker, PendingConfirmedRequest,
68};
69pub use device_bindings::DeviceBinding;
70use device_bindings::{register_configured_binding, DeviceBindingTable};
71use learned_router_cache::LearnedRouterCache;
72use lso_replay::{LsoAdmission, PendingLsoReplay};
73use notification_transactions::{
74    canonical_direct_peer, canonical_routed_peer, run_notification_under_dcc,
75    run_notification_worker, InitiationRestricted, NotificationTransactions,
76    NotificationWorkerResult,
77};
78use request_services::{DispatchContext, RequestOrigin, RequestServices, UnconfirmedServices};
79use requests::confirmed_response::ResponseTarget;
80use requests::ConfirmedRequestOwnership;
81use segmentation::{ComplexAckParams, SegmentedSendResources};
82
83/// Maximum number of concurrent segmented reassembly sessions.
84const MAX_SEG_RECEIVERS: usize = 128;
85
86/// Hard per-request reassembly ceiling: the sequence-number space (#364).
87///
88/// A local storage bound, not a protocol one. Clause 20.1.2.7 makes the
89/// request sequence number modulo 256, so a longer request is entirely
90/// representable on the wire — this server simply keys its segment store by
91/// that `u8` and cannot tell segment 256 from segment 0. A Device object
92/// configured to receive segments does publish a tighter advertisement — its
93/// `Max_Segments_Accepted` (Clause 12.11) defaults to `Unsigned(65)` — but
94/// enforcing it here is deliberately not done: accepting more segments than
95/// advertised is permissive, not a violation, while the sequence space is the
96/// line past which acceptance silently corrupts. Exactly 256 segments
97/// reassemble correctly and must keep working; 257 is the first that would
98/// corrupt the payload.
99const MAX_REQUEST_SEGMENTS: usize = 256;
100
101/// Maximum number of concurrent segmented response send sessions.
102const MAX_SEG_SENDERS: usize = 128;
103
104/// Maximum negative SegmentAck retries during segmented response send.
105const MAX_NEG_SEGMENT_ACK_RETRIES: u8 = 3;
106
107/// Default timeout while waiting for SegmentACK during segmented response send.
108const DEFAULT_APDU_SEGMENT_TIMEOUT: Duration = Duration::from_secs(5);
109
110/// Default retransmission budget for segmented response segments.
111const DEFAULT_APDU_SEGMENT_RETRIES: u8 = MAX_NEG_SEGMENT_ACK_RETRIES;
112
113/// Default number of APDU retries for confirmed notifications and the
114/// WriteProperty requests Command actions send to other devices.
115const DEFAULT_APDU_RETRIES: u8 = 3;
116
117/// Data from a TimeSynchronization request.
118#[derive(Debug, Clone)]
119pub struct TimeSyncData {
120    /// Raw service request bytes (caller can decode if needed).
121    pub raw_service_data: Bytes,
122    /// Whether this was a UTC time sync (vs. local).
123    pub is_utc: bool,
124    /// Transport-native source MAC; on SC this is the source VMAC, not a
125    /// certificate principal. This metadata is a claimed identity only.
126    pub source_mac: MacAddr,
127    /// Claimed routed NPDU source, if present; takes precedence for policy matching.
128    pub source_network: Option<NpduAddress>,
129    /// Honest transport + origin provenance, threaded for diagnostics only
130    /// (RB-07 compat mode: decisions unchanged; RB-09 consumes it later).
131    pub provenance: bacnet_transport::port::TransportProvenance,
132}
133
134mod config;
135pub use config::ServerConfig;
136mod audit_batch_queue;
137mod audit_batch_runtime;
138mod audit_context_preparation;
139mod audit_forwarder;
140#[cfg(test)]
141mod audit_forwarder_tests;
142mod audit_recipient;
143mod audit_recipient_routes;
144mod audit_reporter;
145mod audit_reporter_changes;
146mod audit_send_now;
147mod heap_futures;
148pub use audit_reporter::{valid_bip_audit_address, AuditReportersConfig};
149#[cfg(test)]
150mod audit_reporter_tests;
151
152/// Generic builder for BACnetServer with a pre-built transport.
153pub struct ServerBuilder<T: TransportPort> {
154    config: ServerConfig,
155    db: Option<ObjectDatabase>,
156    transport: Option<T>,
157    configured_device_bindings: Vec<DeviceBinding>,
158}
159
160impl<T: TransportPort + 'static> ServerBuilder<T> {
161    /// Set the one Device identity. With no supplied database, startup creates
162    /// a stock Device after resolving the owned transport's receive capacity.
163    pub fn device(mut self, identity: bacnet_objects::DeviceIdentity) -> Self {
164        self.config.device = identity;
165        self
166    }
167
168    /// Set the object database (transfers ownership).
169    pub fn database(mut self, db: ObjectDatabase) -> Self {
170        self.db = Some(db);
171        self
172    }
173
174    /// Set the pre-built transport.
175    pub fn transport(mut self, transport: T) -> Self {
176        self.transport = Some(transport);
177        self
178    }
179
180    /// Register one explicit unicast route for a Device recipient.
181    pub fn device_binding(mut self, binding: DeviceBinding) -> Result<Self, Error> {
182        register_configured_binding(&mut self.configured_device_bindings, binding)?;
183        Ok(self)
184    }
185
186    /// Select the only local Audit Log that receives authorized notifications.
187    pub fn audit_notification_sink(mut self, sink: ObjectIdentifier) -> Self {
188        self.config.audit_notification_sink = Some(sink);
189        self
190    }
191
192    /// Enable periodic fault detection / reliability evaluation.
193    ///
194    /// When enabled, every object's opt-in reliability hook runs every 10
195    /// seconds; the default hook is a no-op.
196    ///
197    /// Reliability evaluation only; Event Enrollment evaluation is configured
198    /// by [`enable_event_enrollment`](Self::enable_event_enrollment).
199    pub fn enable_fault_detection(mut self, enabled: bool) -> Self {
200        self.config.enable_fault_detection = enabled;
201        self
202    }
203
204    /// Enable periodic Event Enrollment evaluation (default `true`).
205    pub fn enable_event_enrollment(mut self, enabled: bool) -> Self {
206        self.config.enable_event_enrollment = enabled;
207        self
208    }
209
210    /// Set the interval in seconds between Event Enrollment evaluation passes
211    /// (default 10).
212    pub fn event_enrollment_interval_secs(mut self, secs: u64) -> Self {
213        self.config.event_enrollment_interval_secs = secs;
214        self
215    }
216
217    /// Set the discovery rate-limiting and duplicate suppression policy.
218    pub fn discovery_policy(mut self, policy: DiscoveryPolicy) -> Self {
219        self.config.discovery_policy = policy;
220        self
221    }
222
223    /// Set the COV quota and notification work budget policy, checked by
224    /// [`CovPolicy::validate`] before transport startup.
225    pub fn cov_policy(mut self, policy: CovPolicy) -> Self {
226        self.config.cov_policy = policy;
227        self
228    }
229
230    /// Build and start the server.
231    pub async fn build(mut self) -> Result<BACnetServer<T>, Error> {
232        let transport = self
233            .transport
234            .ok_or_else(|| Error::Encoding("transport not set on ServerBuilder".into()))?;
235        let db = crate::local_device::prepare_database(
236            &mut self.config,
237            self.db,
238            transport.local_receive_apdu_capacity(),
239        )?;
240        BACnetServer::start_with_clock_mode_and_bindings(
241            self.config,
242            db,
243            transport,
244            Some(ClockConfig::default()),
245            self.configured_device_bindings,
246        )
247        .await
248    }
249}
250
251/// BIP-specific builder that constructs `BipTransport` from interface/port/broadcast fields.
252pub struct BipServerBuilder {
253    config: ServerConfig,
254    db: Option<ObjectDatabase>,
255    configured_device_bindings: Vec<DeviceBinding>,
256    mode: BipMode,
257    multicast: Option<BipMulticast>,
258}
259
260impl BipServerBuilder {
261    /// Set the one Device identity. With no supplied database, startup creates
262    /// a stock Device after resolving the owned transport's receive capacity.
263    pub fn device(mut self, identity: bacnet_objects::DeviceIdentity) -> Self {
264        self.config.device = identity;
265        self
266    }
267
268    /// Set the object database (transfers ownership).
269    pub fn database(mut self, db: ObjectDatabase) -> Self {
270        self.db = Some(db);
271        self
272    }
273
274    /// Register one explicit unicast route for a Device recipient.
275    pub fn device_binding(mut self, binding: DeviceBinding) -> Result<Self, Error> {
276        register_configured_binding(&mut self.configured_device_bindings, binding)?;
277        Ok(self)
278    }
279
280    /// Select the only local Audit Log that receives authorized notifications.
281    pub fn audit_notification_sink(mut self, sink: ObjectIdentifier) -> Self {
282        self.config.audit_notification_sink = Some(sink);
283        self
284    }
285
286    /// Enable periodic fault detection / reliability evaluation.
287    ///
288    /// When enabled, every object's opt-in reliability hook runs every 10
289    /// seconds; the default hook is a no-op.
290    ///
291    /// Reliability evaluation only; Event Enrollment evaluation is configured
292    /// by [`enable_event_enrollment`](Self::enable_event_enrollment).
293    pub fn enable_fault_detection(mut self, enabled: bool) -> Self {
294        self.config.enable_fault_detection = enabled;
295        self
296    }
297
298    /// Enable periodic Event Enrollment evaluation (default `true`).
299    pub fn enable_event_enrollment(mut self, enabled: bool) -> Self {
300        self.config.enable_event_enrollment = enabled;
301        self
302    }
303
304    /// Set the interval in seconds between Event Enrollment evaluation passes
305    /// (default 10).
306    pub fn event_enrollment_interval_secs(mut self, secs: u64) -> Self {
307        self.config.event_enrollment_interval_secs = secs;
308        self
309    }
310
311    /// Set the discovery rate-limiting and duplicate suppression policy.
312    pub fn discovery_policy(mut self, policy: DiscoveryPolicy) -> Self {
313        self.config.discovery_policy = policy;
314        self
315    }
316
317    /// Set the COV quota and notification work budget policy, checked by
318    /// [`CovPolicy::validate`] before transport startup.
319    pub fn cov_policy(mut self, policy: CovPolicy) -> Self {
320        self.config.cov_policy = policy;
321        self
322    }
323
324    /// Build and start the server, constructing a BipTransport from the config.
325    pub async fn build(mut self) -> Result<BACnetServer<BipTransport>, Error> {
326        let config = &self.config;
327        let mut transport =
328            BipTransport::new(config.interface, config.port, config.broadcast_address);
329        transport.set_share_port_by_address(config.share_port_by_address);
330        transport.set_mode(self.mode);
331        transport.set_multicast(self.multicast)?;
332        let db = crate::local_device::prepare_database(
333            &mut self.config,
334            self.db,
335            transport.local_receive_apdu_capacity(),
336        )?;
337        BACnetServer::start_with_clock_mode_and_bindings(
338            self.config,
339            db,
340            transport,
341            Some(ClockConfig::default()),
342            self.configured_device_bindings,
343        )
344        .await
345    }
346}
347
348type TransportCleanup<T> = JoinHandle<(NetworkLayer<T>, Result<(), Error>)>;
349
350/// BACnet server with APDU dispatch and service handling.
351pub struct BACnetServer<T: TransportPort> {
352    target_audit: Option<Arc<audit_recipient::TargetAudit<T>>>,
353    /// Shared with dispatch and every task the server starts (#1521).
354    config: Arc<ServerConfig>,
355    discovery_limiter: Arc<DiscoveryLimiter>,
356    #[allow(dead_code)] // Retained with the server, including direct dispatch tests.
357    time_sync_limiter: Arc<TimeSyncLimiter>,
358    /// Server-owned clock controller; absent in explicit clockless mode.
359    _clock: Option<Arc<ServerClock>>,
360    /// Shared network layer (also held by dispatch task; read by
361    /// [`write_local`](Self::write_local) for post-write COV/event sends).
362    network: Option<Arc<NetworkLayer<T>>>,
363    broadcaster: Arc<broadcaster::BroadcasterState<T>>,
364    transport_cleanup: Option<TransportCleanup<T>>,
365    transport_cleanup_error: Option<String>,
366    network_number_task: Option<JoinHandle<()>>,
367    /// Shared object database.
368    db: Arc<RwLock<ObjectDatabase>>,
369    /// COV subscription table (also held by dispatch task; read by
370    /// [`write_local`](Self::write_local) to fire post-write notifications).
371    cov_table: Arc<RwLock<CovSubscriptionTable>>,
372    cov_counters: Arc<crate::cov::AtomicCovCounters>,
373    /// Channels for routing segmented-send events to in-progress segmented sends.
374    #[allow(dead_code)]
375    seg_ack_senders: Arc<segmented_send::SegmentedSendRegistry>,
376    /// Permits that cap live segmented response sender tasks, including
377    /// cancelled senders that have not yet exited a transport send.
378    #[allow(dead_code)]
379    seg_send_permits: Arc<Semaphore>,
380    /// Operational cap of 255 concurrent confirmed COV notification workers.
381    /// Invoke-ID ownership is handled by `notification_transactions`.
382    cov_in_flight: Arc<Semaphore>,
383    /// Bounded DNET-to-router cache learned from admitted notification terminals.
384    learned_routers: Arc<Mutex<LearnedRouterCache>>,
385    /// Invoke-ID ownership and terminal admission for confirmed notifications.
386    notification_transactions: Arc<NotificationTransactions>,
387    /// Server-lifetime exact inbound ConfirmedRequest duplicate state.
388    confirmed_request_tracker: Arc<ConfirmedRequestTracker>,
389    /// Shared configured and passively observed Device recipient authority.
390    device_bindings: Arc<RwLock<DeviceBindingTable>>,
391    /// DeviceCommunicationControl state; only `dcc_timer` changes it.
392    comm_state: Arc<CommState>,
393    /// DCC timer owner and replacement/expiry serialization boundary.
394    /// Valid replacement and explicit stop abort and join before clearing it.
395    dcc_timer: Arc<Mutex<crate::server::dcc_timer::TimerSlot>>,
396    dcc_outcomes: Arc<dcc_outcomes::DccOutcomes>,
397    /// Lifetime totals of undelivered event notifications (#1142).
398    event_suppressions: Arc<event_suppression::EventSuppressions>,
399    mutation_decisions: Arc<crate::mutation::MutationDecisions>,
400    dispatch_task: Option<JoinHandle<()>>,
401    request_tasks: Arc<request_tasks::RequestTasks>,
402    cov_purge_task: Option<JoinHandle<()>>,
403    fault_detection_task: Option<JoinHandle<()>>,
404    event_enrollment_task: Option<JoinHandle<()>>,
405    trend_log_task: Option<JoinHandle<()>>,
406    schedule_tick_task: Option<JoinHandle<()>>,
407    /// One-second `Time_Delay` confirmation task for intrinsic reporting.
408    intrinsic_reporting_task: Option<JoinHandle<()>>,
409    /// Monotonic object-operation task: Binary Lighting Output
410    /// WARN_OFF/WARN_RELINQUISH egress and Access Door pulse relock (#1073).
411    binary_lighting_operation_task: Option<JoinHandle<()>>,
412    /// Follow-up fanout after acknowledged confirmed COV reports (#896).
413    cov_revisit_task: Option<JoinHandle<()>>,
414    local_mac: MacAddr,
415}
416
417/// Cloneable handle for unsolicited I-Am announcements while its server runs.
418///
419/// At most 32 local broadcasts may be in flight. Admission fails immediately
420/// at capacity or after shutdown begins. Admitted sends belong to the server:
421/// cancelling their caller only drops its waiter; stop cancels and joins them.
422/// Retaining this handle does not retain the transport after shutdown.
423pub struct IAmBroadcaster<T: TransportPort> {
424    state: std::sync::Weak<broadcaster::BroadcasterState<T>>,
425}
426
427impl<T: TransportPort> Clone for IAmBroadcaster<T> {
428    fn clone(&self) -> Self {
429        Self {
430            state: self.state.clone(),
431        }
432    }
433}
434
435impl BACnetServer<BipTransport> {
436    /// Create a BIP-specific builder with interface/port/broadcast fields.
437    pub fn bip_builder() -> BipServerBuilder {
438        BipServerBuilder {
439            config: ServerConfig::default(),
440            db: None,
441            configured_device_bindings: Vec::new(),
442            mode: BipMode::Normal,
443            multicast: None,
444        }
445    }
446}
447
448mod clock;
449mod command_runs;
450mod reinitialize;
451mod time_sync_policy;
452#[doc(hidden)]
453pub use reinitialize::EndpointReinitializeAudit as __endpoint_ReinitializeAudit;
454pub use reinitialize::{ReinitializeContext, ReinitializeHandler};
455mod write_group;
456#[cfg(test)]
457pub(crate) use clock::clocked_test_database;
458pub use clock::ClockConfig;
459use clock::ServerClock;
460use time_sync_policy::{request_limiters, TimeSyncLimiter};
461pub use time_sync_policy::{
462    TimeSyncPolicy, TimeSyncRateLimit, TimeSyncSource, TimeSyncSourceRestriction,
463};
464mod binary_lighting_lifecycle;
465#[cfg(test)]
466mod confirmed_issuance_tests;
467mod confirmed_request_tracker;
468mod cov_clock;
469mod cov_encoding;
470mod cov_fanout;
471mod cov_notifications;
472mod cov_notify_context;
473mod cov_snapshot;
474mod dcc_disable_rate;
475pub(crate) mod dcc_outcomes;
476mod dcc_policy;
477mod lso_replay;
478pub use dcc_disable_rate::DccDisableRateLimit;
479mod dcc_timer;
480pub use dcc_outcomes::DccOutcomeCounters;
481pub use dcc_policy::{DccPolicy, DccSource, DccSourceRestriction};
482use dcc_timer::CommState;
483pub use dcc_timer::DccState;
484mod binding_probes;
485mod device_bindings;
486mod discovery;
487#[doc(hidden)]
488pub use discovery::iam_request_for as discovery_iam_for_test;
489pub use discovery::{DiscoveryCounters, DiscoveryPolicy};
490pub(crate) use discovery::{DiscoveryLimiter, PreCheckDecision, WhoHasTarget};
491mod audit_log_purge;
492mod dispatch;
493mod durable_writes;
494#[doc(hidden)]
495pub use durable_writes::settle_endpoint as __endpoint_settle_durable_writes;
496mod event_delivery;
497mod event_enrollment_lifecycle;
498mod event_forwarding;
499pub use event_forwarding::MAX_FORWARDED_DESTINATIONS;
500mod event_forwarding_repeats;
501mod event_message_policy;
502pub(crate) mod event_notification_payload;
503mod event_notifications;
504mod event_recipient_route;
505mod event_send;
506mod event_suppression;
507pub use event_suppression::EventNotificationCounters;
508mod received_event_log;
509pub use received_event_log::{RECEIVED_EVENT_LOG_RATE, RECEIVED_EVENT_LOG_SOURCES};
510mod confirmed_answer;
511pub(crate) mod event_timestamp;
512mod handles;
513mod learned_router_cache;
514mod lifecycle;
515mod local_writes;
516mod network_port;
517#[cfg(test)]
518mod network_port_tests;
519/// Reruns a test whose closing rebind loses the freed port to another
520/// process (#1419).
521#[cfg(test)]
522#[path = "../../../bacnet-endpoint/tests/support/port_retry.rs"]
523mod port_retry;
524pub use confirmed_answer::{CovAckResult, Refusal};
525mod remote_writes;
526pub(crate) use remote_writes::RemoteRequestError;
527mod notification_transactions;
528#[doc(hidden)]
529pub use notification_transactions::{
530    canonical_direct_peer as __endpoint_canonical_direct_peer,
531    canonical_routed_peer as __endpoint_canonical_routed_peer,
532    run_notification_worker as __endpoint_run_notification_worker,
533    AuditFailureContext as __endpoint_AuditFailureContext,
534    AuditFailureQueue as __endpoint_AuditFailureQueue,
535    AuditFailureTicket as __endpoint_AuditFailureTicket,
536    NotificationOperation as __endpoint_NotificationOperation,
537    NotificationReserveError as __endpoint_NotificationReserveError,
538    NotificationTransactions as __endpoint_NotificationTransactions,
539    NotificationWorkerResult as __endpoint_NotificationWorkerResult,
540};
541mod requests;
542#[doc(hidden)]
543pub use requests::endpoint_responder::EndpointResponder as __endpoint_EndpointResponder;
544#[cfg(feature = "sc-tls")]
545mod sc_builder;
546#[cfg(test)]
547pub(crate) use requests::{EXECUTED_CONFIRMED, EXECUTED_UNCONFIRMED};
548#[cfg(test)]
549mod audit_notification_tests;
550#[cfg(test)]
551mod unconfirmed_audit_notification_tests;
552#[cfg(feature = "sc-tls")]
553pub use sc_builder::ScServerBuilder;
554mod responses;
555mod segmentation;
556mod segmented_receive;
557mod segmented_send;
558pub(crate) use segmented_send::*;
559mod request_admission;
560mod rpm_budget;
561pub use rpm_budget::ReadPropertyMultipleBudget;
562mod alarm_summary_budget;
563pub use alarm_summary_budget::GetAlarmSummaryBudget;
564mod atomic_read_file_budget;
565mod atomic_write_file_budget;
566mod read_range_budget;
567pub use read_range_budget::ReadRangeBudget;
568mod event_information_budget;
569pub use event_information_budget::GetEventInformationBudget;
570mod enrollment_summary_budget;
571pub use atomic_read_file_budget::AtomicReadFileBudget;
572pub use atomic_write_file_budget::AtomicWriteFileBudget;
573pub use enrollment_summary_budget::GetEnrollmentSummaryBudget;
574#[cfg(test)]
575mod atomic_read_file_tests;
576#[cfg(test)]
577mod atomic_write_file_tests;
578#[cfg(test)]
579mod enrollment_summary_tests;
580mod request_peer;
581mod request_services;
582mod request_tasks;
583pub use request_admission::{RequestAdmissionCounters, RequestAdmissionPolicy};
584mod shutdown;
585pub use shutdown::drop_database_off_runtime;
586
587#[cfg(test)]
588mod access_door_event_tests;
589#[cfg(test)]
590mod access_door_pulse_task_tests;
591#[cfg(test)]
592mod access_zone_event_tests;
593#[cfg(test)]
594mod acknowledge_alarm_tests;
595#[cfg(test)]
596mod active_cov_subscriptions_tests;
597#[cfg(test)]
598mod audit_log_query_tests;
599#[cfg(test)]
600mod averaging_reference_write_tests;
601#[cfg(test)]
602mod averaging_sample_tests;
603#[cfg(test)]
604mod binary_lighting_task_tests;
605#[cfg(test)]
606mod channel_member_concurrency_tests;
607#[cfg(test)]
608mod channel_reliability_tests;
609#[cfg(test)]
610mod channel_remote_datatype_tests;
611#[cfg(test)]
612mod channel_remote_write_tests;
613#[cfg(test)]
614mod channel_run_tests;
615#[cfg(test)]
616mod channel_wire_tests;
617#[cfg(test)]
618mod color_channel_member_tests;
619#[cfg(test)]
620mod color_engine_task_tests;
621#[cfg(test)]
622mod command_action_run_tests;
623#[cfg(test)]
624mod command_action_wire_tests;
625#[cfg(test)]
626mod command_remote_write_tests;
627#[cfg(test)]
628mod command_run_cancel_tests;
629#[cfg(test)]
630mod command_run_stop_tests;
631#[cfg(test)]
632mod confirmed_broadcast_tests;
633#[cfg(test)]
634mod cov_background_tests;
635#[cfg(test)]
636mod cov_budget_tests;
637#[cfg(test)]
638mod cov_confirmed_baseline_tests;
639#[cfg(test)]
640mod cov_confirmed_context_tests;
641#[cfg(test)]
642mod cov_multiple_admission_tests;
643#[cfg(test)]
644mod cov_notifications_tests;
645#[cfg(test)]
646mod cov_quota_tests;
647#[cfg(test)]
648mod cov_timed_capture_order_tests;
649#[cfg(test)]
650mod cov_timed_chunk_tests;
651#[cfg(test)]
652mod cov_timed_deadline_tests;
653#[cfg(test)]
654mod cov_timed_multiple_tests;
655#[cfg(test)]
656mod cov_timed_producer_tests;
657#[cfg(test)]
658mod cov_timed_value_split_tests;
659#[cfg(test)]
660mod cov_untimed_split_tests;
661#[cfg(test)]
662mod cov_wire_test_support;
663#[cfg(test)]
664mod dcc_event_detection_tests;
665#[cfg(test)]
666mod device_bindings_tests;
667#[cfg(test)]
668mod device_recipient_routing_tests;
669#[cfg(all(test, feature = "sc-tls"))]
670mod direct_principal_tests;
671#[cfg(test)]
672mod discovery_dcc_tests;
673#[cfg(test)]
674mod discovery_tests;
675#[cfg(test)]
676mod event_confirmed_routing_tests;
677#[cfg(test)]
678mod event_enable_distribution_tests;
679#[cfg(test)]
680mod event_enrollment_task_tests;
681#[cfg(test)]
682mod event_forwarding_bounds_tests;
683#[cfg(test)]
684mod event_forwarding_cap_tests;
685#[cfg(test)]
686mod event_forwarding_origin_tests;
687#[cfg(test)]
688mod event_forwarding_rule_tests;
689#[cfg(test)]
690mod event_forwarding_tests;
691#[cfg(test)]
692mod event_log_notification_tests;
693#[cfg(test)]
694mod event_network_priority_tests;
695#[cfg(test)]
696mod event_notifications_tests;
697#[cfg(test)]
698mod event_recipient_routing_tests;
699#[cfg(test)]
700mod life_safety_application_tests;
701#[cfg(test)]
702mod life_safety_cov_tests;
703#[cfg(test)]
704mod life_safety_operation_replay_tests;
705#[cfg(test)]
706mod life_safety_operation_tests;
707#[cfg(test)]
708mod lighting_command_member_tests;
709#[cfg(test)]
710mod lighting_engine_task_tests;
711#[cfg(test)]
712mod lighting_present_value_tests;
713#[cfg(test)]
714mod list_element_event_tests;
715#[cfg(test)]
716mod local_network_number_tests;
717#[cfg(test)]
718mod local_network_outbound_tests;
719#[cfg(test)]
720mod log_reference_resize_tests;
721#[cfg(test)]
722mod loop_controlled_variable_tests;
723#[cfg(test)]
724mod loop_cov_tests;
725#[cfg(test)]
726mod mistagged_request_wire_tests;
727#[cfg(test)]
728mod noncommandable_null_cov_tests;
729#[cfg(test)]
730mod notification_dcc_tests;
731#[cfg(test)]
732mod notification_transactions_tests;
733#[cfg(test)]
734mod pulse_converter_cov_tests;
735#[cfg(test)]
736mod pulse_converter_reference_tests;
737#[cfg(test)]
738mod rb07_provenance_tests;
739#[cfg(test)]
740mod reference_write_wire_tests;
741#[cfg(test)]
742mod remote_write_answer_tests;
743#[cfg(test)]
744mod remote_write_discovery_tests;
745#[cfg(test)]
746mod run_cycle_tests;
747#[cfg(test)]
748mod schedule_create_retry_tests;
749#[cfg(test)]
750mod schedule_reference_list_tests;
751#[cfg(test)]
752mod schedule_reference_write_tests;
753#[cfg(test)]
754mod schedule_write_tests;
755#[cfg(test)]
756mod segmentation_tests;
757#[cfg(test)]
758mod staging_cov_tests;
759#[cfg(test)]
760mod staging_reference_write_tests;
761#[cfg(test)]
762mod subscribed_recipients_expiry_tests;
763#[cfg(test)]
764mod table_13_1_cov_tests;
765#[cfg(test)]
766pub(crate) mod test_forwarder;
767#[cfg(test)]
768pub(crate) mod test_transport;
769#[cfg(test)]
770mod tests;
771#[cfg(test)]
772mod trailing_octet_wire_tests;
773#[cfg(test)]
774mod truncated_request_wire_tests;
775#[cfg(test)]
776mod value_cov_increment_tests;
777#[cfg(test)]
778mod value_source_local_tests;
779#[cfg(test)]
780mod warn_spy;
781
782impl<T: TransportPort + 'static> BACnetServer<T> {
783    /// Get a snapshot of discovery rate-limiting counters.
784    pub fn discovery_counters(&self) -> DiscoveryCounters {
785        self.discovery_limiter.counters()
786    }
787
788    /// Get a snapshot of COV operational and telemetry counters.
789    pub fn cov_counters(&self) -> CovCounters {
790        self.cov_counters.snapshot()
791    }
792
793    /// Sample lifetime mutation authorization decisions, including after `stop()`.
794    /// Counts decisions, not completed handlers or response delivery; see
795    /// [`MutationDecisionCounters`](crate::mutation::MutationDecisionCounters).
796    pub fn mutation_decision_counters(&self) -> crate::mutation::MutationDecisionCounters {
797        self.mutation_decisions.snapshot()
798    }
799
800    /// Remove COV subscriptions using this current immediate MAC and routed source.
801    /// Cleanup of an obsolete router leaves subscriptions renewed onto another route.
802    /// Only removed entries release their contribution to the shared quota group.
803    pub async fn remove_peer_subscriptions(
804        &self,
805        mac: &[u8],
806        network: Option<&NpduAddress>,
807    ) -> usize {
808        let mut table = self.cov_table.write().await;
809        table.remove_peer_subscriptions(mac, network)
810    }
811}
812
813#[cfg(test)]
814mod cov_identity_completion_tests;
815
816mod broadcaster;
817#[cfg(test)]
818mod r7_reply_tests;