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 stamp_costs: Default::default(),
388 id: InterfaceId(id),
389 name: String::from("test"),
390 mode: crate::constants::MODE_FULL,
391 gravity: 0,
392 recursive_prs: false,
393 announces_from_internal: true,
394 announces_to_internal: None,
395 out_capable: true,
396 in_capable: true,
397 bitrate,
398 airtime_profile: None,
399 announce_rate_target: None,
400 announce_rate_grace: 0,
401 announce_rate_penalty: 0.0,
402 announce_cap: constants::ANNOUNCE_CAP,
403 is_local_client: false,
404 wants_tunnel: false,
405 tunnel_id: None,
406 mtu: constants::MTU as u32,
407 ingress_control: crate::transport::types::IngressControlConfig::disabled(),
408 ia_freq: 0.0,
409 ip_freq: 0.0,
410 op_freq: 0.0,
411 op_samples: 0,
412 started: 0.0,
413 }
414 }
415
416 #[test]
419 fn test_queue_entry_creation() {
420 let entry = make_entry(0xAA, 3, 1000.0);
421 assert_eq!(entry.hops, 3);
422 assert_eq!(entry.destination_hash, [0xAA; 16]);
423 }
424
425 #[test]
426 fn test_queue_insert_and_select() {
427 let mut queue = InterfaceAnnounceQueue::new();
428 queue.insert(make_entry(0x01, 3, 100.0));
429 queue.insert(make_entry(0x02, 1, 200.0));
430 queue.insert(make_entry(0x03, 2, 150.0));
431
432 let idx = queue.select_next().unwrap();
434 assert_eq!(queue.entries[idx].destination_hash, [0x02; 16]);
435 }
436
437 #[test]
438 fn test_queue_select_fifo_on_same_hops() {
439 let mut queue = InterfaceAnnounceQueue::new();
440 queue.insert(make_entry(0x01, 2, 200.0)); queue.insert(make_entry(0x02, 2, 100.0)); let idx = queue.select_next().unwrap();
445 assert_eq!(queue.entries[idx].destination_hash, [0x02; 16]);
446 }
447
448 #[test]
449 fn test_queue_dedup_update() {
450 let mut queue = InterfaceAnnounceQueue::new();
451 queue.insert(make_entry(0x01, 3, 100.0));
452 assert_eq!(queue.entries.len(), 1);
453
454 queue.insert(make_entry(0x01, 1, 200.0));
456 assert_eq!(queue.entries.len(), 1);
457 assert_eq!(queue.entries[0].hops, 1);
458
459 queue.insert(make_entry(0x01, 5, 300.0));
461 assert_eq!(queue.entries.len(), 1);
462 assert_eq!(queue.entries[0].hops, 1);
463 }
464
465 #[test]
466 fn newer_duplicate_updates_matched_entry_not_trailing_entry() {
467 let mut queue = InterfaceAnnounceQueue::new();
468 queue.insert(make_entry(0x01, 3, 100.0));
469 queue.insert(make_entry(0x02, 4, 150.0));
470
471 let mut replacement = make_entry(0x01, 3, 200.0);
472 replacement.raw = vec![0x09, 0x08].into();
473 queue.insert(replacement);
474
475 assert_eq!(queue.entries.len(), 2);
476 assert_eq!(queue.entries[0].destination_hash, [0x01; 16]);
477 assert_eq!(queue.entries[0].time, 200.0);
478 assert_eq!(queue.entries[0].hops, 3);
479 assert_eq!(queue.entries[0].emitted, 200.0);
480 assert_eq!(&*queue.entries[0].raw, &[0x09, 0x08]);
481
482 assert_eq!(queue.entries[1].destination_hash, [0x02; 16]);
483 assert_eq!(queue.entries[1].time, 150.0);
484 assert_eq!(queue.entries[1].hops, 4);
485 assert_eq!(queue.entries[1].emitted, 150.0);
486 assert_eq!(&*queue.entries[1].raw, &[0x01, 0x02, 0x03]);
487 }
488
489 #[test]
490 fn duplicate_lookup_updates_only_first_preexisting_match() {
491 let mut queue = InterfaceAnnounceQueue::new();
492 queue.entries.push(make_entry(0x01, 3, 100.0));
493 queue.entries.push(make_entry(0x01, 3, 150.0));
494
495 let mut replacement = make_entry(0x01, 3, 200.0);
496 replacement.raw = vec![0x09].into();
497 queue.insert(replacement);
498
499 assert_eq!(queue.entries.len(), 2);
500 assert_eq!(queue.entries[0].time, 200.0);
501 assert_eq!(queue.entries[0].emitted, 200.0);
502 assert_eq!(&*queue.entries[0].raw, &[0x09]);
503 assert_eq!(queue.entries[1].time, 150.0);
504 assert_eq!(queue.entries[1].emitted, 150.0);
505 assert_eq!(&*queue.entries[1].raw, &[0x01, 0x02, 0x03]);
506 }
507
508 #[test]
509 fn test_queue_stale_removal() {
510 let mut queue = InterfaceAnnounceQueue::new();
511 queue.insert(make_entry(0x01, 1, 100.0));
512 queue.insert(make_entry(0x02, 2, 200.0));
513
514 queue.remove_stale(100.0 + constants::QUEUED_ANNOUNCE_LIFE + 1.0);
516 assert_eq!(queue.entries.len(), 1);
517 assert_eq!(queue.entries[0].destination_hash, [0x02; 16]);
518 }
519
520 #[test]
521 fn test_queue_max_size() {
522 let mut queue = InterfaceAnnounceQueue::new();
523 for i in 0..constants::MAX_QUEUED_ANNOUNCES {
524 queue.insert(AnnounceQueueEntry {
525 destination_hash: {
526 let mut d = [0u8; 16];
527 d[0] = (i >> 8) as u8;
528 d[1] = i as u8;
529 d
530 },
531 time: i as f64,
532 hops: 1,
533 emitted: i as f64,
534 raw: vec![0x01].into(),
535 });
536 }
537 assert_eq!(queue.entries.len(), constants::MAX_QUEUED_ANNOUNCES);
538
539 queue.insert(make_entry(0xFF, 1, 99999.0));
541 assert_eq!(queue.entries.len(), constants::MAX_QUEUED_ANNOUNCES);
542 }
543
544 #[test]
545 fn test_queue_empty_select() {
546 let queue = InterfaceAnnounceQueue::new();
547 assert!(queue.select_next().is_none());
548 }
549
550 #[test]
551 fn test_bandwidth_allowed() {
552 let mut queue = InterfaceAnnounceQueue::new();
553 assert!(queue.is_allowed(0.0));
554 assert!(queue.is_allowed(100.0));
555
556 queue.announce_allowed_at = 200.0;
557 assert!(!queue.is_allowed(100.0));
558 assert!(!queue.is_allowed(199.9));
559 assert!(queue.is_allowed(200.0));
560 assert!(queue.is_allowed(300.0));
561 }
562
563 #[test]
564 fn test_calculate_next_allowed() {
565 let next = InterfaceAnnounceQueue::calculate_next_allowed(1000.0, 100, 1000, None, 0.02);
569 assert!((next - 1040.0).abs() < 0.001);
570 }
571
572 #[test]
573 fn test_calculate_next_allowed_zero_bitrate() {
574 let next = InterfaceAnnounceQueue::calculate_next_allowed(1000.0, 100, 0, None, 0.02);
575 assert_eq!(next, 1000.0); }
577
578 #[test]
579 fn test_calculate_next_allowed_uses_lora_airtime() {
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 next =
590 InterfaceAnnounceQueue::calculate_next_allowed(1000.0, 100, 0, Some(profile), 0.02);
591
592 assert!((next - 1015.3856).abs() < 0.0001);
596 }
597
598 #[test]
601 fn test_gate_announce_no_bitrate_immediate() {
602 let mut queues = AnnounceQueues::new(1024);
603 let result = queues.gate_announce(
604 InterfaceId(1),
605 vec![0x01, 0x02, 0x03].into(),
606 [0xAA; 16],
607 2,
608 1000.0,
609 1000.0,
610 None, None,
612 0.02,
613 );
614 assert!(result.is_some());
615 assert!(matches!(
616 result.unwrap(),
617 TransportAction::SendOnInterface { .. }
618 ));
619 }
620
621 #[test]
622 fn test_gate_announce_uses_airtime_profile_without_bitrate() {
623 let mut queues = AnnounceQueues::new(1024);
624 let profile = AirtimeProfile::Lora {
625 bandwidth: 125_000,
626 spreading_factor: 8,
627 coding_rate: 5,
628 preamble_symbols: 8,
629 explicit_header: true,
630 crc: true,
631 };
632
633 let first = queues.gate_announce(
634 InterfaceId(1),
635 vec![0x01; 100].into(),
636 [0xAA; 16],
637 2,
638 1000.0,
639 1000.0,
640 None,
641 Some(profile),
642 0.02,
643 );
644 assert!(first.is_some());
645
646 let queue = queues.queue_for(&InterfaceId(1)).unwrap();
647 assert!((queue.announce_allowed_at - 1015.3856).abs() < 0.0001);
648
649 let second = queues.gate_announce(
650 InterfaceId(1),
651 vec![0x02; 100].into(),
652 [0xBB; 16],
653 2,
654 1000.0,
655 1000.0,
656 None,
657 Some(profile),
658 0.02,
659 );
660 assert!(second.is_none());
661 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
662 }
663
664 #[test]
665 fn test_gate_announce_bandwidth_available() {
666 let mut queues = AnnounceQueues::new(1024);
667 let result = queues.gate_announce(
668 InterfaceId(1),
669 vec![0x01; 100].into(),
670 [0xBB; 16],
671 2,
672 1000.0,
673 1000.0,
674 Some(10000), None,
676 0.02,
677 );
678 assert!(result.is_some());
680
681 let queue = queues.queue_for(&InterfaceId(1)).unwrap();
683 assert!(queue.announce_allowed_at > 1000.0);
684 }
685
686 #[test]
687 fn test_gate_announce_bandwidth_exhausted_queues() {
688 let mut queues = AnnounceQueues::new(1024);
689
690 let r1 = queues.gate_announce(
692 InterfaceId(1),
693 vec![0x01; 100].into(),
694 [0xAA; 16],
695 2,
696 1000.0,
697 1000.0,
698 Some(1000), None,
700 0.02,
701 );
702 assert!(r1.is_some());
703
704 let r2 = queues.gate_announce(
706 InterfaceId(1),
707 vec![0x02; 100].into(),
708 [0xBB; 16],
709 3,
710 1000.0,
711 1000.0,
712 Some(1000),
713 None,
714 0.02,
715 );
716 assert!(r2.is_none()); let queue = queues.queue_for(&InterfaceId(1)).unwrap();
719 assert_eq!(queue.entries.len(), 1);
720 }
721
722 #[test]
723 fn test_process_queues_dequeues_when_allowed() {
724 let mut queues = AnnounceQueues::new(1024);
725
726 let _ = queues.gate_announce(
728 InterfaceId(1),
729 vec![0x01; 10].into(),
730 [0xAA; 16],
731 2,
732 0.0,
733 0.0,
734 Some(1000),
735 None,
736 0.02,
737 );
738 let _ = queues.gate_announce(
739 InterfaceId(1),
740 vec![0x02; 10].into(),
741 [0xBB; 16],
742 3,
743 0.0,
744 0.0,
745 Some(1000),
746 None,
747 0.02,
748 );
749
750 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
752
753 let mut interfaces = BTreeMap::new();
754 interfaces.insert(InterfaceId(1), make_interface_info(1, Some(1000)));
755
756 let allowed_at = queues
758 .queue_for(&InterfaceId(1))
759 .unwrap()
760 .announce_allowed_at;
761 let actions = queues.process_queues(allowed_at + 1.0, &interfaces);
762
763 assert_eq!(actions.len(), 1);
764 assert!(matches!(
765 &actions[0],
766 TransportAction::SendOnInterface { interface, .. } if *interface == InterfaceId(1)
767 ));
768
769 assert!(queues.queue_for(&InterfaceId(1)).is_none());
771 }
772
773 #[test]
774 fn test_local_announce_bypasses_cap() {
775 let mut queues = AnnounceQueues::new(1024);
779
780 let _ = queues.gate_announce(
782 InterfaceId(1),
783 vec![0x01; 100].into(),
784 [0xAA; 16],
785 2,
786 0.0,
787 0.0,
788 Some(1000),
789 None,
790 0.02,
791 );
792
793 let r = queues.gate_announce(
796 InterfaceId(1),
797 vec![0x02; 100].into(),
798 [0xBB; 16],
799 0,
800 0.0,
801 0.0,
802 Some(1000),
803 None,
804 0.02,
805 );
806 assert!(r.is_none()); }
808
809 #[test]
810 fn test_remove_interface_queue() {
811 let mut queues = AnnounceQueues::new(1024);
812 let _ = queues.gate_announce(
813 InterfaceId(1),
814 vec![0x01; 100].into(),
815 [0xAA; 16],
816 2,
817 0.0,
818 0.0,
819 Some(1000),
820 None,
821 0.02,
822 );
823 let _ = queues.gate_announce(
824 InterfaceId(1),
825 vec![0x02; 100].into(),
826 [0xBB; 16],
827 3,
828 0.0,
829 0.0,
830 Some(1000),
831 None,
832 0.02,
833 );
834
835 assert!(queues.queue_for(&InterfaceId(1)).is_some());
836 assert!(queues.remove_interface(InterfaceId(1)));
837 assert!(queues.queue_for(&InterfaceId(1)).is_none());
838 assert!(!queues.remove_interface(InterfaceId(1)));
839 }
840
841 #[test]
842 fn test_process_queues_prunes_empty_queue() {
843 let mut queues = AnnounceQueues::new(1024);
844
845 let _ = queues.gate_announce(
846 InterfaceId(1),
847 vec![0x01; 10].into(),
848 [0xAA; 16],
849 2,
850 0.0,
851 0.0,
852 Some(1000),
853 None,
854 0.02,
855 );
856 let _ = queues.gate_announce(
857 InterfaceId(1),
858 vec![0x02; 10].into(),
859 [0xBB; 16],
860 3,
861 0.0,
862 0.0,
863 Some(1000),
864 None,
865 0.02,
866 );
867
868 let mut interfaces = BTreeMap::new();
869 interfaces.insert(InterfaceId(1), make_interface_info(1, Some(1000)));
870 let allowed_at = queues
871 .queue_for(&InterfaceId(1))
872 .unwrap()
873 .announce_allowed_at;
874
875 let actions = queues.process_queues(allowed_at + 1.0, &interfaces);
876 assert_eq!(actions.len(), 1);
877 assert!(queues.queue_for(&InterfaceId(1)).is_none());
878 assert_eq!(queues.queue_count(), 0);
879 }
880
881 #[test]
882 fn test_process_queues_keeps_nonempty_queue() {
883 let mut queues = AnnounceQueues::new(1024);
884 let _ = queues.gate_announce(
885 InterfaceId(1),
886 vec![0x01; 100].into(),
887 [0xAA; 16],
888 2,
889 0.0,
890 0.0,
891 Some(1000),
892 None,
893 0.02,
894 );
895 let _ = queues.gate_announce(
896 InterfaceId(1),
897 vec![0x02; 100].into(),
898 [0xBB; 16],
899 3,
900 0.0,
901 0.0,
902 Some(1000),
903 None,
904 0.02,
905 );
906 let _ = queues.gate_announce(
907 InterfaceId(1),
908 vec![0x03; 100].into(),
909 [0xCC; 16],
910 4,
911 0.0,
912 0.0,
913 Some(1000),
914 None,
915 0.02,
916 );
917
918 let mut interfaces = BTreeMap::new();
919 interfaces.insert(InterfaceId(1), make_interface_info(1, Some(1000)));
920 let allowed_at = queues
921 .queue_for(&InterfaceId(1))
922 .unwrap()
923 .announce_allowed_at;
924
925 let actions = queues.process_queues(allowed_at + 1.0, &interfaces);
926 assert_eq!(actions.len(), 1);
927 assert!(queues.queue_for(&InterfaceId(1)).is_some());
928 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
929 }
930
931 #[test]
932 fn test_gate_announce_refuses_new_interface_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 second = 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!(second.is_none());
958 assert_eq!(queues.queue_count(), 1);
959
960 let rejected = queues.gate_announce(
961 InterfaceId(2),
962 vec![0x03; 100].into(),
963 [0xCC; 16],
964 4,
965 0.0,
966 0.0,
967 Some(1000),
968 None,
969 0.02,
970 );
971 assert!(rejected.is_none());
972 assert_eq!(queues.queue_count(), 1);
973 assert!(queues.queue_for(&InterfaceId(2)).is_none());
974 assert_eq!(queues.interface_cap_drop_count(), 1);
975 }
976
977 #[test]
978 fn test_gate_announce_allows_existing_queue_when_at_capacity() {
979 let mut queues = AnnounceQueues::new(1);
980
981 let _ = queues.gate_announce(
982 InterfaceId(1),
983 vec![0x01; 100].into(),
984 [0xAA; 16],
985 2,
986 0.0,
987 0.0,
988 Some(1000),
989 None,
990 0.02,
991 );
992 let queued = queues.gate_announce(
993 InterfaceId(1),
994 vec![0x02; 100].into(),
995 [0xBB; 16],
996 3,
997 0.0,
998 0.0,
999 Some(1000),
1000 None,
1001 0.02,
1002 );
1003 assert!(queued.is_none());
1004 assert_eq!(queues.queue_count(), 1);
1005 assert_eq!(queues.queue_for(&InterfaceId(1)).unwrap().entries.len(), 1);
1006 assert_eq!(queues.interface_cap_drop_count(), 0);
1007 }
1008}