1pub mod enum_macros;
193
194#[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
211pub 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
231pub 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
249pub 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
287pub 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
316pub struct Buffer<'a> {
318 data: &'a [u8],
319 position: usize,
320}
321
322impl<'a> Buffer<'a> {
323 pub fn new(data: &'a [u8]) -> Self {
325 Self { data, position: 0 }
326 }
327
328 pub fn remaining(&self) -> usize {
330 self.data.len().saturating_sub(self.position)
331 }
332
333 pub fn has_remaining(&self, n: usize) -> bool {
335 self.remaining() >= n
336 }
337
338 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 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 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 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 pub fn position(&self) -> usize {
390 self.position
391 }
392
393 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
404pub 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 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 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 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
445pub mod priority {
447 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 pub fn is_valid(priority: u8) -> bool {
467 (1..=16).contains(&priority)
468 }
469}
470
471#[cfg(feature = "std")]
473pub mod performance {
474 use super::*;
475
476 #[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 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 pub fn new() -> Self {
506 Self::default()
507 }
508
509 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 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 pub fn get_metrics(&self, operation: &str) -> Option<OperationMetrics> {
546 let metrics = self.metrics.lock().unwrap();
547 metrics.get(operation).cloned()
548 }
549
550 pub fn get_all_metrics(&self) -> Vec<OperationMetrics> {
552 let metrics = self.metrics.lock().unwrap();
553 metrics.values().cloned().collect()
554 }
555
556 pub fn clear(&self) {
558 self.metrics.lock().unwrap().clear();
559 self.active_timers.lock().unwrap().clear();
560 }
561 }
562
563 pub struct ScopedTimer<'a> {
565 monitor: &'a PerformanceMonitor,
566 operation: String,
567 }
568
569 impl<'a> ScopedTimer<'a> {
570 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
587pub mod statistics {
589 use super::*;
590
591 #[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 pub fn new() -> Self {
610 Self::default()
611 }
612
613 pub fn record_sent(&mut self, bytes: usize) {
615 self.messages_sent += 1;
616 self.bytes_sent += bytes as u64;
617 }
618
619 pub fn record_received(&mut self, bytes: usize) {
621 self.messages_received += 1;
622 self.bytes_received += bytes as u64;
623 }
624
625 pub fn record_error(&mut self) {
627 self.errors += 1;
628 }
629
630 pub fn record_timeout(&mut self) {
632 self.timeouts += 1;
633 }
634
635 pub fn record_retry(&mut self) {
637 self.retries += 1;
638 }
639
640 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 pub fn reset(&mut self) {
652 *self = Self::default();
653 }
654 }
655
656 #[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 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 pub fn record_response_time(&mut self, ms: f64) {
683 self.response_times_ms.push(ms);
684 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 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 pub fn mark_offline(&mut self) {
703 self.online = false;
704 }
705 }
706
707 #[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 pub fn new() -> Self {
728 Self::default()
729 }
730
731 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 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 pub fn get_global_stats(&self) -> CommunicationStats {
753 self.global_stats.lock().unwrap().clone()
754 }
755
756 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 pub fn get_all_device_stats(&self) -> Vec<DeviceStats> {
767 let devices = self.devices.lock().unwrap();
768 devices.values().cloned().collect()
769 }
770
771 pub fn clear(&self) {
773 self.devices.lock().unwrap().clear();
774 self.global_stats.lock().unwrap().reset();
775 }
776 }
777}
778
779pub fn is_valid_network_number(_network: u16) -> bool {
783 true
785}
786
787pub fn is_local_network(network: u16) -> bool {
789 network == 0
790}
791
792pub fn is_broadcast_network(network: u16) -> bool {
794 network == 65535
795}
796
797#[cfg(feature = "std")]
799pub fn parse_bacnet_address(address: &str) -> Result<std::net::SocketAddr, String> {
800 use std::net::ToSocketAddrs;
801
802 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
816pub 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
839pub 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#[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 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#[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 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 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 pub fn items(&self) -> Vec<T> {
914 let mut result = Vec::with_capacity(self.size);
915
916 if self.size < self.capacity {
917 for i in 0..self.size {
919 if let Some(item) = &self.buffer[i] {
920 result.push(item.clone());
921 }
922 }
923 } else {
924 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 pub fn len(&self) -> usize {
938 self.size
939 }
940
941 pub fn is_empty(&self) -> bool {
943 self.size == 0
944 }
945
946 pub fn clear(&mut self) {
948 self.buffer = vec![None; self.capacity];
949 self.head = 0;
950 self.size = 0;
951 }
952}
953
954pub mod debug {
956 use crate::object::ObjectIdentifier;
957
958 use super::*;
959
960 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 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 result.push_str(&format_unsigned_integer(data));
981 }
982 0x31 => {
983 result.push_str(&format_signed_integer(data));
985 }
986 0x44 => {
987 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 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 result.push_str(&format_character_string(data));
1010 }
1011 0x81..=0x8F => {
1012 result.push_str(&format_octet_string(data));
1014 }
1015 0x91 => {
1016 result.push_str(&format_enumerated(data));
1018 }
1019 0xA1 => {
1020 result.push_str(&format_date(data));
1022 }
1023 0xB1 => {
1024 result.push_str(&format_time(data));
1026 }
1027 0xC4 => {
1028 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 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 String::from_utf8_lossy(string_data).to_string()
1104 }
1105 4 => {
1106 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 pub fn format_service_choice(service_choice: u8) -> String {
1192 let service_name = match service_choice {
1193 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let boolean_data = &[0x11, 0x01]; let formatted = debug::format_property_value(boolean_data);
1633 assert!(formatted.contains("Boolean(true)"));
1634
1635 let real_data = &[0x44, 0x42, 0x28, 0x00, 0x00]; let formatted = debug::format_property_value(real_data);
1637 assert!(formatted.contains("Real(42)"));
1638
1639 let formatted = debug::format_service_choice(12);
1641 assert!(formatted.contains("readProperty"));
1642
1643 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}