Skip to main content

bacnet_rs/util/
mod.rs

1//! BACnet Utility Functions and Debugging Tools
2//!
3//! This module provides comprehensive utility functions, debugging tools, and helper utilities
4//! used throughout the BACnet stack implementation. It includes low-level utilities for data
5//! processing, performance monitoring, debugging assistance, and protocol-specific calculations.
6//!
7//! # Overview
8//!
9//! The utility module is organized into several functional areas:
10//!
11//! ## Core Utilities
12//! - **CRC Calculations**: MS/TP header and data CRC algorithms
13//! - **Object ID Encoding**: Conversion between object type/instance and 32-bit identifiers  
14//! - **Data Conversion**: Byte order handling, bit manipulation, type conversions
15//! - **Validation**: Input validation and range checking functions
16//!
17//! ## Performance Monitoring
18//! - **Statistics Collection**: Network and processing performance metrics
19//! - **Timing Measurements**: High-precision timing for profiling
20//! - **Resource Monitoring**: Memory and CPU usage tracking
21//! - **Circular Buffers**: Efficient data structures for logging and history
22//!
23//! ## Debugging and Analysis
24//! - **Protocol Debugging**: Deep packet inspection and analysis tools
25//! - **Hex Dumping**: Formatted binary data display with annotations
26//! - **Property Formatters**: Human-readable display of BACnet values
27//! - **Service Analysis**: Request/response parsing and validation
28//!
29//! ## Retry and Reliability
30//! - **Exponential Backoff**: Adaptive retry algorithms
31//! - **Timeout Management**: Configurable timeout strategies
32//! - **Error Recovery**: Automatic recovery from transient failures
33//!
34//! # Core Functions
35//!
36//! ## CRC Calculations
37//!
38//! BACnet uses different CRC algorithms for different data link types:
39//!
40//! ```rust
41//! use bacnet_rs::util::crc16_mstp;
42//!
43//! // Calculate CRC for MS/TP frame data
44//! let data = b"Hello BACnet";
45//! let crc = crc16_mstp(data);
46//! println!("CRC-16: 0x{:04X}", crc);
47//! ```
48//!
49//! ## Object ID Encoding
50//!
51//! BACnet object identifiers combine object type and instance into a 32-bit value:
52//!
53//! ```rust
54//! use bacnet_rs::object::{ObjectIdentifier, ObjectType};
55//!
56//! // Encode object type 0 (Analog Input), instance 42
57//! let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 42);
58//! let encoded: u32 = match object_id.try_into() {
59//!     Ok(value) => value,
60//!     Err(_) => panic!("Object identifier encoding failed"),
61//! };
62//! println!("Encoded: 0x{:08X}", encoded);
63//!
64//! // Decode back to type and instance
65//! let object_id: ObjectIdentifier = encoded.into();
66//! assert_eq!(object_id.object_type, ObjectType::AnalogInput);
67//! assert_eq!(object_id.instance, 42);
68//! ```
69//!
70//! # Performance Monitoring
71//!
72//! The performance monitoring subsystem provides detailed metrics collection:
73//!
74//! ```rust
75//! // Performance monitoring example
76//! #[cfg(feature = "std")]
77//! {
78//!     use std::time::Instant;
79//!     let start = Instant::now();
80//!     // Perform operation
81//!     let duration = start.elapsed();
82//!     println!("Operation took: {:?}", duration);
83//! }
84//! ```
85//!
86//! # Statistics Collection
87//!
88//! Track communication and processing statistics:
89//!
90//! ```rust
91//! // Statistics collection example
92//! #[derive(Default)]
93//! struct SimpleStats {
94//!     messages_sent: u64,
95//!     bytes_received: u64,
96//!     errors: u64,
97//! }
98//!
99//! let mut stats = SimpleStats::default();
100//! stats.messages_sent += 1;
101//! stats.bytes_received += 100;
102//! println!("Stats: {} sent, {} received", stats.messages_sent, stats.bytes_received);
103//! ```
104//!
105//! # Debug Formatting
106//!
107//! Comprehensive debugging tools for protocol analysis:
108//!
109//! ```rust
110//! use bacnet_rs::util::hex_dump;
111//!
112//! // Create hex dumps for debugging
113//! let frame_data = vec![0x81, 0x0A, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00];
114//! let dump = hex_dump(&frame_data, "Frame");
115//! println!("Frame data:\n{}", dump);
116//! ```
117//!
118//! # Retry Mechanisms
119//!
120//! Configurable retry strategies for reliable communication:
121//!
122//! ```rust
123//! use bacnet_rs::util::RetryConfig;
124//!
125//! let config = RetryConfig {
126//!     max_attempts: 3,
127//!     initial_delay_ms: 100,
128//!     max_delay_ms: 5000,
129//!     backoff_multiplier: 2.0,
130//! };
131//!
132//! // Use in retry loop
133//! for attempt in 0..config.max_attempts {
134//!     match try_operation() {
135//!         Ok(_result) => break,
136//!         Err(_) if attempt < config.max_attempts - 1 => {
137//!             let delay_ms = config.initial_delay_ms * 2_u64.pow(attempt as u32);
138//!             let delay_ms = delay_ms.min(config.max_delay_ms);
139//!             #[cfg(feature = "std")]
140//!             std::thread::sleep(std::time::Duration::from_millis(delay_ms));
141//!         }
142//!         Err(_) => break, // Stop on error
143//!     }
144//! }
145//! # fn try_operation() -> Result<(), Box<dyn std::error::Error>> { Ok(()) }
146//! ```
147//!
148//! # Circular Buffers
149//!
150//! Efficient data structures for event logging and history:
151//!
152//! ```rust
153//! use bacnet_rs::util::CircularBuffer;
154//!
155//! let mut buffer = CircularBuffer::new(100); // Capacity of 100 items
156//!
157//! // Add items (oldest are automatically removed when full)
158//! for i in 0..150 {
159//!     buffer.push(format!("Event {}", i));
160//! }
161//!
162//! // Buffer contains the last 100 items
163//! assert_eq!(buffer.len(), 100);
164//! let items = buffer.items();
165//! assert_eq!(items[0], "Event 50"); // Oldest remaining
166//! assert_eq!(items[99], "Event 149"); // Newest
167//! ```
168//!
169//! # No-std Compatibility
170//!
171//! Most utilities work in `no_std` environments with appropriate feature flags:
172//!
173//! ```rust
174//! use bacnet_rs::util::crc16_mstp;
175//! use bacnet_rs::object::{ObjectIdentifier, ObjectType};
176//!
177//! fn main() {
178//!     // CRC calculation works without std
179//!     let data = b"test";
180//!     let crc = crc16_mstp(data);
181//!
182//!     // Object ID encoding works without std
183//!     let object_id = ObjectIdentifier::new(ObjectType::Device, 42);
184//!     let encoded: u32 = match object_id.try_into() {
185//!         Ok(value) => value,
186//!         Err(_) => panic!("Object identifier encoding failed"),
187//!     };
188//!     println!("CRC: 0x{:04X}, Encoded ID: 0x{:08X}", crc, encoded);
189//! }
190//! ```
191
192pub mod enum_macros;
193
194// Debug formatting utilities
195#[cfg(not(feature = "std"))]
196use core::fmt;
197
198#[cfg(not(feature = "std"))]
199use alloc::{format, string::String, vec::Vec};
200
201#[cfg(feature = "std")]
202use std::{
203    collections::HashMap,
204    sync::{Arc, Mutex},
205    time::{Duration, Instant},
206};
207
208#[cfg(not(feature = "std"))]
209use alloc::collections::BTreeMap as HashMap;
210
211/// Calculate CRC-16 for MS/TP frames
212///
213/// Uses the polynomial x^16 + x^15 + x^2 + 1 (0xA001)
214pub fn crc16_mstp(data: &[u8]) -> u16 {
215    let mut crc = 0xFFFF;
216
217    for byte in data {
218        crc ^= *byte as u16;
219        for _ in 0..8 {
220            if crc & 0x0001 != 0 {
221                crc = (crc >> 1) ^ 0xA001;
222            } else {
223                crc >>= 1;
224            }
225        }
226    }
227
228    !crc
229}
230
231/// Calculate CRC-32C (Castagnoli) for BACnet/SC
232pub fn crc32c(data: &[u8]) -> u32 {
233    let mut crc = 0xFFFFFFFF;
234
235    for byte in data {
236        crc ^= *byte as u32;
237        for _ in 0..8 {
238            if crc & 1 != 0 {
239                crc = (crc >> 1) ^ 0x82F63B78;
240            } else {
241                crc >>= 1;
242            }
243        }
244    }
245
246    !crc
247}
248
249/// Convert BACnet date to string representation
250pub fn bacnet_date_to_string(year: u16, month: u8, day: u8, weekday: u8) -> String {
251    let year_str = if year == 255 {
252        String::from("*")
253    } else {
254        format!("{}", year)
255    };
256    let month_str = match month {
257        13 => String::from("odd"),
258        14 => String::from("even"),
259        255 => String::from("*"),
260        _ => format!("{}", month),
261    };
262    let day_str = if day == 32 {
263        String::from("last")
264    } else if day == 255 {
265        String::from("*")
266    } else {
267        format!("{}", day)
268    };
269    let weekday_str = if weekday == 255 {
270        String::from("*")
271    } else {
272        String::from(match weekday {
273            1 => "Mon",
274            2 => "Tue",
275            3 => "Wed",
276            4 => "Thu",
277            5 => "Fri",
278            6 => "Sat",
279            7 => "Sun",
280            _ => "?",
281        })
282    };
283
284    format!("{}/{}/{} ({})", year_str, month_str, day_str, weekday_str)
285}
286
287/// Convert BACnet time to string representation
288pub fn bacnet_time_to_string(hour: u8, minute: u8, second: u8, hundredths: u8) -> String {
289    let hour_str = if hour == 255 {
290        String::from("*")
291    } else {
292        format!("{:02}", hour)
293    };
294    let minute_str = if minute == 255 {
295        String::from("*")
296    } else {
297        format!("{:02}", minute)
298    };
299    let second_str = if second == 255 {
300        String::from("*")
301    } else {
302        format!("{:02}", second)
303    };
304    let hundredths_str = if hundredths == 255 {
305        String::from("*")
306    } else {
307        format!("{:02}", hundredths)
308    };
309
310    format!(
311        "{}:{}:{}.{}",
312        hour_str, minute_str, second_str, hundredths_str
313    )
314}
315
316/// Buffer utilities for reading/writing data
317pub struct Buffer<'a> {
318    data: &'a [u8],
319    position: usize,
320}
321
322impl<'a> Buffer<'a> {
323    /// Create a new buffer reader
324    pub fn new(data: &'a [u8]) -> Self {
325        Self { data, position: 0 }
326    }
327
328    /// Get remaining bytes
329    pub fn remaining(&self) -> usize {
330        self.data.len().saturating_sub(self.position)
331    }
332
333    /// Check if buffer has at least n bytes remaining
334    pub fn has_remaining(&self, n: usize) -> bool {
335        self.remaining() >= n
336    }
337
338    /// Read a single byte
339    pub fn read_u8(&mut self) -> Option<u8> {
340        if self.has_remaining(1) {
341            let value = self.data[self.position];
342            self.position += 1;
343            Some(value)
344        } else {
345            None
346        }
347    }
348
349    /// Read a 16-bit value (big-endian)
350    pub fn read_u16(&mut self) -> Option<u16> {
351        if self.has_remaining(2) {
352            let value =
353                u16::from_be_bytes([self.data[self.position], self.data[self.position + 1]]);
354            self.position += 2;
355            Some(value)
356        } else {
357            None
358        }
359    }
360
361    /// Read a 32-bit value (big-endian)
362    pub fn read_u32(&mut self) -> Option<u32> {
363        if self.has_remaining(4) {
364            let value = u32::from_be_bytes([
365                self.data[self.position],
366                self.data[self.position + 1],
367                self.data[self.position + 2],
368                self.data[self.position + 3],
369            ]);
370            self.position += 4;
371            Some(value)
372        } else {
373            None
374        }
375    }
376
377    /// Read n bytes
378    pub fn read_bytes(&mut self, n: usize) -> Option<&'a [u8]> {
379        if self.has_remaining(n) {
380            let bytes = &self.data[self.position..self.position + n];
381            self.position += n;
382            Some(bytes)
383        } else {
384            None
385        }
386    }
387
388    /// Get current position
389    pub fn position(&self) -> usize {
390        self.position
391    }
392
393    /// Skip n bytes
394    pub fn skip(&mut self, n: usize) -> bool {
395        if self.has_remaining(n) {
396            self.position += n;
397            true
398        } else {
399            false
400        }
401    }
402}
403
404/// Hex dump utility for debugging
405pub fn hex_dump(data: &[u8], prefix: &str) -> String {
406    let mut result = String::new();
407
408    for (i, chunk) in data.chunks(16).enumerate() {
409        result.push_str(prefix);
410        result.push_str(&format!("{:04X}: ", i * 16));
411
412        // Hex bytes
413        for (j, byte) in chunk.iter().enumerate() {
414            if j == 8 {
415                result.push(' ');
416            }
417            result.push_str(&format!("{:02X} ", byte));
418        }
419
420        // Padding
421        for j in chunk.len()..16 {
422            if j == 8 {
423                result.push(' ');
424            }
425            result.push_str("   ");
426        }
427
428        result.push_str(" |");
429
430        // ASCII representation
431        for byte in chunk {
432            if byte.is_ascii_graphic() || *byte == b' ' {
433                result.push(*byte as char);
434            } else {
435                result.push('.');
436            }
437        }
438
439        result.push_str("|\n");
440    }
441
442    result
443}
444
445/// Priority array utilities
446pub mod priority {
447    /// BACnet priority levels (1-16, where 1 is highest)
448    pub const MANUAL_LIFE_SAFETY: u8 = 1;
449    pub const AUTOMATIC_LIFE_SAFETY: u8 = 2;
450    pub const AVAILABLE_3: u8 = 3;
451    pub const AVAILABLE_4: u8 = 4;
452    pub const CRITICAL_EQUIPMENT_CONTROL: u8 = 5;
453    pub const MINIMUM_ON_OFF: u8 = 6;
454    pub const AVAILABLE_7: u8 = 7;
455    pub const MANUAL_OPERATOR: u8 = 8;
456    pub const AVAILABLE_9: u8 = 9;
457    pub const AVAILABLE_10: u8 = 10;
458    pub const AVAILABLE_11: u8 = 11;
459    pub const AVAILABLE_12: u8 = 12;
460    pub const AVAILABLE_13: u8 = 13;
461    pub const AVAILABLE_14: u8 = 14;
462    pub const AVAILABLE_15: u8 = 15;
463    pub const LOWEST: u8 = 16;
464
465    /// Check if priority is valid (1-16)
466    pub fn is_valid(priority: u8) -> bool {
467        (1..=16).contains(&priority)
468    }
469}
470
471/// Performance monitoring utilities
472#[cfg(feature = "std")]
473pub mod performance {
474    use super::*;
475
476    /// Performance metrics for a BACnet operation
477    #[derive(Debug, Clone)]
478    pub struct OperationMetrics {
479        pub name: String,
480        pub count: u64,
481        pub total_duration_ms: f64,
482        pub min_duration_ms: f64,
483        pub max_duration_ms: f64,
484        pub avg_duration_ms: f64,
485        pub last_duration_ms: f64,
486    }
487
488    /// Performance monitor for tracking operation timing
489    pub struct PerformanceMonitor {
490        metrics: Arc<Mutex<HashMap<String, OperationMetrics>>>,
491        active_timers: Arc<Mutex<HashMap<String, Instant>>>,
492    }
493
494    impl Default for PerformanceMonitor {
495        fn default() -> Self {
496            Self {
497                metrics: Arc::new(Mutex::new(HashMap::new())),
498                active_timers: Arc::new(Mutex::new(HashMap::new())),
499            }
500        }
501    }
502
503    impl PerformanceMonitor {
504        /// Create a new performance monitor
505        pub fn new() -> Self {
506            Self::default()
507        }
508
509        /// Start timing an operation
510        pub fn start_timer(&self, operation: &str) {
511            let mut timers = self.active_timers.lock().unwrap();
512            timers.insert(operation.to_string(), Instant::now());
513        }
514
515        /// Stop timing an operation and record metrics
516        pub fn stop_timer(&self, operation: &str) {
517            let mut timers = self.active_timers.lock().unwrap();
518            if let Some(start_time) = timers.remove(operation) {
519                let duration = start_time.elapsed();
520                let duration_ms = duration.as_secs_f64() * 1000.0;
521
522                let mut metrics = self.metrics.lock().unwrap();
523                let metric = metrics
524                    .entry(operation.to_string())
525                    .or_insert(OperationMetrics {
526                        name: operation.to_string(),
527                        count: 0,
528                        total_duration_ms: 0.0,
529                        min_duration_ms: f64::MAX,
530                        max_duration_ms: 0.0,
531                        avg_duration_ms: 0.0,
532                        last_duration_ms: 0.0,
533                    });
534
535                metric.count += 1;
536                metric.total_duration_ms += duration_ms;
537                metric.min_duration_ms = metric.min_duration_ms.min(duration_ms);
538                metric.max_duration_ms = metric.max_duration_ms.max(duration_ms);
539                metric.avg_duration_ms = metric.total_duration_ms / metric.count as f64;
540                metric.last_duration_ms = duration_ms;
541            }
542        }
543
544        /// Get metrics for a specific operation
545        pub fn get_metrics(&self, operation: &str) -> Option<OperationMetrics> {
546            let metrics = self.metrics.lock().unwrap();
547            metrics.get(operation).cloned()
548        }
549
550        /// Get all metrics
551        pub fn get_all_metrics(&self) -> Vec<OperationMetrics> {
552            let metrics = self.metrics.lock().unwrap();
553            metrics.values().cloned().collect()
554        }
555
556        /// Clear all metrics
557        pub fn clear(&self) {
558            self.metrics.lock().unwrap().clear();
559            self.active_timers.lock().unwrap().clear();
560        }
561    }
562
563    /// RAII timer for automatic performance tracking
564    pub struct ScopedTimer<'a> {
565        monitor: &'a PerformanceMonitor,
566        operation: String,
567    }
568
569    impl<'a> ScopedTimer<'a> {
570        /// Create a new scoped timer
571        pub fn new(monitor: &'a PerformanceMonitor, operation: &str) -> Self {
572            monitor.start_timer(operation);
573            Self {
574                monitor,
575                operation: operation.to_string(),
576            }
577        }
578    }
579
580    impl Drop for ScopedTimer<'_> {
581        fn drop(&mut self) {
582            self.monitor.stop_timer(&self.operation);
583        }
584    }
585}
586
587/// Statistics collection helpers
588pub mod statistics {
589    use super::*;
590
591    /// BACnet communication statistics
592    #[derive(Debug, Default, Clone)]
593    pub struct CommunicationStats {
594        pub messages_sent: u64,
595        pub messages_received: u64,
596        pub bytes_sent: u64,
597        pub bytes_received: u64,
598        pub errors: u64,
599        pub timeouts: u64,
600        pub retries: u64,
601        pub acks_received: u64,
602        pub naks_received: u64,
603        pub rejects_received: u64,
604        pub aborts_received: u64,
605    }
606
607    impl CommunicationStats {
608        /// Create new statistics
609        pub fn new() -> Self {
610            Self::default()
611        }
612
613        /// Record a sent message
614        pub fn record_sent(&mut self, bytes: usize) {
615            self.messages_sent += 1;
616            self.bytes_sent += bytes as u64;
617        }
618
619        /// Record a received message
620        pub fn record_received(&mut self, bytes: usize) {
621            self.messages_received += 1;
622            self.bytes_received += bytes as u64;
623        }
624
625        /// Record an error
626        pub fn record_error(&mut self) {
627            self.errors += 1;
628        }
629
630        /// Record a timeout
631        pub fn record_timeout(&mut self) {
632            self.timeouts += 1;
633        }
634
635        /// Record a retry
636        pub fn record_retry(&mut self) {
637            self.retries += 1;
638        }
639
640        /// Get success rate percentage
641        pub fn success_rate(&self) -> f64 {
642            let total = self.messages_sent as f64;
643            if total == 0.0 {
644                return 100.0;
645            }
646            let failures = (self.errors + self.timeouts) as f64;
647            ((total - failures) / total) * 100.0
648        }
649
650        /// Reset all statistics
651        pub fn reset(&mut self) {
652            *self = Self::default();
653        }
654    }
655
656    /// Device-specific statistics
657    #[derive(Debug, Clone)]
658    pub struct DeviceStats {
659        pub device_id: u32,
660        pub address: String,
661        pub comm_stats: CommunicationStats,
662        pub last_seen: Option<Instant>,
663        pub response_times_ms: Vec<f64>,
664        pub online: bool,
665    }
666
667    #[cfg(feature = "std")]
668    impl DeviceStats {
669        /// Create new device statistics
670        pub fn new(device_id: u32, address: String) -> Self {
671            Self {
672                device_id,
673                address,
674                comm_stats: CommunicationStats::new(),
675                last_seen: None,
676                response_times_ms: Vec::new(),
677                online: false,
678            }
679        }
680
681        /// Record a response time
682        pub fn record_response_time(&mut self, ms: f64) {
683            self.response_times_ms.push(ms);
684            // Keep only last 100 response times
685            if self.response_times_ms.len() > 100 {
686                self.response_times_ms.remove(0);
687            }
688            self.last_seen = Some(Instant::now());
689            self.online = true;
690        }
691
692        /// Get average response time
693        pub fn avg_response_time(&self) -> Option<f64> {
694            if self.response_times_ms.is_empty() {
695                return None;
696            }
697            let sum: f64 = self.response_times_ms.iter().sum();
698            Some(sum / self.response_times_ms.len() as f64)
699        }
700
701        /// Mark device as offline
702        pub fn mark_offline(&mut self) {
703            self.online = false;
704        }
705    }
706
707    /// Statistics collector for multiple devices
708    #[cfg(feature = "std")]
709    pub struct StatsCollector {
710        devices: Arc<Mutex<HashMap<u32, DeviceStats>>>,
711        global_stats: Arc<Mutex<CommunicationStats>>,
712    }
713
714    #[cfg(feature = "std")]
715    impl Default for StatsCollector {
716        fn default() -> Self {
717            Self {
718                devices: Arc::new(Mutex::new(HashMap::new())),
719                global_stats: Arc::new(Mutex::new(CommunicationStats::new())),
720            }
721        }
722    }
723
724    #[cfg(feature = "std")]
725    impl StatsCollector {
726        /// Create a new statistics collector
727        pub fn new() -> Self {
728            Self::default()
729        }
730
731        /// Get or create device statistics
732        pub fn get_device_stats(&self, device_id: u32, address: String) -> DeviceStats {
733            let mut devices = self.devices.lock().unwrap();
734            devices
735                .entry(device_id)
736                .or_insert_with(|| DeviceStats::new(device_id, address))
737                .clone()
738        }
739
740        /// Update device statistics
741        pub fn update_device_stats<F>(&self, device_id: u32, updater: F)
742        where
743            F: FnOnce(&mut DeviceStats),
744        {
745            let mut devices = self.devices.lock().unwrap();
746            if let Some(stats) = devices.get_mut(&device_id) {
747                updater(stats);
748            }
749        }
750
751        /// Get global statistics
752        pub fn get_global_stats(&self) -> CommunicationStats {
753            self.global_stats.lock().unwrap().clone()
754        }
755
756        /// Update global statistics
757        pub fn update_global_stats<F>(&self, updater: F)
758        where
759            F: FnOnce(&mut CommunicationStats),
760        {
761            let mut stats = self.global_stats.lock().unwrap();
762            updater(&mut stats);
763        }
764
765        /// Get all device statistics
766        pub fn get_all_device_stats(&self) -> Vec<DeviceStats> {
767            let devices = self.devices.lock().unwrap();
768            devices.values().cloned().collect()
769        }
770
771        /// Clear all statistics
772        pub fn clear(&self) {
773            self.devices.lock().unwrap().clear();
774            self.global_stats.lock().unwrap().reset();
775        }
776    }
777}
778
779/// Additional utility functions
780///
781/// Validate BACnet network number (0-65534, 65535 is broadcast)
782pub fn is_valid_network_number(_network: u16) -> bool {
783    // All u16 values are valid network numbers
784    true
785}
786
787/// Check if network number is local (0)
788pub fn is_local_network(network: u16) -> bool {
789    network == 0
790}
791
792/// Check if network number is broadcast (65535)
793pub fn is_broadcast_network(network: u16) -> bool {
794    network == 65535
795}
796
797/// Parse BACnet address from string (e.g., "192.168.1.100:47808")
798#[cfg(feature = "std")]
799pub fn parse_bacnet_address(address: &str) -> Result<std::net::SocketAddr, String> {
800    use std::net::ToSocketAddrs;
801
802    // If no port specified, add default BACnet port
803    let addr_with_port = if address.contains(':') {
804        address.to_string()
805    } else {
806        format!("{}:47808", address)
807    };
808
809    addr_with_port
810        .to_socket_addrs()
811        .map_err(|e| format!("Invalid address: {}", e))?
812        .next()
813        .ok_or_else(|| "No valid address found".to_string())
814}
815
816/// Format bytes as human-readable size
817pub fn format_bytes(bytes: u64) -> String {
818    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
819
820    if bytes == 0 {
821        return "0 B".to_string();
822    }
823
824    let mut size = bytes as f64;
825    let mut unit_index = 0;
826
827    while size >= 1024.0 && unit_index < UNITS.len() - 1 {
828        size /= 1024.0;
829        unit_index += 1;
830    }
831
832    if unit_index == 0 {
833        format!("{} {}", bytes, UNITS[unit_index])
834    } else {
835        format!("{:.2} {}", size, UNITS[unit_index])
836    }
837}
838
839/// Calculate message throughput
840pub fn calculate_throughput(bytes: u64, duration_secs: f64) -> String {
841    if duration_secs == 0.0 {
842        return "N/A".to_string();
843    }
844
845    let bytes_per_sec = bytes as f64 / duration_secs;
846    format!("{}/s", format_bytes(bytes_per_sec as u64))
847}
848
849/// Retry configuration
850#[derive(Debug, Clone)]
851pub struct RetryConfig {
852    pub max_attempts: u32,
853    pub initial_delay_ms: u64,
854    pub max_delay_ms: u64,
855    pub backoff_multiplier: f64,
856}
857
858impl Default for RetryConfig {
859    fn default() -> Self {
860        Self {
861            max_attempts: 3,
862            initial_delay_ms: 100,
863            max_delay_ms: 5000,
864            backoff_multiplier: 2.0,
865        }
866    }
867}
868
869impl RetryConfig {
870    /// Calculate delay for a given attempt (0-based)
871    pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
872        let delay_ms = if attempt == 0 {
873            self.initial_delay_ms
874        } else {
875            let delay = self.initial_delay_ms as f64 * self.backoff_multiplier.powi(attempt as i32);
876            delay.min(self.max_delay_ms as f64) as u64
877        };
878
879        Duration::from_millis(delay_ms)
880    }
881}
882
883/// Circular buffer for maintaining history
884#[derive(Debug, Clone)]
885pub struct CircularBuffer<T> {
886    buffer: Vec<Option<T>>,
887    capacity: usize,
888    head: usize,
889    size: usize,
890}
891
892impl<T: Clone> CircularBuffer<T> {
893    /// Create a new circular buffer with given capacity
894    pub fn new(capacity: usize) -> Self {
895        Self {
896            buffer: vec![None; capacity],
897            capacity,
898            head: 0,
899            size: 0,
900        }
901    }
902
903    /// Add an item to the buffer
904    pub fn push(&mut self, item: T) {
905        self.buffer[self.head] = Some(item);
906        self.head = (self.head + 1) % self.capacity;
907        if self.size < self.capacity {
908            self.size += 1;
909        }
910    }
911
912    /// Get all items in order (oldest to newest)
913    pub fn items(&self) -> Vec<T> {
914        let mut result = Vec::with_capacity(self.size);
915
916        if self.size < self.capacity {
917            // Buffer not full, items are from 0 to head
918            for i in 0..self.size {
919                if let Some(item) = &self.buffer[i] {
920                    result.push(item.clone());
921                }
922            }
923        } else {
924            // Buffer full, items wrap around
925            for i in 0..self.capacity {
926                let idx = (self.head + i) % self.capacity;
927                if let Some(item) = &self.buffer[idx] {
928                    result.push(item.clone());
929                }
930            }
931        }
932
933        result
934    }
935
936    /// Get the number of items in the buffer
937    pub fn len(&self) -> usize {
938        self.size
939    }
940
941    /// Check if buffer is empty
942    pub fn is_empty(&self) -> bool {
943        self.size == 0
944    }
945
946    /// Clear the buffer
947    pub fn clear(&mut self) {
948        self.buffer = vec![None; self.capacity];
949        self.head = 0;
950        self.size = 0;
951    }
952}
953
954/// Debug formatting utilities for BACnet data structures and protocol analysis
955pub mod debug {
956    use crate::object::ObjectIdentifier;
957
958    use super::*;
959
960    /// Format a BACnet property value for debugging
961    pub fn format_property_value(data: &[u8]) -> String {
962        if data.is_empty() {
963            return "[empty]".to_string();
964        }
965
966        let mut result = String::new();
967        let tag = data[0];
968
969        match tag {
970            0x11 => {
971                // Boolean
972                if data.len() >= 2 {
973                    result.push_str(&format!("Boolean({})", data[1] != 0));
974                } else {
975                    result.push_str("Boolean(invalid)");
976                }
977            }
978            0x21 => {
979                // Unsigned integer
980                result.push_str(&format_unsigned_integer(data));
981            }
982            0x31 => {
983                // Signed integer
984                result.push_str(&format_signed_integer(data));
985            }
986            0x44 => {
987                // Real (float)
988                if data.len() >= 5 {
989                    let bytes = [data[1], data[2], data[3], data[4]];
990                    let value = f32::from_be_bytes(bytes);
991                    result.push_str(&format!("Real({})", value));
992                } else {
993                    result.push_str("Real(invalid)");
994                }
995            }
996            0x55 => {
997                // Double
998                if data.len() >= 9 {
999                    let mut bytes = [0u8; 8];
1000                    bytes.copy_from_slice(&data[1..9]);
1001                    let value = f64::from_be_bytes(bytes);
1002                    result.push_str(&format!("Double({})", value));
1003                } else {
1004                    result.push_str("Double(invalid)");
1005                }
1006            }
1007            0x75 => {
1008                // Character string
1009                result.push_str(&format_character_string(data));
1010            }
1011            0x81..=0x8F => {
1012                // Octet string
1013                result.push_str(&format_octet_string(data));
1014            }
1015            0x91 => {
1016                // Enumerated
1017                result.push_str(&format_enumerated(data));
1018            }
1019            0xA1 => {
1020                // Date
1021                result.push_str(&format_date(data));
1022            }
1023            0xB1 => {
1024                // Time
1025                result.push_str(&format_time(data));
1026            }
1027            0xC4 => {
1028                // Object identifier
1029                result.push_str(&format_object_identifier(data));
1030            }
1031            _ => {
1032                result.push_str(&format!(
1033                    "Unknown(tag=0x{:02X}, data={})",
1034                    tag,
1035                    hex_dump(data, "")
1036                ));
1037            }
1038        }
1039
1040        result
1041    }
1042
1043    fn format_unsigned_integer(data: &[u8]) -> String {
1044        if data.len() < 2 {
1045            return "UnsignedInt(invalid)".to_string();
1046        }
1047
1048        let length = (data[0] & 0x07) as usize;
1049        if data.len() < 1 + length {
1050            return "UnsignedInt(invalid length)".to_string();
1051        }
1052
1053        let mut value = 0u64;
1054        for i in 0..length {
1055            value = (value << 8) | (data[1 + i] as u64);
1056        }
1057
1058        format!("UnsignedInt({})", value)
1059    }
1060
1061    fn format_signed_integer(data: &[u8]) -> String {
1062        if data.len() < 2 {
1063            return "SignedInt(invalid)".to_string();
1064        }
1065
1066        let length = (data[0] & 0x07) as usize;
1067        if data.len() < 1 + length {
1068            return "SignedInt(invalid length)".to_string();
1069        }
1070
1071        let mut value = 0i64;
1072        let sign_bit = data[1] & 0x80 != 0;
1073
1074        for i in 0..length {
1075            value = (value << 8) | (data[1 + i] as i64);
1076        }
1077
1078        // Sign extend if negative
1079        if sign_bit {
1080            let shift = 64 - (length * 8);
1081            value = (value << shift) >> shift;
1082        }
1083
1084        format!("SignedInt({})", value)
1085    }
1086
1087    fn format_character_string(data: &[u8]) -> String {
1088        if data.len() < 3 {
1089            return "CharString(invalid)".to_string();
1090        }
1091
1092        let length = data[1] as usize;
1093        if data.len() < 2 + length {
1094            return "CharString(invalid length)".to_string();
1095        }
1096
1097        let encoding = data[2];
1098        let string_data = &data[3..2 + length];
1099
1100        let decoded = match encoding {
1101            0 => {
1102                // ANSI X3.4 (ASCII)
1103                String::from_utf8_lossy(string_data).to_string()
1104            }
1105            4 => {
1106                // UCS-2 (UTF-16)
1107                let mut utf16_chars = Vec::new();
1108                for chunk in string_data.chunks_exact(2) {
1109                    let char_code = u16::from_be_bytes([chunk[0], chunk[1]]);
1110                    utf16_chars.push(char_code);
1111                }
1112                String::from_utf16_lossy(&utf16_chars)
1113            }
1114            _ => {
1115                format!("<encoding={}>", encoding)
1116            }
1117        };
1118
1119        format!("CharString(\"{}\")", decoded)
1120    }
1121
1122    fn format_octet_string(data: &[u8]) -> String {
1123        if data.is_empty() {
1124            return "OctetString(invalid)".to_string();
1125        }
1126
1127        let length = (data[0] & 0x07) as usize;
1128        if data.len() < 1 + length {
1129            return "OctetString(invalid length)".to_string();
1130        }
1131
1132        let octets = &data[1..1 + length];
1133        let hex_string = octets
1134            .iter()
1135            .map(|b| format!("{:02X}", b))
1136            .collect::<Vec<_>>()
1137            .join(" ");
1138
1139        format!("OctetString([{}])", hex_string)
1140    }
1141
1142    fn format_enumerated(data: &[u8]) -> String {
1143        if data.len() < 2 {
1144            return "Enumerated(invalid)".to_string();
1145        }
1146
1147        let value = data[1] as u32;
1148        format!("Enumerated({})", value)
1149    }
1150
1151    fn format_date(data: &[u8]) -> String {
1152        if data.len() < 5 {
1153            return "Date(invalid)".to_string();
1154        }
1155
1156        let year = data[1] as u16 + 1900;
1157        let month = data[2];
1158        let day = data[3];
1159        let weekday = data[4];
1160
1161        format!("Date({})", bacnet_date_to_string(year, month, day, weekday))
1162    }
1163
1164    fn format_time(data: &[u8]) -> String {
1165        if data.len() < 5 {
1166            return "Time(invalid)".to_string();
1167        }
1168
1169        let hour = data[1];
1170        let minute = data[2];
1171        let second = data[3];
1172        let hundredths = data[4];
1173
1174        format!(
1175            "Time({})",
1176            bacnet_time_to_string(hour, minute, second, hundredths)
1177        )
1178    }
1179
1180    fn format_object_identifier(data: &[u8]) -> String {
1181        if data.len() < 5 {
1182            return "ObjectID(invalid)".to_string();
1183        }
1184
1185        let obj_id = u32::from_be_bytes([data[1], data[2], data[3], data[4]]);
1186        let obj_id: ObjectIdentifier = obj_id.into();
1187        format!("ObjectID({} {})", obj_id.object_type, obj_id.instance)
1188    }
1189
1190    /// Format BACnet service choice for debugging
1191    pub fn format_service_choice(service_choice: u8) -> String {
1192        let service_name = match service_choice {
1193            // Confirmed services
1194            0 => "acknowledgeAlarm",
1195            1 => "confirmedCOVNotification",
1196            2 => "confirmedEventNotification",
1197            3 => "getAlarmSummary",
1198            4 => "getEnrollmentSummary",
1199            5 => "getEventInformation",
1200            6 => "atomicReadFile",
1201            7 => "atomicWriteFile",
1202            8 => "addListElement",
1203            9 => "removeListElement",
1204            10 => "createObject",
1205            11 => "deleteObject",
1206            12 => "readProperty",
1207            13 => "readPropertyConditional",
1208            14 => "readPropertyMultiple",
1209            15 => "writeProperty",
1210            16 => "writePropertyMultiple",
1211            17 => "deviceCommunicationControl",
1212            18 => "confirmedPrivateTransfer",
1213            19 => "confirmedTextMessage",
1214            20 => "reinitializeDevice",
1215            21 => "vtOpen",
1216            22 => "vtClose",
1217            23 => "vtData",
1218            24 => "authenticate",
1219            25 => "requestKey",
1220            26 => "readRange",
1221            27 => "lifeSafetyOperation",
1222            28 => "subscribeCOV",
1223            29 => "subscribeCOVProperty",
1224            30 => "getEventInformation",
1225            _ => "unknown",
1226        };
1227
1228        format!("{}({})", service_name, service_choice)
1229    }
1230
1231    /// Format BACnet error for debugging
1232    pub fn format_bacnet_error(error_class: u8, error_code: u8) -> String {
1233        let class_name = match error_class {
1234            0 => "device",
1235            1 => "object",
1236            2 => "property",
1237            3 => "resources",
1238            4 => "security",
1239            5 => "services",
1240            6 => "vt",
1241            7 => "communication",
1242            _ => "unknown",
1243        };
1244
1245        format!("Error({} class, code {})", class_name, error_code)
1246    }
1247
1248    /// Create a detailed hex dump with annotations
1249    pub fn annotated_hex_dump(data: &[u8], annotations: &[(usize, String)]) -> String {
1250        let mut result = String::new();
1251        let mut annotation_map: std::collections::HashMap<usize, String> =
1252            annotations.iter().cloned().collect();
1253
1254        for (i, chunk) in data.chunks(16).enumerate() {
1255            let offset = i * 16;
1256            result.push_str(&format!("{:04X}: ", offset));
1257
1258            // Hex bytes with spacing
1259            for (j, byte) in chunk.iter().enumerate() {
1260                if j == 8 {
1261                    result.push(' ');
1262                }
1263                result.push_str(&format!("{:02X} ", byte));
1264            }
1265
1266            // Padding for incomplete lines
1267            for j in chunk.len()..16 {
1268                if j == 8 {
1269                    result.push(' ');
1270                }
1271                result.push_str("   ");
1272            }
1273
1274            result.push_str(" |");
1275
1276            // ASCII representation
1277            for byte in chunk {
1278                if byte.is_ascii_graphic() || *byte == b' ' {
1279                    result.push(*byte as char);
1280                } else {
1281                    result.push('.');
1282                }
1283            }
1284
1285            result.push('|');
1286
1287            // Check for annotations on this line
1288            for pos in offset..offset + chunk.len() {
1289                if let Some(annotation) = annotation_map.remove(&pos) {
1290                    result.push_str(&format!(" <- {}", annotation));
1291                    break;
1292                }
1293            }
1294
1295            result.push('\n');
1296        }
1297
1298        result
1299    }
1300
1301    /// Debug formatter for BACnet APDU structure
1302    pub fn format_apdu_structure(data: &[u8]) -> String {
1303        if data.is_empty() {
1304            return "Empty APDU".to_string();
1305        }
1306
1307        let mut result = String::new();
1308        result.push_str("APDU Structure:\n");
1309
1310        let pdu_type = (data[0] >> 4) & 0x0F;
1311        let pdu_flags = data[0] & 0x0F;
1312
1313        result.push_str(&format!(
1314            "  PDU Type: {} ({})",
1315            pdu_type,
1316            match pdu_type {
1317                0 => "Confirmed-Request",
1318                1 => "Unconfirmed-Request",
1319                2 => "Simple-ACK",
1320                3 => "Complex-ACK",
1321                4 => "Segment-ACK",
1322                5 => "Error",
1323                6 => "Reject",
1324                7 => "Abort",
1325                _ => "Reserved",
1326            }
1327        ));
1328
1329        result.push_str(&format!("  PDU Flags: 0x{:X}\n", pdu_flags));
1330
1331        match pdu_type {
1332            0 => {
1333                // Confirmed Request
1334                if data.len() >= 4 {
1335                    result.push_str(&format!("  Max Segments: {}\n", (pdu_flags >> 1) & 0x07));
1336                    result.push_str(&format!(
1337                        "  Max APDU: {}\n",
1338                        (pdu_flags & 0x01) | ((data[1] & 0xF0) >> 3)
1339                    ));
1340                    result.push_str(&format!("  Invoke ID: {}\n", data[1] & 0x0F));
1341                    if data.len() > 2 {
1342                        result.push_str(&format!(
1343                            "  Service Choice: {}\n",
1344                            format_service_choice(data[2])
1345                        ));
1346                    }
1347                }
1348            }
1349            1 => {
1350                // Unconfirmed Request
1351                if data.len() >= 2 {
1352                    result.push_str(&format!(
1353                        "  Service Choice: {}\n",
1354                        format_service_choice(data[1])
1355                    ));
1356                }
1357            }
1358            3 => {
1359                // Complex ACK
1360                if data.len() >= 3 {
1361                    result.push_str(&format!("  Invoke ID: {}\n", data[1]));
1362                    result.push_str(&format!(
1363                        "  Service Choice: {}\n",
1364                        format_service_choice(data[2])
1365                    ));
1366                }
1367            }
1368            5 => {
1369                // Error
1370                if data.len() >= 4 {
1371                    result.push_str(&format!("  Invoke ID: {}\n", data[1]));
1372                    result.push_str(&format!(
1373                        "  Service Choice: {}\n",
1374                        format_service_choice(data[2])
1375                    ));
1376                    result.push_str(&format!(
1377                        "  Error: {}\n",
1378                        format_bacnet_error(data[3], data[4])
1379                    ));
1380                }
1381            }
1382            _ => {
1383                result.push_str(&format!("  Raw data: {}\n", hex_dump(&data[1..], "    ")));
1384            }
1385        }
1386
1387        result
1388    }
1389
1390    /// Debug formatter for network layer (NPDU)
1391    pub fn format_npdu_structure(data: &[u8]) -> String {
1392        if data.is_empty() {
1393            return "Empty NPDU".to_string();
1394        }
1395
1396        let mut result = String::new();
1397        result.push_str("NPDU Structure:\n");
1398
1399        let version = data[0];
1400        result.push_str(&format!("  Version: {}\n", version));
1401
1402        if data.len() < 2 {
1403            return result;
1404        }
1405
1406        let control = data[1];
1407        result.push_str(&format!("  Control: 0x{:02X}\n", control));
1408
1409        let has_dest = (control & 0x20) != 0;
1410        let has_src = (control & 0x08) != 0;
1411        let expecting_reply = (control & 0x04) != 0;
1412        let priority = control & 0x03;
1413
1414        result.push_str(&format!("    Destination Present: {}\n", has_dest));
1415        result.push_str(&format!("    Source Present: {}\n", has_src));
1416        result.push_str(&format!("    Expecting Reply: {}\n", expecting_reply));
1417        result.push_str(&format!(
1418            "    Priority: {} ({})\n",
1419            priority,
1420            match priority {
1421                0 => "Normal",
1422                1 => "Urgent",
1423                2 => "Critical",
1424                3 => "Life Safety",
1425                _ => "Unknown",
1426            }
1427        ));
1428
1429        let mut pos = 2;
1430
1431        if has_dest && data.len() > pos + 2 {
1432            let dest_net = u16::from_be_bytes([data[pos], data[pos + 1]]);
1433            pos += 2;
1434            result.push_str(&format!("  Destination Network: {}\n", dest_net));
1435
1436            if data.len() > pos {
1437                let dest_len = data[pos] as usize;
1438                pos += 1;
1439                if data.len() >= pos + dest_len {
1440                    let dest_addr = &data[pos..pos + dest_len];
1441                    pos += dest_len;
1442                    result.push_str(&format!("  Destination Address: {:02X?}\n", dest_addr));
1443                }
1444            }
1445        }
1446
1447        if has_src && data.len() > pos + 2 {
1448            let src_net = u16::from_be_bytes([data[pos], data[pos + 1]]);
1449            pos += 2;
1450            result.push_str(&format!("  Source Network: {}\n", src_net));
1451
1452            if data.len() > pos {
1453                let src_len = data[pos] as usize;
1454                pos += 1;
1455                if data.len() >= pos + src_len {
1456                    let src_addr = &data[pos..pos + src_len];
1457                    pos += src_len;
1458                    result.push_str(&format!("  Source Address: {:02X?}\n", src_addr));
1459                }
1460            }
1461        }
1462
1463        if data.len() > pos {
1464            result.push_str(&format!("  Hop Count: {}\n", data[pos]));
1465            pos += 1;
1466        }
1467
1468        if data.len() > pos {
1469            result.push_str(&format!("  APDU Length: {} bytes\n", data.len() - pos));
1470        }
1471
1472        result
1473    }
1474
1475    /// Debug formatter for BVLL (BACnet Virtual Link Layer)
1476    pub fn format_bvll_structure(data: &[u8]) -> String {
1477        if data.len() < 4 {
1478            return "Invalid BVLL (too short)".to_string();
1479        }
1480
1481        let mut result = String::new();
1482        result.push_str("BVLL Structure:\n");
1483
1484        let bvll_type = data[0];
1485        let function = data[1];
1486        let length = u16::from_be_bytes([data[2], data[3]]);
1487
1488        result.push_str(&format!(
1489            "  Type: 0x{:02X} ({})\n",
1490            bvll_type,
1491            match bvll_type {
1492                0x81 => "BACnet/IP",
1493                _ => "Unknown",
1494            }
1495        ));
1496
1497        result.push_str(&format!(
1498            "  Function: 0x{:02X} ({})\n",
1499            function,
1500            match function {
1501                0x00 => "Result",
1502                0x01 => "Write-BDT",
1503                0x02 => "Read-BDT",
1504                0x03 => "Read-BDT-Ack",
1505                0x04 => "Forwarded-NPDU",
1506                0x05 => "Register-Foreign-Device",
1507                0x06 => "Read-FDT",
1508                0x07 => "Read-FDT-Ack",
1509                0x08 => "Delete-FDT-Entry",
1510                0x09 => "Distribute-Broadcast-To-Network",
1511                0x0A => "Original-Unicast-NPDU",
1512                0x0B => "Original-Broadcast-NPDU",
1513                0x0C => "Secure-BVLL",
1514                _ => "Unknown",
1515            }
1516        ));
1517
1518        result.push_str(&format!("  Length: {} bytes\n", length));
1519
1520        if data.len() != length as usize {
1521            result.push_str(&format!("  WARNING: Actual length {} bytes\n", data.len()));
1522        }
1523
1524        if data.len() > 4 {
1525            result.push_str(&format!("  Data Length: {} bytes\n", data.len() - 4));
1526        }
1527
1528        result
1529    }
1530}
1531
1532#[cfg(test)]
1533mod tests {
1534    use super::*;
1535
1536    #[test]
1537    fn test_format_bytes() {
1538        assert_eq!(format_bytes(0), "0 B");
1539        assert_eq!(format_bytes(512), "512 B");
1540        assert_eq!(format_bytes(1024), "1.00 KB");
1541        assert_eq!(format_bytes(1536), "1.50 KB");
1542        assert_eq!(format_bytes(1048576), "1.00 MB");
1543        assert_eq!(format_bytes(1073741824), "1.00 GB");
1544    }
1545
1546    #[test]
1547    fn test_circular_buffer() {
1548        let mut buffer = CircularBuffer::new(3);
1549
1550        assert!(buffer.is_empty());
1551        assert_eq!(buffer.len(), 0);
1552
1553        buffer.push(1);
1554        buffer.push(2);
1555        buffer.push(3);
1556
1557        assert_eq!(buffer.items(), vec![1, 2, 3]);
1558        assert_eq!(buffer.len(), 3);
1559
1560        // Test wraparound
1561        buffer.push(4);
1562        assert_eq!(buffer.items(), vec![2, 3, 4]);
1563
1564        buffer.push(5);
1565        assert_eq!(buffer.items(), vec![3, 4, 5]);
1566    }
1567
1568    #[test]
1569    fn test_retry_config() {
1570        let config = RetryConfig::default();
1571
1572        assert_eq!(config.delay_for_attempt(0).as_millis(), 100);
1573        assert_eq!(config.delay_for_attempt(1).as_millis(), 200);
1574        assert_eq!(config.delay_for_attempt(2).as_millis(), 400);
1575        assert_eq!(config.delay_for_attempt(3).as_millis(), 800);
1576
1577        // Test max delay
1578        assert_eq!(config.delay_for_attempt(10).as_millis(), 5000);
1579    }
1580
1581    #[cfg(feature = "std")]
1582    #[test]
1583    fn test_parse_bacnet_address() {
1584        assert!(parse_bacnet_address("192.168.1.100:47808").is_ok());
1585        assert!(parse_bacnet_address("192.168.1.100").is_ok());
1586        assert!(parse_bacnet_address("invalid").is_err());
1587    }
1588
1589    #[cfg(feature = "std")]
1590    #[test]
1591    fn test_communication_stats() {
1592        let mut stats = statistics::CommunicationStats::new();
1593
1594        stats.record_sent(100);
1595        stats.record_received(150);
1596
1597        assert_eq!(stats.messages_sent, 1);
1598        assert_eq!(stats.messages_received, 1);
1599        assert_eq!(stats.bytes_sent, 100);
1600        assert_eq!(stats.bytes_received, 150);
1601        assert_eq!(stats.success_rate(), 100.0);
1602
1603        stats.record_error();
1604        stats.record_timeout();
1605
1606        assert!(stats.success_rate() < 100.0);
1607    }
1608
1609    #[cfg(feature = "std")]
1610    #[test]
1611    fn test_performance_monitor() {
1612        use std::thread;
1613        use std::time::Duration;
1614
1615        let monitor = performance::PerformanceMonitor::new();
1616
1617        {
1618            let _timer = performance::ScopedTimer::new(&monitor, "test_operation");
1619            thread::sleep(Duration::from_millis(10));
1620        }
1621
1622        let metrics = monitor.get_metrics("test_operation").unwrap();
1623        assert_eq!(metrics.count, 1);
1624        assert!(metrics.last_duration_ms >= 10.0);
1625        assert_eq!(metrics.min_duration_ms, metrics.max_duration_ms);
1626    }
1627
1628    #[test]
1629    fn test_debug_formatting() {
1630        // Test property value formatting
1631        let boolean_data = &[0x11, 0x01]; // Boolean true
1632        let formatted = debug::format_property_value(boolean_data);
1633        assert!(formatted.contains("Boolean(true)"));
1634
1635        let real_data = &[0x44, 0x42, 0x28, 0x00, 0x00]; // Real 42.0
1636        let formatted = debug::format_property_value(real_data);
1637        assert!(formatted.contains("Real(42)"));
1638
1639        // Test service choice formatting
1640        let formatted = debug::format_service_choice(12);
1641        assert!(formatted.contains("readProperty"));
1642
1643        // Test error formatting
1644        let formatted = debug::format_bacnet_error(1, 2);
1645        assert!(formatted.contains("object"));
1646    }
1647
1648    #[test]
1649    fn test_annotated_hex_dump() {
1650        let data = &[0x01, 0x02, 0x03, 0x04];
1651        let annotations = vec![(0, "Start".to_string()), (2, "Middle".to_string())];
1652        let result = debug::annotated_hex_dump(data, &annotations);
1653
1654        assert!(result.contains("0000:"));
1655        assert!(result.contains("01 02 03 04"));
1656        assert!(result.contains("Start") || result.contains("Middle"));
1657    }
1658}