1use std::collections::HashMap;
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
36#[repr(u8)]
37pub enum MessagePriority {
38 Urgent = 0,
40 Normal = 1,
42 Background = 2,
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct QueuedMessage {
51 pub message_id: u64,
53 pub peer_id: String,
55 pub priority: MessagePriority,
57 pub payload_bytes: u64,
59 pub enqueued_at_tick: u64,
61}
62
63#[derive(Clone, Debug)]
67pub struct QueueConfig {
68 pub max_total_bytes: u64,
70 pub max_per_peer_messages: usize,
73 pub urgent_weight: u32,
75 pub normal_weight: u32,
77 pub background_weight: u32,
79}
80
81impl Default for QueueConfig {
82 fn default() -> Self {
83 Self {
84 max_total_bytes: 10_485_760, max_per_peer_messages: 100,
86 urgent_weight: 4,
87 normal_weight: 2,
88 background_weight: 1,
89 }
90 }
91}
92
93#[derive(Clone, Debug, Default)]
97pub struct QueueStats {
98 pub total_enqueued: u64,
100 pub total_dequeued: u64,
102 pub total_dropped: u64,
104 pub current_bytes: u64,
106 pub current_message_count: usize,
108 pub by_priority: HashMap<MessagePriority, usize>,
110}
111
112pub struct PeerPriorityQueue {
119 pub urgent: Vec<QueuedMessage>,
121 pub normal: Vec<QueuedMessage>,
123 pub background: Vec<QueuedMessage>,
125 pub peer_counts: HashMap<String, usize>,
127 pub current_bytes: u64,
129 pub config: QueueConfig,
131 pub stats: QueueStats,
133}
134
135impl PeerPriorityQueue {
136 pub fn new(config: QueueConfig) -> Self {
138 Self {
139 urgent: Vec::new(),
140 normal: Vec::new(),
141 background: Vec::new(),
142 peer_counts: HashMap::new(),
143 current_bytes: 0,
144 config,
145 stats: QueueStats::default(),
146 }
147 }
148
149 pub fn enqueue(&mut self, msg: QueuedMessage) -> bool {
155 let would_exceed_bytes = self
157 .current_bytes
158 .checked_add(msg.payload_bytes)
159 .is_none_or(|total| total > self.config.max_total_bytes);
160
161 let peer_count = self
162 .peer_counts
163 .get(msg.peer_id.as_str())
164 .copied()
165 .unwrap_or(0);
166 let would_exceed_peer = peer_count >= self.config.max_per_peer_messages;
167
168 if would_exceed_bytes || would_exceed_peer {
169 self.stats.total_dropped += 1;
170 return false;
171 }
172
173 self.current_bytes += msg.payload_bytes;
175 *self.peer_counts.entry(msg.peer_id.clone()).or_insert(0) += 1;
176
177 *self.stats.by_priority.entry(msg.priority).or_insert(0) += 1;
179
180 self.stats.total_enqueued += 1;
181 self.stats.current_bytes = self.current_bytes;
182 self.stats.current_message_count += 1;
183
184 match msg.priority {
185 MessagePriority::Urgent => self.urgent.push(msg),
186 MessagePriority::Normal => self.normal.push(msg),
187 MessagePriority::Background => self.background.push(msg),
188 }
189
190 true
191 }
192
193 pub fn dequeue(&mut self) -> Option<QueuedMessage> {
198 let msg = if !self.urgent.is_empty() {
200 self.urgent.remove(0)
201 } else if !self.normal.is_empty() {
202 self.normal.remove(0)
203 } else if !self.background.is_empty() {
204 self.background.remove(0)
205 } else {
206 return None;
207 };
208
209 self.current_bytes = self.current_bytes.saturating_sub(msg.payload_bytes);
211
212 let peer_count = self.peer_counts.entry(msg.peer_id.clone()).or_insert(0);
215 if *peer_count > 0 {
216 *peer_count -= 1;
217 }
218 if *peer_count == 0 {
219 self.peer_counts.remove(&msg.peer_id);
220 }
221
222 if let Some(cnt) = self.stats.by_priority.get_mut(&msg.priority) {
224 *cnt = cnt.saturating_sub(1);
225 }
226
227 self.stats.total_dequeued += 1;
228 self.stats.current_bytes = self.current_bytes;
229 self.stats.current_message_count = self.stats.current_message_count.saturating_sub(1);
230
231 Some(msg)
232 }
233
234 pub fn dequeue_n(&mut self, n: usize) -> Vec<QueuedMessage> {
236 let mut out = Vec::with_capacity(n);
237 for _ in 0..n {
238 match self.dequeue() {
239 Some(msg) => out.push(msg),
240 None => break,
241 }
242 }
243 out
244 }
245
246 pub fn len(&self) -> usize {
248 self.urgent.len() + self.normal.len() + self.background.len()
249 }
250
251 pub fn is_empty(&self) -> bool {
253 self.urgent.is_empty() && self.normal.is_empty() && self.background.is_empty()
254 }
255
256 pub fn stats(&self) -> &QueueStats {
258 &self.stats
259 }
260
261 pub fn remove_peer(&mut self, peer_id: &str) -> usize {
266 let mut removed = 0usize;
267
268 macro_rules! drain_queue {
270 ($queue:expr) => {{
271 let before = $queue.len();
272 let mut freed_bytes = 0u64;
273 let mut by_prio: HashMap<MessagePriority, usize> = HashMap::new();
274
275 $queue.retain(|msg| {
276 if msg.peer_id == peer_id {
277 freed_bytes += msg.payload_bytes;
278 *by_prio.entry(msg.priority).or_insert(0) += 1;
279 false
280 } else {
281 true
282 }
283 });
284
285 let count = before - $queue.len();
286 removed += count;
287 self.current_bytes = self.current_bytes.saturating_sub(freed_bytes);
288
289 for (prio, n) in by_prio {
290 if let Some(c) = self.stats.by_priority.get_mut(&prio) {
291 *c = c.saturating_sub(n);
292 }
293 }
294 }};
295 }
296
297 drain_queue!(self.urgent);
298 drain_queue!(self.normal);
299 drain_queue!(self.background);
300
301 self.peer_counts.remove(peer_id);
303
304 self.stats.current_bytes = self.current_bytes;
306 self.stats.current_message_count = self.stats.current_message_count.saturating_sub(removed);
307
308 removed
309 }
310}
311
312#[cfg(test)]
315mod tests {
316 use super::*;
317
318 fn default_queue() -> PeerPriorityQueue {
321 PeerPriorityQueue::new(QueueConfig::default())
322 }
323
324 fn msg(id: u64, peer: &str, priority: MessagePriority, bytes: u64) -> QueuedMessage {
325 QueuedMessage {
326 message_id: id,
327 peer_id: peer.to_string(),
328 priority,
329 payload_bytes: bytes,
330 enqueued_at_tick: id,
331 }
332 }
333
334 #[test]
337 fn enqueue_urgent_goes_to_urgent_queue() {
338 let mut q = default_queue();
339 assert!(q.enqueue(msg(1, "peer-a", MessagePriority::Urgent, 100)));
340 assert_eq!(q.urgent.len(), 1);
341 assert_eq!(q.normal.len(), 0);
342 assert_eq!(q.background.len(), 0);
343 }
344
345 #[test]
346 fn enqueue_normal_goes_to_normal_queue() {
347 let mut q = default_queue();
348 assert!(q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 100)));
349 assert_eq!(q.normal.len(), 1);
350 assert_eq!(q.urgent.len(), 0);
351 assert_eq!(q.background.len(), 0);
352 }
353
354 #[test]
355 fn enqueue_background_goes_to_background_queue() {
356 let mut q = default_queue();
357 assert!(q.enqueue(msg(1, "peer-a", MessagePriority::Background, 100)));
358 assert_eq!(q.background.len(), 1);
359 assert_eq!(q.urgent.len(), 0);
360 assert_eq!(q.normal.len(), 0);
361 }
362
363 #[test]
364 fn enqueue_increments_stats() {
365 let mut q = default_queue();
366 q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 256));
367 assert_eq!(q.stats().total_enqueued, 1);
368 assert_eq!(q.stats().current_message_count, 1);
369 assert_eq!(q.stats().current_bytes, 256);
370 }
371
372 #[test]
375 fn enqueue_drops_when_byte_budget_exceeded() {
376 let config = QueueConfig {
377 max_total_bytes: 500,
378 ..Default::default()
379 };
380 let mut q = PeerPriorityQueue::new(config);
381
382 assert!(q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 500)));
384 assert!(!q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 1)));
386 assert_eq!(q.stats().total_dropped, 1);
387 assert_eq!(q.len(), 1);
388 }
389
390 #[test]
391 fn enqueue_accepts_when_byte_budget_exactly_full() {
392 let config = QueueConfig {
393 max_total_bytes: 1000,
394 ..Default::default()
395 };
396 let mut q = PeerPriorityQueue::new(config);
397 assert!(q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 600)));
399 assert!(q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 400)));
400 assert_eq!(q.len(), 2);
401 assert_eq!(q.stats().total_dropped, 0);
402 }
403
404 #[test]
405 fn enqueue_byte_budget_recovers_after_dequeue() {
406 let config = QueueConfig {
407 max_total_bytes: 500,
408 ..Default::default()
409 };
410 let mut q = PeerPriorityQueue::new(config);
411 assert!(q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 400)));
412 assert!(!q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 200)));
414 let _ = q.dequeue();
416 assert!(q.enqueue(msg(3, "peer-a", MessagePriority::Normal, 200)));
418 assert_eq!(q.len(), 1);
419 }
420
421 #[test]
424 fn enqueue_drops_when_per_peer_limit_exceeded() {
425 let config = QueueConfig {
426 max_per_peer_messages: 3,
427 max_total_bytes: u64::MAX,
428 ..Default::default()
429 };
430 let mut q = PeerPriorityQueue::new(config);
431 assert!(q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 10)));
432 assert!(q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 10)));
433 assert!(q.enqueue(msg(3, "peer-a", MessagePriority::Normal, 10)));
434 assert!(!q.enqueue(msg(4, "peer-a", MessagePriority::Normal, 10)));
436 assert_eq!(q.stats().total_dropped, 1);
437 }
438
439 #[test]
440 fn per_peer_limit_is_per_peer_not_global() {
441 let config = QueueConfig {
442 max_per_peer_messages: 2,
443 max_total_bytes: u64::MAX,
444 ..Default::default()
445 };
446 let mut q = PeerPriorityQueue::new(config);
447 assert!(q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 10)));
448 assert!(q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 10)));
449 assert!(!q.enqueue(msg(3, "peer-a", MessagePriority::Normal, 10)));
451 assert!(q.enqueue(msg(4, "peer-b", MessagePriority::Normal, 10)));
452 assert_eq!(q.stats().total_dropped, 1);
453 assert_eq!(q.len(), 3);
454 }
455
456 #[test]
459 fn dequeue_returns_urgent_before_normal() {
460 let mut q = default_queue();
461 q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 10));
462 q.enqueue(msg(2, "peer-a", MessagePriority::Urgent, 10));
463 let first = q.dequeue().expect("should have a message");
464 assert_eq!(first.priority, MessagePriority::Urgent);
465 }
466
467 #[test]
468 fn dequeue_returns_urgent_before_background() {
469 let mut q = default_queue();
470 q.enqueue(msg(1, "peer-a", MessagePriority::Background, 10));
471 q.enqueue(msg(2, "peer-a", MessagePriority::Urgent, 10));
472 let first = q.dequeue().expect("should have a message");
473 assert_eq!(first.priority, MessagePriority::Urgent);
474 }
475
476 #[test]
477 fn dequeue_returns_normal_when_urgent_empty() {
478 let mut q = default_queue();
479 q.enqueue(msg(1, "peer-a", MessagePriority::Background, 10));
480 q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 10));
481 let first = q.dequeue().expect("should have a message");
482 assert_eq!(first.priority, MessagePriority::Normal);
483 }
484
485 #[test]
486 fn dequeue_returns_background_last() {
487 let mut q = default_queue();
488 q.enqueue(msg(1, "peer-a", MessagePriority::Background, 10));
489 q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 10));
490 q.enqueue(msg(3, "peer-a", MessagePriority::Urgent, 10));
491 let m1 = q.dequeue().expect("msg");
492 let m2 = q.dequeue().expect("msg");
493 let m3 = q.dequeue().expect("msg");
494 assert_eq!(m1.priority, MessagePriority::Urgent);
495 assert_eq!(m2.priority, MessagePriority::Normal);
496 assert_eq!(m3.priority, MessagePriority::Background);
497 }
498
499 #[test]
500 fn dequeue_returns_none_when_all_empty() {
501 let mut q = default_queue();
502 assert!(q.dequeue().is_none());
503 }
504
505 #[test]
506 fn dequeue_updates_stats() {
507 let mut q = default_queue();
508 q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 100));
509 let _ = q.dequeue();
510 assert_eq!(q.stats().total_dequeued, 1);
511 assert_eq!(q.stats().current_message_count, 0);
512 assert_eq!(q.stats().current_bytes, 0);
513 }
514
515 #[test]
518 fn fifo_ordering_within_urgent() {
519 let mut q = default_queue();
520 q.enqueue(msg(1, "peer-a", MessagePriority::Urgent, 10));
521 q.enqueue(msg(2, "peer-a", MessagePriority::Urgent, 10));
522 q.enqueue(msg(3, "peer-a", MessagePriority::Urgent, 10));
523 assert_eq!(
524 q.dequeue()
525 .expect("test: dequeue should return urgent message with id 1")
526 .message_id,
527 1
528 );
529 assert_eq!(
530 q.dequeue()
531 .expect("test: dequeue should return urgent message with id 2")
532 .message_id,
533 2
534 );
535 assert_eq!(
536 q.dequeue()
537 .expect("test: dequeue should return urgent message with id 3")
538 .message_id,
539 3
540 );
541 }
542
543 #[test]
544 fn fifo_ordering_within_normal() {
545 let mut q = default_queue();
546 for id in [10u64, 20, 30] {
547 q.enqueue(msg(id, "peer-b", MessagePriority::Normal, 5));
548 }
549 assert_eq!(
550 q.dequeue()
551 .expect("test: dequeue should return normal message with id 10")
552 .message_id,
553 10
554 );
555 assert_eq!(
556 q.dequeue()
557 .expect("test: dequeue should return normal message with id 20")
558 .message_id,
559 20
560 );
561 assert_eq!(
562 q.dequeue()
563 .expect("test: dequeue should return normal message with id 30")
564 .message_id,
565 30
566 );
567 }
568
569 #[test]
570 fn fifo_ordering_within_background() {
571 let mut q = default_queue();
572 for id in [100u64, 200, 300] {
573 q.enqueue(msg(id, "peer-c", MessagePriority::Background, 5));
574 }
575 assert_eq!(
576 q.dequeue()
577 .expect("test: dequeue should return background message with id 100")
578 .message_id,
579 100
580 );
581 assert_eq!(
582 q.dequeue()
583 .expect("test: dequeue should return background message with id 200")
584 .message_id,
585 200
586 );
587 assert_eq!(
588 q.dequeue()
589 .expect("test: dequeue should return background message with id 300")
590 .message_id,
591 300
592 );
593 }
594
595 #[test]
598 fn dequeue_n_returns_up_to_n() {
599 let mut q = default_queue();
600 for id in 1u64..=5 {
601 q.enqueue(msg(id, "peer-a", MessagePriority::Normal, 10));
602 }
603 let drained = q.dequeue_n(3);
604 assert_eq!(drained.len(), 3);
605 assert_eq!(q.len(), 2);
606 }
607
608 #[test]
609 fn dequeue_n_returns_fewer_when_queue_has_less() {
610 let mut q = default_queue();
611 q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 10));
612 q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 10));
613 let drained = q.dequeue_n(10);
614 assert_eq!(drained.len(), 2);
615 assert!(q.is_empty());
616 }
617
618 #[test]
619 fn dequeue_n_zero_returns_empty_vec() {
620 let mut q = default_queue();
621 q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 10));
622 let drained = q.dequeue_n(0);
623 assert!(drained.is_empty());
624 assert_eq!(q.len(), 1);
625 }
626
627 #[test]
630 fn remove_peer_removes_all_messages_for_peer() {
631 let mut q = default_queue();
632 q.enqueue(msg(1, "peer-a", MessagePriority::Urgent, 10));
633 q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 20));
634 q.enqueue(msg(3, "peer-a", MessagePriority::Background, 30));
635 q.enqueue(msg(4, "peer-b", MessagePriority::Normal, 40));
636
637 let removed = q.remove_peer("peer-a");
638 assert_eq!(removed, 3);
639 assert_eq!(q.len(), 1);
640 assert_eq!(
641 q.dequeue()
642 .expect("test: dequeue should return peer-b message after remove_peer")
643 .peer_id,
644 "peer-b"
645 );
646 }
647
648 #[test]
649 fn remove_peer_updates_current_bytes() {
650 let mut q = default_queue();
651 q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 300));
652 q.enqueue(msg(2, "peer-b", MessagePriority::Normal, 200));
653 q.remove_peer("peer-a");
654 assert_eq!(q.current_bytes, 200);
655 assert_eq!(q.stats().current_bytes, 200);
656 }
657
658 #[test]
659 fn remove_peer_for_unknown_peer_returns_zero() {
660 let mut q = default_queue();
661 q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 10));
662 let removed = q.remove_peer("peer-x");
663 assert_eq!(removed, 0);
664 assert_eq!(q.len(), 1);
665 }
666
667 #[test]
670 fn by_priority_tracks_enqueued_counts() {
671 let mut q = default_queue();
672 q.enqueue(msg(1, "peer-a", MessagePriority::Urgent, 10));
673 q.enqueue(msg(2, "peer-a", MessagePriority::Urgent, 10));
674 q.enqueue(msg(3, "peer-a", MessagePriority::Normal, 10));
675 let stats = q.stats();
676 assert_eq!(
677 stats
678 .by_priority
679 .get(&MessagePriority::Urgent)
680 .copied()
681 .unwrap_or(0),
682 2
683 );
684 assert_eq!(
685 stats
686 .by_priority
687 .get(&MessagePriority::Normal)
688 .copied()
689 .unwrap_or(0),
690 1
691 );
692 assert_eq!(
693 stats
694 .by_priority
695 .get(&MessagePriority::Background)
696 .copied()
697 .unwrap_or(0),
698 0
699 );
700 }
701
702 #[test]
703 fn by_priority_decrements_on_dequeue() {
704 let mut q = default_queue();
705 q.enqueue(msg(1, "peer-a", MessagePriority::Urgent, 10));
706 q.enqueue(msg(2, "peer-a", MessagePriority::Urgent, 10));
707 q.dequeue();
708 let stats = q.stats();
709 assert_eq!(
710 stats
711 .by_priority
712 .get(&MessagePriority::Urgent)
713 .copied()
714 .unwrap_or(0),
715 1
716 );
717 }
718
719 #[test]
722 fn current_bytes_increases_on_enqueue() {
723 let mut q = default_queue();
724 q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 128));
725 q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 256));
726 assert_eq!(q.current_bytes, 384);
727 }
728
729 #[test]
730 fn current_bytes_decreases_on_dequeue() {
731 let mut q = default_queue();
732 q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 512));
733 q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 256));
734 q.dequeue();
735 assert_eq!(q.current_bytes, 256);
736 }
737
738 #[test]
739 fn current_bytes_zero_after_all_dequeued() {
740 let mut q = default_queue();
741 q.enqueue(msg(1, "peer-a", MessagePriority::Urgent, 100));
742 q.enqueue(msg(2, "peer-b", MessagePriority::Normal, 200));
743 q.dequeue();
744 q.dequeue();
745 assert_eq!(q.current_bytes, 0);
746 }
747
748 #[test]
751 fn len_sums_all_sub_queues() {
752 let mut q = default_queue();
753 q.enqueue(msg(1, "peer-a", MessagePriority::Urgent, 10));
754 q.enqueue(msg(2, "peer-a", MessagePriority::Normal, 10));
755 q.enqueue(msg(3, "peer-a", MessagePriority::Background, 10));
756 assert_eq!(q.len(), 3);
757 }
758
759 #[test]
760 fn is_empty_true_when_no_messages() {
761 let q = default_queue();
762 assert!(q.is_empty());
763 }
764
765 #[test]
766 fn is_empty_false_after_enqueue() {
767 let mut q = default_queue();
768 q.enqueue(msg(1, "peer-a", MessagePriority::Normal, 10));
769 assert!(!q.is_empty());
770 }
771}