1use std::collections::{HashMap, VecDeque};
17
18pub fn fnv1a_64(data: &[u8]) -> u64 {
27 const FNV_OFFSET: u64 = 14_695_981_039_346_656_037;
28 const FNV_PRIME: u64 = 1_099_511_628_211;
29 let mut hash = FNV_OFFSET;
30 for &byte in data {
31 hash ^= u64::from(byte);
32 hash = hash.wrapping_mul(FNV_PRIME);
33 }
34 hash
35}
36
37fn fnv1a_strings(parts: &[&str]) -> u64 {
39 const FNV_OFFSET: u64 = 14_695_981_039_346_656_037;
40 const FNV_PRIME: u64 = 1_099_511_628_211;
41 let mut hash = FNV_OFFSET;
42 for part in parts {
43 for &byte in part.as_bytes() {
44 hash ^= u64::from(byte);
45 hash = hash.wrapping_mul(FNV_PRIME);
46 }
47 }
48 hash
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub struct MessageTopic(pub String);
56
57impl MessageTopic {
58 pub fn new(topic: impl Into<String>) -> Self {
60 Self(topic.into())
61 }
62
63 pub fn as_str(&self) -> &str {
65 &self.0
66 }
67}
68
69impl std::fmt::Display for MessageTopic {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.write_str(&self.0)
72 }
73}
74
75#[derive(Debug, Clone)]
82pub struct RoutingMessage {
83 pub id: u64,
85 pub topic: MessageTopic,
87 pub payload_size: usize,
89 pub sender: String,
91 pub timestamp: u64,
93 pub ttl: u8,
96 pub priority: u8,
98}
99
100impl RoutingMessage {
101 pub fn new(
103 topic: MessageTopic,
104 payload_size: usize,
105 sender: impl Into<String>,
106 timestamp: u64,
107 ttl: u8,
108 priority: u8,
109 ) -> Self {
110 let sender = sender.into();
111 let ts_str = timestamp.to_string();
112 let id = fnv1a_strings(&[sender.as_str(), topic.as_str(), ts_str.as_str()]);
113 Self {
114 id,
115 topic,
116 payload_size,
117 sender,
118 timestamp,
119 ttl,
120 priority,
121 }
122 }
123}
124
125#[derive(Debug, Clone)]
132pub enum SubscriptionFilter {
133 All,
135 BySender(String),
137 ByPriority { min: u8 },
139 BySize { max_bytes: usize },
141 And(Box<SubscriptionFilter>, Box<SubscriptionFilter>),
143 Or(Box<SubscriptionFilter>, Box<SubscriptionFilter>),
145}
146
147impl SubscriptionFilter {
148 pub fn and(left: SubscriptionFilter, right: SubscriptionFilter) -> Self {
150 Self::And(Box::new(left), Box::new(right))
151 }
152
153 pub fn or(left: SubscriptionFilter, right: SubscriptionFilter) -> Self {
155 Self::Or(Box::new(left), Box::new(right))
156 }
157}
158
159#[derive(Debug, Clone)]
165pub struct Subscription {
166 pub id: u64,
168 pub peer_id: String,
170 pub topic: MessageTopic,
172 pub filter: SubscriptionFilter,
174 pub created_at: u64,
176 pub message_count: u64,
178}
179
180impl Subscription {
181 pub fn new(
183 peer_id: impl Into<String>,
184 topic: MessageTopic,
185 filter: SubscriptionFilter,
186 created_at: u64,
187 ) -> Self {
188 let peer_id = peer_id.into();
189 let ts_str = created_at.to_string();
190 let id = fnv1a_strings(&[peer_id.as_str(), topic.as_str(), ts_str.as_str()]);
191 Self {
192 id,
193 peer_id,
194 topic,
195 filter,
196 created_at,
197 message_count: 0,
198 }
199 }
200}
201
202#[derive(Debug, Clone)]
206pub struct DeliveryRecord {
207 pub message_id: u64,
209 pub subscription_id: u64,
211 pub delivered_at: u64,
213 pub success: bool,
215}
216
217#[derive(Debug, Clone, PartialEq)]
224pub struct SubRouterStats {
225 pub total_subscriptions: usize,
227 pub total_routed: u64,
229 pub total_delivered: u64,
231 pub total_dropped: u64,
233 pub delivery_rate: f64,
235 pub topics: usize,
237}
238
239pub struct SubscriptionRouter {
258 subscriptions: HashMap<u64, Subscription>,
259 delivery_log: VecDeque<DeliveryRecord>,
260 max_log_size: usize,
261 pub total_routed: u64,
263 pub total_delivered: u64,
265 pub total_dropped: u64,
267}
268
269impl SubscriptionRouter {
270 pub fn new(max_log_size: usize) -> Self {
273 Self {
274 subscriptions: HashMap::new(),
275 delivery_log: VecDeque::new(),
276 max_log_size,
277 total_routed: 0,
278 total_delivered: 0,
279 total_dropped: 0,
280 }
281 }
282
283 pub fn subscribe(
290 &mut self,
291 peer_id: String,
292 topic: MessageTopic,
293 filter: SubscriptionFilter,
294 now: u64,
295 ) -> u64 {
296 let sub = Subscription::new(peer_id, topic, filter, now);
297 let id = sub.id;
298 self.subscriptions.insert(id, sub);
299 id
300 }
301
302 pub fn unsubscribe(&mut self, subscription_id: u64) -> bool {
307 self.subscriptions.remove(&subscription_id).is_some()
308 }
309
310 pub fn route(&mut self, message: &RoutingMessage, now: u64) -> Vec<u64> {
325 self.total_routed = self.total_routed.saturating_add(1);
326
327 if message.ttl == 0 {
329 self.total_dropped = self.total_dropped.saturating_add(1);
330 self.push_delivery_record(DeliveryRecord {
331 message_id: message.id,
332 subscription_id: 0,
333 delivered_at: now,
334 success: false,
335 });
336 return Vec::new();
337 }
338
339 let matching: Vec<u64> = self
341 .subscriptions
342 .values()
343 .filter(|sub| Self::matches_static(sub, message))
344 .map(|sub| sub.id)
345 .collect();
346
347 if matching.is_empty() {
348 self.total_dropped = self.total_dropped.saturating_add(1);
349 self.push_delivery_record(DeliveryRecord {
350 message_id: message.id,
351 subscription_id: 0,
352 delivered_at: now,
353 success: false,
354 });
355 return Vec::new();
356 }
357
358 for &sub_id in &matching {
360 if let Some(sub) = self.subscriptions.get_mut(&sub_id) {
361 sub.message_count = sub.message_count.saturating_add(1);
362 }
363 self.total_delivered = self.total_delivered.saturating_add(1);
364 self.push_delivery_record(DeliveryRecord {
365 message_id: message.id,
366 subscription_id: sub_id,
367 delivered_at: now,
368 success: true,
369 });
370 }
371
372 matching
373 }
374
375 fn push_delivery_record(&mut self, record: DeliveryRecord) {
378 if self.max_log_size == 0 {
379 return;
380 }
381 if self.delivery_log.len() >= self.max_log_size {
382 self.delivery_log.pop_front();
383 }
384 self.delivery_log.push_back(record);
385 }
386
387 pub fn matches(sub: &Subscription, msg: &RoutingMessage) -> bool {
394 Self::matches_static(sub, msg)
395 }
396
397 fn matches_static(sub: &Subscription, msg: &RoutingMessage) -> bool {
400 if sub.topic != msg.topic {
401 return false;
402 }
403 Self::filter_matches(&sub.filter, msg)
404 }
405
406 pub fn filter_matches(filter: &SubscriptionFilter, msg: &RoutingMessage) -> bool {
410 match filter {
411 SubscriptionFilter::All => true,
412 SubscriptionFilter::BySender(sender) => &msg.sender == sender,
413 SubscriptionFilter::ByPriority { min } => msg.priority >= *min,
414 SubscriptionFilter::BySize { max_bytes } => msg.payload_size <= *max_bytes,
415 SubscriptionFilter::And(left, right) => {
416 Self::filter_matches(left, msg) && Self::filter_matches(right, msg)
417 }
418 SubscriptionFilter::Or(left, right) => {
419 Self::filter_matches(left, msg) || Self::filter_matches(right, msg)
420 }
421 }
422 }
423
424 pub fn subscriptions_for_topic(&self, topic: &MessageTopic) -> Vec<&Subscription> {
428 self.subscriptions
429 .values()
430 .filter(|sub| &sub.topic == topic)
431 .collect()
432 }
433
434 pub fn subscriptions_for_peer(&self, peer_id: &str) -> Vec<&Subscription> {
436 self.subscriptions
437 .values()
438 .filter(|sub| sub.peer_id == peer_id)
439 .collect()
440 }
441
442 pub fn delivery_rate(&self, since: u64) -> f64 {
446 let relevant: Vec<&DeliveryRecord> = self
447 .delivery_log
448 .iter()
449 .filter(|r| r.delivered_at >= since)
450 .collect();
451
452 if relevant.is_empty() {
453 return 1.0;
454 }
455
456 let delivered = relevant.iter().filter(|r| r.success).count() as f64;
457 let total = relevant.len() as f64;
458 delivered / total
459 }
460
461 pub fn recent_deliveries(&self, n: usize) -> Vec<&DeliveryRecord> {
465 let len = self.delivery_log.len();
466 let skip = len.saturating_sub(n);
467 self.delivery_log.iter().skip(skip).collect()
468 }
469
470 pub fn evict_stale_subscriptions(&mut self, max_age_ms: u64, now: u64) -> usize {
475 let before = self.subscriptions.len();
476 self.subscriptions.retain(|_, sub| {
477 sub.message_count > 0 || now.saturating_sub(sub.created_at) <= max_age_ms
479 });
480 before - self.subscriptions.len()
481 }
482
483 pub fn stats(&self, now: u64) -> SubRouterStats {
486 let topics: std::collections::HashSet<&MessageTopic> =
487 self.subscriptions.values().map(|s| &s.topic).collect();
488
489 SubRouterStats {
490 total_subscriptions: self.subscriptions.len(),
491 total_routed: self.total_routed,
492 total_delivered: self.total_delivered,
493 total_dropped: self.total_dropped,
494 delivery_rate: self.delivery_rate(now),
495 topics: topics.len(),
496 }
497 }
498}
499
500#[cfg(test)]
503mod subscription_router_tests {
504 use crate::subscription_router::{
505 MessageTopic, RoutingMessage, Subscription, SubscriptionFilter, SubscriptionRouter,
506 };
507
508 fn topic(s: &str) -> MessageTopic {
511 MessageTopic::new(s)
512 }
513
514 fn msg(
515 topic: &str,
516 sender: &str,
517 payload_size: usize,
518 priority: u8,
519 ttl: u8,
520 timestamp: u64,
521 ) -> RoutingMessage {
522 RoutingMessage::new(
523 MessageTopic::new(topic),
524 payload_size,
525 sender,
526 timestamp,
527 ttl,
528 priority,
529 )
530 }
531
532 fn all_filter() -> SubscriptionFilter {
533 SubscriptionFilter::All
534 }
535
536 #[test]
540 fn test_new_starts_empty() {
541 let router = SubscriptionRouter::new(100);
542 assert_eq!(router.total_routed, 0);
543 assert_eq!(router.total_delivered, 0);
544 assert_eq!(router.total_dropped, 0);
545 let s = router.stats(0);
546 assert_eq!(s.total_subscriptions, 0);
547 assert_eq!(s.topics, 0);
548 }
549
550 #[test]
552 fn test_new_zero_log_size() {
553 let mut router = SubscriptionRouter::new(0);
554 let m = msg("t", "alice", 10, 5, 1, 100);
555 router.subscribe("peer".to_string(), topic("t"), all_filter(), 0);
556 router.route(&m, 0);
557 assert!(router.recent_deliveries(10).is_empty());
559 }
560
561 #[test]
565 fn test_subscribe_returns_nonzero_id() {
566 let mut router = SubscriptionRouter::new(100);
567 let id = router.subscribe("alice".to_string(), topic("news"), all_filter(), 1000);
568 assert_ne!(id, 0);
569 }
570
571 #[test]
573 fn test_subscribe_distinct_ids() {
574 let mut router = SubscriptionRouter::new(100);
575 let id1 = router.subscribe("alice".to_string(), topic("news"), all_filter(), 1000);
576 let id2 = router.subscribe("bob".to_string(), topic("news"), all_filter(), 1000);
577 assert_ne!(id1, id2);
578 }
579
580 #[test]
582 fn test_unsubscribe_returns_true() {
583 let mut router = SubscriptionRouter::new(100);
584 let id = router.subscribe("alice".to_string(), topic("news"), all_filter(), 1000);
585 assert!(router.unsubscribe(id));
586 }
587
588 #[test]
590 fn test_unsubscribe_returns_false() {
591 let mut router = SubscriptionRouter::new(100);
592 assert!(!router.unsubscribe(999_999));
593 }
594
595 #[test]
597 fn test_subscription_count_after_unsubscribe() {
598 let mut router = SubscriptionRouter::new(100);
599 let id = router.subscribe("alice".to_string(), topic("news"), all_filter(), 1000);
600 assert_eq!(router.stats(0).total_subscriptions, 1);
601 router.unsubscribe(id);
602 assert_eq!(router.stats(0).total_subscriptions, 0);
603 }
604
605 #[test]
609 fn test_ttl_zero_dropped() {
610 let mut router = SubscriptionRouter::new(100);
611 router.subscribe("alice".to_string(), topic("t"), all_filter(), 0);
612 let m = msg("t", "sender", 10, 5, 0, 1000);
613 let delivered = router.route(&m, 1000);
614 assert!(delivered.is_empty());
615 assert_eq!(router.total_dropped, 1);
616 assert_eq!(router.total_delivered, 0);
617 }
618
619 #[test]
621 fn test_ttl_zero_leaves_failed_record() {
622 let mut router = SubscriptionRouter::new(100);
623 let m = msg("t", "sender", 10, 5, 0, 1000);
624 router.route(&m, 2000);
625 let records = router.recent_deliveries(10);
626 assert_eq!(records.len(), 1);
627 assert!(!records[0].success);
628 }
629
630 #[test]
634 fn test_basic_delivery() {
635 let mut router = SubscriptionRouter::new(100);
636 let id = router.subscribe("alice".to_string(), topic("news"), all_filter(), 0);
637 let m = msg("news", "bob", 100, 10, 3, 1000);
638 let delivered = router.route(&m, 1000);
639 assert_eq!(delivered, vec![id]);
640 assert_eq!(router.total_delivered, 1);
641 }
642
643 #[test]
645 fn test_multiple_subscribers_delivery() {
646 let mut router = SubscriptionRouter::new(100);
647 let id1 = router.subscribe("alice".to_string(), topic("news"), all_filter(), 0);
648 let id2 = router.subscribe("bob".to_string(), topic("news"), all_filter(), 0);
649 let m = msg("news", "carol", 50, 5, 1, 1000);
650 let mut delivered = router.route(&m, 1000);
651 delivered.sort_unstable();
652 let mut expected = vec![id1, id2];
653 expected.sort_unstable();
654 assert_eq!(delivered, expected);
655 assert_eq!(router.total_delivered, 2);
656 }
657
658 #[test]
660 fn test_wrong_topic_not_delivered() {
661 let mut router = SubscriptionRouter::new(100);
662 router.subscribe("alice".to_string(), topic("sports"), all_filter(), 0);
663 let m = msg("news", "bob", 50, 5, 1, 1000);
664 let delivered = router.route(&m, 1000);
665 assert!(delivered.is_empty());
666 assert_eq!(router.total_dropped, 1);
667 }
668
669 #[test]
671 fn test_message_count_increments() {
672 let mut router = SubscriptionRouter::new(100);
673 let id = router.subscribe("alice".to_string(), topic("t"), all_filter(), 0);
674 let m = msg("t", "bob", 10, 1, 1, 100);
675 router.route(&m, 100);
676 router.route(&m, 200);
677 let sub = router.subscriptions.get(&id).expect("sub exists");
678 assert_eq!(sub.message_count, 2);
679 }
680
681 #[test]
685 fn test_by_sender_accepts() {
686 let mut router = SubscriptionRouter::new(100);
687 let filter = SubscriptionFilter::BySender("alice".to_string());
688 router.subscribe("peer".to_string(), topic("t"), filter, 0);
689 let m = msg("t", "alice", 10, 5, 1, 100);
690 let delivered = router.route(&m, 100);
691 assert_eq!(delivered.len(), 1);
692 }
693
694 #[test]
696 fn test_by_sender_rejects() {
697 let mut router = SubscriptionRouter::new(100);
698 let filter = SubscriptionFilter::BySender("alice".to_string());
699 router.subscribe("peer".to_string(), topic("t"), filter, 0);
700 let m = msg("t", "bob", 10, 5, 1, 100);
701 let delivered = router.route(&m, 100);
702 assert!(delivered.is_empty());
703 }
704
705 #[test]
709 fn test_by_priority_accepts() {
710 let mut router = SubscriptionRouter::new(100);
711 router.subscribe(
712 "p".to_string(),
713 topic("t"),
714 SubscriptionFilter::ByPriority { min: 5 },
715 0,
716 );
717 let m = msg("t", "s", 10, 5, 1, 100);
718 assert_eq!(router.route(&m, 100).len(), 1);
719 }
720
721 #[test]
723 fn test_by_priority_rejects() {
724 let mut router = SubscriptionRouter::new(100);
725 router.subscribe(
726 "p".to_string(),
727 topic("t"),
728 SubscriptionFilter::ByPriority { min: 10 },
729 0,
730 );
731 let m = msg("t", "s", 10, 5, 1, 100);
732 assert!(router.route(&m, 100).is_empty());
733 }
734
735 #[test]
739 fn test_by_size_accepts() {
740 let mut router = SubscriptionRouter::new(100);
741 router.subscribe(
742 "p".to_string(),
743 topic("t"),
744 SubscriptionFilter::BySize { max_bytes: 100 },
745 0,
746 );
747 let m = msg("t", "s", 100, 5, 1, 100);
748 assert_eq!(router.route(&m, 100).len(), 1);
749 }
750
751 #[test]
753 fn test_by_size_rejects() {
754 let mut router = SubscriptionRouter::new(100);
755 router.subscribe(
756 "p".to_string(),
757 topic("t"),
758 SubscriptionFilter::BySize { max_bytes: 99 },
759 0,
760 );
761 let m = msg("t", "s", 100, 5, 1, 100);
762 assert!(router.route(&m, 100).is_empty());
763 }
764
765 #[test]
769 fn test_and_filter_both_must_pass() {
770 let filter = SubscriptionFilter::and(
771 SubscriptionFilter::BySender("alice".to_string()),
772 SubscriptionFilter::ByPriority { min: 5 },
773 );
774 let mut router = SubscriptionRouter::new(100);
775 router.subscribe("p".to_string(), topic("t"), filter, 0);
776
777 let m_ok = msg("t", "alice", 10, 7, 1, 100);
779 assert_eq!(router.route(&m_ok, 100).len(), 1);
780
781 let m_bad_pri = msg("t", "alice", 10, 3, 1, 200);
783 assert!(router.route(&m_bad_pri, 200).is_empty());
784
785 let m_bad_sender = msg("t", "bob", 10, 7, 1, 300);
787 assert!(router.route(&m_bad_sender, 300).is_empty());
788 }
789
790 #[test]
792 fn test_or_filter_either_can_pass() {
793 let filter = SubscriptionFilter::or(
794 SubscriptionFilter::BySender("alice".to_string()),
795 SubscriptionFilter::ByPriority { min: 200 },
796 );
797 let mut router = SubscriptionRouter::new(100);
798 router.subscribe("p".to_string(), topic("t"), filter, 0);
799
800 let m_sender = msg("t", "alice", 10, 5, 1, 100);
802 assert_eq!(router.route(&m_sender, 100).len(), 1);
803
804 let m_prio = msg("t", "carol", 10, 200, 1, 200);
806 assert_eq!(router.route(&m_prio, 200).len(), 1);
807
808 let m_none = msg("t", "dave", 10, 50, 1, 300);
810 assert!(router.route(&m_none, 300).is_empty());
811 }
812
813 #[test]
817 fn test_subscriptions_for_topic() {
818 let mut router = SubscriptionRouter::new(100);
819 router.subscribe("alice".to_string(), topic("news"), all_filter(), 0);
820 router.subscribe("bob".to_string(), topic("news"), all_filter(), 1);
821 router.subscribe("carol".to_string(), topic("sports"), all_filter(), 2);
822
823 let news_subs = router.subscriptions_for_topic(&topic("news"));
824 assert_eq!(news_subs.len(), 2);
825
826 let sports_subs = router.subscriptions_for_topic(&topic("sports"));
827 assert_eq!(sports_subs.len(), 1);
828 }
829
830 #[test]
832 fn test_subscriptions_for_topic_empty() {
833 let router = SubscriptionRouter::new(100);
834 assert!(router.subscriptions_for_topic(&topic("unknown")).is_empty());
835 }
836
837 #[test]
841 fn test_subscriptions_for_peer() {
842 let mut router = SubscriptionRouter::new(100);
843 router.subscribe("alice".to_string(), topic("news"), all_filter(), 0);
844 router.subscribe("alice".to_string(), topic("sports"), all_filter(), 1);
845 router.subscribe("bob".to_string(), topic("news"), all_filter(), 2);
846
847 let alice_subs = router.subscriptions_for_peer("alice");
848 assert_eq!(alice_subs.len(), 2);
849
850 let bob_subs = router.subscriptions_for_peer("bob");
851 assert_eq!(bob_subs.len(), 1);
852 }
853
854 #[test]
858 fn test_delivery_rate_no_records() {
859 let router = SubscriptionRouter::new(100);
860 assert!((router.delivery_rate(0) - 1.0).abs() < f64::EPSILON);
861 }
862
863 #[test]
865 fn test_delivery_rate_all_success() {
866 let mut router = SubscriptionRouter::new(100);
867 router.subscribe("p".to_string(), topic("t"), all_filter(), 0);
868 let m = msg("t", "s", 10, 1, 1, 100);
869 router.route(&m, 100);
870 router.route(&m, 200);
871 let rate = router.delivery_rate(0);
873 assert!((rate - 1.0).abs() < f64::EPSILON);
874 }
875
876 #[test]
878 fn test_delivery_rate_all_failures() {
879 let mut router = SubscriptionRouter::new(100);
880 let m = msg("t", "s", 10, 1, 1, 100);
882 router.route(&m, 100);
883 router.route(&m, 200);
884 let rate = router.delivery_rate(0);
885 assert!((rate - 0.0).abs() < f64::EPSILON);
886 }
887
888 #[test]
890 fn test_delivery_rate_since_filter() {
891 let mut router = SubscriptionRouter::new(100);
892 let m = msg("t", "s", 10, 1, 1, 100);
894 router.route(&m, 50); router.subscribe("p".to_string(), topic("t"), all_filter(), 0);
897 let m2 = msg("t", "s", 10, 1, 1, 200);
898 router.route(&m2, 200); let rate = router.delivery_rate(100);
901 assert!((rate - 1.0).abs() < f64::EPSILON);
902 }
903
904 #[test]
908 fn test_recent_deliveries() {
909 let mut router = SubscriptionRouter::new(100);
910 for ts in 0..5u64 {
912 let m = msg("t", "s", 10, 1, 1, ts * 100);
913 router.route(&m, ts * 100);
914 }
915 let recent = router.recent_deliveries(3);
916 assert_eq!(recent.len(), 3);
917 assert_eq!(recent[2].delivered_at, 400);
919 }
920
921 #[test]
923 fn test_recent_deliveries_all() {
924 let mut router = SubscriptionRouter::new(100);
925 let m = msg("t", "s", 10, 1, 1, 100);
926 router.route(&m, 100);
927 let recent = router.recent_deliveries(1000);
928 assert_eq!(recent.len(), 1);
929 }
930
931 #[test]
935 fn test_evict_stale_removes_old_unused() {
936 let mut router = SubscriptionRouter::new(100);
937 router.subscribe("alice".to_string(), topic("t"), all_filter(), 0);
939 let evicted = router.evict_stale_subscriptions(1000, 2000);
941 assert_eq!(evicted, 1);
942 assert_eq!(router.stats(2000).total_subscriptions, 0);
943 }
944
945 #[test]
947 fn test_evict_stale_keeps_young() {
948 let mut router = SubscriptionRouter::new(100);
949 router.subscribe("alice".to_string(), topic("t"), all_filter(), 1500);
950 let evicted = router.evict_stale_subscriptions(1000, 2000);
952 assert_eq!(evicted, 0);
953 assert_eq!(router.stats(2000).total_subscriptions, 1);
954 }
955
956 #[test]
958 fn test_evict_stale_keeps_active() {
959 let mut router = SubscriptionRouter::new(100);
960 let _id = router.subscribe("alice".to_string(), topic("t"), all_filter(), 0);
961 let m = msg("t", "s", 10, 1, 1, 100);
963 router.route(&m, 100);
964 let evicted = router.evict_stale_subscriptions(1000, 5000);
966 assert_eq!(evicted, 0);
967 }
968
969 #[test]
973 fn test_stats_topic_count() {
974 let mut router = SubscriptionRouter::new(100);
975 router.subscribe("alice".to_string(), topic("news"), all_filter(), 0);
976 router.subscribe("bob".to_string(), topic("sports"), all_filter(), 0);
977 router.subscribe("carol".to_string(), topic("news"), all_filter(), 0);
978 let s = router.stats(0);
979 assert_eq!(s.topics, 2);
980 assert_eq!(s.total_subscriptions, 3);
981 }
982
983 #[test]
985 fn test_stats_total_routed() {
986 let mut router = SubscriptionRouter::new(100);
987 let m = msg("t", "s", 10, 1, 1, 100);
988 router.route(&m, 100);
989 router.route(&m, 200);
990 assert_eq!(router.stats(0).total_routed, 2);
991 }
992
993 #[test]
997 fn test_delivery_log_capped() {
998 let cap = 5;
999 let mut router = SubscriptionRouter::new(cap);
1000 let m = msg("t", "s", 10, 1, 1, 100);
1001 for _ in 0..20 {
1002 router.route(&m, 100);
1003 }
1004 assert_eq!(router.recent_deliveries(100).len(), cap);
1005 }
1006
1007 #[test]
1011 fn test_routing_message_id_derived() {
1012 let m1 = RoutingMessage::new(topic("t"), 10, "alice", 100, 1, 5);
1013 let m2 = RoutingMessage::new(topic("t"), 10, "alice", 100, 1, 5);
1014 assert_eq!(m1.id, m2.id);
1015
1016 let m3 = RoutingMessage::new(topic("t"), 10, "bob", 100, 1, 5);
1017 assert_ne!(m1.id, m3.id);
1018 }
1019
1020 #[test]
1022 fn test_subscription_id_derived() {
1023 let s1 = Subscription::new("alice", topic("t"), SubscriptionFilter::All, 1000);
1024 let s2 = Subscription::new("alice", topic("t"), SubscriptionFilter::All, 1000);
1025 assert_eq!(s1.id, s2.id);
1026
1027 let s3 = Subscription::new("bob", topic("t"), SubscriptionFilter::All, 1000);
1028 assert_ne!(s1.id, s3.id);
1029 }
1030
1031 #[test]
1033 fn test_message_topic_display() {
1034 let t = MessageTopic::new("hello");
1035 assert_eq!(t.as_str(), "hello");
1036 assert_eq!(t.to_string(), "hello");
1037 }
1038
1039 #[test]
1041 fn test_delivery_record_fields_on_success() {
1042 let mut router = SubscriptionRouter::new(100);
1043 router.subscribe("alice".to_string(), topic("t"), all_filter(), 0);
1044 let m = msg("t", "bob", 10, 5, 1, 999);
1045 router.route(&m, 999);
1046 let records = router.recent_deliveries(10);
1047 assert_eq!(records.len(), 1);
1048 let r = records[0];
1049 assert_eq!(r.message_id, m.id);
1050 assert!(r.success);
1051 assert_eq!(r.delivered_at, 999);
1052 }
1053
1054 #[test]
1058 fn test_fnv1a_consistent() {
1059 use crate::subscription_router::fnv1a_64;
1060 let h1 = fnv1a_64(b"hello");
1061 let h2 = fnv1a_64(b"hello");
1062 assert_eq!(h1, h2);
1063 let h3 = fnv1a_64(b"world");
1064 assert_ne!(h1, h3);
1065 }
1066
1067 #[test]
1069 fn test_and_filter_deeply_nested() {
1070 let filter = SubscriptionFilter::and(
1071 SubscriptionFilter::And(
1072 Box::new(SubscriptionFilter::ByPriority { min: 1 }),
1073 Box::new(SubscriptionFilter::BySize { max_bytes: 500 }),
1074 ),
1075 SubscriptionFilter::BySender("trusted".to_string()),
1076 );
1077 let m = RoutingMessage::new(topic("t"), 100, "trusted", 100, 1, 10);
1078 assert!(SubscriptionRouter::filter_matches(&filter, &m));
1079
1080 let m_bad = RoutingMessage::new(topic("t"), 1000, "trusted", 100, 1, 10);
1081 assert!(!SubscriptionRouter::filter_matches(&filter, &m_bad));
1082 }
1083
1084 #[test]
1086 fn test_or_filter_both_fail() {
1087 let filter = SubscriptionFilter::or(
1088 SubscriptionFilter::BySender("alice".to_string()),
1089 SubscriptionFilter::ByPriority { min: 200 },
1090 );
1091 let m = RoutingMessage::new(topic("t"), 10, "dave", 100, 1, 50);
1092 assert!(!SubscriptionRouter::filter_matches(&filter, &m));
1093 }
1094}