1use std::collections::HashMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Arc;
9use tokio::sync::{broadcast, mpsc, RwLock};
10
11use crate::CHANNEL_SECRET_LEN;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum EventType {
16 Connected,
18 Disconnected,
19
20 Ok,
22 Error,
23
24 Contacts,
26 NewContact,
27 NextContact,
28
29 SelfInfo,
31 DeviceInfo,
32 Battery,
33 CurrentTime,
34 PrivateKey,
35 CustomVars,
36 ChannelInfo,
37 StatsCore,
38 StatsRadio,
39 StatsPackets,
40 AutoAddConfig,
41
42 ContactMsgRecv,
44 ChannelMsgRecv,
45 MsgSent,
46 NoMoreMessages,
47 ContactUri,
48
49 Advertisement,
51 PathUpdate,
52 Ack,
53 MessagesWaiting,
54 RawData,
55 LoginSuccess,
56 LoginFailed,
57
58 StatusResponse,
60 TelemetryResponse,
61 MmaResponse,
62 AclResponse,
63 NeighboursResponse,
64 BinaryResponse,
65 PathDiscoveryResponse,
66
67 TraceData,
69 LogData,
70
71 SignStart,
73 Signature,
74 Disabled,
75
76 ControlData,
78 DiscoverResponse,
79 AdvertResponse,
80
81 Unknown,
83}
84
85#[derive(Debug, Clone)]
87pub enum EventPayload {
88 None,
90 String(String),
92 Bytes(Vec<u8>),
94 Contacts(Vec<Contact>),
96 Contact(Contact),
98 SelfInfo(SelfInfo),
100 DeviceInfo(DeviceInfoData),
102 Battery(BatteryInfo),
104 Time(u32),
106 ContactMessage(ContactMessage),
108 ChannelMessage(ChannelMessage),
110 MsgSent(MsgSentInfo),
112 Status(StatusData),
114 ChannelInfo(ChannelInfoData),
116 CustomVars(HashMap<String, String>),
118 PrivateKey([u8; 64]),
120 Signature(Vec<u8>),
122 SignStart { max_length: u32 },
124 Advertisement(AdvertisementData),
126 PathUpdate(PathUpdateData),
128 Ack { tag: [u8; 4] },
130 TraceData(TraceInfo),
132 Telemetry(Vec<u8>),
134 Mma(Vec<MmaEntry>),
136 Acl(Vec<AclEntry>),
138 Neighbours(NeighboursData),
140 BinaryResponse { tag: [u8; 4], data: Vec<u8> },
142 DiscoverResponse(Vec<DiscoverEntry>),
144 AdvertResponse(AdvertResponseData),
146 Stats(StatsData),
148 AutoAddConfig { flags: u8 },
150 LogData(LogData),
152}
153
154#[derive(Debug, Clone)]
156pub struct Contact {
157 pub public_key: [u8; 32],
159 pub contact_type: u8,
161 pub flags: u8,
163 pub path_len: i8,
165 pub out_path: Vec<u8>,
167 pub adv_name: String,
169 pub last_advert: u32,
171 pub adv_lat: i32,
173 pub adv_lon: i32,
175 pub last_modification_timestamp: u32,
177}
178
179impl Contact {
180 pub fn prefix(&self) -> [u8; 6] {
182 let mut prefix = [0u8; 6];
183 prefix.copy_from_slice(&self.public_key[..6]);
184 prefix
185 }
186
187 pub fn public_key_hex(&self) -> String {
189 crate::parsing::hex_encode(&self.public_key)
190 }
191
192 pub fn prefix_hex(&self) -> String {
194 crate::parsing::hex_encode(&self.prefix())
195 }
196
197 pub fn latitude(&self) -> f64 {
199 self.adv_lat as f64 / 1_000_000.0
200 }
201
202 pub fn longitude(&self) -> f64 {
204 self.adv_lon as f64 / 1_000_000.0
205 }
206}
207
208#[derive(Debug, Clone, Default)]
210pub struct SelfInfo {
211 pub adv_type: u8,
213 pub tx_power: u8,
215 pub max_tx_power: u8,
217 pub public_key: [u8; 32],
219 pub adv_lat: i32,
221 pub adv_lon: i32,
223 pub multi_acks: u8,
225 pub adv_loc_policy: u8,
227 pub telemetry_mode_base: u8,
229 pub telemetry_mode_loc: u8,
231 pub telemetry_mode_env: u8,
233 pub manual_add_contacts: bool,
235 pub radio_freq: u32,
237 pub radio_bw: u32,
239 pub sf: u8,
241 pub cr: u8,
243 pub name: String,
245}
246
247#[derive(Debug, Clone, Default)]
249pub struct DeviceInfoData {
250 pub fw_version_code: u8,
252 pub max_contacts: Option<u8>,
254 pub max_channels: Option<u8>,
256 pub ble_pin: Option<u32>,
258 pub fw_build: Option<String>,
260 pub model: Option<String>,
262 pub version: Option<String>,
264 pub repeat: Option<bool>,
266}
267
268#[derive(Debug, Clone)]
270pub struct BatteryInfo {
271 pub battery_mv: u16,
273 pub used_kb: Option<u32>,
275 pub total_kb: Option<u32>,
277}
278
279impl BatteryInfo {
280 const MIN_MV: u16 = 3000;
282 const MAX_MV: u16 = 3930;
284
285 pub fn voltage(&self) -> f32 {
287 self.battery_mv as f32 / 1000.0
288 }
289
290 pub fn percentage(&self) -> u8 {
293 if self.battery_mv <= Self::MIN_MV {
294 0
295 } else if self.battery_mv >= Self::MAX_MV {
296 100
297 } else {
298 ((self.battery_mv - Self::MIN_MV) as u32 * 100 / (Self::MAX_MV - Self::MIN_MV) as u32)
299 as u8
300 }
301 }
302}
303
304#[derive(Debug, Clone)]
306pub struct ContactMessage {
307 pub sender_prefix: [u8; 6],
309 pub path_len: u8,
311 pub txt_type: u8,
313 pub sender_timestamp: u32,
315 pub text: String,
317 pub snr: Option<f32>,
319 pub signature: Option<[u8; 4]>,
321}
322
323impl ContactMessage {
324 pub fn message_id(&self) -> u64 {
326 let mut bytes = [0u8; 8];
327 bytes[0..4].copy_from_slice(&self.sender_prefix[0..4]);
329 bytes[4..8].copy_from_slice(&self.sender_timestamp.to_be_bytes());
331 u64::from_be_bytes(bytes)
332 }
333
334 pub fn sender_prefix_hex(&self) -> String {
336 crate::parsing::hex_encode(&self.sender_prefix)
337 }
338}
339
340#[derive(Debug, Clone)]
342pub struct ChannelMessage {
343 pub channel_idx: u8,
345 pub path_len: u8,
347 pub txt_type: u8,
349 pub sender_timestamp: u32,
351 pub text: String,
353 pub snr: Option<f32>,
355}
356
357impl ChannelMessage {
358 pub fn message_id(&self) -> u64 {
360 let mut bytes = [0u8; 8];
361 bytes[0] = self.channel_idx;
363 bytes[4..8].copy_from_slice(&self.sender_timestamp.to_be_bytes());
365 u64::from_be_bytes(bytes)
366 }
367}
368
369#[derive(Debug, Clone)]
371pub struct MsgSentInfo {
372 pub message_type: u8,
374 pub expected_ack: [u8; 4],
376 pub suggested_timeout: u32,
378}
379
380#[derive(Debug, Clone)]
382pub struct StatusData {
383 pub battery_mv: u16,
385 pub tx_queue_len: u16,
387 pub noise_floor: i16,
389 pub last_rssi: i16,
391 pub nb_recv: u32,
393 pub nb_sent: u32,
395 pub airtime: u32,
397 pub uptime: u32,
399 pub flood_sent: u32,
401 pub direct_sent: u32,
403 pub snr: f32,
405 pub dup_count: u32,
407 pub rx_airtime: u32,
409 pub sender_prefix: [u8; 6],
411}
412
413#[derive(Debug, Clone)]
415pub struct ChannelInfoData {
416 pub channel_idx: u8,
418 pub name: String,
420 pub secret: [u8; CHANNEL_SECRET_LEN],
422}
423
424#[derive(Debug, Clone)]
426pub struct AdvertisementData {
427 pub prefix: [u8; 6],
429 pub name: String,
431 pub lat: i32,
433 pub lon: i32,
435}
436
437#[derive(Debug, Clone)]
439pub struct PathUpdateData {
440 pub prefix: [u8; 6],
442 pub path_len: i8,
444 pub path: Vec<u8>,
446}
447
448#[derive(Debug, Clone)]
450pub struct TraceInfo {
451 pub hops: Vec<TraceHop>,
453}
454
455#[derive(Debug, Clone)]
457pub struct TraceHop {
458 pub prefix: [u8; 6],
460 pub snr: f32,
462}
463
464#[derive(Debug, Clone)]
466pub struct MmaEntry {
467 pub channel: u8,
469 pub entry_type: u8,
471 pub min: f32,
473 pub max: f32,
475 pub avg: f32,
477}
478
479#[derive(Debug, Clone)]
481pub struct AclEntry {
482 pub prefix: [u8; 6],
484 pub permissions: u8,
486}
487
488#[derive(Debug, Clone)]
490pub struct NeighboursData {
491 pub total: u16,
493 pub neighbours: Vec<Neighbour>,
495}
496
497#[derive(Debug, Clone)]
499pub struct Neighbour {
500 pub pubkey: Vec<u8>,
502 pub secs_ago: i32,
504 pub snr: f32,
506}
507
508#[derive(Debug, Clone)]
510pub struct DiscoverEntry {
511 pub pubkey: Vec<u8>,
513 pub name: String,
515}
516
517#[derive(Debug, Clone)]
519pub struct AdvertResponseData {
520 pub tag: [u8; 4],
522 pub pubkey: [u8; 32],
524 pub adv_type: u8,
526 pub node_name: String,
528 pub timestamp: u32,
530 pub flags: u8,
532 pub lat: Option<i32>,
534 pub lon: Option<i32>,
536 pub node_desc: Option<String>,
538}
539
540#[derive(Debug, Clone)]
542pub struct StatsData {
543 pub category: StatsCategory,
545 pub raw: Vec<u8>,
547}
548
549#[derive(Debug, Clone, Copy, PartialEq, Eq)]
551pub enum StatsCategory {
552 Core,
553 Radio,
554 Packets,
555}
556
557#[derive(Debug, Clone)]
559pub struct LogData {
560 pub snr: f32,
562 pub rssi: i16,
564 pub payload: Vec<u8>,
566}
567
568#[derive(Debug, Clone)]
570pub struct MeshCoreEvent {
571 pub event_type: EventType,
573 pub payload: EventPayload,
575 pub attributes: HashMap<String, String>,
577}
578
579impl MeshCoreEvent {
580 pub fn new(event_type: EventType, payload: EventPayload) -> Self {
582 Self {
583 event_type,
584 payload,
585 attributes: HashMap::new(),
586 }
587 }
588
589 pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
591 self.attributes.insert(key.into(), value.into());
592 self
593 }
594
595 pub fn ok() -> Self {
597 Self::new(EventType::Ok, EventPayload::None)
598 }
599
600 pub fn error(msg: impl Into<String>) -> Self {
602 Self::new(EventType::Error, EventPayload::String(msg.into()))
603 }
604
605 pub fn matches_filters(&self, filters: &HashMap<String, String>) -> bool {
607 filters
608 .iter()
609 .all(|(k, v)| self.attributes.get(k) == Some(v))
610 }
611}
612
613#[derive(Debug)]
615pub struct Subscription {
616 id: u64,
617 #[allow(dead_code)]
618 event_type: EventType,
619 unsubscribe_tx: mpsc::Sender<u64>,
620}
621
622impl Subscription {
623 pub async fn unsubscribe(self) {
625 let _ = self.unsubscribe_tx.send(self.id).await;
626 }
627}
628
629pub type EventCallback = Box<dyn Fn(MeshCoreEvent) + Send + Sync>;
631
632struct SubscriptionEntry {
633 id: u64,
634 event_type: EventType,
635 filters: HashMap<String, String>,
636 callback: EventCallback,
637}
638
639pub struct EventDispatcher {
641 subscriptions: Arc<RwLock<Vec<SubscriptionEntry>>>,
642 next_id: AtomicU64,
643 broadcast_tx: broadcast::Sender<MeshCoreEvent>,
644 unsubscribe_tx: mpsc::Sender<u64>,
645 unsubscribe_rx: Arc<RwLock<mpsc::Receiver<u64>>>,
646}
647
648impl EventDispatcher {
649 pub fn new() -> Self {
651 let (broadcast_tx, _) = broadcast::channel(256);
652 let (unsubscribe_tx, unsubscribe_rx) = mpsc::channel(64);
653
654 Self {
655 subscriptions: Arc::new(RwLock::new(Vec::new())),
656 next_id: AtomicU64::new(1),
657 broadcast_tx,
658 unsubscribe_tx,
659 unsubscribe_rx: Arc::new(RwLock::new(unsubscribe_rx)),
660 }
661 }
662
663 pub async fn subscribe<F>(
665 &self,
666 event_type: EventType,
667 filters: HashMap<String, String>,
668 callback: F,
669 ) -> Subscription
670 where
671 F: Fn(MeshCoreEvent) + Send + Sync + 'static,
672 {
673 let id = self.next_id.fetch_add(1, Ordering::SeqCst);
674
675 let entry = SubscriptionEntry {
676 id,
677 event_type,
678 filters,
679 callback: Box::new(callback),
680 };
681
682 self.subscriptions.write().await.push(entry);
683
684 Subscription {
685 id,
686 event_type,
687 unsubscribe_tx: self.unsubscribe_tx.clone(),
688 }
689 }
690
691 pub async fn emit(&self, event: MeshCoreEvent) {
693 {
695 let mut rx = self.unsubscribe_rx.write().await;
696 while let Ok(id) = rx.try_recv() {
697 self.subscriptions.write().await.retain(|s| s.id != id);
698 }
699 }
700
701 let subs = self.subscriptions.read().await;
703 for sub in subs.iter() {
704 if sub.event_type == event.event_type && event.matches_filters(&sub.filters) {
705 (sub.callback)(event.clone());
706 }
707 }
708
709 let _ = self.broadcast_tx.send(event);
711 }
712
713 pub async fn wait_for_event(
715 &self,
716 event_type: Option<EventType>,
717 filters: HashMap<String, String>,
718 timeout: std::time::Duration,
719 ) -> Option<MeshCoreEvent> {
720 let mut rx = self.broadcast_tx.subscribe();
721
722 tokio::select! {
723 _ = tokio::time::sleep(timeout) => None,
724 result = async {
725 loop {
726 match rx.recv().await {
727 Ok(event) => {
728 match event_type {
729 None => {
730 if event.matches_filters(&filters) {
731 return Some(event);
732 }
733 }
734 Some(event_type_filter) => {
735 if event.event_type == event_type_filter && event.matches_filters(&filters) {
736 return Some(event);
737 }
738 }
739 }
740 }
741 Err(_) => return None,
742 }
743 }
744 } => result,
745 }
746 }
747
748 pub fn receiver(&self) -> broadcast::Receiver<MeshCoreEvent> {
750 self.broadcast_tx.subscribe()
751 }
752}
753
754impl Default for EventDispatcher {
755 fn default() -> Self {
756 Self::new()
757 }
758}
759
760#[cfg(test)]
761mod tests {
762 use super::*;
763 use std::sync::atomic::{AtomicUsize, Ordering};
764 use std::time::Duration;
765
766 #[test]
767 fn test_event_new() {
768 let event = MeshCoreEvent::new(EventType::Ok, EventPayload::None);
769 assert_eq!(event.event_type, EventType::Ok);
770 assert!(matches!(event.payload, EventPayload::None));
771 assert!(event.attributes.is_empty());
772 }
773
774 #[test]
775 fn test_event_with_attribute() {
776 let event = MeshCoreEvent::new(EventType::Ok, EventPayload::None)
777 .with_attribute("key1", "value1")
778 .with_attribute("key2", "value2");
779
780 assert_eq!(event.attributes.get("key1"), Some(&"value1".to_string()));
781 assert_eq!(event.attributes.get("key2"), Some(&"value2".to_string()));
782 }
783
784 #[test]
785 fn test_event_ok() {
786 let event = MeshCoreEvent::ok();
787 assert_eq!(event.event_type, EventType::Ok);
788 assert!(matches!(event.payload, EventPayload::None));
789 }
790
791 #[test]
792 fn test_event_error() {
793 let event = MeshCoreEvent::error("test error");
794 assert_eq!(event.event_type, EventType::Error);
795 match event.payload {
796 EventPayload::String(s) => assert_eq!(s, "test error"),
797 _ => panic!("Expected String payload"),
798 }
799 }
800
801 #[test]
802 fn test_event_matches_filters_empty() {
803 let event = MeshCoreEvent::new(EventType::Ok, EventPayload::None);
804 let filters = HashMap::new();
805 assert!(event.matches_filters(&filters));
806 }
807
808 #[test]
809 fn test_event_matches_filters_match() {
810 let event =
811 MeshCoreEvent::new(EventType::Ok, EventPayload::None).with_attribute("tag", "abc123");
812
813 let mut filters = HashMap::new();
814 filters.insert("tag".to_string(), "abc123".to_string());
815 assert!(event.matches_filters(&filters));
816 }
817
818 #[test]
819 fn test_event_matches_filters_no_match() {
820 let event =
821 MeshCoreEvent::new(EventType::Ok, EventPayload::None).with_attribute("tag", "abc123");
822
823 let mut filters = HashMap::new();
824 filters.insert("tag".to_string(), "xyz789".to_string());
825 assert!(!event.matches_filters(&filters));
826 }
827
828 #[test]
829 fn test_event_matches_filters_missing_attr() {
830 let event = MeshCoreEvent::new(EventType::Ok, EventPayload::None);
831
832 let mut filters = HashMap::new();
833 filters.insert("tag".to_string(), "abc123".to_string());
834 assert!(!event.matches_filters(&filters));
835 }
836
837 #[test]
838 fn test_contact_prefix() {
839 let contact = Contact {
840 public_key: [
841 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
842 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
843 0x1D, 0x1E, 0x1F, 0x20,
844 ],
845 contact_type: 1,
846 flags: 0,
847 path_len: -1,
848 out_path: Vec::new(),
849 adv_name: "Test".to_string(),
850 last_advert: 0,
851 adv_lat: 0,
852 adv_lon: 0,
853 last_modification_timestamp: 0,
854 };
855
856 assert_eq!(contact.prefix(), [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
857 }
858
859 #[test]
860 fn test_contact_public_key_hex() {
861 let mut public_key = [0u8; 32];
862 public_key[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
863
864 let contact = Contact {
865 public_key,
866 contact_type: 1,
867 flags: 0,
868 path_len: -1,
869 out_path: Vec::new(),
870 adv_name: "Test".to_string(),
871 last_advert: 0,
872 adv_lat: 0,
873 adv_lon: 0,
874 last_modification_timestamp: 0,
875 };
876
877 assert!(contact.public_key_hex().starts_with("deadbeef"));
878 }
879
880 #[test]
881 fn test_contact_prefix_hex() {
882 let mut public_key = [0u8; 32];
883 public_key[0..6].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02]);
884
885 let contact = Contact {
886 public_key,
887 contact_type: 1,
888 flags: 0,
889 path_len: -1,
890 out_path: Vec::new(),
891 adv_name: "Test".to_string(),
892 last_advert: 0,
893 adv_lat: 0,
894 adv_lon: 0,
895 last_modification_timestamp: 0,
896 };
897
898 assert_eq!(contact.prefix_hex(), "deadbeef0102");
899 }
900
901 #[test]
902 fn test_contact_latitude() {
903 let contact = Contact {
904 public_key: [0u8; 32],
905 contact_type: 1,
906 flags: 0,
907 path_len: -1,
908 out_path: Vec::new(),
909 adv_name: "Test".to_string(),
910 last_advert: 0,
911 adv_lat: 37774900, adv_lon: 0,
913 last_modification_timestamp: 0,
914 };
915
916 assert!((contact.latitude() - 37.7749).abs() < 0.0001);
917 }
918
919 #[test]
920 fn test_contact_longitude() {
921 let contact = Contact {
922 public_key: [0u8; 32],
923 contact_type: 1,
924 flags: 0,
925 path_len: -1,
926 out_path: Vec::new(),
927 adv_name: "Test".to_string(),
928 last_advert: 0,
929 adv_lat: 0,
930 adv_lon: -122419400, last_modification_timestamp: 0,
932 };
933
934 assert!((contact.longitude() - (-122.4194)).abs() < 0.0001);
935 }
936
937 #[tokio::test]
938 async fn test_event_dispatcher_new() {
939 let dispatcher = EventDispatcher::new();
940 let _receiver = dispatcher.receiver();
942 }
943
944 #[tokio::test]
945 async fn test_event_dispatcher_default() {
946 let dispatcher = EventDispatcher::default();
947 let _receiver = dispatcher.receiver();
948 }
949
950 #[tokio::test]
951 async fn test_event_dispatcher_emit() {
952 let dispatcher = EventDispatcher::new();
953 let mut receiver = dispatcher.receiver();
954
955 dispatcher.emit(MeshCoreEvent::ok()).await;
956
957 let received = tokio::time::timeout(Duration::from_millis(100), receiver.recv())
958 .await
959 .unwrap()
960 .unwrap();
961
962 assert_eq!(received.event_type, EventType::Ok);
963 }
964
965 #[tokio::test]
966 async fn test_event_dispatcher_subscribe() {
967 let dispatcher = Arc::new(EventDispatcher::new());
968 let call_count = Arc::new(AtomicUsize::new(0));
969 let call_count_clone = call_count.clone();
970
971 let _subscription = dispatcher
972 .subscribe(EventType::Ok, HashMap::new(), move |_event| {
973 call_count_clone.fetch_add(1, Ordering::SeqCst);
974 })
975 .await;
976
977 dispatcher.emit(MeshCoreEvent::ok()).await;
978
979 tokio::time::sleep(Duration::from_millis(10)).await;
981
982 assert_eq!(call_count.load(Ordering::SeqCst), 1);
983 }
984
985 #[tokio::test]
986 async fn test_event_dispatcher_subscribe_with_filter() {
987 let dispatcher = Arc::new(EventDispatcher::new());
988 let call_count = Arc::new(AtomicUsize::new(0));
989 let call_count_clone = call_count.clone();
990
991 let mut filters = HashMap::new();
992 filters.insert("tag".to_string(), "match".to_string());
993
994 let _subscription = dispatcher
995 .subscribe(EventType::Ack, filters, move |_event| {
996 call_count_clone.fetch_add(1, Ordering::SeqCst);
997 })
998 .await;
999
1000 dispatcher
1002 .emit(
1003 MeshCoreEvent::new(EventType::Ack, EventPayload::None)
1004 .with_attribute("tag", "nomatch"),
1005 )
1006 .await;
1007
1008 dispatcher
1010 .emit(
1011 MeshCoreEvent::new(EventType::Ack, EventPayload::None)
1012 .with_attribute("tag", "match"),
1013 )
1014 .await;
1015
1016 tokio::time::sleep(Duration::from_millis(10)).await;
1017
1018 assert_eq!(call_count.load(Ordering::SeqCst), 1);
1019 }
1020
1021 #[tokio::test]
1022 async fn test_event_dispatcher_unsubscribe() {
1023 let dispatcher = Arc::new(EventDispatcher::new());
1024 let call_count = Arc::new(AtomicUsize::new(0));
1025 let call_count_clone = call_count.clone();
1026
1027 let subscription = dispatcher
1028 .subscribe(EventType::Ok, HashMap::new(), move |_event| {
1029 call_count_clone.fetch_add(1, Ordering::SeqCst);
1030 })
1031 .await;
1032
1033 dispatcher.emit(MeshCoreEvent::ok()).await;
1035 tokio::time::sleep(Duration::from_millis(10)).await;
1036 assert_eq!(call_count.load(Ordering::SeqCst), 1);
1037
1038 subscription.unsubscribe().await;
1040
1041 dispatcher.emit(MeshCoreEvent::ok()).await;
1043 tokio::time::sleep(Duration::from_millis(10)).await;
1044
1045 dispatcher.emit(MeshCoreEvent::ok()).await;
1047 tokio::time::sleep(Duration::from_millis(10)).await;
1048
1049 assert!(call_count.load(Ordering::SeqCst) <= 2);
1051 }
1052
1053 #[tokio::test]
1054 async fn test_event_dispatcher_wait_for_event() {
1055 let dispatcher = Arc::new(EventDispatcher::new());
1056 let dispatcher_clone = dispatcher.clone();
1057
1058 tokio::spawn(async move {
1060 tokio::time::sleep(Duration::from_millis(10)).await;
1061 dispatcher_clone.emit(MeshCoreEvent::ok()).await;
1062 });
1063
1064 let result = dispatcher
1065 .wait_for_event(
1066 Some(EventType::Ok),
1067 HashMap::new(),
1068 Duration::from_millis(100),
1069 )
1070 .await;
1071
1072 assert!(result.is_some());
1073 assert_eq!(result.unwrap().event_type, EventType::Ok);
1074 }
1075
1076 #[tokio::test]
1077 async fn test_event_dispatcher_wait_for_event_timeout() {
1078 let dispatcher = EventDispatcher::new();
1079
1080 let result = dispatcher
1081 .wait_for_event(
1082 Some(EventType::Ok),
1083 HashMap::new(),
1084 Duration::from_millis(10),
1085 )
1086 .await;
1087
1088 assert!(result.is_none());
1089 }
1090
1091 #[tokio::test]
1092 async fn test_event_dispatcher_wait_for_event_with_filter() {
1093 let dispatcher = Arc::new(EventDispatcher::new());
1094 let dispatcher_clone = dispatcher.clone();
1095
1096 tokio::spawn(async move {
1098 tokio::time::sleep(Duration::from_millis(5)).await;
1099 dispatcher_clone
1101 .emit(
1102 MeshCoreEvent::new(EventType::Ack, EventPayload::None)
1103 .with_attribute("tag", "wrong"),
1104 )
1105 .await;
1106 tokio::time::sleep(Duration::from_millis(5)).await;
1107 dispatcher_clone
1109 .emit(
1110 MeshCoreEvent::new(EventType::Ack, EventPayload::None)
1111 .with_attribute("tag", "correct"),
1112 )
1113 .await;
1114 });
1115
1116 let mut filters = HashMap::new();
1117 filters.insert("tag".to_string(), "correct".to_string());
1118
1119 let result = dispatcher
1120 .wait_for_event(Some(EventType::Ack), filters, Duration::from_millis(100))
1121 .await;
1122
1123 assert!(result.is_some());
1124 assert_eq!(
1125 result.unwrap().attributes.get("tag"),
1126 Some(&"correct".to_string())
1127 );
1128 }
1129
1130 #[test]
1131 fn test_event_type_debug() {
1132 assert_eq!(format!("{:?}", EventType::Connected), "Connected");
1133 assert_eq!(format!("{:?}", EventType::Disconnected), "Disconnected");
1134 }
1135
1136 #[test]
1137 fn test_event_type_clone_eq() {
1138 let e1 = EventType::SelfInfo;
1139 let e2 = e1;
1140 assert_eq!(e1, e2);
1141 }
1142
1143 #[test]
1144 fn test_event_payload_clone() {
1145 let payload = EventPayload::String("test".to_string());
1146 let cloned = payload.clone();
1147 match cloned {
1148 EventPayload::String(s) => assert_eq!(s, "test"),
1149 _ => panic!("Wrong payload type"),
1150 }
1151 }
1152
1153 #[test]
1154 fn test_stats_category_eq() {
1155 assert_eq!(StatsCategory::Core, StatsCategory::Core);
1156 assert_ne!(StatsCategory::Core, StatsCategory::Radio);
1157 }
1158
1159 #[test]
1160 fn test_self_info_clone() {
1161 let info = SelfInfo {
1162 adv_type: 1,
1163 tx_power: 20,
1164 max_tx_power: 30,
1165 public_key: [0u8; 32],
1166 adv_lat: 0,
1167 adv_lon: 0,
1168 multi_acks: 0,
1169 adv_loc_policy: 0,
1170 telemetry_mode_base: 0,
1171 telemetry_mode_loc: 0,
1172 telemetry_mode_env: 0,
1173 manual_add_contacts: false,
1174 radio_freq: 915000000,
1175 radio_bw: 125000,
1176 sf: 7,
1177 cr: 5,
1178 name: "Test".to_string(),
1179 };
1180
1181 let cloned = info.clone();
1182 assert_eq!(cloned.tx_power, 20);
1183 assert_eq!(cloned.name, "Test");
1184 }
1185
1186 #[test]
1187 fn test_contact_message_clone() {
1188 let msg = ContactMessage {
1189 sender_prefix: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
1190 path_len: 2,
1191 txt_type: 1,
1192 sender_timestamp: 1234567890,
1193 text: "Hello".to_string(),
1194 snr: Some(10.0),
1195 signature: None,
1196 };
1197
1198 let cloned = msg.clone();
1199 assert_eq!(cloned.text, "Hello");
1200 assert_eq!(cloned.snr, Some(10.0));
1201 }
1202
1203 #[test]
1204 fn test_contact_message_message_id() {
1205 let msg = ContactMessage {
1206 sender_prefix: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
1207 path_len: 2,
1208 txt_type: 1,
1209 sender_timestamp: 0x12345678,
1210 text: "Hello".to_string(),
1211 snr: None,
1212 signature: None,
1213 };
1214
1215 let id = msg.message_id();
1216 assert_eq!(id, 0x0102030412345678);
1219 }
1220
1221 #[test]
1222 fn test_contact_message_sender_prefix_hex() {
1223 let msg = ContactMessage {
1224 sender_prefix: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
1225 path_len: 0,
1226 txt_type: 0,
1227 sender_timestamp: 0,
1228 text: "".to_string(),
1229 snr: None,
1230 signature: None,
1231 };
1232
1233 assert_eq!(msg.sender_prefix_hex(), "aabbccddeeff");
1234 }
1235
1236 #[test]
1237 fn test_channel_message_clone() {
1238 let msg = ChannelMessage {
1239 channel_idx: 5,
1240 path_len: 1,
1241 txt_type: 0,
1242 sender_timestamp: 1234567890,
1243 text: "Channel msg".to_string(),
1244 snr: Some(8.5),
1245 };
1246
1247 let cloned = msg.clone();
1248 assert_eq!(cloned.channel_idx, 5);
1249 assert_eq!(cloned.text, "Channel msg");
1250 }
1251
1252 #[test]
1253 fn test_channel_message_message_id() {
1254 let msg = ChannelMessage {
1255 channel_idx: 5,
1256 path_len: 1,
1257 txt_type: 0,
1258 sender_timestamp: 0x12345678,
1259 text: "".to_string(),
1260 snr: None,
1261 };
1262
1263 let id = msg.message_id();
1264 assert_eq!(id, 0x0500000012345678);
1267 }
1268
1269 #[test]
1270 fn test_battery_info_debug() {
1271 let info = BatteryInfo {
1272 battery_mv: 4200,
1273 used_kb: Some(512),
1274 total_kb: Some(4096),
1275 };
1276 let debug_str = format!("{:?}", info);
1277 assert!(debug_str.contains("4200"));
1278 }
1279
1280 #[test]
1281 fn test_battery_info_voltage() {
1282 let info = BatteryInfo {
1283 battery_mv: 3700,
1284 used_kb: None,
1285 total_kb: None,
1286 };
1287 assert!((info.voltage() - 3.7).abs() < 0.001);
1288 }
1289
1290 #[test]
1291 fn test_battery_info_no_storage() {
1292 let info = BatteryInfo {
1293 battery_mv: 4100,
1294 used_kb: None,
1295 total_kb: None,
1296 };
1297 assert_eq!(info.battery_mv, 4100);
1298 assert!(info.used_kb.is_none());
1299 assert!(info.total_kb.is_none());
1300 }
1301
1302 #[test]
1303 fn test_battery_info_percentage_full() {
1304 let info = BatteryInfo {
1305 battery_mv: 3930,
1306 used_kb: None,
1307 total_kb: None,
1308 };
1309 assert_eq!(info.percentage(), 100);
1310 }
1311
1312 #[test]
1313 fn test_battery_info_percentage_empty() {
1314 let info = BatteryInfo {
1315 battery_mv: 3000,
1316 used_kb: None,
1317 total_kb: None,
1318 };
1319 assert_eq!(info.percentage(), 0);
1320 }
1321
1322 #[test]
1323 fn test_battery_info_percentage_half() {
1324 let info = BatteryInfo {
1325 battery_mv: 3465, used_kb: None,
1327 total_kb: None,
1328 };
1329 assert_eq!(info.percentage(), 50);
1330 }
1331
1332 #[test]
1333 fn test_battery_info_percentage_below_min() {
1334 let info = BatteryInfo {
1335 battery_mv: 2800,
1336 used_kb: None,
1337 total_kb: None,
1338 };
1339 assert_eq!(info.percentage(), 0);
1340 }
1341
1342 #[test]
1343 fn test_battery_info_percentage_above_max() {
1344 let info = BatteryInfo {
1345 battery_mv: 4200,
1346 used_kb: None,
1347 total_kb: None,
1348 };
1349 assert_eq!(info.percentage(), 100);
1350 }
1351
1352 #[test]
1353 fn test_channel_info_data_clone() {
1354 let info = ChannelInfoData {
1355 channel_idx: 1,
1356 name: "General".to_string(),
1357 secret: [0xAA; CHANNEL_SECRET_LEN],
1358 };
1359 let cloned = info.clone();
1360 assert_eq!(cloned.channel_idx, 1);
1361 assert_eq!(cloned.name, "General");
1362 }
1363
1364 #[test]
1365 fn test_advertisement_data_debug() {
1366 let advert = AdvertisementData {
1367 prefix: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
1368 name: "Node1".to_string(),
1369 lat: 37774900,
1370 lon: -122419400,
1371 };
1372 let debug_str = format!("{:?}", advert);
1373 assert!(debug_str.contains("Node1"));
1374 }
1375
1376 #[test]
1377 fn test_path_update_data_clone() {
1378 let update = PathUpdateData {
1379 prefix: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
1380 path_len: 3,
1381 path: vec![0x0A, 0x0B, 0x0C],
1382 };
1383 let cloned = update.clone();
1384 assert_eq!(cloned.path_len, 3);
1385 assert_eq!(cloned.path, vec![0x0A, 0x0B, 0x0C]);
1386 }
1387
1388 #[test]
1389 fn test_trace_hop_clone() {
1390 let hop = TraceHop {
1391 prefix: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
1392 snr: 10.5,
1393 };
1394 let cloned = hop.clone();
1395 assert_eq!(cloned.snr, 10.5);
1396 }
1397
1398 #[test]
1399 fn test_neighbour_clone() {
1400 let neighbour = Neighbour {
1401 pubkey: vec![0x01, 0x02, 0x03],
1402 secs_ago: 300,
1403 snr: 8.0,
1404 };
1405 let cloned = neighbour.clone();
1406 assert_eq!(cloned.secs_ago, 300);
1407 }
1408
1409 #[test]
1410 fn test_discover_entry_clone() {
1411 let entry = DiscoverEntry {
1412 pubkey: vec![0x01, 0x02, 0x03],
1413 name: "Node".to_string(),
1414 };
1415 let cloned = entry.clone();
1416 assert_eq!(cloned.name, "Node");
1417 }
1418
1419 #[test]
1420 fn test_event_payload_variants() {
1421 let _none = EventPayload::None;
1423 let _string = EventPayload::String("test".to_string());
1424 let _bytes = EventPayload::Bytes(vec![1, 2, 3]);
1425 let _time = EventPayload::Time(1234567890);
1426 let _private_key = EventPayload::PrivateKey([0u8; 64]);
1427 let _signature = EventPayload::Signature(vec![1, 2, 3, 4]);
1428 let _sign_start = EventPayload::SignStart { max_length: 1000 };
1429 let _ack = EventPayload::Ack {
1430 tag: [0x01, 0x02, 0x03, 0x04],
1431 };
1432 let _binary = EventPayload::BinaryResponse {
1433 tag: [0x01, 0x02, 0x03, 0x04],
1434 data: vec![5, 6, 7, 8],
1435 };
1436 let _auto_add = EventPayload::AutoAddConfig { flags: 0x01 };
1437 let _log_data = EventPayload::LogData(LogData {
1438 snr: 10.5,
1439 rssi: -80,
1440 payload: vec![0x01, 0x02, 0x03],
1441 });
1442 }
1443
1444 #[test]
1445 fn test_log_data_clone() {
1446 let log_data = LogData {
1447 snr: 12.25,
1448 rssi: -75,
1449 payload: vec![0xAA, 0xBB, 0xCC],
1450 };
1451 let cloned = log_data.clone();
1452 assert_eq!(cloned.snr, 12.25);
1453 assert_eq!(cloned.rssi, -75);
1454 assert_eq!(cloned.payload, vec![0xAA, 0xBB, 0xCC]);
1455 }
1456
1457 #[test]
1458 fn test_log_data_debug() {
1459 let log_data = LogData {
1460 snr: 5.5,
1461 rssi: -90,
1462 payload: vec![0x01, 0x02],
1463 };
1464 let debug_str = format!("{:?}", log_data);
1465 assert!(debug_str.contains("snr"));
1466 assert!(debug_str.contains("rssi"));
1467 assert!(debug_str.contains("payload"));
1468 }
1469
1470 #[test]
1471 fn test_log_data_snr_conversion() {
1472 let log_data = LogData {
1475 snr: 10.0, rssi: -85,
1477 payload: vec![],
1478 };
1479 assert_eq!(log_data.snr, 10.0);
1480 }
1481
1482 #[test]
1483 fn test_log_data_negative_snr() {
1484 let log_data = LogData {
1485 snr: -5.25, rssi: -100,
1487 payload: vec![0x01],
1488 };
1489 assert_eq!(log_data.snr, -5.25);
1490 }
1491
1492 #[test]
1493 fn test_log_data_empty_payload() {
1494 let log_data = LogData {
1495 snr: 0.0,
1496 rssi: 0,
1497 payload: vec![],
1498 };
1499 assert!(log_data.payload.is_empty());
1500 }
1501}