1use std::cmp::Ordering;
20use std::collections::{BinaryHeap, HashMap, VecDeque};
21use std::sync::{
22 atomic::{AtomicU64, Ordering as AOrdering},
23 Arc, Mutex, RwLock,
24};
25use std::time::{Duration, Instant};
26
27use thiserror::Error;
28
29const MAX_DEAD_LETTER: usize = 100;
33
34const DEFAULT_TTL: Duration = Duration::from_secs(30);
36
37#[derive(Error, Debug, Clone, PartialEq, Eq)]
41pub enum RouterError {
42 #[error("Duplicate handler: {0}")]
44 DuplicateHandler(String),
45
46 #[error("Handler not found: {0}")]
48 HandlerNotFound(String),
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
58#[repr(u8)]
59pub enum MessagePriority {
60 Low = 0,
62 #[default]
64 Normal = 1,
65 High = 2,
67 Critical = 3,
69}
70
71#[derive(Debug, Clone)]
75pub struct RoutedMessage {
76 pub id: u64,
78 pub from_peer: String,
80 pub topic: String,
82 pub payload: Vec<u8>,
84 pub priority: MessagePriority,
86 pub enqueued_at: Instant,
88 pub ttl: Duration,
90}
91
92impl RoutedMessage {
93 pub fn new(
95 id: u64,
96 from_peer: impl Into<String>,
97 topic: impl Into<String>,
98 payload: Vec<u8>,
99 priority: MessagePriority,
100 ) -> Self {
101 Self {
102 id,
103 from_peer: from_peer.into(),
104 topic: topic.into(),
105 payload,
106 priority,
107 enqueued_at: Instant::now(),
108 ttl: DEFAULT_TTL,
109 }
110 }
111
112 pub fn is_expired(&self, now: Instant) -> bool {
114 now.saturating_duration_since(self.enqueued_at) >= self.ttl
115 }
116}
117
118#[derive(Debug, Clone)]
122pub struct HandlerRegistration {
123 pub topic_pattern: String,
129
130 pub priority: MessagePriority,
133
134 pub handler_id: String,
136}
137
138impl HandlerRegistration {
139 pub fn matches(&self, topic: &str) -> bool {
141 let pattern = &self.topic_pattern;
142
143 if pattern == "*" {
144 return true;
145 }
146
147 if let Some(prefix) = pattern.strip_suffix('*') {
148 return topic.starts_with(prefix);
149 }
150
151 pattern == topic
152 }
153}
154
155#[derive(Debug, Clone)]
163struct QueuedMessage(RoutedMessage);
164
165impl PartialEq for QueuedMessage {
166 fn eq(&self, other: &Self) -> bool {
167 self.0.id == other.0.id
168 }
169}
170
171impl Eq for QueuedMessage {}
172
173impl PartialOrd for QueuedMessage {
174 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
175 Some(self.cmp(other))
176 }
177}
178
179impl Ord for QueuedMessage {
180 fn cmp(&self, other: &Self) -> Ordering {
181 let pri = self.0.priority.cmp(&other.0.priority);
183 if pri != Ordering::Equal {
184 return pri;
185 }
186 other.0.enqueued_at.cmp(&self.0.enqueued_at)
188 }
189}
190
191#[derive(Debug, Default)]
195pub struct RouterStats {
196 pub total_routed: AtomicU64,
198 pub total_matched: AtomicU64,
200 pub total_dead_lettered: AtomicU64,
202 pub total_expired_dropped: AtomicU64,
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub struct RouterStatsSnapshot {
209 pub total_routed: u64,
211 pub total_matched: u64,
213 pub total_dead_lettered: u64,
215 pub total_expired_dropped: u64,
217}
218
219impl RouterStats {
220 pub fn snapshot(&self) -> RouterStatsSnapshot {
222 RouterStatsSnapshot {
223 total_routed: self.total_routed.load(AOrdering::Relaxed),
224 total_matched: self.total_matched.load(AOrdering::Relaxed),
225 total_dead_lettered: self.total_dead_lettered.load(AOrdering::Relaxed),
226 total_expired_dropped: self.total_expired_dropped.load(AOrdering::Relaxed),
227 }
228 }
229}
230
231pub struct MessageRouter {
241 handlers: RwLock<Vec<HandlerRegistration>>,
243
244 queues: Mutex<HashMap<String, BinaryHeap<QueuedMessage>>>,
246
247 dead_letter: Mutex<VecDeque<RoutedMessage>>,
249
250 next_id: AtomicU64,
252
253 pub stats: Arc<RouterStats>,
255}
256
257impl Default for MessageRouter {
258 fn default() -> Self {
259 Self::new()
260 }
261}
262
263impl MessageRouter {
264 pub fn new() -> Self {
266 Self {
267 handlers: RwLock::new(Vec::new()),
268 queues: Mutex::new(HashMap::new()),
269 dead_letter: Mutex::new(VecDeque::new()),
270 next_id: AtomicU64::new(0),
271 stats: Arc::new(RouterStats::default()),
272 }
273 }
274
275 pub fn next_id(&self) -> u64 {
277 self.next_id.fetch_add(1, AOrdering::Relaxed)
278 }
279
280 pub fn register_handler(&self, reg: HandlerRegistration) -> Result<(), RouterError> {
287 let mut handlers = self.handlers.write().unwrap_or_else(|e| e.into_inner());
288
289 if handlers.iter().any(|h| h.handler_id == reg.handler_id) {
290 return Err(RouterError::DuplicateHandler(reg.handler_id.clone()));
291 }
292
293 handlers.push(reg);
294 handlers.sort_by_key(|h| std::cmp::Reverse(h.priority));
297
298 let mut queues = self.queues.lock().unwrap_or_else(|e| e.into_inner());
300 queues
301 .entry(
302 handlers
303 .last()
304 .map(|h| h.handler_id.clone())
305 .unwrap_or_default(),
306 )
307 .or_default();
308
309 for h in handlers.iter() {
312 queues.entry(h.handler_id.clone()).or_default();
313 }
314
315 Ok(())
316 }
317
318 pub fn unregister_handler(&self, handler_id: &str) {
324 let mut handlers = self.handlers.write().unwrap_or_else(|e| e.into_inner());
325
326 handlers.retain(|h| h.handler_id != handler_id);
327
328 }
331
332 pub fn handler_count(&self) -> usize {
334 self.handlers
335 .read()
336 .unwrap_or_else(|e| e.into_inner())
337 .len()
338 }
339
340 pub fn route(&self, msg: RoutedMessage) -> usize {
351 self.stats.total_routed.fetch_add(1, AOrdering::Relaxed);
352
353 let now = Instant::now();
354 if msg.is_expired(now) {
355 self.stats
356 .total_expired_dropped
357 .fetch_add(1, AOrdering::Relaxed);
358 return 0;
359 }
360
361 let handlers = self.handlers.read().unwrap_or_else(|e| e.into_inner());
362
363 let matching: Vec<String> = handlers
364 .iter()
365 .filter(|h| h.matches(&msg.topic))
366 .map(|h| h.handler_id.clone())
367 .collect();
368
369 let matched_count = matching.len();
370
371 if matched_count == 0 {
372 self.stats
373 .total_dead_lettered
374 .fetch_add(1, AOrdering::Relaxed);
375
376 let mut dl = self.dead_letter.lock().unwrap_or_else(|e| e.into_inner());
377 if dl.len() >= MAX_DEAD_LETTER {
378 dl.pop_front();
379 }
380 dl.push_back(msg);
381 } else {
382 self.stats.total_matched.fetch_add(1, AOrdering::Relaxed);
383
384 let mut queues = self.queues.lock().unwrap_or_else(|e| e.into_inner());
385
386 for handler_id in &matching {
387 let queue = queues.entry(handler_id.clone()).or_default();
388 queue.push(QueuedMessage(msg.clone()));
389 }
390 }
391
392 matched_count
393 }
394
395 pub fn drain_for_handler(&self, handler_id: &str) -> Vec<RoutedMessage> {
401 let mut queues = self.queues.lock().unwrap_or_else(|e| e.into_inner());
402
403 match queues.get_mut(handler_id) {
404 None => Vec::new(),
405 Some(heap) => {
406 let mut out = Vec::with_capacity(heap.len());
407 while let Some(qm) = heap.pop() {
408 out.push(qm.0);
409 }
410 out
411 }
412 }
413 }
414
415 pub fn dead_letter_count(&self) -> usize {
419 self.dead_letter
420 .lock()
421 .unwrap_or_else(|e| e.into_inner())
422 .len()
423 }
424
425 pub fn drain_dead_letter(&self) -> Vec<RoutedMessage> {
427 let mut dl = self.dead_letter.lock().unwrap_or_else(|e| e.into_inner());
428 dl.drain(..).collect()
429 }
430}
431
432#[derive(Debug, Clone, PartialEq, Eq, Hash)]
438pub enum MessageType {
439 BlockRequest,
441 BlockResponse,
443 ProofRequest,
445 ProofResponse,
447 Gossip { topic: String },
449 Control,
451}
452
453#[derive(Debug, Clone)]
455pub struct PeerRoutedMessage {
456 pub msg_id: u64,
458 pub msg_type: MessageType,
460 pub from_peer: String,
462 pub to_peer: Option<String>,
464 pub payload_bytes: u64,
466 pub created_at_tick: u64,
468}
469
470#[derive(Debug, Clone)]
475pub struct RouteRule {
476 pub rule_id: u64,
478 pub match_type: MessageType,
483 pub target_peer: Option<String>,
486 pub priority: u32,
488}
489
490#[derive(Debug, Clone, Default, PartialEq, Eq)]
492pub struct MessageRouterStats {
493 pub total_routed: u64,
495 pub total_broadcast: u64,
498 pub total_dropped: u64,
500}
501
502fn same_message_type_variant(a: &MessageType, b: &MessageType) -> bool {
506 match (a, b) {
507 (MessageType::BlockRequest, MessageType::BlockRequest) => true,
508 (MessageType::BlockResponse, MessageType::BlockResponse) => true,
509 (MessageType::ProofRequest, MessageType::ProofRequest) => true,
510 (MessageType::ProofResponse, MessageType::ProofResponse) => true,
511 (MessageType::Gossip { .. }, MessageType::Gossip { .. }) => true,
513 (MessageType::Control, MessageType::Control) => true,
514 _ => false,
515 }
516}
517
518pub struct PeerMessageRouter {
534 pub rules: Vec<RouteRule>,
536 pub subscriptions: HashMap<String, Vec<String>>,
538 pub next_msg_id: u64,
540 pub stats: MessageRouterStats,
542}
543
544impl Default for PeerMessageRouter {
545 fn default() -> Self {
546 Self::new()
547 }
548}
549
550impl PeerMessageRouter {
551 pub fn new() -> Self {
553 Self {
554 rules: Vec::new(),
555 subscriptions: HashMap::new(),
556 next_msg_id: 0,
557 stats: MessageRouterStats::default(),
558 }
559 }
560
561 pub fn add_rule(&mut self, rule: RouteRule) {
566 let pos = self.rules.partition_point(|r| r.priority >= rule.priority);
569 self.rules.insert(pos, rule);
570 }
571
572 pub fn remove_rule(&mut self, rule_id: u64) -> bool {
576 let before = self.rules.len();
577 self.rules.retain(|r| r.rule_id != rule_id);
578 self.rules.len() < before
579 }
580
581 pub fn subscribe(&mut self, peer_id: &str, topic: &str) {
587 let peers = self.subscriptions.entry(topic.to_string()).or_default();
588
589 if !peers.iter().any(|p| p == peer_id) {
590 peers.push(peer_id.to_string());
591 }
592 }
593
594 pub fn unsubscribe(&mut self, peer_id: &str, topic: &str) -> bool {
598 if let Some(peers) = self.subscriptions.get_mut(topic) {
599 let before = peers.len();
600 peers.retain(|p| p != peer_id);
601 return peers.len() < before;
602 }
603 false
604 }
605
606 pub fn route(
613 &mut self,
614 msg_type: MessageType,
615 from_peer: &str,
616 to_peer: Option<&str>,
617 payload_bytes: u64,
618 tick: u64,
619 ) -> Vec<PeerRoutedMessage> {
620 let msg_id = self.next_msg_id;
621 self.next_msg_id = self.next_msg_id.saturating_add(1);
622
623 match to_peer {
624 Some(dest) => {
626 self.stats.total_routed = self.stats.total_routed.saturating_add(1);
627 vec![PeerRoutedMessage {
628 msg_id,
629 msg_type,
630 from_peer: from_peer.to_string(),
631 to_peer: Some(dest.to_string()),
632 payload_bytes,
633 created_at_tick: tick,
634 }]
635 }
636
637 None => {
639 let recipients: Vec<String> = match &msg_type {
640 MessageType::Gossip { topic } => {
641 self.subscriptions
644 .get(topic.as_str())
645 .map(|peers| {
646 peers
647 .iter()
648 .filter(|p| p.as_str() != from_peer)
649 .cloned()
650 .collect()
651 })
652 .unwrap_or_default()
653 }
654 other => {
655 let has_wildcard_rule = self.rules.iter().any(|r| {
659 r.target_peer.is_none()
660 && same_message_type_variant(&r.match_type, other)
661 });
662
663 if has_wildcard_rule {
664 let mut peers: Vec<String> = self
666 .subscriptions
667 .values()
668 .flat_map(|ps| ps.iter().cloned())
669 .collect();
670 peers.sort();
671 peers.dedup();
672 peers
673 } else {
674 Vec::new()
675 }
676 }
677 };
678
679 if recipients.is_empty() {
680 self.stats.total_dropped = self.stats.total_dropped.saturating_add(1);
681 Vec::new()
682 } else {
683 self.stats.total_broadcast = self.stats.total_broadcast.saturating_add(1);
684 recipients
685 .into_iter()
686 .map(|peer| PeerRoutedMessage {
687 msg_id,
688 msg_type: msg_type.clone(),
689 from_peer: from_peer.to_string(),
690 to_peer: Some(peer),
691 payload_bytes,
692 created_at_tick: tick,
693 })
694 .collect()
695 }
696 }
697 }
698 }
699
700 pub fn subscribers(&self, topic: &str) -> Vec<&str> {
704 let mut peers: Vec<&str> = self
705 .subscriptions
706 .get(topic)
707 .map(|ps| ps.iter().map(String::as_str).collect())
708 .unwrap_or_default();
709 peers.sort_unstable();
710 peers
711 }
712
713 pub fn stats(&self) -> &MessageRouterStats {
715 &self.stats
716 }
717}
718
719#[cfg(test)]
722mod peer_router_tests {
723 use super::{MessageRouterStats, MessageType, PeerMessageRouter, RouteRule};
724
725 fn make_rule(rule_id: u64, match_type: MessageType, priority: u32) -> RouteRule {
726 RouteRule {
727 rule_id,
728 match_type,
729 target_peer: None,
730 priority,
731 }
732 }
733
734 #[test]
736 fn test_new_starts_empty() {
737 let router = PeerMessageRouter::new();
738 assert!(router.rules.is_empty());
739 assert!(router.subscriptions.is_empty());
740 assert_eq!(router.next_msg_id, 0);
741 assert_eq!(router.stats, MessageRouterStats::default());
742 }
743
744 #[test]
746 fn test_add_rule_maintains_priority_order() {
747 let mut router = PeerMessageRouter::new();
748 router.add_rule(make_rule(1, MessageType::BlockRequest, 10));
749 router.add_rule(make_rule(2, MessageType::BlockResponse, 50));
750 router.add_rule(make_rule(3, MessageType::Control, 30));
751
752 assert_eq!(router.rules[0].priority, 50);
753 assert_eq!(router.rules[1].priority, 30);
754 assert_eq!(router.rules[2].priority, 10);
755 }
756
757 #[test]
759 fn test_remove_rule_found() {
760 let mut router = PeerMessageRouter::new();
761 router.add_rule(make_rule(42, MessageType::Control, 5));
762 assert!(router.remove_rule(42));
763 assert!(router.rules.is_empty());
764 }
765
766 #[test]
768 fn test_remove_rule_not_found() {
769 let mut router = PeerMessageRouter::new();
770 assert!(!router.remove_rule(99));
771 }
772
773 #[test]
775 fn test_subscribe_adds_peer() {
776 let mut router = PeerMessageRouter::new();
777 router.subscribe("peer-A", "news");
778 let subs = router.subscribers("news");
779 assert_eq!(subs, vec!["peer-A"]);
780 }
781
782 #[test]
784 fn test_subscribe_idempotent() {
785 let mut router = PeerMessageRouter::new();
786 router.subscribe("peer-A", "news");
787 router.subscribe("peer-A", "news");
788 assert_eq!(router.subscribers("news").len(), 1);
789 }
790
791 #[test]
793 fn test_unsubscribe_removes_peer() {
794 let mut router = PeerMessageRouter::new();
795 router.subscribe("peer-A", "news");
796 let removed = router.unsubscribe("peer-A", "news");
797 assert!(removed);
798 assert!(router.subscribers("news").is_empty());
799 }
800
801 #[test]
803 fn test_unsubscribe_not_subscribed() {
804 let mut router = PeerMessageRouter::new();
805 assert!(!router.unsubscribe("nobody", "news"));
806 }
807
808 #[test]
810 fn test_unsubscribe_unknown_topic() {
811 let mut router = PeerMessageRouter::new();
812 router.subscribe("peer-A", "news");
813 assert!(!router.unsubscribe("peer-A", "sports"));
814 }
815
816 #[test]
818 fn test_route_direct_single_message() {
819 let mut router = PeerMessageRouter::new();
820 let msgs = router.route(
821 MessageType::BlockRequest,
822 "sender",
823 Some("receiver"),
824 128,
825 1,
826 );
827 assert_eq!(msgs.len(), 1);
828 assert_eq!(msgs[0].to_peer, Some("receiver".to_string()));
829 }
830
831 #[test]
833 fn test_route_direct_increments_total_routed() {
834 let mut router = PeerMessageRouter::new();
835 router.route(MessageType::BlockRequest, "s", Some("r"), 0, 0);
836 router.route(MessageType::BlockResponse, "s", Some("r"), 0, 0);
837 assert_eq!(router.stats().total_routed, 2);
838 assert_eq!(router.stats().total_broadcast, 0);
839 }
840
841 #[test]
843 fn test_route_broadcast_gossip_to_subscribers() {
844 let mut router = PeerMessageRouter::new();
845 router.subscribe("peer-A", "chain");
846 router.subscribe("peer-B", "chain");
847
848 let msgs = router.route(
849 MessageType::Gossip {
850 topic: "chain".to_string(),
851 },
852 "sender",
853 None,
854 64,
855 5,
856 );
857 assert_eq!(msgs.len(), 2);
858 }
859
860 #[test]
862 fn test_route_broadcast_gossip_excludes_sender() {
863 let mut router = PeerMessageRouter::new();
864 router.subscribe("peer-A", "chain");
865 router.subscribe("origin", "chain");
866
867 let msgs = router.route(
868 MessageType::Gossip {
869 topic: "chain".to_string(),
870 },
871 "origin",
872 None,
873 64,
874 1,
875 );
876 assert_eq!(msgs.len(), 1);
877 assert_eq!(msgs[0].to_peer, Some("peer-A".to_string()));
878 }
879
880 #[test]
882 fn test_route_broadcast_gossip_no_subscribers_dropped() {
883 let mut router = PeerMessageRouter::new();
884 router.subscribe("origin", "chain");
886
887 let msgs = router.route(
888 MessageType::Gossip {
889 topic: "chain".to_string(),
890 },
891 "origin",
892 None,
893 0,
894 0,
895 );
896 assert!(msgs.is_empty());
897 assert_eq!(router.stats().total_dropped, 1);
898 }
899
900 #[test]
902 fn test_route_broadcast_no_subscribers_drops() {
903 let mut router = PeerMessageRouter::new();
904 let msgs = router.route(MessageType::BlockRequest, "s", None, 0, 0);
905 assert!(msgs.is_empty());
906 assert_eq!(router.stats().total_dropped, 1);
907 }
908
909 #[test]
911 fn test_route_broadcast_non_gossip_with_rule() {
912 let mut router = PeerMessageRouter::new();
913 router.add_rule(make_rule(1, MessageType::BlockRequest, 10));
914 router.subscribe("peer-A", "any-topic");
915 router.subscribe("peer-B", "other-topic");
916
917 let msgs = router.route(MessageType::BlockRequest, "s", None, 0, 0);
918 assert_eq!(msgs.len(), 2);
919 assert_eq!(router.stats().total_broadcast, 1);
920 }
921
922 #[test]
924 fn test_route_broadcast_increments_total_broadcast() {
925 let mut router = PeerMessageRouter::new();
926 router.subscribe("peer-A", "t");
927
928 router.route(
929 MessageType::Gossip {
930 topic: "t".to_string(),
931 },
932 "s",
933 None,
934 0,
935 0,
936 );
937 assert_eq!(router.stats().total_broadcast, 1);
938 assert_eq!(router.stats().total_routed, 0);
939 }
940
941 #[test]
943 fn test_route_dropped_increments_total_dropped() {
944 let mut router = PeerMessageRouter::new();
945 router.route(MessageType::Control, "s", None, 0, 0);
946 router.route(MessageType::Control, "s", None, 0, 0);
947 assert_eq!(router.stats().total_dropped, 2);
948 }
949
950 #[test]
952 fn test_msg_id_monotonically_increasing() {
953 let mut router = PeerMessageRouter::new();
954 let r1 = router.route(MessageType::BlockRequest, "s", Some("r"), 0, 0);
955 let r2 = router.route(MessageType::BlockResponse, "s", Some("r"), 0, 0);
956 let r3 = router.route(MessageType::Control, "s", Some("r"), 0, 0);
957 assert!(r1[0].msg_id < r2[0].msg_id);
958 assert!(r2[0].msg_id < r3[0].msg_id);
959 }
960
961 #[test]
963 fn test_routed_message_fields_direct() {
964 let mut router = PeerMessageRouter::new();
965 let msgs = router.route(MessageType::ProofRequest, "alice", Some("bob"), 256, 42);
966 let m = &msgs[0];
967 assert_eq!(m.msg_id, 0);
968 assert_eq!(m.msg_type, MessageType::ProofRequest);
969 assert_eq!(m.from_peer, "alice");
970 assert_eq!(m.to_peer, Some("bob".to_string()));
971 assert_eq!(m.payload_bytes, 256);
972 assert_eq!(m.created_at_tick, 42);
973 }
974
975 #[test]
977 fn test_subscribers_sorted() {
978 let mut router = PeerMessageRouter::new();
979 router.subscribe("charlie", "topic");
980 router.subscribe("alice", "topic");
981 router.subscribe("bob", "topic");
982
983 let subs = router.subscribers("topic");
984 assert_eq!(subs, vec!["alice", "bob", "charlie"]);
985 }
986
987 #[test]
989 fn test_multiple_subscribers_all_receive_broadcast() {
990 let mut router = PeerMessageRouter::new();
991 for i in 0..5 {
992 router.subscribe(&format!("peer-{}", i), "t");
993 }
994 let msgs = router.route(
995 MessageType::Gossip {
996 topic: "t".to_string(),
997 },
998 "outsider",
999 None,
1000 10,
1001 1,
1002 );
1003 assert_eq!(msgs.len(), 5);
1004 }
1005
1006 #[test]
1008 fn test_stats_independence() {
1009 let mut router = PeerMessageRouter::new();
1010 router.subscribe("p", "t");
1011
1012 router.route(MessageType::BlockRequest, "s", Some("r"), 0, 0); router.route(
1014 MessageType::Gossip {
1015 topic: "t".to_string(),
1016 },
1017 "s",
1018 None,
1019 0,
1020 0,
1021 ); let s = router.stats();
1024 assert_eq!(s.total_routed, 1);
1025 assert_eq!(s.total_broadcast, 1);
1026 assert_eq!(s.total_dropped, 0);
1027 }
1028
1029 #[test]
1031 fn test_remove_rule_selective() {
1032 let mut router = PeerMessageRouter::new();
1033 router.add_rule(make_rule(1, MessageType::BlockRequest, 10));
1034 router.add_rule(make_rule(2, MessageType::BlockResponse, 20));
1035 router.add_rule(make_rule(3, MessageType::Control, 30));
1036
1037 assert!(router.remove_rule(2));
1038 assert_eq!(router.rules.len(), 2);
1039 assert!(router.rules.iter().all(|r| r.rule_id != 2));
1040 }
1041
1042 #[test]
1044 fn test_default_matches_new() {
1045 let a = PeerMessageRouter::new();
1046 let b = PeerMessageRouter::default();
1047 assert_eq!(a.rules.len(), b.rules.len());
1048 assert_eq!(a.next_msg_id, b.next_msg_id);
1049 }
1050}
1051
1052#[cfg(test)]
1055mod tests {
1056 use super::*;
1057 use std::time::Duration;
1058
1059 fn make_msg(topic: &str, priority: MessagePriority) -> RoutedMessage {
1060 RoutedMessage {
1061 id: 0,
1062 from_peer: "peer-1".to_string(),
1063 topic: topic.to_string(),
1064 payload: b"hello".to_vec(),
1065 priority,
1066 enqueued_at: Instant::now(),
1067 ttl: DEFAULT_TTL,
1068 }
1069 }
1070
1071 fn reg(pattern: &str, priority: MessagePriority, id: &str) -> HandlerRegistration {
1072 HandlerRegistration {
1073 topic_pattern: pattern.to_string(),
1074 priority,
1075 handler_id: id.to_string(),
1076 }
1077 }
1078
1079 #[test]
1081 fn test_register_and_route_exact_match() {
1082 let router = MessageRouter::new();
1083 router
1084 .register_handler(reg("blocks", MessagePriority::Normal, "h1"))
1085 .expect("register failed");
1086
1087 let matched = router.route(make_msg("blocks", MessagePriority::Normal));
1088 assert_eq!(matched, 1);
1089 assert_eq!(router.dead_letter_count(), 0);
1090 }
1091
1092 #[test]
1094 fn test_exact_match_no_false_positive() {
1095 let router = MessageRouter::new();
1096 router
1097 .register_handler(reg("blocks", MessagePriority::Normal, "h1"))
1098 .expect("register failed");
1099
1100 let matched = router.route(make_msg("transactions", MessagePriority::Normal));
1101 assert_eq!(matched, 0);
1102 assert_eq!(router.dead_letter_count(), 1);
1103 }
1104
1105 #[test]
1107 fn test_wildcard_prefix_matching() {
1108 let router = MessageRouter::new();
1109 router
1110 .register_handler(reg("block.*", MessagePriority::Normal, "h1"))
1111 .expect("register failed");
1112
1113 assert_eq!(
1114 router.route(make_msg("block.add", MessagePriority::Normal)),
1115 1
1116 );
1117 assert_eq!(
1118 router.route(make_msg("block.get", MessagePriority::Normal)),
1119 1
1120 );
1121 assert_eq!(
1122 router.route(make_msg("transaction.add", MessagePriority::Normal)),
1123 0
1124 );
1125 }
1126
1127 #[test]
1129 fn test_catch_all_wildcard() {
1130 let router = MessageRouter::new();
1131 router
1132 .register_handler(reg("*", MessagePriority::Normal, "h1"))
1133 .expect("register failed");
1134
1135 assert_eq!(
1136 router.route(make_msg("anything", MessagePriority::Normal)),
1137 1
1138 );
1139 assert_eq!(
1140 router.route(make_msg("block.add", MessagePriority::Normal)),
1141 1
1142 );
1143 assert_eq!(router.route(make_msg("", MessagePriority::Normal)), 1);
1144 assert_eq!(router.dead_letter_count(), 0);
1145 }
1146
1147 #[test]
1149 fn test_unmatched_goes_to_dead_letter() {
1150 let router = MessageRouter::new();
1151 router.route(make_msg("orphan", MessagePriority::Normal));
1152 assert_eq!(router.dead_letter_count(), 1);
1153 let dl = router.drain_dead_letter();
1154 assert_eq!(dl.len(), 1);
1155 assert_eq!(dl[0].topic, "orphan");
1156 }
1157
1158 #[test]
1160 fn test_drain_for_handler() {
1161 let router = MessageRouter::new();
1162 router
1163 .register_handler(reg("t", MessagePriority::Normal, "h1"))
1164 .expect("register failed");
1165
1166 router.route(make_msg("t", MessagePriority::Normal));
1167 router.route(make_msg("t", MessagePriority::Normal));
1168
1169 let msgs = router.drain_for_handler("h1");
1170 assert_eq!(msgs.len(), 2);
1171
1172 assert!(router.drain_for_handler("h1").is_empty());
1174 }
1175
1176 #[test]
1178 fn test_priority_ordering_in_queue() {
1179 let router = MessageRouter::new();
1180 router
1181 .register_handler(reg("t", MessagePriority::Low, "h1"))
1182 .expect("register failed");
1183
1184 let mut low = make_msg("t", MessagePriority::Low);
1185 low.id = 1;
1186 let mut high = make_msg("t", MessagePriority::High);
1187 high.id = 2;
1188 let mut critical = make_msg("t", MessagePriority::Critical);
1189 critical.id = 3;
1190 let mut normal = make_msg("t", MessagePriority::Normal);
1191 normal.id = 4;
1192
1193 router.route(low);
1194 router.route(high);
1195 router.route(critical);
1196 router.route(normal);
1197
1198 let msgs = router.drain_for_handler("h1");
1199 assert_eq!(msgs.len(), 4);
1200 assert_eq!(msgs[0].priority, MessagePriority::Critical);
1202 assert_eq!(msgs[1].priority, MessagePriority::High);
1203 assert_eq!(msgs[2].priority, MessagePriority::Normal);
1204 assert_eq!(msgs[3].priority, MessagePriority::Low);
1205 }
1206
1207 #[test]
1209 fn test_multiple_handlers_all_receive() {
1210 let router = MessageRouter::new();
1211 router
1212 .register_handler(reg("t", MessagePriority::Normal, "h1"))
1213 .expect("register h1");
1214 router
1215 .register_handler(reg("t", MessagePriority::High, "h2"))
1216 .expect("register h2");
1217 router
1218 .register_handler(reg("t", MessagePriority::Low, "h3"))
1219 .expect("register h3");
1220
1221 let matched = router.route(make_msg("t", MessagePriority::Normal));
1222 assert_eq!(matched, 3);
1223
1224 assert_eq!(router.drain_for_handler("h1").len(), 1);
1225 assert_eq!(router.drain_for_handler("h2").len(), 1);
1226 assert_eq!(router.drain_for_handler("h3").len(), 1);
1227 }
1228
1229 #[test]
1231 fn test_duplicate_handler_error() {
1232 let router = MessageRouter::new();
1233 router
1234 .register_handler(reg("t", MessagePriority::Normal, "h1"))
1235 .expect("first register");
1236
1237 let result = router.register_handler(reg("t", MessagePriority::High, "h1"));
1238 assert!(matches!(result, Err(RouterError::DuplicateHandler(ref id)) if id == "h1"));
1239 }
1240
1241 #[test]
1243 fn test_drain_dead_letter_clears_queue() {
1244 let router = MessageRouter::new();
1245 router.route(make_msg("orphan1", MessagePriority::Normal));
1246 router.route(make_msg("orphan2", MessagePriority::Normal));
1247
1248 assert_eq!(router.dead_letter_count(), 2);
1249 let dl = router.drain_dead_letter();
1250 assert_eq!(dl.len(), 2);
1251 assert_eq!(router.dead_letter_count(), 0);
1252 }
1253
1254 #[test]
1256 fn test_stats_accumulation() {
1257 let router = MessageRouter::new();
1258 router
1259 .register_handler(reg("t", MessagePriority::Normal, "h1"))
1260 .expect("register");
1261
1262 router.route(make_msg("t", MessagePriority::Normal)); router.route(make_msg("other", MessagePriority::Normal)); let snap = router.stats.snapshot();
1266 assert_eq!(snap.total_routed, 2);
1267 assert_eq!(snap.total_matched, 1);
1268 assert_eq!(snap.total_dead_lettered, 1);
1269 assert_eq!(snap.total_expired_dropped, 0);
1270 }
1271
1272 #[test]
1274 fn test_expired_message_detection() {
1275 let msg = RoutedMessage {
1276 id: 0,
1277 from_peer: "p".to_string(),
1278 topic: "t".to_string(),
1279 payload: vec![],
1280 priority: MessagePriority::Normal,
1281 enqueued_at: Instant::now() - Duration::from_secs(60),
1282 ttl: Duration::from_secs(30),
1283 };
1284 assert!(msg.is_expired(Instant::now()));
1285 }
1286
1287 #[test]
1289 fn test_non_expired_message() {
1290 let msg = make_msg("t", MessagePriority::Normal);
1291 assert!(!msg.is_expired(Instant::now()));
1292 }
1293
1294 #[test]
1296 fn test_expired_on_arrival_dropped() {
1297 let router = MessageRouter::new();
1298 let msg = RoutedMessage {
1299 id: 0,
1300 from_peer: "p".to_string(),
1301 topic: "orphan".to_string(),
1302 payload: vec![],
1303 priority: MessagePriority::Normal,
1304 enqueued_at: Instant::now() - Duration::from_secs(60),
1305 ttl: Duration::from_secs(30),
1306 };
1307 let matched = router.route(msg);
1308 assert_eq!(matched, 0);
1309 assert_eq!(router.dead_letter_count(), 0);
1310
1311 let snap = router.stats.snapshot();
1312 assert_eq!(snap.total_expired_dropped, 1);
1313 assert_eq!(snap.total_dead_lettered, 0);
1314 }
1315
1316 #[test]
1318 fn test_dead_letter_bounded() {
1319 let router = MessageRouter::new();
1320
1321 for _ in 0..=MAX_DEAD_LETTER + 10 {
1322 router.route(make_msg("orphan", MessagePriority::Normal));
1323 }
1324
1325 assert_eq!(router.dead_letter_count(), MAX_DEAD_LETTER);
1326 }
1327
1328 #[test]
1330 fn test_handler_count() {
1331 let router = MessageRouter::new();
1332 assert_eq!(router.handler_count(), 0);
1333
1334 router
1335 .register_handler(reg("a", MessagePriority::Normal, "h1"))
1336 .expect("register h1");
1337 router
1338 .register_handler(reg("b", MessagePriority::Normal, "h2"))
1339 .expect("register h2");
1340 assert_eq!(router.handler_count(), 2);
1341
1342 router.unregister_handler("h1");
1343 assert_eq!(router.handler_count(), 1);
1344 }
1345
1346 #[test]
1348 fn test_handlers_sorted_by_priority() {
1349 let router = MessageRouter::new();
1350 router
1351 .register_handler(reg("t", MessagePriority::Low, "low"))
1352 .expect("register low");
1353 router
1354 .register_handler(reg("t", MessagePriority::Critical, "critical"))
1355 .expect("register critical");
1356 router
1357 .register_handler(reg("t", MessagePriority::Normal, "normal"))
1358 .expect("register normal");
1359
1360 let handlers = router.handlers.read().expect("lock");
1361 assert_eq!(handlers[0].handler_id, "critical");
1362 assert_eq!(handlers[1].handler_id, "normal");
1363 assert_eq!(handlers[2].handler_id, "low");
1364 }
1365
1366 #[test]
1368 fn test_drain_unknown_handler_empty() {
1369 let router = MessageRouter::new();
1370 assert!(router.drain_for_handler("nonexistent").is_empty());
1371 }
1372
1373 #[test]
1375 fn test_message_priority_ordering() {
1376 assert!(MessagePriority::Critical > MessagePriority::High);
1377 assert!(MessagePriority::High > MessagePriority::Normal);
1378 assert!(MessagePriority::Normal > MessagePriority::Low);
1379 }
1380
1381 #[test]
1383 fn test_next_id_monotonic() {
1384 let router = MessageRouter::new();
1385 let id0 = router.next_id();
1386 let id1 = router.next_id();
1387 let id2 = router.next_id();
1388 assert!(id0 < id1);
1389 assert!(id1 < id2);
1390 }
1391}