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