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::{Mutex, broadcast, mpsc, oneshot};
20use tokio::task::JoinHandle;
21use tokio::time::Duration;
22#[cfg(test)]
23use tokio::time::timeout;
24use tracing::{debug, warn};
25
26use bacnet_encoding::apdu::{
27 self, AbortPdu, Apdu, ConfirmedRequest as ConfirmedRequestPdu, RejectPdu,
28 SegmentAck as SegmentAckPdu, SimpleAck, encode_apdu, validate_max_apdu_length,
29 validate_max_segments,
30};
31use bacnet_encoding::npdu::NpduAddress;
32#[cfg(test)]
33use bacnet_encoding::npdu::{Npdu, encode_npdu};
34use bacnet_endpoint_core::coordinator::{CanonicalPeer, OutboundTransactionCoordinator};
35use bacnet_network::layer::NetworkLayer;
36use bacnet_npdu::NetworkPriority;
37use bacnet_services::cov::COVNotificationRequest;
38use bacnet_transport::any::AnyTransport;
39use bacnet_transport::bip::{BipMode, BipMulticast, BipTransport};
40#[cfg(feature = "ipv6")]
41use bacnet_transport::bip6::{Bip6ForeignDeviceConfig, Bip6Transport};
42use bacnet_transport::port::{TransportPort, TransportProvenance};
43use bacnet_transport::warn_throttle::WarnThrottle;
44use bacnet_transport::warn_throttled;
45use bacnet_types::MacAddr;
46use bacnet_types::enums::{ConfirmedServiceChoice, RejectReason, UnconfirmedServiceChoice};
47use bacnet_types::error::{Error, RemoteError};
48
49use crate::discovery::{DeviceTable, DeviceUpsertResult, DiscoveredDevice, RoutedDeviceConfig};
50use crate::segmentation::{
51 SegmentReceiver, SegmentedPduType, duplicate_in_window, max_segment_payload, split_payload,
52};
53use crate::tsm::{
54 ProgressReceiver, RequestTimerExpiration, SegmentAckPhase, SegmentTimerExpiration,
55 SegmentedResponseAdmission, TransactionOwner, TransactionProgress, Tsm, TsmConfig, TsmResponse,
56};
57#[cfg(test)]
58use transaction_cleanup::{SegmentedCleanupHook, SegmentedPostWaitCleanupHook};
59use transaction_cleanup::{TransactionCleanup, TransactionGuard};
60
61mod sent_services;
62pub use sent_services::SENT_SERVICES;
63
64pub const DEFAULT_COV_CHANNEL_CAPACITY: usize = 64;
66
67pub const MAX_COV_CHANNEL_CAPACITY: usize = 65_536;
69
70pub const DEVICE_EVENT_CHANNEL_CAPACITY: usize = 64;
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum DeviceEventKind {
76 Discovered,
78 Updated,
80 Lost,
82}
83
84#[derive(Debug, Clone)]
86pub struct DeviceEvent {
87 pub kind: DeviceEventKind,
89 pub device: DiscoveredDevice,
91}
92
93#[derive(Debug, Clone)]
95pub struct DeviceCollisionEvent {
96 pub retained: DiscoveredDevice,
98 pub incoming: DiscoveredDevice,
100}
101
102#[derive(Debug, Clone)]
104pub struct ClientConfig {
105 pub interface: Ipv4Addr,
107 pub port: u16,
109 pub broadcast_address: Ipv4Addr,
111 pub share_port_by_address: bool,
117 pub apdu_timeout_ms: u64,
119 pub apdu_retries: u8,
121 pub max_apdu_length: u16,
126 pub max_segments: Option<u8>,
131 pub segmented_response_accepted: bool,
133 pub proposed_window_size: u8,
135 pub min_request_interval_ms: u64,
149}
150
151#[derive(Clone)]
153pub struct ClientOptions {
154 pub cov_channel_capacity: usize,
160 pub event_channel_capacity: usize,
163 confirmed_cov_notification_ack_policy: ConfirmedCOVNotificationAckPolicy,
164}
165
166impl Default for ClientConfig {
167 fn default() -> Self {
168 Self {
169 interface: Ipv4Addr::UNSPECIFIED,
170 port: 0xBAC0,
171 broadcast_address: Ipv4Addr::BROADCAST,
172 share_port_by_address: false,
173 apdu_timeout_ms: 6000,
174 apdu_retries: 3,
175 max_apdu_length: 1476,
176 max_segments: None,
177 segmented_response_accepted: true,
178 proposed_window_size: 1,
179 min_request_interval_ms: 0,
180 }
181 }
182}
183
184impl Default for ClientOptions {
185 fn default() -> Self {
186 Self {
187 cov_channel_capacity: DEFAULT_COV_CHANNEL_CAPACITY,
188 event_channel_capacity: DEFAULT_EVENT_CHANNEL_CAPACITY,
189 confirmed_cov_notification_ack_policy:
190 cov_notifications::default_confirmed_cov_notification_ack_policy(),
191 }
192 }
193}
194
195pub(crate) fn new_coordinated_tsm(
196 config: &ClientConfig,
197 coordinator: Arc<OutboundTransactionCoordinator>,
198) -> Tsm {
199 Tsm::new_coordinated(
200 TsmConfig {
201 apdu_timeout_ms: config.apdu_timeout_ms,
202 apdu_segment_timeout_ms: config.apdu_timeout_ms,
203 apdu_retries: config.apdu_retries,
204 },
205 coordinator,
206 )
207}
208
209pub(crate) fn confirmed_response_result(response: TsmResponse) -> Result<Bytes, Error> {
210 match response {
211 TsmResponse::SimpleAck => Ok(Bytes::new()),
212 TsmResponse::ComplexAck { service_data } => Ok(service_data),
213 TsmResponse::Remote {
214 error: RemoteError::Error { class, code },
215 detail,
216 } => Err(Error::with_detail(class, code, detail)),
217 TsmResponse::Remote { error, .. } => Err(Error::Remote(error)),
218 TsmResponse::NetworkPathTooLong { dnet } => Err(Error::RoutedPathTooLong { dnet }),
219 }
220}
221
222impl ClientOptions {
223 pub fn with_cov_channel_capacity(mut self, capacity: usize) -> Self {
225 self.cov_channel_capacity = capacity;
226 self
227 }
228
229 fn validate(&self) -> Result<(), Error> {
230 if !(1..=MAX_COV_CHANNEL_CAPACITY).contains(&self.cov_channel_capacity) {
231 return Err(Error::Encoding(format!(
232 "invalid cov-channel-capacity {}; expected 1..={}",
233 self.cov_channel_capacity, MAX_COV_CHANNEL_CAPACITY
234 )));
235 }
236 if !(1..=MAX_EVENT_CHANNEL_CAPACITY).contains(&self.event_channel_capacity) {
237 return Err(Error::Encoding(format!(
238 "invalid event-channel-capacity {}; expected 1..={}",
239 self.event_channel_capacity, MAX_EVENT_CHANNEL_CAPACITY
240 )));
241 }
242 Ok(())
243 }
244}
245
246pub struct ClientBuilder<T: TransportPort = AnyTransport> {
249 config: ClientConfig,
250 options: ClientOptions,
251 transport: Option<T>,
252}
253
254impl<T: TransportPort + 'static> ClientBuilder<T> {
255 pub fn transport(mut self, transport: T) -> Self {
257 self.transport = Some(transport);
258 self
259 }
260
261 pub fn apdu_timeout_ms(mut self, ms: u64) -> Self {
263 self.config.apdu_timeout_ms = ms;
264 self
265 }
266
267 pub fn max_apdu_length(mut self, len: u16) -> Self {
269 self.config.max_apdu_length = len;
270 self
271 }
272
273 pub fn cov_channel_capacity(mut self, capacity: usize) -> Self {
275 self.options.cov_channel_capacity = capacity;
276 self
277 }
278
279 pub async fn build(self) -> Result<BACnetClient<T>, Error> {
281 let transport = self
282 .transport
283 .ok_or_else(|| Error::Encoding("transport not set on ClientBuilder".into()))?;
284 BACnetClient::start_with_options(self.config, transport, self.options).await
285 }
286}
287
288pub struct BipClientBuilder {
290 config: ClientConfig,
291 options: ClientOptions,
292 mode: BipMode,
293 multicast: Option<BipMulticast>,
294}
295
296impl BipClientBuilder {
297 pub fn multicast(mut self, multicast: Option<BipMulticast>) -> Self {
320 self.multicast = multicast;
321 self
322 }
323
324 pub fn mode(mut self, mode: BipMode) -> Self {
350 self.mode = mode;
351 self
352 }
353
354 pub fn interface(mut self, ip: Ipv4Addr) -> Self {
356 self.config.interface = ip;
357 self
358 }
359
360 pub fn port(mut self, port: u16) -> Self {
362 self.config.port = port;
363 self
364 }
365
366 pub fn broadcast_address(mut self, addr: Ipv4Addr) -> Self {
368 self.config.broadcast_address = addr;
369 self
370 }
371
372 pub fn share_port_by_address(mut self, enabled: bool) -> Self {
378 self.config.share_port_by_address = enabled;
379 self
380 }
381
382 pub fn apdu_timeout_ms(mut self, ms: u64) -> Self {
384 self.config.apdu_timeout_ms = ms;
385 self
386 }
387
388 pub fn max_apdu_length(mut self, len: u16) -> Self {
390 self.config.max_apdu_length = len;
391 self
392 }
393
394 pub fn cov_channel_capacity(mut self, capacity: usize) -> Self {
396 self.options.cov_channel_capacity = capacity;
397 self
398 }
399}
400
401const DEFAULT_BATCH_CONCURRENCY: usize = 32;
407
408mod batch_debug;
409
410#[derive(Debug, Clone)]
412pub struct DeviceReadRequest {
413 pub device_instance: u32,
415 pub object_identifier: bacnet_types::primitives::ObjectIdentifier,
417 pub property_identifier: bacnet_types::enums::PropertyIdentifier,
419 pub property_array_index: Option<u32>,
421}
422
423pub struct DeviceReadResult {
425 pub request_index: usize,
427 pub device_instance: u32,
429 pub result: Result<bacnet_services::read_property::ReadPropertyACK, Error>,
431}
432
433#[derive(Debug, Clone)]
435pub struct DeviceRpmRequest {
436 pub device_instance: u32,
438 pub specs: Vec<bacnet_types::constructed::ReadAccessSpecification>,
440}
441
442pub struct DeviceRpmResult {
444 pub request_index: usize,
446 pub device_instance: u32,
448 pub result: Result<bacnet_services::rpm::ReadPropertyMultipleACK, Error>,
450}
451
452#[derive(Clone)]
454pub struct DeviceWriteRequest {
455 pub device_instance: u32,
457 pub object_identifier: bacnet_types::primitives::ObjectIdentifier,
459 pub property_identifier: bacnet_types::enums::PropertyIdentifier,
461 pub property_array_index: Option<u32>,
463 pub property_value: Vec<u8>,
465 pub priority: Option<u8>,
467}
468
469#[derive(Debug)]
471pub struct DeviceWriteResult {
472 pub request_index: usize,
474 pub device_instance: u32,
476 pub result: Result<(), Error>,
478}
479
480#[derive(Debug, Clone, Copy)]
487struct ResponseLimits {
488 segmented_response_accepted: bool,
490 max_reassembly_segments: usize,
492}
493
494struct SegmentedReceiveState {
496 receiver: SegmentReceiver,
497 owner: TransactionOwner,
498 provenance: TransportProvenance,
503 reply_mac: MacAddr,
505 reply_network: Option<NpduAddress>,
509 expected_next_seq: u8,
511 initial_sequence_number: u8,
513 last_sequence_number: u8,
515 duplicate_count: u8,
517 window_position: u8,
519 actual_window_size: u8,
521 accepted_segments: usize,
528}
529
530type SegKey = (MacAddr, u8, TransportProvenance);
538
539type SegAckKey = (MacAddr, u8);
545
546struct SegmentAckRoute {
547 owner: TransactionOwner,
548 sender: mpsc::Sender<SegmentAckPdu>,
549}
550
551pub struct BACnetClient<T: TransportPort = AnyTransport> {
559 config: ClientConfig,
560 network: Arc<NetworkLayer<T>>,
561 tsm: Arc<Mutex<Tsm>>,
562 device_table: Arc<Mutex<DeviceTable>>,
563 cov_tx: broadcast::Sender<ReceivedCOVNotification>,
564 event_tx: broadcast::Sender<ReceivedEventNotification>,
565 device_tx: broadcast::Sender<DeviceEvent>,
566 device_collision_tx: broadcast::Sender<DeviceCollisionEvent>,
567 i_have_tx: broadcast::Sender<ReceivedIHave>,
568 network_report_tx: broadcast::Sender<bacnet_network::layer::ReceivedNetworkReport>,
569 dispatch_task: Option<JoinHandle<()>>,
570 network_number_task: Option<JoinHandle<()>>,
571 seg_ack_senders: Arc<Mutex<HashMap<SegAckKey, SegmentAckRoute>>>,
577 cleanup_tx: mpsc::UnboundedSender<TransactionCleanup>,
578 #[cfg(test)]
579 segmented_post_wait_cleanup: Arc<SegmentedPostWaitCleanupHook>,
580 #[cfg(test)]
581 segmented_cleanup: Arc<SegmentedCleanupHook>,
582 #[cfg(test)]
584 decode_warning: Arc<WarnThrottle>,
585 #[cfg(test)]
587 response_warning: Arc<WarnThrottle>,
588 local_mac: MacAddr,
589 routed_path_limits: Arc<RoutedPathLimits>,
590 group_source_request_drops: Arc<std::sync::atomic::AtomicU64>,
592 pacer: Arc<pacing::RequestPacer>,
593}
594
595impl BACnetClient<BipTransport> {
596 pub fn bip_builder() -> BipClientBuilder {
598 BipClientBuilder {
599 config: ClientConfig::default(),
600 options: ClientOptions::default(),
601 mode: BipMode::Normal,
602 multicast: None,
603 }
604 }
605}
606
607#[cfg(feature = "sc-tls")]
608impl BACnetClient<bacnet_transport::sc::ScTransport<bacnet_transport::sc_tls::TlsWebSocket>> {
609 #[cfg_attr(docsrs, doc(cfg(feature = "sc-tls")))]
611 pub fn sc_builder() -> ScClientBuilder {
612 ScClientBuilder {
613 config: ClientConfig::default(),
614 options: ClientOptions::default(),
615 hub_url: String::new(),
616 failover_hub_url: None,
617 tls_config: None,
618 vmac: [0; 6],
619 device_uuid: [0; 16],
620 heartbeat_interval_ms: 30_000,
621 heartbeat_timeout_ms: 60_000,
622 connect_timeout_ms: 10_000,
623 reconnect: None,
624 npdu_admission_policy: bacnet_transport::sc::ScNpduAdmissionPolicy::default(),
625 }
626 }
627}
628
629#[cfg(feature = "sc-tls")]
633#[cfg_attr(docsrs, doc(cfg(feature = "sc-tls")))]
634pub struct ScClientBuilder {
635 config: ClientConfig,
636 options: ClientOptions,
637 hub_url: String,
638 failover_hub_url: Option<String>,
639 tls_config: Option<bacnet_transport::sc_tls::ScNodeTlsConfig>,
640 vmac: bacnet_transport::sc_frame::Vmac,
641 device_uuid: [u8; 16],
642 heartbeat_interval_ms: u64,
643 heartbeat_timeout_ms: u64,
644 connect_timeout_ms: u64,
645 reconnect: Option<bacnet_transport::sc::ScReconnectConfig>,
646 npdu_admission_policy: bacnet_transport::sc::ScNpduAdmissionPolicy,
647}
648
649#[cfg(feature = "sc-tls")]
650impl ScClientBuilder {
651 pub fn hub_url(mut self, url: &str) -> Self {
653 self.hub_url = url.to_string();
654 self
655 }
656
657 pub fn tls_config(mut self, config: bacnet_transport::sc_tls::ScNodeTlsConfig) -> Self {
675 self.tls_config = Some(config);
676 self
677 }
678
679 pub fn vmac(mut self, vmac: [u8; 6]) -> Self {
681 self.vmac = vmac;
682 self
683 }
684
685 pub fn device_uuid(mut self, uuid: [u8; 16]) -> Self {
687 self.device_uuid = uuid;
688 self
689 }
690
691 pub fn apdu_timeout_ms(mut self, ms: u64) -> Self {
693 self.config.apdu_timeout_ms = ms;
694 self
695 }
696
697 pub fn cov_channel_capacity(mut self, capacity: usize) -> Self {
699 self.options.cov_channel_capacity = capacity;
700 self
701 }
702
703 pub fn heartbeat_interval_ms(mut self, ms: u64) -> Self {
705 self.heartbeat_interval_ms = ms;
706 self
707 }
708
709 pub fn heartbeat_timeout_ms(mut self, ms: u64) -> Self {
711 self.heartbeat_timeout_ms = ms;
712 self
713 }
714
715 pub fn connect_timeout_ms(mut self, ms: u64) -> Self {
719 self.connect_timeout_ms = ms;
720 self
721 }
722
723 pub fn npdu_per_origin_limit(mut self, limit: usize) -> Self {
728 self.npdu_admission_policy.per_origin_limit = limit;
729 self
730 }
731
732 pub fn control_rate_limit(mut self, limit: bacnet_transport::sc::ScControlRateLimit) -> Self {
738 self.npdu_admission_policy.control_rate = limit;
739 self
740 }
741
742 pub fn failover_hub_url(mut self, url: &str) -> Self {
757 self.failover_hub_url = Some(url.to_string());
758 self
759 }
760
761 pub fn reconnect(mut self, config: bacnet_transport::sc::ScReconnectConfig) -> Self {
768 self.reconnect = Some(config);
769 self
770 }
771
772 fn validate_identity(&self) -> Result<(), Error> {
773 match self.vmac {
774 bacnet_transport::sc_frame::UNKNOWN_VMAC => Err(Error::Encoding(
775 "SC client builder: vmac must not be the reserved unknown VMAC".into(),
776 )),
777 bacnet_transport::sc_frame::BROADCAST_VMAC => Err(Error::Encoding(
778 "SC client builder: vmac must not be the reserved broadcast VMAC".into(),
779 )),
780 _ if self.device_uuid == [0; 16] => Err(Error::Encoding(
781 "SC client builder: device_uuid is required and must not be all zero".into(),
782 )),
783 _ => Ok(()),
784 }
785 }
786
787 fn sc_transport<W: bacnet_transport::sc::WebSocketPort>(
788 &self,
789 ws: W,
790 ) -> bacnet_transport::sc::ScTransport<W> {
791 bacnet_transport::sc::ScTransport::new(ws, self.vmac)
792 .with_device_uuid(self.device_uuid)
793 .with_npdu_per_origin_limit(self.npdu_admission_policy.per_origin_limit)
794 .with_control_rate_limit(self.npdu_admission_policy.control_rate)
795 .with_heartbeat_interval_ms(self.heartbeat_interval_ms)
796 .with_heartbeat_timeout_ms(self.heartbeat_timeout_ms)
797 .with_connect_timeout_ms(self.connect_timeout_ms)
798 }
799
800 #[cfg(test)]
801 pub(crate) async fn build_with_websocket_for_test<
802 W: bacnet_transport::sc::WebSocketPort + 'static,
803 >(
804 self,
805 ws: W,
806 ) -> Result<BACnetClient<bacnet_transport::sc::ScTransport<W>>, Error> {
807 self.validate()?;
808 let transport = self.sc_transport(ws);
809 BACnetClient::start_with_options(self.config, transport, self.options).await
810 }
811}
812
813#[derive(Clone, Copy)]
815enum ConfirmedTarget<'a> {
816 Local {
817 mac: &'a [u8],
818 },
819 Routed {
820 router_mac: &'a [u8],
821 dest_network: u16,
822 dest_mac: &'a [u8],
823 },
824}
825
826impl<'a> ConfirmedTarget<'a> {
827 fn additional_npdu_header_len(&self) -> u16 {
828 match self {
829 Self::Local { .. } => 0,
830 Self::Routed { dest_mac, .. } => u16::try_from(dest_mac.len())
831 .unwrap_or(u16::MAX)
832 .saturating_add(4),
833 }
834 }
835}
836
837fn max_apdu_bucket_at_or_below(limit: u16) -> Option<u16> {
838 bacnet_encoding::apdu::max_apdu_header_at_or_below(u32::from(limit)).ok()
839}
840
841fn cap_max_apdu_to_transport(configured: u16, transport_limit: u16) -> Result<u16, Error> {
842 let limit = configured.min(transport_limit);
843 max_apdu_bucket_at_or_below(limit).ok_or_else(|| {
844 Error::Encoding(format!(
845 "transport max-APDU-length {transport_limit} leaves no valid BACnet APDU bucket"
846 ))
847 })
848}
849
850mod audit;
851#[cfg(feature = "ipv6")]
852mod builder_bip6;
853#[cfg(feature = "ipv6")]
854pub use builder_bip6::Bip6ClientBuilder;
855mod builder_build;
856mod builder_options;
857mod cov;
858mod cov_notifications;
859mod cov_renewal;
860mod cov_target;
861mod device_events;
862mod device_mgmt;
863mod discovery;
864mod dispatch;
865mod dispatch_context;
866mod inbound_replies;
867use inbound_replies::InboundReply;
868mod event_notifications;
869mod file_list;
870mod i_have;
871mod lifecycle;
872mod network_reports;
873mod object_mgmt;
874pub(crate) mod pacing;
875mod property;
876mod read_range;
877mod requests;
878mod response_admission;
879mod routed_path_limits;
880mod segmentation;
881mod segmentation_abort;
882mod segmentation_context;
883mod segmented_request;
884mod summaries;
885mod transaction_cleanup;
886mod transaction_peer;
887mod transport_access;
888mod write_group;
889pub(crate) use routed_path_limits::check_routed_unicast;
890use routed_path_limits::{
891 RoutedPathLease, RoutedPathLimits, forwarded_npci_len, routed_path_quarantine_horizon,
892};
893pub(crate) use transaction_peer::TransactionPeer;
894
895pub use cov::CovPropertySubscription;
896pub use cov_notifications::{
897 COVNotificationDelivery, ConfirmedCOVNotificationAckPolicy, ConfirmedCOVNotificationResponse,
898 ReceivedCOVNotification,
899};
900pub use cov_renewal::{
901 ManagedCOVSubscription, ManagedCOVSubscriptionEvent, ManagedCOVSubscriptionOptions,
902};
903pub use cov_target::CovTarget;
904pub use event_notifications::{
905 DEFAULT_EVENT_CHANNEL_CAPACITY, EventNotificationDelivery, MAX_EVENT_CHANNEL_CAPACITY,
906 ReceivedEventNotification,
907};
908pub use i_have::{I_HAVE_CHANNEL_CAPACITY, ReceivedIHave};
909pub use network_reports::{DiscoveredRouter, NETWORK_REPORT_CHANNEL_CAPACITY};
910pub use pacing::MAX_MIN_REQUEST_INTERVAL_MS;
911pub use write_group::WriteGroupDestination;
912
913#[cfg(test)]
914mod acknowledge_alarm_tests;
915#[cfg(test)]
916mod audit_tests;
917#[cfg(test)]
918mod batch_tests;
919#[cfg(test)]
920mod broadcast_mac_tests;
921#[cfg(test)]
922mod builder_options_tests;
923#[cfg(test)]
924mod confirmed_request_dispatch_tests;
925#[cfg(test)]
926mod coordinator_tests;
927#[cfg(test)]
928mod cov_notification_tests;
929#[cfg(test)]
930mod cov_renewal_gate_tests;
931#[cfg(test)]
932mod cov_renewal_routed_tests;
933#[cfg(test)]
934mod cov_renewal_tests;
935#[cfg(test)]
936mod cov_tests;
937#[cfg(test)]
938mod device_events_tests;
939#[cfg(test)]
940mod event_notification_tests;
941#[cfg(test)]
942pub(crate) mod fake_device;
943#[cfg(test)]
944mod group_source_request_tests;
945#[cfg(test)]
946mod list_error_tests;
947#[cfg(test)]
948mod list_validation_tests;
949#[cfg(test)]
950mod pacing_tests;
951#[cfg(test)]
952mod peer_max_apdu_tests;
953#[cfg(test)]
954mod peer_segmentation_tests;
955#[cfg(test)]
956mod rb07_provenance_tests;
957#[cfg(test)]
958mod read_range_tests;
959#[cfg(test)]
960mod request_timer_tests;
961#[cfg(test)]
962mod response_correlation_tests;
963#[cfg(test)]
964mod routed_max_apdu_tests;
965#[cfg(test)]
966mod routed_path_limit_tests;
967#[cfg(test)]
968mod routed_reply_tests;
969#[cfg(all(test, feature = "sc-tls"))]
970mod sc_builder_tests;
971#[cfg(test)]
972mod sc_max_apdu_tests;
973#[cfg(test)]
974mod segmentation_retransmit_tests;
975#[cfg(test)]
976mod segmented_receive_duplicate_tests;
977#[cfg(test)]
978mod segmented_receive_lifecycle_tests;
979#[cfg(test)]
980mod segmented_request_ordering_tests;
981#[cfg(test)]
982mod segmented_request_state_tests;
983#[cfg(test)]
984mod segmented_response_admission_tests;
985#[cfg(test)]
986mod segmented_response_capacity_tests;
987#[cfg(test)]
988mod segmented_timeout_tests;
989#[cfg(test)]
990mod sent_services_tests;
991#[cfg(test)]
992mod structured_error_tests;
993#[cfg(test)]
994mod tests;
995#[cfg(test)]
996mod transport_access_tests;
997#[cfg(test)]
998mod typed_error_wire_tests;
999#[cfg(test)]
1000mod wpm_validation_tests;
1001#[cfg(test)]
1002mod write_priority_tests;
1003
1004impl<T: TransportPort + 'static> BACnetClient<T> {
1005 pub fn generic_builder() -> ClientBuilder<T> {
1033 ClientBuilder {
1034 config: ClientConfig::default(),
1035 options: ClientOptions::default(),
1036 transport: None,
1037 }
1038 }
1039}
1040
1041#[cfg(test)]
1042mod number_tests;
1043
1044mod network_number;