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 gravity: 0,
391 recursive_prs: false,
392 announces_from_internal: true,
393 announces_to_internal: None,
394 out_capable: true,
395 in_capable: true,
396 bitrate,
397 airtime_profile: None,
398 announce_rate_target: None,
399 announce_rate_grace: 0,
400 announce_rate_penalty: 0.0,
401 announce_cap: constants::ANNOUNCE_CAP,
402 is_local_client: false,
403 wants_tunnel: false,
404 tunnel_id: None,
405 mtu: constants::MTU as u32,
406 ingress_control: crate::transport::types::IngressControlConfig::disabled(),
407 ia_freq: 0.0,
408 ip_freq: 0.0,
409 op_freq: 0.0,
410 op_samples: 0,
411 started: 0.0,
412 }
413 }
414
415 #[test]
418 fn test_queue_entry_creation() {
419 let entry = make_entry(0xAA, 3, 1000.0);
420 assert_eq!(entry.hops, 3);
421 assert_eq!(entry.destination_hash, [0xAA; 16]);
422 }
423
424 #[test]
425 fn test_queue_insert_and_select() {
426 let mut queue = InterfaceAnnounceQueue::new();
427 queue.insert(make_entry(0x01, 3, 100.0));
428 queue.insert(make_entry(0x02, 1, 200.0));
429 queue.insert(make_entry(0x03, 2, 150.0));
430
431 let idx = queue.select_next().unwrap();
433 assert_eq!(queue.entries[idx].destination_hash, [0x02; 16]);
434 }
435
436 #[test]
437 fn test_queue_select_fifo_on_same_hops() {
438 let mut queue = InterfaceAnnounceQueue::new();
439 queue.insert(make_entry(0x01, 2, 200.0)); queue.insert(make_entry(0x02, 2, 100.0)); let idx = queue.select_next().unwrap();
444 assert_eq!(queue.entries[idx].destination_hash, [0x02; 16]);
445 }
446
447 #[test]
448 fn test_queue_dedup_update() {
449 let mut queue = InterfaceAnnounceQueue::new();
450 queue.insert(make_entry(0x01, 3, 100.0));
451 assert_eq!(queue.entries.len(), 1);
452
453 queue.insert(make_entry(0x01, 1, 200.0));
455 assert_eq!(queue.entries.len(), 1);
456 assert_eq!(queue.entries[0].hops, 1);
457
458 queue.insert(make_entry(0x01, 5, 300.0));
460 assert_eq!(queue.entries.len(), 1);
461 assert_eq!(queue.entries[0].hops, 1);
462 }
463
464 #[test]
465 fn test_queue_stale_removal() {
466 let mut queue = InterfaceAnnounceQueue::new();
467 queue.insert(make_entry(0x01, 1, 100.0));
468 queue.insert(make_entry(0x02, 2, 200.0));
469
470 queue.remove_stale(86501.0);
472 assert_eq!(queue.entries.len(), 1);
473 assert_eq!(queue.entries[0].destination_hash, [0x02; 16]);
474 }
475
476 #[test]
477 fn test_queue_max_size() {
478 let mut queue = InterfaceAnnounceQueue::new();
479 for i in 0..constants::MAX_QUEUED_ANNOUNCES {
480 queue.insert(AnnounceQueueEntry {
481 destination_hash: {
482 let mut d = [0u8; 16];
483 d[0] = (i >> 8) as u8;
484 d[1] = i as u8;
485 d
486 },
487 time: i as f64,
488 hops: 1,
489 emitted: i as f64,
490 raw: vec![0x01].into(),
491 });
492 }
493 assert_eq!(queue.entries.len(), constants::MAX_QUEUED_ANNOUNCES);
494
495 queue.insert(make_entry(0xFF, 1, 99999.0));
497 assert_eq!(queue.entries.len(), constants::MAX_QUEUED_ANNOUNCES);
498 }
499
500 #[test]
501 fn test_queue_empty_select() {
502 let queue = InterfaceAnnounceQueue::new();
503 assert!(queue.select_next().is_none());
504 }
505
506 #[test]
507 fn test_bandwidth_allowed() {
508 let mut queue = InterfaceAnnounceQueue::new();
509 assert!(queue.is_allowed(0.0));
510 assert!(queue.is_allowed(100.0));
511
512 queue.announce_allowed_at = 200.0;
513 assert!(!queue.is_allowed(100.0));
514 assert!(!queue.is_allowed(199.9));
515 assert!(queue.is_allowed(200.0));
516 assert!(queue.is_allowed(300.0));
517 }
518
519 #[test]
520 fn test_calculate_next_allowed() {
521 let next = InterfaceAnnounceQueue::calculate_next_allowed(1000.0, 100, 1000, None, 0.02);
525 assert!((next - 1040.0).abs() < 0.001);
526 }
527
528 #[test]
529 fn test_calculate_next_allowed_zero_bitrate() {
530 let next = InterfaceAnnounceQueue::calculate_next_allowed(1000.0, 100, 0, None, 0.02);
531 assert_eq!(next, 1000.0); }
533
534 #[test]
535 fn test_calculate_next_allowed_uses_lora_airtime() {
536 let profile = AirtimeProfile::Lora {
537 bandwidth: 125_000,
538 spreading_factor: 8,
539 coding_rate: 5,
540 preamble_symbols: 8,
541 explicit_header: true,
542 crc: true,
543 };
544
545 let next =
546 InterfaceAnnounceQueue::calculate_next_allowed(1000.0, 100, 0, Some(profile), 0.02);
547
548 assert!((next - 1015.3856).abs() < 0.0001);
552 }
553
554 #[test]
557 fn test_gate_announce_no_bitrate_immediate() {
558 let mut queues = AnnounceQueues::new(1024);
559 let result = queues.gate_announce(
560 InterfaceId(1),
561 vec![0x01, 0x02, 0x03].into(),
562 [0xAA; 16],
563 2,
564 1000.0,
565 1000.0,
566 None, None,
568 0.02,
569 );
570 assert!(result.is_some());
571 assert!(matches!(
572 result.unwrap(),
573 TransportAction::SendOnInterface { .. }
574 ));
575 }
576
577 #[test]
578 fn test_gate_announce_uses_airtime_profile_without_bitrate() {
579 let mut queues = AnnounceQueues::new(1024);
580 let profile = AirtimeProfile::Lora {
581 bandwidth: 125_000,
582 spreading_factor: 8,
583 coding_rate: 5,
584 preamble_symbols: 8,
585 explicit_header: true,
586 crc: true,
587 };
588
589 let first = queues.gate_announce(
590 InterfaceId(1),
591 vec![0x01; 100].into(),
592 [0xAA; 16],
593 2,
594 1000.0,
595 1000.0,
596 None,
597 Some(profile),
598 0.02,
599 );
600 assert!(first.is_some());
601
602 let queue = queues.queue_for(&InterfaceId(1)).unwrap();
603 assert!((queue.announce_allowed_at - 1015.3856).abs() < 0.0001);
604
605 let second = queues.gate_announce(
606 InterfaceId(1),
607 vec![0x02; 100].into(),
608 [0xBB; 16],
609 2,
610 1000.0,
611 1000.0,
612 None,
613 Some(profile),
614 0.02,
615 );
616 assert!(second.is_none());
617 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
618 }
619
620 #[test]
621 fn test_gate_announce_bandwidth_available() {
622 let mut queues = AnnounceQueues::new(1024);
623 let result = queues.gate_announce(
624 InterfaceId(1),
625 vec![0x01; 100].into(),
626 [0xBB; 16],
627 2,
628 1000.0,
629 1000.0,
630 Some(10000), None,
632 0.02,
633 );
634 assert!(result.is_some());
636
637 let queue = queues.queue_for(&InterfaceId(1)).unwrap();
639 assert!(queue.announce_allowed_at > 1000.0);
640 }
641
642 #[test]
643 fn test_gate_announce_bandwidth_exhausted_queues() {
644 let mut queues = AnnounceQueues::new(1024);
645
646 let r1 = queues.gate_announce(
648 InterfaceId(1),
649 vec![0x01; 100].into(),
650 [0xAA; 16],
651 2,
652 1000.0,
653 1000.0,
654 Some(1000), None,
656 0.02,
657 );
658 assert!(r1.is_some());
659
660 let r2 = queues.gate_announce(
662 InterfaceId(1),
663 vec![0x02; 100].into(),
664 [0xBB; 16],
665 3,
666 1000.0,
667 1000.0,
668 Some(1000),
669 None,
670 0.02,
671 );
672 assert!(r2.is_none()); let queue = queues.queue_for(&InterfaceId(1)).unwrap();
675 assert_eq!(queue.entries.len(), 1);
676 }
677
678 #[test]
679 fn test_process_queues_dequeues_when_allowed() {
680 let mut queues = AnnounceQueues::new(1024);
681
682 let _ = queues.gate_announce(
684 InterfaceId(1),
685 vec![0x01; 10].into(),
686 [0xAA; 16],
687 2,
688 0.0,
689 0.0,
690 Some(1000),
691 None,
692 0.02,
693 );
694 let _ = queues.gate_announce(
695 InterfaceId(1),
696 vec![0x02; 10].into(),
697 [0xBB; 16],
698 3,
699 0.0,
700 0.0,
701 Some(1000),
702 None,
703 0.02,
704 );
705
706 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
708
709 let mut interfaces = BTreeMap::new();
710 interfaces.insert(InterfaceId(1), make_interface_info(1, Some(1000)));
711
712 let allowed_at = queues
714 .queue_for(&InterfaceId(1))
715 .unwrap()
716 .announce_allowed_at;
717 let actions = queues.process_queues(allowed_at + 1.0, &interfaces);
718
719 assert_eq!(actions.len(), 1);
720 assert!(matches!(
721 &actions[0],
722 TransportAction::SendOnInterface { interface, .. } if *interface == InterfaceId(1)
723 ));
724
725 assert!(queues.queue_for(&InterfaceId(1)).is_none());
727 }
728
729 #[test]
730 fn test_local_announce_bypasses_cap() {
731 let mut queues = AnnounceQueues::new(1024);
735
736 let _ = queues.gate_announce(
738 InterfaceId(1),
739 vec![0x01; 100].into(),
740 [0xAA; 16],
741 2,
742 0.0,
743 0.0,
744 Some(1000),
745 None,
746 0.02,
747 );
748
749 let r = queues.gate_announce(
752 InterfaceId(1),
753 vec![0x02; 100].into(),
754 [0xBB; 16],
755 0,
756 0.0,
757 0.0,
758 Some(1000),
759 None,
760 0.02,
761 );
762 assert!(r.is_none()); }
764
765 #[test]
766 fn test_remove_interface_queue() {
767 let mut queues = AnnounceQueues::new(1024);
768 let _ = queues.gate_announce(
769 InterfaceId(1),
770 vec![0x01; 100].into(),
771 [0xAA; 16],
772 2,
773 0.0,
774 0.0,
775 Some(1000),
776 None,
777 0.02,
778 );
779 let _ = queues.gate_announce(
780 InterfaceId(1),
781 vec![0x02; 100].into(),
782 [0xBB; 16],
783 3,
784 0.0,
785 0.0,
786 Some(1000),
787 None,
788 0.02,
789 );
790
791 assert!(queues.queue_for(&InterfaceId(1)).is_some());
792 assert!(queues.remove_interface(InterfaceId(1)));
793 assert!(queues.queue_for(&InterfaceId(1)).is_none());
794 assert!(!queues.remove_interface(InterfaceId(1)));
795 }
796
797 #[test]
798 fn test_process_queues_prunes_empty_queue() {
799 let mut queues = AnnounceQueues::new(1024);
800
801 let _ = queues.gate_announce(
802 InterfaceId(1),
803 vec![0x01; 10].into(),
804 [0xAA; 16],
805 2,
806 0.0,
807 0.0,
808 Some(1000),
809 None,
810 0.02,
811 );
812 let _ = queues.gate_announce(
813 InterfaceId(1),
814 vec![0x02; 10].into(),
815 [0xBB; 16],
816 3,
817 0.0,
818 0.0,
819 Some(1000),
820 None,
821 0.02,
822 );
823
824 let mut interfaces = BTreeMap::new();
825 interfaces.insert(InterfaceId(1), make_interface_info(1, Some(1000)));
826 let allowed_at = queues
827 .queue_for(&InterfaceId(1))
828 .unwrap()
829 .announce_allowed_at;
830
831 let actions = queues.process_queues(allowed_at + 1.0, &interfaces);
832 assert_eq!(actions.len(), 1);
833 assert!(queues.queue_for(&InterfaceId(1)).is_none());
834 assert_eq!(queues.queue_count(), 0);
835 }
836
837 #[test]
838 fn test_process_queues_keeps_nonempty_queue() {
839 let mut queues = AnnounceQueues::new(1024);
840 let _ = queues.gate_announce(
841 InterfaceId(1),
842 vec![0x01; 100].into(),
843 [0xAA; 16],
844 2,
845 0.0,
846 0.0,
847 Some(1000),
848 None,
849 0.02,
850 );
851 let _ = queues.gate_announce(
852 InterfaceId(1),
853 vec![0x02; 100].into(),
854 [0xBB; 16],
855 3,
856 0.0,
857 0.0,
858 Some(1000),
859 None,
860 0.02,
861 );
862 let _ = queues.gate_announce(
863 InterfaceId(1),
864 vec![0x03; 100].into(),
865 [0xCC; 16],
866 4,
867 0.0,
868 0.0,
869 Some(1000),
870 None,
871 0.02,
872 );
873
874 let mut interfaces = BTreeMap::new();
875 interfaces.insert(InterfaceId(1), make_interface_info(1, Some(1000)));
876 let allowed_at = queues
877 .queue_for(&InterfaceId(1))
878 .unwrap()
879 .announce_allowed_at;
880
881 let actions = queues.process_queues(allowed_at + 1.0, &interfaces);
882 assert_eq!(actions.len(), 1);
883 assert!(queues.queue_for(&InterfaceId(1)).is_some());
884 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
885 }
886
887 #[test]
888 fn test_gate_announce_refuses_new_interface_when_at_capacity() {
889 let mut queues = AnnounceQueues::new(1);
890
891 let _ = queues.gate_announce(
892 InterfaceId(1),
893 vec![0x01; 100].into(),
894 [0xAA; 16],
895 2,
896 0.0,
897 0.0,
898 Some(1000),
899 None,
900 0.02,
901 );
902 let second = queues.gate_announce(
903 InterfaceId(1),
904 vec![0x02; 100].into(),
905 [0xBB; 16],
906 3,
907 0.0,
908 0.0,
909 Some(1000),
910 None,
911 0.02,
912 );
913 assert!(second.is_none());
914 assert_eq!(queues.queue_count(), 1);
915
916 let rejected = queues.gate_announce(
917 InterfaceId(2),
918 vec![0x03; 100].into(),
919 [0xCC; 16],
920 4,
921 0.0,
922 0.0,
923 Some(1000),
924 None,
925 0.02,
926 );
927 assert!(rejected.is_none());
928 assert_eq!(queues.queue_count(), 1);
929 assert!(queues.queue_for(&InterfaceId(2)).is_none());
930 assert_eq!(queues.interface_cap_drop_count(), 1);
931 }
932
933 #[test]
934 fn test_gate_announce_allows_existing_queue_when_at_capacity() {
935 let mut queues = AnnounceQueues::new(1);
936
937 let _ = queues.gate_announce(
938 InterfaceId(1),
939 vec![0x01; 100].into(),
940 [0xAA; 16],
941 2,
942 0.0,
943 0.0,
944 Some(1000),
945 None,
946 0.02,
947 );
948 let queued = queues.gate_announce(
949 InterfaceId(1),
950 vec![0x02; 100].into(),
951 [0xBB; 16],
952 3,
953 0.0,
954 0.0,
955 Some(1000),
956 None,
957 0.02,
958 );
959 assert!(queued.is_none());
960 assert_eq!(queues.queue_count(), 1);
961 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
962 assert_eq!(queues.interface_cap_drop_count(), 0);
963 }
964}