Skip to main content

ipfrs_network/
message_router.rs

1//! MessageRouter — Priority-based message dispatch with dead-letter queue.
2//!
3//! Routes incoming [`RoutedMessage`]s to registered [`HandlerRegistration`]s using
4//! exact-match or wildcard topic patterns.  Each handler maintains its own
5//! `BinaryHeap`-backed priority queue.  Messages that match no handler are
6//! forwarded to a bounded dead-letter queue.
7//!
8//! ## Design highlights
9//!
10//! - **Priority dispatch**: handlers are stored sorted by priority descending so
11//!   higher-priority handlers are always evaluated first.
12//! - **Wildcard matching**: supports exact match, prefix wildcard (`block.*`), and
13//!   catch-all (`*`).
14//! - **Dead-letter queue**: bounded at 100 items; oldest entries are evicted when
15//!   full.
16//! - **Atomic statistics**: all counters use `AtomicU64` for lock-free reads.
17//! - **No `unwrap()`**: all fallible operations surface errors explicitly.
18
19use 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
29// ─── Constants ────────────────────────────────────────────────────────────────
30
31/// Maximum number of items retained in the dead-letter queue.
32const MAX_DEAD_LETTER: usize = 100;
33
34/// Default message TTL.
35const DEFAULT_TTL: Duration = Duration::from_secs(30);
36
37// ─── Errors ───────────────────────────────────────────────────────────────────
38
39/// Errors that can be returned by [`MessageRouter`].
40#[derive(Error, Debug, Clone, PartialEq, Eq)]
41pub enum RouterError {
42    /// A handler with the same `handler_id` is already registered.
43    #[error("Duplicate handler: {0}")]
44    DuplicateHandler(String),
45
46    /// No handler with the given `handler_id` exists.
47    #[error("Handler not found: {0}")]
48    HandlerNotFound(String),
49}
50
51// ─── MessagePriority ──────────────────────────────────────────────────────────
52
53/// Message priority levels.
54///
55/// `Critical` is the highest priority.  The integer discriminants (3 > 2 > 1 > 0)
56/// drive the derived `Ord` implementation so that `Critical > High > Normal > Low`.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
58#[repr(u8)]
59pub enum MessagePriority {
60    /// Lowest priority — background / best-effort.
61    Low = 0,
62    /// Normal priority — default.
63    #[default]
64    Normal = 1,
65    /// High priority — time-sensitive.
66    High = 2,
67    /// Critical priority — must be processed immediately.
68    Critical = 3,
69}
70
71// ─── RoutedMessage ────────────────────────────────────────────────────────────
72
73/// A message travelling through the router.
74#[derive(Debug, Clone)]
75pub struct RoutedMessage {
76    /// Monotonically increasing message identifier assigned by the router.
77    pub id: u64,
78    /// Originating peer identifier.
79    pub from_peer: String,
80    /// Topic this message belongs to.
81    pub topic: String,
82    /// Raw payload bytes.
83    pub payload: Vec<u8>,
84    /// Priority of this message.
85    pub priority: MessagePriority,
86    /// Wall-clock time at which the message was enqueued.
87    pub enqueued_at: Instant,
88    /// How long the message remains valid after `enqueued_at`.
89    pub ttl: Duration,
90}
91
92impl RoutedMessage {
93    /// Create a new [`RoutedMessage`] with the default TTL of 30 s.
94    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    /// Returns `true` when this message has exceeded its TTL relative to `now`.
113    pub fn is_expired(&self, now: Instant) -> bool {
114        now.saturating_duration_since(self.enqueued_at) >= self.ttl
115    }
116}
117
118// ─── HandlerRegistration ─────────────────────────────────────────────────────
119
120/// Describes a handler that the [`MessageRouter`] should dispatch messages to.
121#[derive(Debug, Clone)]
122pub struct HandlerRegistration {
123    /// Topic pattern used for matching.
124    ///
125    /// - `"blocks"` — exact match only.
126    /// - `"block.*"` — prefix match: matches any topic whose prefix before `*` is `"block."`.
127    /// - `"*"` — matches every topic.
128    pub topic_pattern: String,
129
130    /// Handler priority; higher-priority handlers are stored first and called first
131    /// when multiple handlers share a topic.
132    pub priority: MessagePriority,
133
134    /// Unique name for this handler.
135    pub handler_id: String,
136}
137
138impl HandlerRegistration {
139    /// Returns `true` if `topic` matches this registration's `topic_pattern`.
140    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// ─── QueuedMessage ────────────────────────────────────────────────────────────
156
157/// Private wrapper that adds `Ord` to [`RoutedMessage`] so it can live in a
158/// `BinaryHeap`.
159///
160/// Ordering: higher [`MessagePriority`] → larger; for equal priority, *earlier*
161/// `enqueued_at` → larger (FIFO within the same priority level).
162#[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        // Higher priority → greater
182        let pri = self.0.priority.cmp(&other.0.priority);
183        if pri != Ordering::Equal {
184            return pri;
185        }
186        // Earlier enqueued_at → greater (FIFO within same priority)
187        other.0.enqueued_at.cmp(&self.0.enqueued_at)
188    }
189}
190
191// ─── RouterStats ──────────────────────────────────────────────────────────────
192
193/// Atomic statistics counters for a [`MessageRouter`].
194#[derive(Debug, Default)]
195pub struct RouterStats {
196    /// Total messages submitted to `route()`.
197    pub total_routed: AtomicU64,
198    /// Total messages matched to at least one handler.
199    pub total_matched: AtomicU64,
200    /// Total messages forwarded to the dead-letter queue.
201    pub total_dead_lettered: AtomicU64,
202    /// Total messages dropped because they were already expired on arrival.
203    pub total_expired_dropped: AtomicU64,
204}
205
206/// Snapshot of [`RouterStats`] at a point in time.
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub struct RouterStatsSnapshot {
209    /// Total messages submitted to `route()`.
210    pub total_routed: u64,
211    /// Total messages matched to at least one handler.
212    pub total_matched: u64,
213    /// Total messages forwarded to the dead-letter queue.
214    pub total_dead_lettered: u64,
215    /// Total messages dropped because they were already expired on arrival.
216    pub total_expired_dropped: u64,
217}
218
219impl RouterStats {
220    /// Returns a consistent snapshot of all counters.
221    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
231// ─── MessageRouter ────────────────────────────────────────────────────────────
232
233/// Priority-based message router with per-handler queues and a dead-letter queue.
234///
235/// # Thread safety
236///
237/// All public methods are safe to call from multiple threads simultaneously.
238/// Handlers are protected by a `RwLock`; per-handler queues and the dead-letter
239/// queue are protected by a `Mutex`.
240pub struct MessageRouter {
241    /// Registered handlers, kept sorted by priority descending.
242    handlers: RwLock<Vec<HandlerRegistration>>,
243
244    /// Per-handler priority queues keyed by `handler_id`.
245    queues: Mutex<HashMap<String, BinaryHeap<QueuedMessage>>>,
246
247    /// Unmatched messages; bounded at [`MAX_DEAD_LETTER`].
248    dead_letter: Mutex<VecDeque<RoutedMessage>>,
249
250    /// Monotonically increasing message-ID source.
251    next_id: AtomicU64,
252
253    /// Aggregate statistics.
254    pub stats: Arc<RouterStats>,
255}
256
257impl Default for MessageRouter {
258    fn default() -> Self {
259        Self::new()
260    }
261}
262
263impl MessageRouter {
264    /// Create a new, empty [`MessageRouter`].
265    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    /// Allocate the next monotonic message ID.
276    pub fn next_id(&self) -> u64 {
277        self.next_id.fetch_add(1, AOrdering::Relaxed)
278    }
279
280    // ── Handler management ───────────────────────────────────────────────────
281
282    /// Register a new handler.
283    ///
284    /// Returns [`RouterError::DuplicateHandler`] if a handler with the same
285    /// `handler_id` is already registered.
286    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        // Keep sorted: highest priority first.
295        // sort_by_key with Reverse gives descending priority order.
296        handlers.sort_by_key(|h| std::cmp::Reverse(h.priority));
297
298        // Ensure a queue slot exists for this handler.
299        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        // Re-insert after sort; the handler we just pushed may have moved.
310        // Rebuild queue slots for all known handlers.
311        for h in handlers.iter() {
312            queues.entry(h.handler_id.clone()).or_default();
313        }
314
315        Ok(())
316    }
317
318    /// Unregister a handler by `handler_id`.
319    ///
320    /// This is a no-op if the handler does not exist (no error is returned so
321    /// callers can safely call this during shutdown without worrying about race
322    /// conditions with double-unregister).
323    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        // Leave the queue in place so in-flight messages are not lost; callers
329        // should drain before unregistering if they care about ordering.
330    }
331
332    /// Return the number of currently registered handlers.
333    pub fn handler_count(&self) -> usize {
334        self.handlers
335            .read()
336            .unwrap_or_else(|e| e.into_inner())
337            .len()
338    }
339
340    // ── Routing ───────────────────────────────────────────────────────────────
341
342    /// Route `msg` to all matching handlers.
343    ///
344    /// Returns the number of handlers the message was dispatched to.  When no
345    /// handler matches, the message is pushed onto the dead-letter queue (evicting
346    /// the oldest entry if the queue is full).
347    ///
348    /// Messages that are already expired on arrival are counted as
349    /// `total_expired_dropped` and discarded immediately.
350    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    // ── Message retrieval ────────────────────────────────────────────────────
396
397    /// Drain all queued messages for `handler_id` in priority order (highest first).
398    ///
399    /// Returns an empty `Vec` if the handler has no queued messages or is unknown.
400    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    // ── Dead-letter queue ────────────────────────────────────────────────────
416
417    /// Return the current number of messages in the dead-letter queue.
418    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    /// Drain and return all messages from the dead-letter queue.
426    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// ═══════════════════════════════════════════════════════════════════════════════
433// PeerMessageRouter — subscription-aware peer message router
434// ═══════════════════════════════════════════════════════════════════════════════
435
436/// Classification of a message moving through the [`PeerMessageRouter`].
437#[derive(Debug, Clone, PartialEq, Eq, Hash)]
438pub enum MessageType {
439    /// Request for a specific block.
440    BlockRequest,
441    /// Response carrying block data.
442    BlockResponse,
443    /// Request for a validity proof.
444    ProofRequest,
445    /// Response carrying proof data.
446    ProofResponse,
447    /// Gossip message on a named topic.
448    Gossip { topic: String },
449    /// Control-plane message (e.g., keep-alive, disconnect).
450    Control,
451}
452
453/// A message that has been routed (or queued for routing) by [`PeerMessageRouter`].
454#[derive(Debug, Clone)]
455pub struct PeerRoutedMessage {
456    /// Monotonically increasing message identifier assigned by the router.
457    pub msg_id: u64,
458    /// Type (and topic, for gossip) of the message.
459    pub msg_type: MessageType,
460    /// Originating peer identifier.
461    pub from_peer: String,
462    /// Destination peer identifier; `None` means the message was broadcast.
463    pub to_peer: Option<String>,
464    /// Size of the raw payload in bytes.
465    pub payload_bytes: u64,
466    /// Logical clock tick at which the message was created.
467    pub created_at_tick: u64,
468}
469
470/// A rule that influences how the [`PeerMessageRouter`] dispatches messages.
471///
472/// Rules are evaluated in descending priority order.  The first matching rule
473/// determines whether/how the message is forwarded.
474#[derive(Debug, Clone)]
475pub struct RouteRule {
476    /// Unique identifier for this rule.
477    pub rule_id: u64,
478    /// The [`MessageType`] that this rule matches.
479    ///
480    /// For [`MessageType::Gossip`], the topic field is ignored — any gossip
481    /// message matches regardless of topic.
482    pub match_type: MessageType,
483    /// When `Some`, the rule only applies when the destination peer equals this
484    /// value.  `None` means the rule applies to any peer.
485    pub target_peer: Option<String>,
486    /// Higher-priority rules are evaluated first.
487    pub priority: u32,
488}
489
490/// Aggregate statistics for a [`PeerMessageRouter`].
491#[derive(Debug, Clone, Default, PartialEq, Eq)]
492pub struct MessageRouterStats {
493    /// Total messages routed directly (to_peer was Some).
494    pub total_routed: u64,
495    /// Total messages broadcast (to_peer was None and at least one recipient
496    /// was found).
497    pub total_broadcast: u64,
498    /// Total messages dropped because no recipient could be determined.
499    pub total_dropped: u64,
500}
501
502// ─── Helpers ──────────────────────────────────────────────────────────────────
503
504/// Returns `true` when `a` and `b` are the same variant, ignoring inner data.
505fn 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        // Gossip matches any Gossip regardless of topic.
512        (MessageType::Gossip { .. }, MessageType::Gossip { .. }) => true,
513        (MessageType::Control, MessageType::Control) => true,
514        _ => false,
515    }
516}
517
518// ─── PeerMessageRouter ────────────────────────────────────────────────────────
519
520/// Routes messages to appropriate peer handlers based on message type and topic
521/// subscriptions, supporting both direct and broadcast routing.
522///
523/// # Routing semantics
524///
525/// - **Direct** (`to_peer = Some`): a single [`PeerRoutedMessage`] is produced
526///   addressed to the specified peer.
527/// - **Broadcast** (`to_peer = None`):
528///   - [`MessageType::Gossip`]: delivered to all peers subscribed to the gossip
529///     topic, excluding `from_peer`.
530///   - All other types: delivered to all peers that appear as keys in the
531///     subscription map *and* match at least one rule with `target_peer = None`.
532///   - If no recipients are found the message is counted as dropped.
533pub struct PeerMessageRouter {
534    /// Routing rules kept sorted by priority descending.
535    pub rules: Vec<RouteRule>,
536    /// Topic → list of subscribed peer IDs.
537    pub subscriptions: HashMap<String, Vec<String>>,
538    /// Next message ID to assign (post-increment semantics).
539    pub next_msg_id: u64,
540    /// Aggregate statistics.
541    pub stats: MessageRouterStats,
542}
543
544impl Default for PeerMessageRouter {
545    fn default() -> Self {
546        Self::new()
547    }
548}
549
550impl PeerMessageRouter {
551    /// Create a new, empty [`PeerMessageRouter`].
552    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    // ── Rule management ───────────────────────────────────────────────────────
562
563    /// Insert `rule`, maintaining the invariant that `self.rules` is sorted by
564    /// `priority` descending.
565    pub fn add_rule(&mut self, rule: RouteRule) {
566        // Binary-search for the insertion position so we stay O(n) in the worst
567        // case but avoid a full sort on every insertion.
568        let pos = self.rules.partition_point(|r| r.priority >= rule.priority);
569        self.rules.insert(pos, rule);
570    }
571
572    /// Remove the rule with the given `rule_id`.
573    ///
574    /// Returns `true` if a rule was found and removed, `false` otherwise.
575    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    // ── Subscription management ───────────────────────────────────────────────
582
583    /// Subscribe `peer_id` to `topic`.
584    ///
585    /// If the peer is already subscribed this is a no-op.
586    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    /// Unsubscribe `peer_id` from `topic`.
595    ///
596    /// Returns `true` if the peer was subscribed, `false` if it was not.
597    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    // ── Routing ───────────────────────────────────────────────────────────────
607
608    /// Route a message, returning one [`PeerRoutedMessage`] per recipient.
609    ///
610    /// `msg_id` is assigned from `next_msg_id` (pre-increment — the assigned
611    /// value is used, then `next_msg_id` is incremented).
612    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            // ── Direct routing ────────────────────────────────────────────────
625            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            // ── Broadcast routing ─────────────────────────────────────────────
638            None => {
639                let recipients: Vec<String> = match &msg_type {
640                    MessageType::Gossip { topic } => {
641                        // Route to all subscribers of the specific topic, excluding
642                        // the originating peer.
643                        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                        // For non-gossip broadcasts: find all peers (keys of
656                        // subscriptions) that match at least one rule with
657                        // target_peer = None.
658                        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                            // Collect unique peer IDs across all topics.
665                            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    // ── Query helpers ─────────────────────────────────────────────────────────
701
702    /// Return all peer IDs subscribed to `topic`, sorted lexicographically.
703    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    /// Return a reference to the current statistics.
714    pub fn stats(&self) -> &MessageRouterStats {
715        &self.stats
716    }
717}
718
719// ─── Tests (PeerMessageRouter) ────────────────────────────────────────────────
720
721#[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    // 1. new() starts empty
735    #[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    // 2. add_rule maintains priority order (descending)
745    #[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    // 3. remove_rule returns true when found
758    #[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    // 4. remove_rule returns false when not found
767    #[test]
768    fn test_remove_rule_not_found() {
769        let mut router = PeerMessageRouter::new();
770        assert!(!router.remove_rule(99));
771    }
772
773    // 5. subscribe adds peer to topic
774    #[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    // 6. subscribe is idempotent (no duplicates)
783    #[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    // 7. unsubscribe removes peer and returns true
792    #[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    // 8. unsubscribe returns false when peer is not subscribed
802    #[test]
803    fn test_unsubscribe_not_subscribed() {
804        let mut router = PeerMessageRouter::new();
805        assert!(!router.unsubscribe("nobody", "news"));
806    }
807
808    // 9. unsubscribe returns false on unknown topic
809    #[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    // 10. route direct (to_peer Some) creates exactly one message
817    #[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    // 11. route direct increments total_routed
832    #[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    // 12. route broadcast Gossip goes to topic subscribers
842    #[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    // 13. route broadcast Gossip excludes from_peer
861    #[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    // 14. route broadcast Gossip with no other subscribers drops message
881    #[test]
882    fn test_route_broadcast_gossip_no_subscribers_dropped() {
883        let mut router = PeerMessageRouter::new();
884        // Only the sender is subscribed; result should be empty.
885        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    // 15. route broadcast no subscribers at all drops message
901    #[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    // 16. route broadcast non-gossip with matching wildcard rule routes to all peers
910    #[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    // 17. route broadcast increments total_broadcast (not total_routed)
923    #[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    // 18. route dropped increments total_dropped
942    #[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    // 19. msg_id is monotonically increasing across calls
951    #[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    // 20. PeerRoutedMessage fields set correctly for direct route
962    #[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    // 21. subscribers() returns sorted list
976    #[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    // 22. multiple subscribers all receive broadcast
988    #[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    // 23. stats total_routed and total_broadcast are independent
1007    #[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); // direct
1013        router.route(
1014            MessageType::Gossip {
1015                topic: "t".to_string(),
1016            },
1017            "s",
1018            None,
1019            0,
1020            0,
1021        ); // broadcast
1022
1023        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    // 24. remove_rule only removes the matching rule
1030    #[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    // 25. default() matches new()
1043    #[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// ─── Tests ────────────────────────────────────────────────────────────────────
1053
1054#[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    // 1. Register and route exact match
1080    #[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    // 2. Exact match does NOT match a different topic
1093    #[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    // 3. Prefix wildcard "block.*" matching
1106    #[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    // 4. Catch-all "*" matches every topic
1128    #[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    // 5. Unmatched message goes to dead-letter queue
1148    #[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    // 6. drain_for_handler returns correct messages
1159    #[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        // Queue is now empty
1173        assert!(router.drain_for_handler("h1").is_empty());
1174    }
1175
1176    // 7. Priority ordering within handler queue
1177    #[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        // Should come out Critical, High, Normal, Low
1201        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    // 8. Multiple handlers for the same topic all receive the message
1208    #[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    // 9. Duplicate handler registration returns an error
1230    #[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    // 10. drain_dead_letter clears the queue
1242    #[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    // 11. Stats accumulation
1255    #[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)); // matched
1263        router.route(make_msg("other", MessagePriority::Normal)); // dead-lettered
1264
1265        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    // 12. Expired message detection
1273    #[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    // 13. Non-expired message
1288    #[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    // 14. Expired message on arrival is dropped (not dead-lettered)
1295    #[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    // 15. Dead-letter queue is bounded at MAX_DEAD_LETTER
1317    #[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    // 16. handler_count reflects registrations and unregistrations
1329    #[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    // 17. Priority ordering of handlers (higher-priority handler stored first)
1347    #[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    // 18. drain_for_handler on unknown handler returns empty vec
1367    #[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    // 19. MessagePriority ordering
1374    #[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    // 20. next_id is monotonically increasing
1382    #[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}