1use alloc::collections::BTreeMap;
10use alloc::vec::Vec;
11
12use super::types::{AirtimeProfile, InterfaceId, PacketBytes, TransportAction};
13use crate::constants;
14
15#[derive(Debug, Clone)]
17pub struct AnnounceQueueEntry {
18 pub destination_hash: [u8; 16],
20 pub time: f64,
22 pub hops: u8,
24 pub emitted: f64,
26 pub raw: PacketBytes,
28}
29
30#[derive(Debug, Clone)]
32pub struct InterfaceAnnounceQueue {
33 pub entries: Vec<AnnounceQueueEntry>,
35 pub announce_allowed_at: f64,
37}
38
39impl InterfaceAnnounceQueue {
40 pub fn new() -> Self {
41 InterfaceAnnounceQueue {
42 entries: Vec::new(),
43 announce_allowed_at: 0.0,
44 }
45 }
46
47 pub fn insert(&mut self, entry: AnnounceQueueEntry) {
51 if let Some(pos) = self
53 .entries
54 .iter()
55 .position(|e| e.destination_hash == entry.destination_hash)
56 {
57 let existing = &self.entries[pos];
58 if entry.hops < existing.hops
60 || (entry.hops == existing.hops && entry.emitted > existing.emitted)
61 {
62 self.entries[pos] = entry;
63 }
64 } else {
66 if self.entries.len() >= constants::MAX_QUEUED_ANNOUNCES {
68 self.entries.remove(0);
70 }
71 self.entries.push(entry);
72 }
73 }
74
75 pub fn remove_stale(&mut self, now: f64) {
77 self.entries
78 .retain(|e| now - e.time < constants::QUEUED_ANNOUNCE_LIFE);
79 }
80
81 pub fn select_next(&self) -> Option<usize> {
84 if self.entries.is_empty() {
85 return None;
86 }
87 let mut best_idx = 0;
88 let mut best_hops = self.entries[0].hops;
89 let mut best_time = self.entries[0].time;
90
91 for (i, entry) in self.entries.iter().enumerate().skip(1) {
92 if entry.hops < best_hops || (entry.hops == best_hops && entry.time < best_time) {
93 best_idx = i;
94 best_hops = entry.hops;
95 best_time = entry.time;
96 }
97 }
98 Some(best_idx)
99 }
100
101 pub fn is_allowed(&self, now: f64) -> bool {
103 now >= self.announce_allowed_at
104 }
105
106 pub fn calculate_next_allowed(
111 now: f64,
112 raw_len: usize,
113 bitrate: u64,
114 airtime_profile: Option<AirtimeProfile>,
115 announce_cap: f64,
116 ) -> f64 {
117 if announce_cap <= 0.0 {
118 return now; }
120
121 let time_to_send = airtime_profile
122 .map(|profile| profile.transmit_time_secs(raw_len))
123 .unwrap_or_else(|| {
124 if bitrate == 0 {
125 0.0
126 } else {
127 let bits = (raw_len * 8) as f64;
128 bits / (bitrate as f64)
129 }
130 });
131 if time_to_send <= 0.0 {
132 return now;
133 }
134 let delay = time_to_send / announce_cap;
135 now + delay
136 }
137}
138
139impl Default for InterfaceAnnounceQueue {
140 fn default() -> Self {
141 Self::new()
142 }
143}
144
145#[derive(Debug, Clone)]
147pub struct AnnounceQueues {
148 queues: BTreeMap<InterfaceId, InterfaceAnnounceQueue>,
149 max_interfaces: usize,
150 interface_cap_drops: u64,
151}
152
153impl AnnounceQueues {
154 pub fn new(max_interfaces: usize) -> Self {
155 AnnounceQueues {
156 queues: BTreeMap::new(),
157 max_interfaces,
158 interface_cap_drops: 0,
159 }
160 }
161
162 #[allow(clippy::too_many_arguments)]
167 pub fn gate_announce(
168 &mut self,
169 interface: InterfaceId,
170 raw: PacketBytes,
171 dest_hash: [u8; 16],
172 hops: u8,
173 emitted: f64,
174 now: f64,
175 bitrate: Option<u64>,
176 airtime_profile: Option<AirtimeProfile>,
177 announce_cap: f64,
178 ) -> Option<TransportAction> {
179 let bitrate = match bitrate {
181 Some(br) if br > 0 => br,
182 _ if airtime_profile.is_none() => {
183 return Some(TransportAction::SendOnInterface { interface, raw });
184 }
185 _ => 0,
186 };
187
188 if !self.queues.contains_key(&interface) && self.queues.len() >= self.max_interfaces {
189 self.interface_cap_drops = self.interface_cap_drops.saturating_add(1);
190 return None;
191 }
192
193 let queue = self.queues.entry(interface).or_default();
194
195 if queue.is_allowed(now) {
196 queue.announce_allowed_at = InterfaceAnnounceQueue::calculate_next_allowed(
198 now,
199 raw.len(),
200 bitrate,
201 airtime_profile,
202 announce_cap,
203 );
204 Some(TransportAction::SendOnInterface { interface, raw })
205 } else {
206 queue.insert(AnnounceQueueEntry {
208 destination_hash: dest_hash,
209 time: now,
210 hops,
211 emitted,
212 raw,
213 });
214 None
215 }
216 }
217
218 pub fn process_queues(
221 &mut self,
222 now: f64,
223 interfaces: &BTreeMap<InterfaceId, super::types::InterfaceInfo>,
224 ) -> Vec<TransportAction> {
225 let mut actions = Vec::new();
226 let mut empty_queues = Vec::new();
227
228 for (iface_id, queue) in self.queues.iter_mut() {
229 queue.remove_stale(now);
231
232 while queue.is_allowed(now) {
234 if let Some(idx) = queue.select_next() {
235 let entry = queue.entries.remove(idx);
236
237 let (bitrate, airtime_profile, announce_cap) =
239 if let Some(info) = interfaces.get(iface_id) {
240 (
241 info.bitrate.unwrap_or(0),
242 info.airtime_profile,
243 info.announce_cap,
244 )
245 } else {
246 (0, None, constants::ANNOUNCE_CAP)
247 };
248
249 if bitrate > 0 || airtime_profile.is_some() {
250 queue.announce_allowed_at = InterfaceAnnounceQueue::calculate_next_allowed(
251 now,
252 entry.raw.len(),
253 bitrate,
254 airtime_profile,
255 announce_cap,
256 );
257 } else {
258 queue.announce_allowed_at = now;
259 }
260
261 actions.push(TransportAction::SendOnInterface {
262 interface: *iface_id,
263 raw: entry.raw,
264 });
265 } else {
266 break;
267 }
268 }
269
270 if queue.entries.is_empty() {
271 empty_queues.push(*iface_id);
272 }
273 }
274
275 for iface_id in empty_queues {
276 self.queues.remove(&iface_id);
277 }
278
279 actions
280 }
281
282 pub fn blocks_recursive_path_request(&self, interface: InterfaceId, now: f64) -> bool {
284 self.queues
285 .get(&interface)
286 .is_some_and(|queue| !queue.entries.is_empty() || !queue.is_allowed(now))
287 }
288
289 pub fn reserve_recursive_path_request(
291 &mut self,
292 interface: InterfaceId,
293 raw_len: usize,
294 now: f64,
295 bitrate: Option<u64>,
296 airtime_profile: Option<AirtimeProfile>,
297 announce_cap: f64,
298 ) {
299 let bitrate = match bitrate {
300 Some(br) if br > 0 => br,
301 _ if airtime_profile.is_none() => return,
302 _ => 0,
303 };
304
305 if !self.queues.contains_key(&interface) && self.queues.len() >= self.max_interfaces {
306 self.interface_cap_drops = self.interface_cap_drops.saturating_add(1);
307 return;
308 }
309
310 let queue = self.queues.entry(interface).or_default();
311 queue.announce_allowed_at = InterfaceAnnounceQueue::calculate_next_allowed(
312 now,
313 raw_len,
314 bitrate,
315 airtime_profile,
316 announce_cap,
317 );
318 }
319
320 pub fn remove_interface(&mut self, interface: InterfaceId) -> bool {
322 self.queues.remove(&interface).is_some()
323 }
324
325 pub fn queue_count(&self) -> usize {
327 self.queues.len()
328 }
329
330 pub fn nonempty_queue_count(&self) -> usize {
332 self.queues
333 .values()
334 .filter(|queue| !queue.entries.is_empty())
335 .count()
336 }
337
338 pub fn total_queued_announces(&self) -> usize {
340 self.queues.values().map(|queue| queue.entries.len()).sum()
341 }
342
343 pub fn total_queued_bytes(&self) -> usize {
345 self.queues
346 .values()
347 .flat_map(|queue| queue.entries.iter())
348 .map(|entry| entry.raw.len())
349 .sum()
350 }
351
352 pub fn interface_cap_drop_count(&self) -> u64 {
354 self.interface_cap_drops
355 }
356
357 #[cfg(test)]
359 pub fn queue_for(&self, id: &InterfaceId) -> Option<&InterfaceAnnounceQueue> {
360 self.queues.get(id)
361 }
362}
363
364impl Default for AnnounceQueues {
365 fn default() -> Self {
366 Self::new(1024)
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373 use alloc::string::String;
374
375 fn make_entry(dest: u8, hops: u8, time: f64) -> AnnounceQueueEntry {
376 AnnounceQueueEntry {
377 destination_hash: [dest; 16],
378 time,
379 hops,
380 emitted: time,
381 raw: vec![0x01, 0x02, 0x03].into(),
382 }
383 }
384
385 fn make_interface_info(id: u64, bitrate: Option<u64>) -> super::super::types::InterfaceInfo {
386 super::super::types::InterfaceInfo {
387 id: InterfaceId(id),
388 name: String::from("test"),
389 mode: crate::constants::MODE_FULL,
390 recursive_prs: false,
391 announces_from_internal: true,
392 out_capable: true,
393 in_capable: true,
394 bitrate,
395 airtime_profile: None,
396 announce_rate_target: None,
397 announce_rate_grace: 0,
398 announce_rate_penalty: 0.0,
399 announce_cap: constants::ANNOUNCE_CAP,
400 is_local_client: false,
401 wants_tunnel: false,
402 tunnel_id: None,
403 mtu: constants::MTU as u32,
404 ingress_control: crate::transport::types::IngressControlConfig::disabled(),
405 ia_freq: 0.0,
406 ip_freq: 0.0,
407 op_freq: 0.0,
408 op_samples: 0,
409 started: 0.0,
410 }
411 }
412
413 #[test]
416 fn test_queue_entry_creation() {
417 let entry = make_entry(0xAA, 3, 1000.0);
418 assert_eq!(entry.hops, 3);
419 assert_eq!(entry.destination_hash, [0xAA; 16]);
420 }
421
422 #[test]
423 fn test_queue_insert_and_select() {
424 let mut queue = InterfaceAnnounceQueue::new();
425 queue.insert(make_entry(0x01, 3, 100.0));
426 queue.insert(make_entry(0x02, 1, 200.0));
427 queue.insert(make_entry(0x03, 2, 150.0));
428
429 let idx = queue.select_next().unwrap();
431 assert_eq!(queue.entries[idx].destination_hash, [0x02; 16]);
432 }
433
434 #[test]
435 fn test_queue_select_fifo_on_same_hops() {
436 let mut queue = InterfaceAnnounceQueue::new();
437 queue.insert(make_entry(0x01, 2, 200.0)); queue.insert(make_entry(0x02, 2, 100.0)); let idx = queue.select_next().unwrap();
442 assert_eq!(queue.entries[idx].destination_hash, [0x02; 16]);
443 }
444
445 #[test]
446 fn test_queue_dedup_update() {
447 let mut queue = InterfaceAnnounceQueue::new();
448 queue.insert(make_entry(0x01, 3, 100.0));
449 assert_eq!(queue.entries.len(), 1);
450
451 queue.insert(make_entry(0x01, 1, 200.0));
453 assert_eq!(queue.entries.len(), 1);
454 assert_eq!(queue.entries[0].hops, 1);
455
456 queue.insert(make_entry(0x01, 5, 300.0));
458 assert_eq!(queue.entries.len(), 1);
459 assert_eq!(queue.entries[0].hops, 1);
460 }
461
462 #[test]
463 fn test_queue_stale_removal() {
464 let mut queue = InterfaceAnnounceQueue::new();
465 queue.insert(make_entry(0x01, 1, 100.0));
466 queue.insert(make_entry(0x02, 2, 200.0));
467
468 queue.remove_stale(86501.0);
470 assert_eq!(queue.entries.len(), 1);
471 assert_eq!(queue.entries[0].destination_hash, [0x02; 16]);
472 }
473
474 #[test]
475 fn test_queue_max_size() {
476 let mut queue = InterfaceAnnounceQueue::new();
477 for i in 0..constants::MAX_QUEUED_ANNOUNCES {
478 queue.insert(AnnounceQueueEntry {
479 destination_hash: {
480 let mut d = [0u8; 16];
481 d[0] = (i >> 8) as u8;
482 d[1] = i as u8;
483 d
484 },
485 time: i as f64,
486 hops: 1,
487 emitted: i as f64,
488 raw: vec![0x01].into(),
489 });
490 }
491 assert_eq!(queue.entries.len(), constants::MAX_QUEUED_ANNOUNCES);
492
493 queue.insert(make_entry(0xFF, 1, 99999.0));
495 assert_eq!(queue.entries.len(), constants::MAX_QUEUED_ANNOUNCES);
496 }
497
498 #[test]
499 fn test_queue_empty_select() {
500 let queue = InterfaceAnnounceQueue::new();
501 assert!(queue.select_next().is_none());
502 }
503
504 #[test]
505 fn test_bandwidth_allowed() {
506 let mut queue = InterfaceAnnounceQueue::new();
507 assert!(queue.is_allowed(0.0));
508 assert!(queue.is_allowed(100.0));
509
510 queue.announce_allowed_at = 200.0;
511 assert!(!queue.is_allowed(100.0));
512 assert!(!queue.is_allowed(199.9));
513 assert!(queue.is_allowed(200.0));
514 assert!(queue.is_allowed(300.0));
515 }
516
517 #[test]
518 fn test_calculate_next_allowed() {
519 let next = InterfaceAnnounceQueue::calculate_next_allowed(1000.0, 100, 1000, None, 0.02);
523 assert!((next - 1040.0).abs() < 0.001);
524 }
525
526 #[test]
527 fn test_calculate_next_allowed_zero_bitrate() {
528 let next = InterfaceAnnounceQueue::calculate_next_allowed(1000.0, 100, 0, None, 0.02);
529 assert_eq!(next, 1000.0); }
531
532 #[test]
533 fn test_calculate_next_allowed_uses_lora_airtime() {
534 let profile = AirtimeProfile::Lora {
535 bandwidth: 125_000,
536 spreading_factor: 8,
537 coding_rate: 5,
538 preamble_symbols: 8,
539 explicit_header: true,
540 crc: true,
541 };
542
543 let next =
544 InterfaceAnnounceQueue::calculate_next_allowed(1000.0, 100, 0, Some(profile), 0.02);
545
546 assert!((next - 1015.3856).abs() < 0.0001);
550 }
551
552 #[test]
555 fn test_gate_announce_no_bitrate_immediate() {
556 let mut queues = AnnounceQueues::new(1024);
557 let result = queues.gate_announce(
558 InterfaceId(1),
559 vec![0x01, 0x02, 0x03].into(),
560 [0xAA; 16],
561 2,
562 1000.0,
563 1000.0,
564 None, None,
566 0.02,
567 );
568 assert!(result.is_some());
569 assert!(matches!(
570 result.unwrap(),
571 TransportAction::SendOnInterface { .. }
572 ));
573 }
574
575 #[test]
576 fn test_gate_announce_uses_airtime_profile_without_bitrate() {
577 let mut queues = AnnounceQueues::new(1024);
578 let profile = AirtimeProfile::Lora {
579 bandwidth: 125_000,
580 spreading_factor: 8,
581 coding_rate: 5,
582 preamble_symbols: 8,
583 explicit_header: true,
584 crc: true,
585 };
586
587 let first = queues.gate_announce(
588 InterfaceId(1),
589 vec![0x01; 100].into(),
590 [0xAA; 16],
591 2,
592 1000.0,
593 1000.0,
594 None,
595 Some(profile),
596 0.02,
597 );
598 assert!(first.is_some());
599
600 let queue = queues.queue_for(&InterfaceId(1)).unwrap();
601 assert!((queue.announce_allowed_at - 1015.3856).abs() < 0.0001);
602
603 let second = queues.gate_announce(
604 InterfaceId(1),
605 vec![0x02; 100].into(),
606 [0xBB; 16],
607 2,
608 1000.0,
609 1000.0,
610 None,
611 Some(profile),
612 0.02,
613 );
614 assert!(second.is_none());
615 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
616 }
617
618 #[test]
619 fn test_gate_announce_bandwidth_available() {
620 let mut queues = AnnounceQueues::new(1024);
621 let result = queues.gate_announce(
622 InterfaceId(1),
623 vec![0x01; 100].into(),
624 [0xBB; 16],
625 2,
626 1000.0,
627 1000.0,
628 Some(10000), None,
630 0.02,
631 );
632 assert!(result.is_some());
634
635 let queue = queues.queue_for(&InterfaceId(1)).unwrap();
637 assert!(queue.announce_allowed_at > 1000.0);
638 }
639
640 #[test]
641 fn test_gate_announce_bandwidth_exhausted_queues() {
642 let mut queues = AnnounceQueues::new(1024);
643
644 let r1 = queues.gate_announce(
646 InterfaceId(1),
647 vec![0x01; 100].into(),
648 [0xAA; 16],
649 2,
650 1000.0,
651 1000.0,
652 Some(1000), None,
654 0.02,
655 );
656 assert!(r1.is_some());
657
658 let r2 = queues.gate_announce(
660 InterfaceId(1),
661 vec![0x02; 100].into(),
662 [0xBB; 16],
663 3,
664 1000.0,
665 1000.0,
666 Some(1000),
667 None,
668 0.02,
669 );
670 assert!(r2.is_none()); let queue = queues.queue_for(&InterfaceId(1)).unwrap();
673 assert_eq!(queue.entries.len(), 1);
674 }
675
676 #[test]
677 fn test_process_queues_dequeues_when_allowed() {
678 let mut queues = AnnounceQueues::new(1024);
679
680 let _ = queues.gate_announce(
682 InterfaceId(1),
683 vec![0x01; 10].into(),
684 [0xAA; 16],
685 2,
686 0.0,
687 0.0,
688 Some(1000),
689 None,
690 0.02,
691 );
692 let _ = queues.gate_announce(
693 InterfaceId(1),
694 vec![0x02; 10].into(),
695 [0xBB; 16],
696 3,
697 0.0,
698 0.0,
699 Some(1000),
700 None,
701 0.02,
702 );
703
704 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
706
707 let mut interfaces = BTreeMap::new();
708 interfaces.insert(InterfaceId(1), make_interface_info(1, Some(1000)));
709
710 let allowed_at = queues
712 .queue_for(&InterfaceId(1))
713 .unwrap()
714 .announce_allowed_at;
715 let actions = queues.process_queues(allowed_at + 1.0, &interfaces);
716
717 assert_eq!(actions.len(), 1);
718 assert!(matches!(
719 &actions[0],
720 TransportAction::SendOnInterface { interface, .. } if *interface == InterfaceId(1)
721 ));
722
723 assert!(queues.queue_for(&InterfaceId(1)).is_none());
725 }
726
727 #[test]
728 fn test_local_announce_bypasses_cap() {
729 let mut queues = AnnounceQueues::new(1024);
733
734 let _ = queues.gate_announce(
736 InterfaceId(1),
737 vec![0x01; 100].into(),
738 [0xAA; 16],
739 2,
740 0.0,
741 0.0,
742 Some(1000),
743 None,
744 0.02,
745 );
746
747 let r = queues.gate_announce(
750 InterfaceId(1),
751 vec![0x02; 100].into(),
752 [0xBB; 16],
753 0,
754 0.0,
755 0.0,
756 Some(1000),
757 None,
758 0.02,
759 );
760 assert!(r.is_none()); }
762
763 #[test]
764 fn test_remove_interface_queue() {
765 let mut queues = AnnounceQueues::new(1024);
766 let _ = queues.gate_announce(
767 InterfaceId(1),
768 vec![0x01; 100].into(),
769 [0xAA; 16],
770 2,
771 0.0,
772 0.0,
773 Some(1000),
774 None,
775 0.02,
776 );
777 let _ = queues.gate_announce(
778 InterfaceId(1),
779 vec![0x02; 100].into(),
780 [0xBB; 16],
781 3,
782 0.0,
783 0.0,
784 Some(1000),
785 None,
786 0.02,
787 );
788
789 assert!(queues.queue_for(&InterfaceId(1)).is_some());
790 assert!(queues.remove_interface(InterfaceId(1)));
791 assert!(queues.queue_for(&InterfaceId(1)).is_none());
792 assert!(!queues.remove_interface(InterfaceId(1)));
793 }
794
795 #[test]
796 fn test_process_queues_prunes_empty_queue() {
797 let mut queues = AnnounceQueues::new(1024);
798
799 let _ = queues.gate_announce(
800 InterfaceId(1),
801 vec![0x01; 10].into(),
802 [0xAA; 16],
803 2,
804 0.0,
805 0.0,
806 Some(1000),
807 None,
808 0.02,
809 );
810 let _ = queues.gate_announce(
811 InterfaceId(1),
812 vec![0x02; 10].into(),
813 [0xBB; 16],
814 3,
815 0.0,
816 0.0,
817 Some(1000),
818 None,
819 0.02,
820 );
821
822 let mut interfaces = BTreeMap::new();
823 interfaces.insert(InterfaceId(1), make_interface_info(1, Some(1000)));
824 let allowed_at = queues
825 .queue_for(&InterfaceId(1))
826 .unwrap()
827 .announce_allowed_at;
828
829 let actions = queues.process_queues(allowed_at + 1.0, &interfaces);
830 assert_eq!(actions.len(), 1);
831 assert!(queues.queue_for(&InterfaceId(1)).is_none());
832 assert_eq!(queues.queue_count(), 0);
833 }
834
835 #[test]
836 fn test_process_queues_keeps_nonempty_queue() {
837 let mut queues = AnnounceQueues::new(1024);
838 let _ = queues.gate_announce(
839 InterfaceId(1),
840 vec![0x01; 100].into(),
841 [0xAA; 16],
842 2,
843 0.0,
844 0.0,
845 Some(1000),
846 None,
847 0.02,
848 );
849 let _ = queues.gate_announce(
850 InterfaceId(1),
851 vec![0x02; 100].into(),
852 [0xBB; 16],
853 3,
854 0.0,
855 0.0,
856 Some(1000),
857 None,
858 0.02,
859 );
860 let _ = queues.gate_announce(
861 InterfaceId(1),
862 vec![0x03; 100].into(),
863 [0xCC; 16],
864 4,
865 0.0,
866 0.0,
867 Some(1000),
868 None,
869 0.02,
870 );
871
872 let mut interfaces = BTreeMap::new();
873 interfaces.insert(InterfaceId(1), make_interface_info(1, Some(1000)));
874 let allowed_at = queues
875 .queue_for(&InterfaceId(1))
876 .unwrap()
877 .announce_allowed_at;
878
879 let actions = queues.process_queues(allowed_at + 1.0, &interfaces);
880 assert_eq!(actions.len(), 1);
881 assert!(queues.queue_for(&InterfaceId(1)).is_some());
882 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
883 }
884
885 #[test]
886 fn test_gate_announce_refuses_new_interface_when_at_capacity() {
887 let mut queues = AnnounceQueues::new(1);
888
889 let _ = queues.gate_announce(
890 InterfaceId(1),
891 vec![0x01; 100].into(),
892 [0xAA; 16],
893 2,
894 0.0,
895 0.0,
896 Some(1000),
897 None,
898 0.02,
899 );
900 let second = queues.gate_announce(
901 InterfaceId(1),
902 vec![0x02; 100].into(),
903 [0xBB; 16],
904 3,
905 0.0,
906 0.0,
907 Some(1000),
908 None,
909 0.02,
910 );
911 assert!(second.is_none());
912 assert_eq!(queues.queue_count(), 1);
913
914 let rejected = queues.gate_announce(
915 InterfaceId(2),
916 vec![0x03; 100].into(),
917 [0xCC; 16],
918 4,
919 0.0,
920 0.0,
921 Some(1000),
922 None,
923 0.02,
924 );
925 assert!(rejected.is_none());
926 assert_eq!(queues.queue_count(), 1);
927 assert!(queues.queue_for(&InterfaceId(2)).is_none());
928 assert_eq!(queues.interface_cap_drop_count(), 1);
929 }
930
931 #[test]
932 fn test_gate_announce_allows_existing_queue_when_at_capacity() {
933 let mut queues = AnnounceQueues::new(1);
934
935 let _ = queues.gate_announce(
936 InterfaceId(1),
937 vec![0x01; 100].into(),
938 [0xAA; 16],
939 2,
940 0.0,
941 0.0,
942 Some(1000),
943 None,
944 0.02,
945 );
946 let queued = queues.gate_announce(
947 InterfaceId(1),
948 vec![0x02; 100].into(),
949 [0xBB; 16],
950 3,
951 0.0,
952 0.0,
953 Some(1000),
954 None,
955 0.02,
956 );
957 assert!(queued.is_none());
958 assert_eq!(queues.queue_count(), 1);
959 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
960 assert_eq!(queues.interface_cap_drop_count(), 0);
961 }
962}