1use std::collections::HashMap;
11use std::net::Ipv4Addr;
12#[cfg(feature = "ipv6")]
13use std::net::Ipv6Addr;
14use std::sync::Arc;
15#[cfg(test)]
16use std::time::Instant;
17
18use bytes::{Bytes, BytesMut};
19use tokio::sync::{broadcast, mpsc, oneshot, Mutex};
20use tokio::task::JoinHandle;
21#[cfg(test)]
22use tokio::time::timeout;
23use tokio::time::Duration;
24use tracing::{debug, warn};
25
26use bacnet_encoding::apdu::{
27 self, encode_apdu, validate_max_apdu_length, validate_max_segments, AbortPdu, Apdu,
28 ConfirmedRequest as ConfirmedRequestPdu, RejectPdu, SegmentAck as SegmentAckPdu, SimpleAck,
29};
30use bacnet_encoding::npdu::NpduAddress;
31#[cfg(test)]
32use bacnet_encoding::npdu::{encode_npdu, Npdu};
33use bacnet_endpoint_core::coordinator::{CanonicalPeer, OutboundTransactionCoordinator};
34use bacnet_network::layer::NetworkLayer;
35use bacnet_npdu::NetworkPriority;
36use bacnet_services::cov::COVNotificationRequest;
37use bacnet_transport::bip::{BipMode, BipMulticast, BipTransport};
38#[cfg(feature = "ipv6")]
39use bacnet_transport::bip6::Bip6Transport;
40use bacnet_transport::port::{TransportPort, TransportProvenance};
41use bacnet_transport::warn_throttle::WarnThrottle;
42use bacnet_transport::warn_throttled;
43use bacnet_types::enums::{ConfirmedServiceChoice, RejectReason, UnconfirmedServiceChoice};
44use bacnet_types::error::Error;
45use bacnet_types::MacAddr;
46
47use crate::discovery::{DeviceTable, DeviceUpsertResult, DiscoveredDevice, RoutedDeviceConfig};
48use crate::segmentation::{
49 duplicate_in_window, max_segment_payload, split_payload, SegmentReceiver, SegmentedPduType,
50};
51use crate::tsm::{
52 ProgressReceiver, RequestTimerExpiration, SegmentAckPhase, SegmentTimerExpiration,
53 SegmentedResponseAdmission, TransactionOwner, TransactionProgress, Tsm, TsmConfig, TsmResponse,
54};
55#[cfg(test)]
56use transaction_cleanup::{SegmentedCleanupHook, SegmentedPostWaitCleanupHook};
57use transaction_cleanup::{TransactionCleanup, TransactionGuard};
58
59pub const DEFAULT_COV_CHANNEL_CAPACITY: usize = 64;
61
62pub const MAX_COV_CHANNEL_CAPACITY: usize = 65_536;
64
65pub const DEVICE_EVENT_CHANNEL_CAPACITY: usize = 64;
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum DeviceEventKind {
71 Discovered,
73 Updated,
75 Lost,
77}
78
79#[derive(Debug, Clone)]
81pub struct DeviceEvent {
82 pub kind: DeviceEventKind,
84 pub device: DiscoveredDevice,
86}
87
88#[derive(Debug, Clone)]
90pub struct DeviceCollisionEvent {
91 pub retained: DiscoveredDevice,
93 pub incoming: DiscoveredDevice,
95}
96
97#[derive(Debug, Clone)]
99pub struct ClientConfig {
100 pub interface: Ipv4Addr,
102 pub port: u16,
104 pub broadcast_address: Ipv4Addr,
106 pub share_port_by_address: bool,
112 pub apdu_timeout_ms: u64,
114 pub apdu_retries: u8,
116 pub max_apdu_length: u16,
118 pub max_segments: Option<u8>,
123 pub segmented_response_accepted: bool,
125 pub proposed_window_size: u8,
127 pub min_request_interval_ms: u64,
141}
142
143#[derive(Clone)]
145pub struct ClientOptions {
146 pub cov_channel_capacity: usize,
152 pub event_channel_capacity: usize,
155 confirmed_cov_notification_ack_policy: ConfirmedCOVNotificationAckPolicy,
156}
157
158impl Default for ClientConfig {
159 fn default() -> Self {
160 Self {
161 interface: Ipv4Addr::UNSPECIFIED,
162 port: 0xBAC0,
163 broadcast_address: Ipv4Addr::BROADCAST,
164 share_port_by_address: false,
165 apdu_timeout_ms: 6000,
166 apdu_retries: 3,
167 max_apdu_length: 1476,
168 max_segments: None,
169 segmented_response_accepted: true,
170 proposed_window_size: 1,
171 min_request_interval_ms: 0,
172 }
173 }
174}
175
176impl Default for ClientOptions {
177 fn default() -> Self {
178 Self {
179 cov_channel_capacity: DEFAULT_COV_CHANNEL_CAPACITY,
180 event_channel_capacity: DEFAULT_EVENT_CHANNEL_CAPACITY,
181 confirmed_cov_notification_ack_policy:
182 cov_notifications::default_confirmed_cov_notification_ack_policy(),
183 }
184 }
185}
186
187pub(crate) fn new_coordinated_tsm(
188 config: &ClientConfig,
189 coordinator: Arc<OutboundTransactionCoordinator>,
190) -> Tsm {
191 Tsm::new_coordinated(
192 TsmConfig {
193 apdu_timeout_ms: config.apdu_timeout_ms,
194 apdu_segment_timeout_ms: config.apdu_timeout_ms,
195 apdu_retries: config.apdu_retries,
196 },
197 coordinator,
198 )
199}
200
201pub(crate) fn confirmed_response_result(response: TsmResponse) -> Result<Bytes, Error> {
202 match response {
203 TsmResponse::SimpleAck => Ok(Bytes::new()),
204 TsmResponse::ComplexAck { service_data } => Ok(service_data),
205 TsmResponse::Error {
206 class,
207 code,
208 detail,
209 } => Err(Error::protocol(class, code, detail)),
210 TsmResponse::Reject { reason } => Err(Error::Reject { reason }),
211 TsmResponse::Abort { reason } => Err(Error::Abort { reason }),
212 TsmResponse::NetworkPathTooLong { dnet } => Err(Error::RoutedPathTooLong { dnet }),
213 }
214}
215
216impl ClientOptions {
217 pub fn with_cov_channel_capacity(mut self, capacity: usize) -> Self {
219 self.cov_channel_capacity = capacity;
220 self
221 }
222
223 fn validate(&self) -> Result<(), Error> {
224 if !(1..=MAX_COV_CHANNEL_CAPACITY).contains(&self.cov_channel_capacity) {
225 return Err(Error::Encoding(format!(
226 "invalid cov-channel-capacity {}; expected 1..={}",
227 self.cov_channel_capacity, MAX_COV_CHANNEL_CAPACITY
228 )));
229 }
230 if !(1..=MAX_EVENT_CHANNEL_CAPACITY).contains(&self.event_channel_capacity) {
231 return Err(Error::Encoding(format!(
232 "invalid event-channel-capacity {}; expected 1..={}",
233 self.event_channel_capacity, MAX_EVENT_CHANNEL_CAPACITY
234 )));
235 }
236 Ok(())
237 }
238}
239
240pub struct ClientBuilder<T: TransportPort> {
242 config: ClientConfig,
243 options: ClientOptions,
244 transport: Option<T>,
245}
246
247impl<T: TransportPort + 'static> ClientBuilder<T> {
248 pub fn transport(mut self, transport: T) -> Self {
250 self.transport = Some(transport);
251 self
252 }
253
254 pub fn apdu_timeout_ms(mut self, ms: u64) -> Self {
256 self.config.apdu_timeout_ms = ms;
257 self
258 }
259
260 pub fn max_apdu_length(mut self, len: u16) -> Self {
262 self.config.max_apdu_length = len;
263 self
264 }
265
266 pub fn cov_channel_capacity(mut self, capacity: usize) -> Self {
268 self.options.cov_channel_capacity = capacity;
269 self
270 }
271
272 pub async fn build(self) -> Result<BACnetClient<T>, Error> {
274 let transport = self
275 .transport
276 .ok_or_else(|| Error::Encoding("transport not set on ClientBuilder".into()))?;
277 BACnetClient::start_with_options(self.config, transport, self.options).await
278 }
279}
280
281pub struct BipClientBuilder {
283 config: ClientConfig,
284 options: ClientOptions,
285 mode: BipMode,
286 multicast: Option<BipMulticast>,
287}
288
289impl BipClientBuilder {
290 pub fn multicast(mut self, multicast: Option<BipMulticast>) -> Self {
313 self.multicast = multicast;
314 self
315 }
316
317 pub fn mode(mut self, mode: BipMode) -> Self {
343 self.mode = mode;
344 self
345 }
346
347 pub fn interface(mut self, ip: Ipv4Addr) -> Self {
349 self.config.interface = ip;
350 self
351 }
352
353 pub fn port(mut self, port: u16) -> Self {
355 self.config.port = port;
356 self
357 }
358
359 pub fn broadcast_address(mut self, addr: Ipv4Addr) -> Self {
361 self.config.broadcast_address = addr;
362 self
363 }
364
365 pub fn share_port_by_address(mut self, enabled: bool) -> Self {
371 self.config.share_port_by_address = enabled;
372 self
373 }
374
375 pub fn apdu_timeout_ms(mut self, ms: u64) -> Self {
377 self.config.apdu_timeout_ms = ms;
378 self
379 }
380
381 pub fn max_apdu_length(mut self, len: u16) -> Self {
383 self.config.max_apdu_length = len;
384 self
385 }
386
387 pub fn cov_channel_capacity(mut self, capacity: usize) -> Self {
389 self.options.cov_channel_capacity = capacity;
390 self
391 }
392}
393
394const DEFAULT_BATCH_CONCURRENCY: usize = 32;
400
401mod batch_debug;
402
403#[derive(Debug, Clone)]
405pub struct DeviceReadRequest {
406 pub device_instance: u32,
408 pub object_identifier: bacnet_types::primitives::ObjectIdentifier,
410 pub property_identifier: bacnet_types::enums::PropertyIdentifier,
412 pub property_array_index: Option<u32>,
414}
415
416pub struct DeviceReadResult {
418 pub request_index: usize,
420 pub device_instance: u32,
422 pub result: Result<bacnet_services::read_property::ReadPropertyACK, Error>,
424}
425
426#[derive(Debug, Clone)]
428pub struct DeviceRpmRequest {
429 pub device_instance: u32,
431 pub specs: Vec<bacnet_types::constructed::ReadAccessSpecification>,
433}
434
435pub struct DeviceRpmResult {
437 pub request_index: usize,
439 pub device_instance: u32,
441 pub result: Result<bacnet_services::rpm::ReadPropertyMultipleACK, Error>,
443}
444
445#[derive(Clone)]
447pub struct DeviceWriteRequest {
448 pub device_instance: u32,
450 pub object_identifier: bacnet_types::primitives::ObjectIdentifier,
452 pub property_identifier: bacnet_types::enums::PropertyIdentifier,
454 pub property_array_index: Option<u32>,
456 pub property_value: Vec<u8>,
458 pub priority: Option<u8>,
460}
461
462#[derive(Debug)]
464pub struct DeviceWriteResult {
465 pub request_index: usize,
467 pub device_instance: u32,
469 pub result: Result<(), Error>,
471}
472
473#[derive(Debug, Clone, Copy)]
480struct ResponseLimits {
481 segmented_response_accepted: bool,
483 max_reassembly_segments: usize,
485}
486
487struct SegmentedReceiveState {
489 receiver: SegmentReceiver,
490 owner: TransactionOwner,
491 provenance: TransportProvenance,
496 reply_mac: MacAddr,
498 reply_network: Option<NpduAddress>,
502 expected_next_seq: u8,
504 initial_sequence_number: u8,
506 last_sequence_number: u8,
508 duplicate_count: u8,
510 window_position: u8,
512 actual_window_size: u8,
514 accepted_segments: usize,
521}
522
523type SegKey = (MacAddr, u8, TransportProvenance);
531
532type SegAckKey = (MacAddr, u8);
538
539struct SegmentAckRoute {
540 owner: TransactionOwner,
541 sender: mpsc::Sender<SegmentAckPdu>,
542}
543
544pub struct BACnetClient<T: TransportPort> {
546 config: ClientConfig,
547 network: Arc<NetworkLayer<T>>,
548 tsm: Arc<Mutex<Tsm>>,
549 device_table: Arc<Mutex<DeviceTable>>,
550 cov_tx: broadcast::Sender<ReceivedCOVNotification>,
551 event_tx: broadcast::Sender<ReceivedEventNotification>,
552 device_tx: broadcast::Sender<DeviceEvent>,
553 device_collision_tx: broadcast::Sender<DeviceCollisionEvent>,
554 i_have_tx: broadcast::Sender<ReceivedIHave>,
555 network_report_tx: broadcast::Sender<bacnet_network::layer::ReceivedNetworkReport>,
556 dispatch_task: Option<JoinHandle<()>>,
557 network_number_task: Option<JoinHandle<()>>,
558 seg_ack_senders: Arc<Mutex<HashMap<SegAckKey, SegmentAckRoute>>>,
564 cleanup_tx: mpsc::UnboundedSender<TransactionCleanup>,
565 #[cfg(test)]
566 segmented_post_wait_cleanup: Arc<SegmentedPostWaitCleanupHook>,
567 #[cfg(test)]
568 segmented_cleanup: Arc<SegmentedCleanupHook>,
569 #[cfg(test)]
571 decode_warning: Arc<WarnThrottle>,
572 #[cfg(test)]
574 response_warning: Arc<WarnThrottle>,
575 local_mac: MacAddr,
576 routed_path_limits: Arc<RoutedPathLimits>,
577 group_source_request_drops: Arc<std::sync::atomic::AtomicU64>,
579 pacer: Arc<pacing::RequestPacer>,
580}
581
582impl BACnetClient<BipTransport> {
583 pub fn bip_builder() -> BipClientBuilder {
585 BipClientBuilder {
586 config: ClientConfig::default(),
587 options: ClientOptions::default(),
588 mode: BipMode::Normal,
589 multicast: None,
590 }
591 }
592}
593
594#[cfg(feature = "ipv6")]
595impl BACnetClient<Bip6Transport> {
596 #[cfg_attr(docsrs, doc(cfg(feature = "ipv6")))]
598 pub fn bip6_builder() -> Bip6ClientBuilder {
599 Bip6ClientBuilder {
600 config: ClientConfig::default(),
601 options: ClientOptions::default(),
602 interface: Ipv6Addr::UNSPECIFIED,
603 device_instance: None,
604 }
605 }
606}
607
608#[cfg(feature = "ipv6")]
610#[cfg_attr(docsrs, doc(cfg(feature = "ipv6")))]
611pub struct Bip6ClientBuilder {
612 config: ClientConfig,
613 options: ClientOptions,
614 interface: Ipv6Addr,
615 device_instance: Option<u32>,
616}
617
618#[cfg(feature = "ipv6")]
619impl Bip6ClientBuilder {
620 pub fn interface(mut self, ip: Ipv6Addr) -> Self {
622 self.interface = ip;
623 self
624 }
625
626 pub fn port(mut self, port: u16) -> Self {
628 self.config.port = port;
629 self
630 }
631
632 pub fn device_instance(mut self, instance: u32) -> Self {
634 self.device_instance = Some(instance);
635 self
636 }
637
638 pub fn apdu_timeout_ms(mut self, ms: u64) -> Self {
640 self.config.apdu_timeout_ms = ms;
641 self
642 }
643
644 pub fn max_apdu_length(mut self, len: u16) -> Self {
646 self.config.max_apdu_length = len;
647 self
648 }
649
650 pub fn cov_channel_capacity(mut self, capacity: usize) -> Self {
652 self.options.cov_channel_capacity = capacity;
653 self
654 }
655}
656
657#[cfg(feature = "sc-tls")]
658impl BACnetClient<bacnet_transport::sc::ScTransport<bacnet_transport::sc_tls::TlsWebSocket>> {
659 #[cfg_attr(docsrs, doc(cfg(feature = "sc-tls")))]
661 pub fn sc_builder() -> ScClientBuilder {
662 ScClientBuilder {
663 config: ClientConfig::default(),
664 options: ClientOptions::default(),
665 hub_url: String::new(),
666 failover_hub_url: None,
667 tls_config: None,
668 vmac: [0; 6],
669 device_uuid: [0; 16],
670 heartbeat_interval_ms: 30_000,
671 heartbeat_timeout_ms: 60_000,
672 connect_timeout_ms: 10_000,
673 reconnect: None,
674 npdu_admission_policy: bacnet_transport::sc::ScNpduAdmissionPolicy::default(),
675 }
676 }
677}
678
679#[cfg(feature = "sc-tls")]
683#[cfg_attr(docsrs, doc(cfg(feature = "sc-tls")))]
684pub struct ScClientBuilder {
685 config: ClientConfig,
686 options: ClientOptions,
687 hub_url: String,
688 failover_hub_url: Option<String>,
689 tls_config: Option<bacnet_transport::sc_tls::ScNodeTlsConfig>,
690 vmac: bacnet_transport::sc_frame::Vmac,
691 device_uuid: [u8; 16],
692 heartbeat_interval_ms: u64,
693 heartbeat_timeout_ms: u64,
694 connect_timeout_ms: u64,
695 reconnect: Option<bacnet_transport::sc::ScReconnectConfig>,
696 npdu_admission_policy: bacnet_transport::sc::ScNpduAdmissionPolicy,
697}
698
699#[cfg(feature = "sc-tls")]
700impl ScClientBuilder {
701 pub fn hub_url(mut self, url: &str) -> Self {
703 self.hub_url = url.to_string();
704 self
705 }
706
707 pub fn tls_config(mut self, config: bacnet_transport::sc_tls::ScNodeTlsConfig) -> Self {
725 self.tls_config = Some(config);
726 self
727 }
728
729 pub fn vmac(mut self, vmac: [u8; 6]) -> Self {
731 self.vmac = vmac;
732 self
733 }
734
735 pub fn device_uuid(mut self, uuid: [u8; 16]) -> Self {
737 self.device_uuid = uuid;
738 self
739 }
740
741 pub fn apdu_timeout_ms(mut self, ms: u64) -> Self {
743 self.config.apdu_timeout_ms = ms;
744 self
745 }
746
747 pub fn cov_channel_capacity(mut self, capacity: usize) -> Self {
749 self.options.cov_channel_capacity = capacity;
750 self
751 }
752
753 pub fn heartbeat_interval_ms(mut self, ms: u64) -> Self {
755 self.heartbeat_interval_ms = ms;
756 self
757 }
758
759 pub fn heartbeat_timeout_ms(mut self, ms: u64) -> Self {
761 self.heartbeat_timeout_ms = ms;
762 self
763 }
764
765 pub fn connect_timeout_ms(mut self, ms: u64) -> Self {
769 self.connect_timeout_ms = ms;
770 self
771 }
772
773 pub fn npdu_per_origin_limit(mut self, limit: usize) -> Self {
778 self.npdu_admission_policy.per_origin_limit = limit;
779 self
780 }
781
782 pub fn failover_hub_url(mut self, url: &str) -> Self {
797 self.failover_hub_url = Some(url.to_string());
798 self
799 }
800
801 pub fn reconnect(mut self, config: bacnet_transport::sc::ScReconnectConfig) -> Self {
808 self.reconnect = Some(config);
809 self
810 }
811
812 fn validate_identity(&self) -> Result<(), Error> {
813 match self.vmac {
814 bacnet_transport::sc_frame::UNKNOWN_VMAC => Err(Error::Encoding(
815 "SC client builder: vmac must not be the reserved unknown VMAC".into(),
816 )),
817 bacnet_transport::sc_frame::BROADCAST_VMAC => Err(Error::Encoding(
818 "SC client builder: vmac must not be the reserved broadcast VMAC".into(),
819 )),
820 _ if self.device_uuid == [0; 16] => Err(Error::Encoding(
821 "SC client builder: device_uuid is required and must not be all zero".into(),
822 )),
823 _ => Ok(()),
824 }
825 }
826
827 fn sc_transport<W: bacnet_transport::sc::WebSocketPort>(
828 &self,
829 ws: W,
830 ) -> bacnet_transport::sc::ScTransport<W> {
831 bacnet_transport::sc::ScTransport::new(ws, self.vmac)
832 .with_device_uuid(self.device_uuid)
833 .with_npdu_per_origin_limit(self.npdu_admission_policy.per_origin_limit)
834 .with_heartbeat_interval_ms(self.heartbeat_interval_ms)
835 .with_heartbeat_timeout_ms(self.heartbeat_timeout_ms)
836 .with_connect_timeout_ms(self.connect_timeout_ms)
837 }
838
839 #[cfg(test)]
840 pub(crate) async fn build_with_websocket_for_test<
841 W: bacnet_transport::sc::WebSocketPort + 'static,
842 >(
843 self,
844 ws: W,
845 ) -> Result<BACnetClient<bacnet_transport::sc::ScTransport<W>>, Error> {
846 self.validate()?;
847 let transport = self.sc_transport(ws);
848 BACnetClient::start_with_options(self.config, transport, self.options).await
849 }
850}
851
852#[derive(Clone, Copy)]
854enum ConfirmedTarget<'a> {
855 Local {
856 mac: &'a [u8],
857 },
858 Routed {
859 router_mac: &'a [u8],
860 dest_network: u16,
861 dest_mac: &'a [u8],
862 },
863}
864
865impl<'a> ConfirmedTarget<'a> {
866 fn additional_npdu_header_len(&self) -> u16 {
867 match self {
868 Self::Local { .. } => 0,
869 Self::Routed { dest_mac, .. } => u16::try_from(dest_mac.len())
870 .unwrap_or(u16::MAX)
871 .saturating_add(4),
872 }
873 }
874}
875
876fn max_apdu_bucket_at_or_below(limit: u16) -> Option<u16> {
877 bacnet_encoding::apdu::max_apdu_header_at_or_below(u32::from(limit)).ok()
878}
879
880fn cap_max_apdu_to_transport(configured: u16, transport_limit: u16) -> Result<u16, Error> {
881 let limit = configured.min(transport_limit);
882 max_apdu_bucket_at_or_below(limit).ok_or_else(|| {
883 Error::Encoding(format!(
884 "transport max-APDU-length {transport_limit} leaves no valid BACnet APDU bucket"
885 ))
886 })
887}
888
889mod audit;
890mod builder_build;
891mod builder_options;
892mod cov;
893mod cov_notifications;
894mod cov_renewal;
895mod device_events;
896mod device_mgmt;
897mod discovery;
898mod dispatch;
899mod dispatch_context;
900mod inbound_replies;
901use inbound_replies::InboundReply;
902mod event_notifications;
903mod file_list;
904mod i_have;
905mod lifecycle;
906mod network_reports;
907mod object_mgmt;
908pub(crate) mod pacing;
909mod property;
910mod read_range;
911mod requests;
912mod response_admission;
913mod routed_path_limits;
914mod segmentation;
915mod segmentation_abort;
916mod segmentation_context;
917mod segmented_request;
918mod transaction_cleanup;
919mod transaction_peer;
920mod transport_access;
921mod write_group;
922pub(crate) use routed_path_limits::check_routed_unicast;
923use routed_path_limits::{
924 forwarded_npci_len, routed_path_quarantine_horizon, RoutedPathLease, RoutedPathLimits,
925};
926pub(crate) use transaction_peer::TransactionPeer;
927
928pub use cov::CovPropertySubscription;
929pub use cov_notifications::{
930 COVNotificationDelivery, ConfirmedCOVNotificationAckPolicy, ConfirmedCOVNotificationResponse,
931 ReceivedCOVNotification,
932};
933pub use cov_renewal::{
934 ManagedCOVSubscription, ManagedCOVSubscriptionEvent, ManagedCOVSubscriptionOptions,
935};
936pub use event_notifications::{
937 EventNotificationDelivery, ReceivedEventNotification, DEFAULT_EVENT_CHANNEL_CAPACITY,
938 MAX_EVENT_CHANNEL_CAPACITY,
939};
940pub use i_have::{ReceivedIHave, I_HAVE_CHANNEL_CAPACITY};
941pub use network_reports::{DiscoveredRouter, NETWORK_REPORT_CHANNEL_CAPACITY};
942pub use pacing::MAX_MIN_REQUEST_INTERVAL_MS;
943pub use write_group::WriteGroupDestination;
944
945#[cfg(test)]
946mod acknowledge_alarm_tests;
947#[cfg(test)]
948mod audit_tests;
949#[cfg(test)]
950mod batch_tests;
951#[cfg(test)]
952mod broadcast_mac_tests;
953#[cfg(test)]
954mod builder_options_tests;
955#[cfg(test)]
956mod confirmed_request_dispatch_tests;
957#[cfg(test)]
958mod coordinator_tests;
959#[cfg(test)]
960mod cov_notification_tests;
961#[cfg(test)]
962mod cov_renewal_tests;
963#[cfg(test)]
964mod cov_tests;
965#[cfg(test)]
966mod device_events_tests;
967#[cfg(test)]
968mod event_notification_tests;
969#[cfg(test)]
970pub(crate) mod fake_device;
971#[cfg(test)]
972mod group_source_request_tests;
973#[cfg(test)]
974mod list_error_tests;
975#[cfg(test)]
976mod list_validation_tests;
977#[cfg(test)]
978mod pacing_tests;
979#[cfg(test)]
980mod peer_max_apdu_tests;
981#[cfg(test)]
982mod peer_segmentation_tests;
983#[cfg(test)]
984mod rb07_provenance_tests;
985#[cfg(test)]
986mod read_range_tests;
987#[cfg(test)]
988mod request_timer_tests;
989#[cfg(test)]
990mod response_correlation_tests;
991#[cfg(test)]
992mod routed_max_apdu_tests;
993#[cfg(test)]
994mod routed_path_limit_tests;
995#[cfg(test)]
996mod routed_reply_tests;
997#[cfg(all(test, feature = "sc-tls"))]
998mod sc_builder_tests;
999#[cfg(test)]
1000mod sc_max_apdu_tests;
1001#[cfg(test)]
1002mod segmentation_retransmit_tests;
1003#[cfg(test)]
1004mod segmented_receive_duplicate_tests;
1005#[cfg(test)]
1006mod segmented_receive_lifecycle_tests;
1007#[cfg(test)]
1008mod segmented_request_ordering_tests;
1009#[cfg(test)]
1010mod segmented_request_state_tests;
1011#[cfg(test)]
1012mod segmented_response_admission_tests;
1013#[cfg(test)]
1014mod segmented_response_capacity_tests;
1015#[cfg(test)]
1016mod segmented_timeout_tests;
1017#[cfg(test)]
1018mod structured_error_tests;
1019#[cfg(test)]
1020mod tests;
1021#[cfg(test)]
1022mod transport_access_tests;
1023#[cfg(test)]
1024mod typed_error_wire_tests;
1025#[cfg(test)]
1026mod wpm_validation_tests;
1027#[cfg(test)]
1028mod write_priority_tests;
1029
1030impl<T: TransportPort + 'static> BACnetClient<T> {
1031 pub fn generic_builder() -> ClientBuilder<T> {
1059 ClientBuilder {
1060 config: ClientConfig::default(),
1061 options: ClientOptions::default(),
1062 transport: None,
1063 }
1064 }
1065}
1066
1067#[cfg(test)]
1068mod number_tests;
1069
1070mod network_number;