1pub mod announce_proc;
2pub mod announce_queue;
3pub mod announce_verify_queue;
4pub mod dedup;
5pub mod inbound;
6pub mod ingress_control;
7pub mod jobs;
8pub mod outbound;
9pub mod path_requests;
10pub mod pathfinder;
11pub mod queries;
12pub mod rate_limit;
13pub mod retention;
14pub mod tables;
15pub mod tunnel;
16pub mod types;
17
18use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
19use alloc::string::String;
20use alloc::vec::Vec;
21use core::mem::size_of;
22
23use rns_crypto::Rng;
24
25use crate::announce::AnnounceData;
26use crate::constants;
27use crate::hash;
28use crate::packet::RawPacket;
29
30use self::announce_proc::compute_path_expires;
31use self::announce_queue::AnnounceQueues;
32use self::announce_verify_queue::{AnnounceVerifyKey, AnnounceVerifyQueue, PendingAnnounce};
33use self::dedup::{AnnounceSignatureCache, PacketHashlist};
34use self::inbound::{
35 create_link_entry, create_reverse_entry, forward_transport_packet, route_proof_via_reverse,
36 route_via_link_table,
37};
38use self::ingress_control::IngressControl;
39use self::outbound::{route_outbound, should_transmit_announce};
40use self::pathfinder::{
41 decide_announce_multipath, extract_random_blob, timebase_from_random_blob,
42 timebase_from_random_blobs, MultiPathDecision,
43};
44use self::rate_limit::AnnounceRateLimiter;
45use self::tables::{AnnounceEntry, DiscoveryPathRequest, LinkEntry, PathEntry, PathSet};
46use self::tunnel::TunnelTable;
47use self::types::{
48 BlackholeEntry, InterfaceId, InterfaceInfo, PacketBytes, TransportAction, TransportConfig,
49};
50
51pub type PathTableRow = ([u8; 16], f64, [u8; 16], u8, f64, String);
52pub type RateTableRow = ([u8; 16], f64, u32, f64, Vec<f64>);
53
54#[derive(Debug, Clone, Copy, PartialEq, Default)]
55pub struct RxMetadata {
56 pub rssi: Option<i16>,
57 pub snr: Option<f32>,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq)]
61pub struct InboundFrame<'a> {
62 pub raw: &'a [u8],
63 pub iface: InterfaceId,
64 pub now: f64,
65 pub rx: RxMetadata,
66}
67
68impl<'a> InboundFrame<'a> {
69 pub fn new(raw: &'a [u8], iface: InterfaceId, now: f64) -> Self {
70 Self {
71 raw,
72 iface,
73 now,
74 rx: RxMetadata::default(),
75 }
76 }
77
78 pub fn with_rx(mut self, rx: RxMetadata) -> Self {
79 self.rx = rx;
80 self
81 }
82}
83
84struct InboundPacketCtx {
85 packet: RawPacket,
86 original_raw: Option<Vec<u8>>,
87 iface: InterfaceId,
88 now: f64,
89 from_local_client: bool,
90}
91
92struct VerifiedAnnounceCtx<'a> {
93 packet: &'a RawPacket,
94 original_raw: &'a [u8],
95 iface: InterfaceId,
96 now: f64,
97 validated: crate::announce::ValidatedAnnounce,
98 received_from: [u8; 16],
99 random_blob: [u8; 10],
100 announce_emitted: u64,
101}
102
103struct TickCtx<'a> {
104 now: f64,
105 rng: &'a mut dyn Rng,
106 actions: Vec<TransportAction>,
107}
108
109struct PathRequestCtx<'a> {
110 data: &'a [u8],
111 interface_id: InterfaceId,
112 now: f64,
113 destination_hash: [u8; 16],
114}
115
116pub struct TransportEngine {
121 config: TransportConfig,
122 path_table: BTreeMap<[u8; 16], PathSet>,
123 announce_table: BTreeMap<[u8; 16], AnnounceEntry>,
124 reverse_table: BTreeMap<[u8; 16], tables::ReverseEntry>,
125 link_table: BTreeMap<[u8; 16], LinkEntry>,
126 held_announces: BTreeMap<[u8; 16], AnnounceEntry>,
127 packet_hashlist: PacketHashlist,
128 announce_sig_cache: AnnounceSignatureCache,
129 rate_limiter: AnnounceRateLimiter,
130 path_states: BTreeMap<[u8; 16], u8>,
131 interfaces: BTreeMap<InterfaceId, InterfaceInfo>,
132 local_destinations: BTreeMap<[u8; 16], u8>,
133 blackholed_identities: BTreeMap<[u8; 16], BlackholeEntry>,
134 announce_queues: AnnounceQueues,
135 ingress_control: IngressControl,
136 tunnel_table: TunnelTable,
137 discovery_pr_tags: VecDeque<[u8; 32]>,
138 discovery_pr_tag_set: BTreeSet<[u8; 32]>,
139 discovery_path_requests: BTreeMap<[u8; 16], DiscoveryPathRequest>,
140 path_destination_cap_evict_count: usize,
141 announces_last_checked: f64,
143 tables_last_culled: f64,
144}
145
146impl TransportEngine {
147 pub fn new(config: TransportConfig) -> Self {
148 let packet_hashlist_max_entries = config.packet_hashlist_max_entries;
149 let sig_cache_max = if config.announce_sig_cache_enabled {
150 config.announce_sig_cache_max_entries
151 } else {
152 0
153 };
154 let sig_cache_ttl = config.announce_sig_cache_ttl_secs;
155 let announce_queue_max_interfaces = config.announce_queue_max_interfaces;
156 TransportEngine {
157 config,
158 path_table: BTreeMap::new(),
159 announce_table: BTreeMap::new(),
160 reverse_table: BTreeMap::new(),
161 link_table: BTreeMap::new(),
162 held_announces: BTreeMap::new(),
163 packet_hashlist: PacketHashlist::new(packet_hashlist_max_entries),
164 announce_sig_cache: AnnounceSignatureCache::new(sig_cache_max, sig_cache_ttl),
165 rate_limiter: AnnounceRateLimiter::new(),
166 path_states: BTreeMap::new(),
167 interfaces: BTreeMap::new(),
168 local_destinations: BTreeMap::new(),
169 blackholed_identities: BTreeMap::new(),
170 announce_queues: AnnounceQueues::new(announce_queue_max_interfaces),
171 ingress_control: IngressControl::new(),
172 tunnel_table: TunnelTable::new(),
173 discovery_pr_tags: VecDeque::new(),
174 discovery_pr_tag_set: BTreeSet::new(),
175 discovery_path_requests: BTreeMap::new(),
176 path_destination_cap_evict_count: 0,
177 announces_last_checked: 0.0,
178 tables_last_culled: 0.0,
179 }
180 }
181
182 pub fn register_interface(&mut self, info: InterfaceInfo) {
187 self.interfaces.insert(info.id, info);
188 }
189
190 pub fn deregister_interface(&mut self, id: InterfaceId) {
191 self.interfaces.remove(&id);
192 self.announce_queues.remove_interface(id);
193 self.ingress_control.remove_interface(&id);
194 }
195
196 pub fn register_destination(&mut self, dest_hash: [u8; 16], dest_type: u8) {
201 self.local_destinations.insert(dest_hash, dest_type);
202 }
203
204 pub fn deregister_destination(&mut self, dest_hash: &[u8; 16]) {
205 self.local_destinations.remove(dest_hash);
206 }
207
208 pub fn has_path(&self, dest_hash: &[u8; 16]) -> bool {
213 self.path_table
214 .get(dest_hash)
215 .is_some_and(|ps| !ps.is_empty())
216 }
217
218 pub fn hops_to(&self, dest_hash: &[u8; 16]) -> Option<u8> {
219 self.path_table
220 .get(dest_hash)
221 .and_then(|ps| ps.primary())
222 .map(|e| e.hops)
223 }
224
225 pub fn next_hop(&self, dest_hash: &[u8; 16]) -> Option<[u8; 16]> {
226 self.path_table
227 .get(dest_hash)
228 .and_then(|ps| ps.primary())
229 .map(|e| e.next_hop)
230 }
231
232 pub fn next_hop_interface(&self, dest_hash: &[u8; 16]) -> Option<InterfaceId> {
233 self.path_table
234 .get(dest_hash)
235 .and_then(|ps| ps.primary())
236 .map(|e| e.receiving_interface)
237 }
238
239 pub fn mark_path_unresponsive(
249 &mut self,
250 dest_hash: &[u8; 16],
251 receiving_interface: Option<InterfaceId>,
252 ) {
253 if let Some(iface_id) = receiving_interface {
254 if let Some(info) = self.interfaces.get(&iface_id) {
255 if info.mode == constants::MODE_BOUNDARY {
256 return;
257 }
258 }
259 }
260
261 if let Some(ps) = self.path_table.get_mut(dest_hash) {
263 if ps.len() > 1 {
264 ps.failover(false); self.path_states.remove(dest_hash);
267 return;
268 }
269 }
270
271 self.path_states
272 .insert(*dest_hash, constants::STATE_UNRESPONSIVE);
273 }
274
275 pub fn mark_path_responsive(&mut self, dest_hash: &[u8; 16]) {
276 self.path_states
277 .insert(*dest_hash, constants::STATE_RESPONSIVE);
278 }
279
280 pub fn path_is_unresponsive(&self, dest_hash: &[u8; 16]) -> bool {
281 self.path_states.get(dest_hash) == Some(&constants::STATE_UNRESPONSIVE)
282 }
283
284 pub fn expire_path(&mut self, dest_hash: &[u8; 16]) {
285 if let Some(ps) = self.path_table.get_mut(dest_hash) {
286 ps.expire_all();
287 }
288 }
289
290 pub fn register_link(&mut self, link_id: [u8; 16], entry: LinkEntry) {
295 self.link_table.insert(link_id, entry);
296 }
297
298 pub fn validate_link(&mut self, link_id: &[u8; 16]) {
299 if let Some(entry) = self.link_table.get_mut(link_id) {
300 entry.validated = true;
301 }
302 }
303
304 pub fn remove_link(&mut self, link_id: &[u8; 16]) {
305 self.link_table.remove(link_id);
306 }
307
308 pub fn blackhole_identity(
314 &mut self,
315 identity_hash: [u8; 16],
316 now: f64,
317 duration_hours: Option<f64>,
318 reason: Option<String>,
319 ) {
320 let expires = match duration_hours {
321 Some(h) if h > 0.0 => now + h * 3600.0,
322 _ => 0.0, };
324 self.blackholed_identities.insert(
325 identity_hash,
326 BlackholeEntry {
327 created: now,
328 expires,
329 reason,
330 },
331 );
332 }
333
334 pub fn unblackhole_identity(&mut self, identity_hash: &[u8; 16]) -> bool {
336 self.blackholed_identities.remove(identity_hash).is_some()
337 }
338
339 pub fn is_blackholed(&self, identity_hash: &[u8; 16], now: f64) -> bool {
341 if let Some(entry) = self.blackholed_identities.get(identity_hash) {
342 if entry.expires == 0.0 || entry.expires > now {
343 return true;
344 }
345 }
346 false
347 }
348
349 pub fn blackholed_entries(&self) -> impl Iterator<Item = (&[u8; 16], &BlackholeEntry)> {
351 self.blackholed_identities.iter()
352 }
353
354 fn cull_blackholed(&mut self, now: f64) {
356 self.blackholed_identities
357 .retain(|_, entry| entry.expires == 0.0 || entry.expires > now);
358 }
359
360 pub fn handle_tunnel(
368 &mut self,
369 tunnel_id: [u8; 32],
370 interface: InterfaceId,
371 now: f64,
372 ) -> Vec<TransportAction> {
373 let mut actions = Vec::new();
374
375 if let Some(info) = self.interfaces.get_mut(&interface) {
377 info.tunnel_id = Some(tunnel_id);
378 }
379
380 let restored_paths = self.tunnel_table.handle_tunnel(
381 tunnel_id,
382 interface,
383 now,
384 self.config.destination_timeout_secs,
385 );
386
387 for (dest_hash, tunnel_path) in &restored_paths {
389 let should_restore = match self.path_table.get(dest_hash).and_then(|ps| ps.primary()) {
390 Some(existing) => {
391 if tunnel_path.hops <= existing.hops || existing.expires < now {
394 let existing_timebase = timebase_from_random_blobs(&existing.random_blobs);
395 let tunnel_timebase = timebase_from_random_blobs(&tunnel_path.random_blobs);
396 tunnel_timebase >= existing_timebase
397 } else {
398 false
399 }
400 }
401 None => now < tunnel_path.expires,
402 };
403
404 if should_restore {
405 let entry = PathEntry {
406 timestamp: tunnel_path.timestamp,
407 next_hop: tunnel_path.received_from,
408 hops: tunnel_path.hops,
409 expires: tunnel_path.expires,
410 random_blobs: tunnel_path.random_blobs.clone(),
411 receiving_interface: interface,
412 packet_hash: tunnel_path.packet_hash,
413 announce_raw: None,
414 };
415 self.upsert_path_destination(*dest_hash, entry, now);
416 }
417 }
418
419 actions.push(TransportAction::TunnelEstablished {
420 tunnel_id,
421 interface,
422 });
423
424 actions
425 }
426
427 pub fn synthesize_tunnel(
435 &self,
436 identity: &rns_crypto::identity::Identity,
437 interface_id: InterfaceId,
438 rng: &mut dyn Rng,
439 ) -> Vec<TransportAction> {
440 let mut actions = Vec::new();
441
442 let interface_hash = if let Some(info) = self.interfaces.get(&interface_id) {
444 hash::full_hash(info.name.as_bytes())
445 } else {
446 log::warn!(
447 "Cannot synthesize tunnel on {:?}: unknown interface",
448 interface_id
449 );
450 return actions;
451 };
452
453 match tunnel::build_tunnel_synthesize_data(identity, &interface_hash, rng) {
454 Ok((data, _tunnel_id)) => {
455 let dest_hash = crate::destination::destination_hash(
456 "rnstransport",
457 &["tunnel", "synthesize"],
458 None,
459 );
460 actions.push(TransportAction::TunnelSynthesize {
461 interface: interface_id,
462 data,
463 dest_hash,
464 });
465 }
466 Err(e) => {
467 log::warn!("Cannot synthesize tunnel on {:?}: {}", interface_id, e);
468 }
469 }
470
471 actions
472 }
473
474 pub fn void_tunnel_interface(&mut self, tunnel_id: &[u8; 32]) {
476 self.tunnel_table.void_tunnel_interface(tunnel_id);
477 }
478
479 pub fn tunnel_table(&self) -> &TunnelTable {
481 &self.tunnel_table
482 }
483
484 fn has_local_clients(&self) -> bool {
490 self.interfaces.values().any(|i| i.is_local_client)
491 }
492
493 fn packet_filter(&self, packet: &RawPacket) -> bool {
497 if packet.transport_id.is_some()
499 && packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE
500 {
501 if let Some(ref identity_hash) = self.config.identity_hash {
502 if packet.transport_id.as_ref() != Some(identity_hash) {
503 return false;
504 }
505 }
506 }
507
508 match packet.context {
510 constants::CONTEXT_KEEPALIVE
511 | constants::CONTEXT_RESOURCE_REQ
512 | constants::CONTEXT_RESOURCE_PRF
513 | constants::CONTEXT_RESOURCE
514 | constants::CONTEXT_CACHE_REQUEST
515 | constants::CONTEXT_CHANNEL => return true,
516 _ => {}
517 }
518
519 if packet.flags.destination_type == constants::DESTINATION_PLAIN
521 || packet.flags.destination_type == constants::DESTINATION_GROUP
522 {
523 if packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE {
524 return packet.hops <= 1;
525 } else {
526 return false;
528 }
529 }
530
531 if !self.packet_hashlist.is_duplicate(&packet.packet_hash) {
533 return true;
534 }
535
536 if packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
538 && packet.flags.destination_type == constants::DESTINATION_SINGLE
539 {
540 return true;
541 }
542
543 false
544 }
545
546 pub fn handle_inbound(
554 &mut self,
555 frame: InboundFrame<'_>,
556 rng: &mut dyn Rng,
557 ) -> Vec<TransportAction> {
558 self.handle_inbound_with_announce_queue(frame, rng, None)
559 }
560
561 pub fn handle_inbound_with_announce_queue(
562 &mut self,
563 frame: InboundFrame<'_>,
564 rng: &mut dyn Rng,
565 announce_queue: Option<&mut AnnounceVerifyQueue>,
566 ) -> Vec<TransportAction> {
567 let Some(ctx) = self.prepare_inbound_packet(frame) else {
568 return Vec::new();
569 };
570 let mut actions = Vec::new();
571
572 self.remember_inbound_packet_hash(&ctx.packet);
573 self.bridge_plain_broadcast(&ctx, &mut actions);
574 self.handle_transport_forwarding(&ctx, &mut actions);
575 self.handle_link_table_routing(&ctx, &mut actions);
576 self.handle_inbound_announce(&ctx, rng, announce_queue, &mut actions);
577
578 if ctx.packet.flags.packet_type == constants::PACKET_TYPE_PROOF {
579 self.process_inbound_proof(&ctx.packet, ctx.iface, ctx.now, &mut actions);
580 }
581
582 self.handle_inbound_local_delivery(&ctx, &mut actions);
583 actions
584 }
585
586 fn prepare_inbound_packet(&self, frame: InboundFrame<'_>) -> Option<InboundPacketCtx> {
587 let mut packet = RawPacket::unpack(frame.raw).ok()?;
588 let from_local_client = self
589 .interfaces
590 .get(&frame.iface)
591 .map(|i| i.is_local_client)
592 .unwrap_or(false);
593 packet.hops += 1;
594 packet.rssi = frame.rx.rssi;
595 packet.snr = frame.rx.snr;
596 if from_local_client {
597 packet.hops = packet.hops.saturating_sub(1);
598 }
599 if !self.packet_filter(&packet) {
600 return None;
601 }
602 let retain_original_raw = packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE;
603 Some(InboundPacketCtx {
604 packet,
605 original_raw: if retain_original_raw {
606 Some(frame.raw.to_vec())
607 } else {
608 None
609 },
610 iface: frame.iface,
611 now: frame.now,
612 from_local_client,
613 })
614 }
615
616 fn remember_inbound_packet_hash(&mut self, packet: &RawPacket) {
617 let remember_hash = !(self.link_table.contains_key(&packet.destination_hash)
618 || (packet.flags.packet_type == constants::PACKET_TYPE_PROOF
619 && packet.context == constants::CONTEXT_LRPROOF));
620 if remember_hash {
621 self.packet_hashlist.add(packet.packet_hash);
622 }
623 }
624
625 fn bridge_plain_broadcast(&self, ctx: &InboundPacketCtx, actions: &mut Vec<TransportAction>) {
626 if ctx.packet.flags.destination_type != constants::DESTINATION_PLAIN
627 || ctx.packet.flags.transport_type != constants::TRANSPORT_BROADCAST
628 || !self.has_local_clients()
629 {
630 return;
631 }
632
633 if ctx.from_local_client {
634 actions.push(TransportAction::ForwardPlainBroadcast {
635 raw: PacketBytes::from(ctx.packet.raw.clone()),
636 to_local: false,
637 exclude: Some(ctx.iface),
638 });
639 } else {
640 actions.push(TransportAction::ForwardPlainBroadcast {
641 raw: PacketBytes::from(ctx.packet.raw.clone()),
642 to_local: true,
643 exclude: None,
644 });
645 }
646 }
647
648 fn handle_transport_forwarding(
649 &mut self,
650 ctx: &InboundPacketCtx,
651 actions: &mut Vec<TransportAction>,
652 ) {
653 if !(self.config.transport_enabled || self.config.identity_hash.is_some()) {
654 return;
655 }
656 if ctx.packet.transport_id.is_none()
657 || ctx.packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
658 {
659 return;
660 }
661
662 let Some(identity_hash) = self.config.identity_hash else {
663 return;
664 };
665 if ctx.packet.transport_id != Some(identity_hash) {
666 return;
667 }
668
669 let Some(path_entry) = self
670 .path_table
671 .get(&ctx.packet.destination_hash)
672 .and_then(|ps| ps.primary())
673 else {
674 return;
675 };
676
677 let next_hop = path_entry.next_hop;
678 let remaining_hops = path_entry.hops;
679 let outbound_interface = path_entry.receiving_interface;
680 let new_raw =
681 forward_transport_packet(&ctx.packet, next_hop, remaining_hops, outbound_interface);
682
683 if ctx.packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST {
684 let proof_timeout = ctx.now
685 + constants::LINK_ESTABLISHMENT_TIMEOUT_PER_HOP * (remaining_hops.max(1) as f64);
686 let (link_id, link_entry) = create_link_entry(
687 &ctx.packet,
688 next_hop,
689 outbound_interface,
690 remaining_hops,
691 ctx.iface,
692 ctx.now,
693 proof_timeout,
694 );
695 self.link_table.insert(link_id, link_entry);
696 actions.push(TransportAction::LinkRequestReceived {
697 link_id,
698 destination_hash: ctx.packet.destination_hash,
699 receiving_interface: ctx.iface,
700 });
701 } else {
702 let (trunc_hash, reverse_entry) =
703 create_reverse_entry(&ctx.packet, outbound_interface, ctx.iface, ctx.now);
704 self.reverse_table.insert(trunc_hash, reverse_entry);
705 }
706
707 actions.push(TransportAction::SendOnInterface {
708 interface: outbound_interface,
709 raw: new_raw.into(),
710 });
711
712 if let Some(entry) = self
713 .path_table
714 .get_mut(&ctx.packet.destination_hash)
715 .and_then(|ps| ps.primary_mut())
716 {
717 entry.timestamp = ctx.now;
718 }
719 }
720
721 fn handle_link_table_routing(
722 &mut self,
723 ctx: &InboundPacketCtx,
724 actions: &mut Vec<TransportAction>,
725 ) {
726 if !self.config.transport_enabled && self.config.identity_hash.is_none() {
727 return;
728 }
729 if ctx.packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
730 || ctx.packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST
731 || ctx.packet.context == constants::CONTEXT_LRPROOF
732 {
733 return;
734 }
735
736 let Some(link_entry) = self.link_table.get(&ctx.packet.destination_hash).cloned() else {
737 return;
738 };
739 let Some((outbound_iface, new_raw)) =
740 route_via_link_table(&ctx.packet, &link_entry, ctx.iface)
741 else {
742 return;
743 };
744
745 self.packet_hashlist.add(ctx.packet.packet_hash);
746 actions.push(TransportAction::SendOnInterface {
747 interface: outbound_iface,
748 raw: new_raw.into(),
749 });
750
751 if let Some(entry) = self.link_table.get_mut(&ctx.packet.destination_hash) {
752 entry.timestamp = ctx.now;
753 }
754 }
755
756 fn handle_inbound_announce(
757 &mut self,
758 ctx: &InboundPacketCtx,
759 rng: &mut dyn Rng,
760 announce_queue: Option<&mut AnnounceVerifyQueue>,
761 actions: &mut Vec<TransportAction>,
762 ) {
763 if ctx.packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE {
764 return;
765 }
766
767 if let Some(queue) = announce_queue {
768 self.try_enqueue_announce(ctx, rng, queue, actions);
769 } else {
770 let original_raw = ctx
771 .original_raw
772 .as_deref()
773 .expect("announce packets retain original raw bytes");
774 self.process_inbound_announce(
775 &ctx.packet,
776 original_raw,
777 ctx.iface,
778 ctx.now,
779 rng,
780 actions,
781 );
782 }
783 }
784
785 fn handle_inbound_local_delivery(
786 &self,
787 ctx: &InboundPacketCtx,
788 actions: &mut Vec<TransportAction>,
789 ) {
790 if (ctx.packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST
791 || ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA)
792 && self
793 .local_destinations
794 .contains_key(&ctx.packet.destination_hash)
795 {
796 actions.push(TransportAction::DeliverLocal {
797 destination_hash: ctx.packet.destination_hash,
798 raw: PacketBytes::from(ctx.packet.raw.clone()),
799 packet_hash: ctx.packet.packet_hash,
800 receiving_interface: ctx.iface,
801 });
802 }
803 }
804
805 fn process_inbound_announce(
810 &mut self,
811 packet: &RawPacket,
812 original_raw: &[u8],
813 iface: InterfaceId,
814 now: f64,
815 rng: &mut dyn Rng,
816 actions: &mut Vec<TransportAction>,
817 ) {
818 if packet.flags.destination_type != constants::DESTINATION_SINGLE {
819 return;
820 }
821
822 let has_ratchet = packet.flags.context_flag == constants::FLAG_SET;
823
824 let announce = match AnnounceData::unpack(&packet.data, has_ratchet) {
826 Ok(a) => a,
827 Err(_) => return,
828 };
829
830 if self.should_hold_announce(packet, original_raw, iface, now) {
831 return;
832 }
833
834 let sig_cache_key =
835 Self::announce_sig_cache_key(packet.destination_hash, &announce.signature);
836
837 let validated = if self.announce_sig_cache.contains(&sig_cache_key) {
838 announce.to_validated_unchecked()
839 } else {
840 match announce.validate(&packet.destination_hash) {
841 Ok(v) => {
842 self.announce_sig_cache.insert(sig_cache_key, now);
843 v
844 }
845 Err(_) => return,
846 }
847 };
848
849 let received_from = self.announce_received_from(packet, now);
850 let random_blob = match extract_random_blob(&packet.data) {
851 Some(b) => b,
852 None => return,
853 };
854 let announce_emitted = timebase_from_random_blob(&random_blob);
855
856 self.process_verified_announce(
857 VerifiedAnnounceCtx {
858 packet,
859 original_raw,
860 iface,
861 now,
862 validated,
863 received_from,
864 random_blob,
865 announce_emitted,
866 },
867 rng,
868 actions,
869 );
870 }
871
872 fn announce_sig_cache_key(destination_hash: [u8; 16], signature: &[u8; 64]) -> [u8; 32] {
873 let mut material = [0u8; 80];
874 material[..16].copy_from_slice(&destination_hash);
875 material[16..].copy_from_slice(signature);
876 hash::full_hash(&material)
877 }
878
879 fn announce_received_from(&mut self, packet: &RawPacket, now: f64) -> [u8; 16] {
880 if let Some(transport_id) = packet.transport_id {
881 if self.config.transport_enabled {
882 if let Some(announce_entry) = self.announce_table.get_mut(&packet.destination_hash)
883 {
884 if packet.hops.checked_sub(1) == Some(announce_entry.hops) {
885 announce_entry.local_rebroadcasts += 1;
886 if announce_entry.retries > 0
887 && announce_entry.local_rebroadcasts
888 >= constants::LOCAL_REBROADCASTS_MAX
889 {
890 self.announce_table.remove(&packet.destination_hash);
891 }
892 }
893 if let Some(announce_entry) = self.announce_table.get(&packet.destination_hash)
894 {
895 if packet.hops.checked_sub(1) == Some(announce_entry.hops + 1)
896 && announce_entry.retries > 0
897 && now < announce_entry.retransmit_timeout
898 {
899 self.announce_table.remove(&packet.destination_hash);
900 }
901 }
902 }
903 }
904 transport_id
905 } else {
906 packet.destination_hash
907 }
908 }
909
910 fn should_hold_announce(
911 &mut self,
912 packet: &RawPacket,
913 original_raw: &[u8],
914 iface: InterfaceId,
915 now: f64,
916 ) -> bool {
917 if self.has_path(&packet.destination_hash) {
918 return false;
919 }
920 if self
921 .discovery_path_requests
922 .contains_key(&packet.destination_hash)
923 {
924 return false;
925 }
926 let Some(info) = self.interfaces.get(&iface) else {
927 return false;
928 };
929 if packet.context == constants::CONTEXT_PATH_RESPONSE
930 || !self.ingress_control.should_ingress_limit(
931 iface,
932 &info.ingress_control,
933 info.ia_freq,
934 info.started,
935 now,
936 )
937 {
938 return false;
939 }
940 self.ingress_control.hold_announce(
941 iface,
942 &info.ingress_control,
943 packet.destination_hash,
944 ingress_control::HeldAnnounce {
945 raw: original_raw.to_vec(),
946 hops: packet.hops,
947 receiving_interface: iface,
948 rx: RxMetadata {
949 rssi: packet.rssi,
950 snr: packet.snr,
951 },
952 timestamp: now,
953 },
954 );
955 true
956 }
957
958 fn try_enqueue_announce(
959 &mut self,
960 ctx: &InboundPacketCtx,
961 rng: &mut dyn Rng,
962 announce_queue: &mut AnnounceVerifyQueue,
963 actions: &mut Vec<TransportAction>,
964 ) {
965 if ctx.packet.flags.destination_type != constants::DESTINATION_SINGLE {
966 return;
967 }
968
969 let has_ratchet = ctx.packet.flags.context_flag == constants::FLAG_SET;
970 let announce = match AnnounceData::unpack(&ctx.packet.data, has_ratchet) {
971 Ok(a) => a,
972 Err(_) => return,
973 };
974
975 let received_from = self.announce_received_from(&ctx.packet, ctx.now);
976
977 if self
978 .local_destinations
979 .contains_key(&ctx.packet.destination_hash)
980 {
981 log::debug!(
982 "Announce:skipping local destination {:02x}{:02x}{:02x}{:02x}..",
983 ctx.packet.destination_hash[0],
984 ctx.packet.destination_hash[1],
985 ctx.packet.destination_hash[2],
986 ctx.packet.destination_hash[3],
987 );
988 return;
989 }
990
991 let original_raw = ctx
992 .original_raw
993 .as_deref()
994 .expect("announce packets retain original raw bytes");
995 if self.should_hold_announce(&ctx.packet, original_raw, ctx.iface, ctx.now) {
996 return;
997 }
998
999 let sig_cache_key =
1000 Self::announce_sig_cache_key(ctx.packet.destination_hash, &announce.signature);
1001 if self.announce_sig_cache.contains(&sig_cache_key) {
1002 let validated = announce.to_validated_unchecked();
1003 let random_blob = match extract_random_blob(&ctx.packet.data) {
1004 Some(b) => b,
1005 None => return,
1006 };
1007 let announce_emitted = timebase_from_random_blob(&random_blob);
1008 self.process_verified_announce(
1009 VerifiedAnnounceCtx {
1010 packet: &ctx.packet,
1011 original_raw,
1012 iface: ctx.iface,
1013 now: ctx.now,
1014 validated,
1015 received_from,
1016 random_blob,
1017 announce_emitted,
1018 },
1019 rng,
1020 actions,
1021 );
1022 return;
1023 }
1024
1025 if ctx.packet.context == constants::CONTEXT_PATH_RESPONSE {
1026 let Ok(validated) = announce.validate(&ctx.packet.destination_hash) else {
1027 return;
1028 };
1029 self.announce_sig_cache.insert(sig_cache_key, ctx.now);
1030 let random_blob = match extract_random_blob(&ctx.packet.data) {
1031 Some(b) => b,
1032 None => return,
1033 };
1034 let announce_emitted = timebase_from_random_blob(&random_blob);
1035 self.process_verified_announce(
1036 VerifiedAnnounceCtx {
1037 packet: &ctx.packet,
1038 original_raw,
1039 iface: ctx.iface,
1040 now: ctx.now,
1041 validated,
1042 received_from,
1043 random_blob,
1044 announce_emitted,
1045 },
1046 rng,
1047 actions,
1048 );
1049 return;
1050 }
1051
1052 let random_blob = match extract_random_blob(&ctx.packet.data) {
1053 Some(b) => b,
1054 None => return,
1055 };
1056 let announce_emitted = timebase_from_random_blob(&random_blob);
1057 let key = AnnounceVerifyKey {
1058 destination_hash: ctx.packet.destination_hash,
1059 random_blob,
1060 received_from,
1061 };
1062 let pending = PendingAnnounce {
1063 original_raw: original_raw.to_vec(),
1064 packet: ctx.packet.clone(),
1065 interface: ctx.iface,
1066 received_from,
1067 queued_at: ctx.now,
1068 best_hops: ctx.packet.hops,
1069 emission_ts: announce_emitted,
1070 random_blob,
1071 };
1072 let _ = announce_queue.enqueue(key, pending);
1073 }
1074
1075 pub fn complete_verified_announce(
1076 &mut self,
1077 pending: PendingAnnounce,
1078 validated: crate::announce::ValidatedAnnounce,
1079 sig_cache_key: [u8; 32],
1080 now: f64,
1081 rng: &mut dyn Rng,
1082 ) -> Vec<TransportAction> {
1083 self.announce_sig_cache.insert(sig_cache_key, now);
1084 let mut actions = Vec::new();
1085 self.process_verified_announce(
1086 VerifiedAnnounceCtx {
1087 packet: &pending.packet,
1088 original_raw: &pending.original_raw,
1089 iface: pending.interface,
1090 now,
1091 validated,
1092 received_from: pending.received_from,
1093 random_blob: pending.random_blob,
1094 announce_emitted: pending.emission_ts,
1095 },
1096 rng,
1097 &mut actions,
1098 );
1099 actions
1100 }
1101
1102 pub fn clear_failed_verified_announce(&mut self, _sig_cache_key: [u8; 32], _now: f64) {}
1103
1104 fn process_verified_announce(
1105 &mut self,
1106 ctx: VerifiedAnnounceCtx<'_>,
1107 rng: &mut dyn Rng,
1108 actions: &mut Vec<TransportAction>,
1109 ) {
1110 if self.is_blackholed(&ctx.validated.identity_hash, ctx.now) {
1111 return;
1112 }
1113 if ctx.packet.hops > constants::PATHFINDER_M {
1114 return;
1115 }
1116
1117 let existing_set = self.path_table.get(&ctx.packet.destination_hash);
1118 let was_unknown_destination = existing_set.is_none_or(|ps| ps.is_empty());
1119
1120 if was_unknown_destination {
1123 self.path_states.remove(&ctx.packet.destination_hash);
1124 }
1125
1126 let is_unresponsive = self.path_is_unresponsive(&ctx.packet.destination_hash);
1128
1129 let mp_decision = decide_announce_multipath(
1130 existing_set,
1131 ctx.packet.hops,
1132 ctx.announce_emitted,
1133 &ctx.random_blob,
1134 &ctx.received_from,
1135 is_unresponsive,
1136 ctx.now,
1137 self.config.prefer_shorter_path,
1138 );
1139
1140 if mp_decision == MultiPathDecision::Reject {
1141 log::debug!(
1142 "Announce:path decision REJECT for dest={:02x}{:02x}{:02x}{:02x}..",
1143 ctx.packet.destination_hash[0],
1144 ctx.packet.destination_hash[1],
1145 ctx.packet.destination_hash[2],
1146 ctx.packet.destination_hash[3],
1147 );
1148 return;
1149 }
1150
1151 let rate_blocked = if ctx.packet.context != constants::CONTEXT_PATH_RESPONSE {
1153 if let Some(iface_info) = self.interfaces.get(&ctx.iface) {
1154 self.rate_limiter.check_and_update(
1155 &ctx.packet.destination_hash,
1156 ctx.now,
1157 iface_info.announce_rate_target,
1158 iface_info.announce_rate_grace,
1159 iface_info.announce_rate_penalty,
1160 )
1161 } else {
1162 false
1163 }
1164 } else {
1165 false
1166 };
1167
1168 let interface_mode = self
1170 .interfaces
1171 .get(&ctx.iface)
1172 .map(|i| i.mode)
1173 .unwrap_or(constants::MODE_FULL);
1174
1175 let expires = compute_path_expires(ctx.now, interface_mode);
1176
1177 let existing_blobs = self
1179 .path_table
1180 .get(&ctx.packet.destination_hash)
1181 .and_then(|ps| ps.find_by_next_hop(&ctx.received_from))
1182 .map(|e| e.random_blobs.clone())
1183 .unwrap_or_default();
1184
1185 let mut rng_bytes = [0u8; 8];
1187 rng.fill_bytes(&mut rng_bytes);
1188 let rng_value = (u64::from_le_bytes(rng_bytes) as f64) / (u64::MAX as f64);
1189
1190 let is_path_response = ctx.packet.context == constants::CONTEXT_PATH_RESPONSE;
1191
1192 let (path_entry, announce_entry) = announce_proc::process_validated_announce(
1193 ctx.packet.destination_hash,
1194 ctx.packet.hops,
1195 &ctx.packet.data,
1196 &ctx.packet.raw,
1197 ctx.packet.packet_hash,
1198 ctx.packet.flags.context_flag,
1199 ctx.received_from,
1200 ctx.iface,
1201 ctx.now,
1202 existing_blobs,
1203 ctx.random_blob,
1204 expires,
1205 rng_value,
1206 self.config.transport_enabled,
1207 is_path_response,
1208 rate_blocked,
1209 Some(ctx.original_raw.to_vec()),
1210 );
1211
1212 actions.push(TransportAction::CacheAnnounce {
1214 packet_hash: ctx.packet.packet_hash,
1215 raw: ctx.original_raw.to_vec().into(),
1216 });
1217
1218 self.upsert_path_destination(ctx.packet.destination_hash, path_entry, ctx.now);
1220
1221 if let Some(tunnel_id) = self.interfaces.get(&ctx.iface).and_then(|i| i.tunnel_id) {
1223 let blobs = self
1224 .path_table
1225 .get(&ctx.packet.destination_hash)
1226 .and_then(|ps| ps.find_by_next_hop(&ctx.received_from))
1227 .map(|e| e.random_blobs.clone())
1228 .unwrap_or_default();
1229 self.tunnel_table.store_tunnel_path(
1230 &tunnel_id,
1231 ctx.packet.destination_hash,
1232 tunnel::TunnelPath {
1233 timestamp: ctx.now,
1234 received_from: ctx.received_from,
1235 hops: ctx.packet.hops,
1236 expires,
1237 random_blobs: blobs,
1238 packet_hash: ctx.packet.packet_hash,
1239 },
1240 ctx.now,
1241 self.config.destination_timeout_secs,
1242 self.config.max_tunnel_destinations_total,
1243 );
1244 }
1245
1246 self.path_states.remove(&ctx.packet.destination_hash);
1249
1250 if let Some(ann) = announce_entry {
1252 self.insert_announce_entry(ctx.packet.destination_hash, ann, ctx.now);
1253 }
1254
1255 actions.push(TransportAction::AnnounceReceived {
1257 destination_hash: ctx.packet.destination_hash,
1258 identity_hash: ctx.validated.identity_hash,
1259 public_key: ctx.validated.public_key,
1260 name_hash: ctx.validated.name_hash,
1261 random_hash: ctx.validated.random_hash,
1262 ratchet: ctx.validated.ratchet,
1263 app_data: ctx.validated.app_data,
1264 hops: ctx.packet.hops,
1265 receiving_interface: ctx.iface,
1266 rx: RxMetadata {
1267 rssi: ctx.packet.rssi,
1268 snr: ctx.packet.snr,
1269 },
1270 });
1271
1272 actions.push(TransportAction::PathUpdated {
1273 destination_hash: ctx.packet.destination_hash,
1274 hops: ctx.packet.hops,
1275 next_hop: ctx.received_from,
1276 interface: ctx.iface,
1277 });
1278
1279 if self.has_local_clients() {
1281 actions.push(TransportAction::ForwardToLocalClients {
1282 raw: PacketBytes::from(ctx.packet.raw.clone()),
1283 exclude: Some(ctx.iface),
1284 });
1285 }
1286
1287 if let Some(pr_entry) = self.discovery_path_requests_waiting(&ctx.packet.destination_hash) {
1289 let entry = AnnounceEntry {
1291 timestamp: ctx.now,
1292 retransmit_timeout: ctx.now,
1293 retries: constants::PATHFINDER_R,
1294 received_from: ctx.received_from,
1295 hops: ctx.packet.hops,
1296 packet_raw: ctx.packet.raw.clone(),
1297 packet_data: ctx.packet.data.clone(),
1298 destination_hash: ctx.packet.destination_hash,
1299 context_flag: ctx.packet.flags.context_flag,
1300 local_rebroadcasts: 0,
1301 block_rebroadcasts: true,
1302 attached_interface: Some(pr_entry),
1303 };
1304 self.insert_announce_entry(ctx.packet.destination_hash, entry, ctx.now);
1305 }
1306 }
1307
1308 pub fn announce_sig_cache_contains(&self, sig_cache_key: &[u8; 32]) -> bool {
1309 self.announce_sig_cache.contains(sig_cache_key)
1310 }
1311
1312 fn discovery_path_requests_waiting(&mut self, dest_hash: &[u8; 16]) -> Option<InterfaceId> {
1315 self.discovery_path_requests
1316 .remove(dest_hash)
1317 .map(|req| req.requesting_interface)
1318 }
1319
1320 fn process_inbound_proof(
1325 &mut self,
1326 packet: &RawPacket,
1327 iface: InterfaceId,
1328 _now: f64,
1329 actions: &mut Vec<TransportAction>,
1330 ) {
1331 if packet.context == constants::CONTEXT_LRPROOF {
1332 if (self.config.transport_enabled)
1334 && self.link_table.contains_key(&packet.destination_hash)
1335 {
1336 let link_entry = self.link_table.get(&packet.destination_hash).cloned();
1337 if let Some(entry) = link_entry {
1338 if let Some((outbound_interface, new_raw)) =
1339 route_via_link_table(packet, &entry, iface)
1340 {
1341 if let Some(le) = self.link_table.get_mut(&packet.destination_hash) {
1346 le.validated = true;
1347 }
1348
1349 actions.push(TransportAction::LinkEstablished {
1350 link_id: packet.destination_hash,
1351 interface: outbound_interface,
1352 });
1353
1354 actions.push(TransportAction::SendOnInterface {
1355 interface: outbound_interface,
1356 raw: new_raw.into(),
1357 });
1358 }
1359 }
1360 } else {
1361 actions.push(TransportAction::DeliverLocal {
1363 destination_hash: packet.destination_hash,
1364 raw: PacketBytes::from(packet.raw.clone()),
1365 packet_hash: packet.packet_hash,
1366 receiving_interface: iface,
1367 });
1368 }
1369 } else {
1370 if self.config.transport_enabled {
1372 if let Some(reverse_entry) = self.reverse_table.remove(&packet.destination_hash) {
1373 if let Some(action) = route_proof_via_reverse(packet, &reverse_entry, iface) {
1374 actions.push(action);
1375 }
1376 }
1377 }
1378
1379 actions.push(TransportAction::DeliverLocal {
1381 destination_hash: packet.destination_hash,
1382 raw: PacketBytes::from(packet.raw.clone()),
1383 packet_hash: packet.packet_hash,
1384 receiving_interface: iface,
1385 });
1386 }
1387 }
1388
1389 pub fn handle_outbound(
1395 &mut self,
1396 packet: &RawPacket,
1397 dest_type: u8,
1398 attached_interface: Option<InterfaceId>,
1399 now: f64,
1400 ) -> Vec<TransportAction> {
1401 let actions = route_outbound(
1402 &self.path_table,
1403 &self.interfaces,
1404 &self.local_destinations,
1405 packet,
1406 dest_type,
1407 attached_interface,
1408 now,
1409 );
1410
1411 self.packet_hashlist.add(packet.packet_hash);
1413
1414 if packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE && packet.hops > 0 {
1416 self.gate_announce_actions(actions, &packet.destination_hash, packet.hops, now)
1417 } else {
1418 actions
1419 }
1420 }
1421
1422 fn gate_announce_actions(
1424 &mut self,
1425 actions: Vec<TransportAction>,
1426 dest_hash: &[u8; 16],
1427 hops: u8,
1428 now: f64,
1429 ) -> Vec<TransportAction> {
1430 let mut result = Vec::new();
1431 for action in actions {
1432 match action {
1433 TransportAction::SendOnInterface { interface, raw } => {
1434 let (bitrate, airtime_profile, announce_cap) =
1435 if let Some(info) = self.interfaces.get(&interface) {
1436 (info.bitrate, info.airtime_profile, info.announce_cap)
1437 } else {
1438 (None, None, constants::ANNOUNCE_CAP)
1439 };
1440 if let Some(send_action) = self.announce_queues.gate_announce(
1441 interface,
1442 raw,
1443 *dest_hash,
1444 hops,
1445 now,
1446 now,
1447 bitrate,
1448 airtime_profile,
1449 announce_cap,
1450 ) {
1451 result.push(send_action);
1452 }
1453 }
1455 other => result.push(other),
1456 }
1457 }
1458 result
1459 }
1460
1461 pub fn tick(&mut self, now: f64, rng: &mut dyn Rng) -> Vec<TransportAction> {
1467 let mut ctx = TickCtx {
1468 now,
1469 rng,
1470 actions: Vec::new(),
1471 };
1472 self.process_tick_pending_announces(&mut ctx);
1473
1474 let mut queue_actions = self.announce_queues.process_queues(now, &self.interfaces);
1475 ctx.actions.append(&mut queue_actions);
1476
1477 self.process_tick_ingress_release(&mut ctx);
1478 self.cull_tick_tables(&mut ctx);
1479 ctx.actions
1480 }
1481
1482 fn process_tick_pending_announces(&mut self, ctx: &mut TickCtx<'_>) {
1483 if ctx.now <= self.announces_last_checked + constants::ANNOUNCES_CHECK_INTERVAL {
1484 return;
1485 }
1486
1487 self.cull_expired_announce_entries(ctx.now);
1488 self.enforce_announce_retention_cap(ctx.now);
1489 if let Some(identity_hash) = self.config.identity_hash {
1490 let announce_actions = jobs::process_pending_announces(
1491 &mut self.announce_table,
1492 &mut self.held_announces,
1493 &identity_hash,
1494 ctx.now,
1495 );
1496 let gated = self.gate_retransmit_actions(announce_actions, ctx.now);
1497 ctx.actions.extend(gated);
1498 }
1499 self.cull_expired_announce_entries(ctx.now);
1500 self.enforce_announce_retention_cap(ctx.now);
1501 self.announces_last_checked = ctx.now;
1502 }
1503
1504 fn process_tick_ingress_release(&mut self, ctx: &mut TickCtx<'_>) {
1505 let ic_interfaces = self.ingress_control.interfaces_with_held();
1506 for iface_id in ic_interfaces {
1507 let (ia_freq, started, ingress_config) = match self.interfaces.get(&iface_id) {
1508 Some(info) => (info.ia_freq, info.started, info.ingress_control),
1509 None => continue,
1510 };
1511 if !ingress_config.enabled {
1512 continue;
1513 }
1514 if let Some(held) = self.ingress_control.process_held_announces(
1515 iface_id,
1516 &ingress_config,
1517 ia_freq,
1518 started,
1519 ctx.now,
1520 ) {
1521 let released_actions = self.handle_inbound(
1522 InboundFrame {
1523 raw: &held.raw,
1524 iface: held.receiving_interface,
1525 now: ctx.now,
1526 rx: held.rx,
1527 },
1528 ctx.rng,
1529 );
1530 ctx.actions.extend(released_actions);
1531 }
1532 }
1533 }
1534
1535 fn cull_tick_tables(&mut self, ctx: &mut TickCtx<'_>) {
1536 if ctx.now <= self.tables_last_culled + constants::TABLES_CULL_INTERVAL {
1537 return;
1538 }
1539
1540 jobs::cull_path_table(&mut self.path_table, &self.interfaces, ctx.now);
1541 jobs::cull_reverse_table(&mut self.reverse_table, &self.interfaces, ctx.now);
1542 let (_culled, link_closed_actions) =
1543 jobs::cull_link_table(&mut self.link_table, &self.interfaces, ctx.now);
1544 ctx.actions.extend(link_closed_actions);
1545 jobs::cull_path_states(&mut self.path_states, &self.path_table);
1546 self.cull_blackholed(ctx.now);
1547 self.discovery_path_requests
1548 .retain(|_, req| ctx.now - req.timestamp < constants::DISCOVERY_PATH_REQUEST_TIMEOUT);
1549 self.tunnel_table
1550 .void_missing_interfaces(|id| self.interfaces.contains_key(id));
1551 self.tunnel_table.cull(ctx.now);
1552 self.announce_sig_cache.cull(ctx.now);
1553 self.tables_last_culled = ctx.now;
1554 }
1555
1556 fn gate_retransmit_actions(
1561 &mut self,
1562 actions: Vec<TransportAction>,
1563 now: f64,
1564 ) -> Vec<TransportAction> {
1565 let mut result = Vec::new();
1566 for action in actions {
1567 match action {
1568 TransportAction::SendOnInterface { interface, raw } => {
1569 let (dest_hash, hops) = Self::extract_announce_info(&raw);
1571 let (bitrate, airtime_profile, announce_cap) =
1572 if let Some(info) = self.interfaces.get(&interface) {
1573 (info.bitrate, info.airtime_profile, info.announce_cap)
1574 } else {
1575 (None, None, constants::ANNOUNCE_CAP)
1576 };
1577 if let Some(send_action) = self.announce_queues.gate_announce(
1578 interface,
1579 raw,
1580 dest_hash,
1581 hops,
1582 now,
1583 now,
1584 bitrate,
1585 airtime_profile,
1586 announce_cap,
1587 ) {
1588 result.push(send_action);
1589 }
1590 }
1591 TransportAction::BroadcastOnAllInterfaces { raw, exclude } => {
1592 let (dest_hash, hops) = Self::extract_announce_info(&raw);
1593 let iface_ids: Vec<(
1596 InterfaceId,
1597 Option<u64>,
1598 Option<types::AirtimeProfile>,
1599 f64,
1600 )> = self
1601 .interfaces
1602 .iter()
1603 .filter(|(_, info)| info.out_capable)
1604 .filter(|(id, _)| {
1605 if let Some(ref ex) = exclude {
1606 **id != *ex
1607 } else {
1608 true
1609 }
1610 })
1611 .filter(|(_, info)| {
1612 should_transmit_announce(
1613 info,
1614 &dest_hash,
1615 hops,
1616 &self.local_destinations,
1617 &self.path_table,
1618 &self.interfaces,
1619 )
1620 })
1621 .map(|(id, info)| {
1622 (*id, info.bitrate, info.airtime_profile, info.announce_cap)
1623 })
1624 .collect();
1625
1626 for (iface_id, bitrate, airtime_profile, announce_cap) in iface_ids {
1627 if let Some(send_action) = self.announce_queues.gate_announce(
1628 iface_id,
1629 raw.clone(),
1630 dest_hash,
1631 hops,
1632 now,
1633 now,
1634 bitrate,
1635 airtime_profile,
1636 announce_cap,
1637 ) {
1638 result.push(send_action);
1639 }
1640 }
1641 }
1642 other => result.push(other),
1643 }
1644 }
1645 result
1646 }
1647
1648 fn extract_announce_info(raw: &[u8]) -> ([u8; 16], u8) {
1650 if raw.len() < 18 {
1651 return ([0; 16], 0);
1652 }
1653 let header_type = (raw[0] >> 6) & 0x03;
1654 let hops = raw[1];
1655 if header_type == constants::HEADER_2 && raw.len() >= 34 {
1656 let mut dest = [0u8; 16];
1658 dest.copy_from_slice(&raw[18..34]);
1659 (dest, hops)
1660 } else {
1661 let mut dest = [0u8; 16];
1663 dest.copy_from_slice(&raw[2..18]);
1664 (dest, hops)
1665 }
1666 }
1667
1668 #[cfg(test)]
1669 #[allow(dead_code)]
1670 pub(crate) fn link_table_ref(&self) -> &BTreeMap<[u8; 16], LinkEntry> {
1671 &self.link_table
1672 }
1673}
1674
1675#[cfg(test)]
1676mod tests {
1677 use super::*;
1678 use crate::packet::PacketFlags;
1679
1680 fn make_config(transport_enabled: bool) -> TransportConfig {
1681 TransportConfig {
1682 transport_enabled,
1683 identity_hash: if transport_enabled {
1684 Some([0x42; 16])
1685 } else {
1686 None
1687 },
1688 prefer_shorter_path: false,
1689 max_paths_per_destination: 1,
1690 packet_hashlist_max_entries: constants::HASHLIST_MAXSIZE,
1691 max_discovery_pr_tags: constants::MAX_PR_TAGS,
1692 max_path_destinations: usize::MAX,
1693 max_tunnel_destinations_total: usize::MAX,
1694 destination_timeout_secs: constants::DESTINATION_TIMEOUT,
1695 announce_table_ttl_secs: constants::ANNOUNCE_TABLE_TTL,
1696 announce_table_max_bytes: constants::ANNOUNCE_TABLE_MAX_BYTES,
1697 announce_sig_cache_enabled: true,
1698 announce_sig_cache_max_entries: constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
1699 announce_sig_cache_ttl_secs: constants::ANNOUNCE_SIG_CACHE_TTL,
1700 announce_queue_max_entries: 256,
1701 announce_queue_max_interfaces: 1024,
1702 }
1703 }
1704
1705 fn make_interface(id: u64, mode: u8) -> InterfaceInfo {
1706 InterfaceInfo {
1707 id: InterfaceId(id),
1708 name: String::from("test"),
1709 mode,
1710 out_capable: true,
1711 in_capable: true,
1712 bitrate: None,
1713 airtime_profile: None,
1714 announce_rate_target: None,
1715 announce_rate_grace: 0,
1716 announce_rate_penalty: 0.0,
1717 announce_cap: constants::ANNOUNCE_CAP,
1718 is_local_client: false,
1719 wants_tunnel: false,
1720 tunnel_id: None,
1721 mtu: constants::MTU as u32,
1722 ingress_control: crate::transport::types::IngressControlConfig::disabled(),
1723 ia_freq: 0.0,
1724 ip_freq: 0.0,
1725 op_freq: 0.0,
1726 op_samples: 0,
1727 started: 0.0,
1728 }
1729 }
1730
1731 fn make_announce_entry(dest_hash: [u8; 16], timestamp: f64, fill_len: usize) -> AnnounceEntry {
1732 AnnounceEntry {
1733 timestamp,
1734 retransmit_timeout: timestamp,
1735 retries: 0,
1736 received_from: [0xAA; 16],
1737 hops: 2,
1738 packet_raw: vec![0x01; fill_len],
1739 packet_data: vec![0x02; fill_len],
1740 destination_hash: dest_hash,
1741 context_flag: 0,
1742 local_rebroadcasts: 0,
1743 block_rebroadcasts: false,
1744 attached_interface: None,
1745 }
1746 }
1747
1748 fn make_path_entry(
1749 timestamp: f64,
1750 hops: u8,
1751 receiving_interface: InterfaceId,
1752 next_hop: [u8; 16],
1753 ) -> PathEntry {
1754 PathEntry {
1755 timestamp,
1756 next_hop,
1757 hops,
1758 expires: timestamp + 10_000.0,
1759 random_blobs: Vec::new(),
1760 receiving_interface,
1761 packet_hash: [0; 32],
1762 announce_raw: None,
1763 }
1764 }
1765
1766 fn make_unique_tag(dest_hash: [u8; 16], tag: &[u8]) -> [u8; 32] {
1767 let mut unique_tag = [0u8; 32];
1768 let tag_len = tag.len().min(16);
1769 unique_tag[..16].copy_from_slice(&dest_hash);
1770 unique_tag[16..16 + tag_len].copy_from_slice(&tag[..tag_len]);
1771 unique_tag
1772 }
1773
1774 fn make_random_blob(timebase: u64) -> [u8; 10] {
1775 let mut blob = [0u8; 10];
1776 let bytes = timebase.to_be_bytes();
1777 blob[5..10].copy_from_slice(&bytes[3..8]);
1778 blob
1779 }
1780
1781 #[test]
1782 fn test_empty_engine() {
1783 let engine = TransportEngine::new(make_config(false));
1784 assert!(!engine.has_path(&[0; 16]));
1785 assert!(engine.hops_to(&[0; 16]).is_none());
1786 assert!(engine.next_hop(&[0; 16]).is_none());
1787 }
1788
1789 #[test]
1790 fn test_register_deregister_interface() {
1791 let mut engine = TransportEngine::new(make_config(false));
1792 engine.register_interface(make_interface(1, constants::MODE_FULL));
1793 assert!(engine.interfaces.contains_key(&InterfaceId(1)));
1794
1795 engine.deregister_interface(InterfaceId(1));
1796 assert!(!engine.interfaces.contains_key(&InterfaceId(1)));
1797 }
1798
1799 #[test]
1800 fn test_deregister_interface_removes_announce_queue_state() {
1801 let mut engine = TransportEngine::new(make_config(false));
1802 engine.register_interface(make_interface(1, constants::MODE_FULL));
1803
1804 let _ = engine.announce_queues.gate_announce(
1805 InterfaceId(1),
1806 vec![0x01; 100].into(),
1807 [0xAA; 16],
1808 2,
1809 0.0,
1810 0.0,
1811 Some(1000),
1812 None,
1813 constants::ANNOUNCE_CAP,
1814 );
1815 let _ = engine.announce_queues.gate_announce(
1816 InterfaceId(1),
1817 vec![0x02; 100].into(),
1818 [0xBB; 16],
1819 3,
1820 0.0,
1821 0.0,
1822 Some(1000),
1823 None,
1824 constants::ANNOUNCE_CAP,
1825 );
1826 assert_eq!(engine.announce_queue_count(), 1);
1827
1828 engine.deregister_interface(InterfaceId(1));
1829 assert_eq!(engine.announce_queue_count(), 0);
1830 }
1831
1832 #[test]
1833 fn test_deregister_interface_preserves_other_announce_queues() {
1834 let mut engine = TransportEngine::new(make_config(false));
1835 engine.register_interface(make_interface(1, constants::MODE_FULL));
1836 engine.register_interface(make_interface(2, constants::MODE_FULL));
1837
1838 let _ = engine.announce_queues.gate_announce(
1839 InterfaceId(1),
1840 vec![0x01; 100].into(),
1841 [0xAA; 16],
1842 2,
1843 0.0,
1844 0.0,
1845 Some(1000),
1846 None,
1847 constants::ANNOUNCE_CAP,
1848 );
1849 let _ = engine.announce_queues.gate_announce(
1850 InterfaceId(1),
1851 vec![0x02; 100].into(),
1852 [0xAB; 16],
1853 3,
1854 0.0,
1855 0.0,
1856 Some(1000),
1857 None,
1858 constants::ANNOUNCE_CAP,
1859 );
1860 let _ = engine.announce_queues.gate_announce(
1861 InterfaceId(2),
1862 vec![0x03; 100].into(),
1863 [0xBA; 16],
1864 2,
1865 0.0,
1866 0.0,
1867 Some(1000),
1868 None,
1869 constants::ANNOUNCE_CAP,
1870 );
1871 let _ = engine.announce_queues.gate_announce(
1872 InterfaceId(2),
1873 vec![0x04; 100].into(),
1874 [0xBB; 16],
1875 3,
1876 0.0,
1877 0.0,
1878 Some(1000),
1879 None,
1880 constants::ANNOUNCE_CAP,
1881 );
1882
1883 engine.deregister_interface(InterfaceId(1));
1884 assert_eq!(engine.announce_queue_count(), 1);
1885 assert_eq!(engine.nonempty_announce_queue_count(), 1);
1886 }
1887
1888 #[test]
1889 fn test_register_deregister_destination() {
1890 let mut engine = TransportEngine::new(make_config(false));
1891 let dest = [0x11; 16];
1892 engine.register_destination(dest, constants::DESTINATION_SINGLE);
1893 assert!(engine.local_destinations.contains_key(&dest));
1894
1895 engine.deregister_destination(&dest);
1896 assert!(!engine.local_destinations.contains_key(&dest));
1897 }
1898
1899 #[test]
1900 fn test_path_state() {
1901 let mut engine = TransportEngine::new(make_config(false));
1902 let dest = [0x22; 16];
1903
1904 assert!(!engine.path_is_unresponsive(&dest));
1905
1906 engine.mark_path_unresponsive(&dest, None);
1907 assert!(engine.path_is_unresponsive(&dest));
1908
1909 engine.mark_path_responsive(&dest);
1910 assert!(!engine.path_is_unresponsive(&dest));
1911 }
1912
1913 #[test]
1914 fn test_announce_clears_stale_path_state_for_unknown_destination() {
1915 use crate::announce::AnnounceData;
1916 use crate::destination::{destination_hash, name_hash};
1917
1918 let mut engine = TransportEngine::new(make_config(false));
1919 engine.register_interface(make_interface(1, constants::MODE_FULL));
1920
1921 let identity =
1922 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x61; 32]));
1923 let dest_hash = destination_hash("pathfix", &["announce"], Some(identity.hash()));
1924 let name_h = name_hash("pathfix", &["announce"]);
1925 let random_hash = [0x24u8; 10];
1926
1927 let (announce_data, _) =
1928 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
1929
1930 let packet = RawPacket::pack(
1931 PacketFlags {
1932 header_type: constants::HEADER_1,
1933 context_flag: constants::FLAG_UNSET,
1934 transport_type: constants::TRANSPORT_BROADCAST,
1935 destination_type: constants::DESTINATION_SINGLE,
1936 packet_type: constants::PACKET_TYPE_ANNOUNCE,
1937 },
1938 0,
1939 &dest_hash,
1940 None,
1941 constants::CONTEXT_NONE,
1942 &announce_data,
1943 )
1944 .unwrap();
1945
1946 engine.mark_path_unresponsive(&dest_hash, None);
1947 assert!(engine.path_is_unresponsive(&dest_hash));
1948 assert!(!engine.has_path(&dest_hash));
1949
1950 let mut rng = rns_crypto::FixedRng::new(&[0x62; 32]);
1951 let actions = engine.handle_inbound(
1952 InboundFrame {
1953 raw: &packet.raw,
1954 iface: InterfaceId(1),
1955 now: 1000.0,
1956 rx: RxMetadata {
1957 rssi: None,
1958 snr: None,
1959 },
1960 },
1961 &mut rng,
1962 );
1963
1964 assert!(engine.has_path(&dest_hash));
1965 assert!(
1966 !engine.path_is_unresponsive(&dest_hash),
1967 "stale path state should be cleared for newly installed paths"
1968 );
1969 assert!(actions.iter().any(|action| matches!(
1970 action,
1971 TransportAction::PathUpdated {
1972 destination_hash,
1973 interface,
1974 ..
1975 } if *destination_hash == dest_hash && *interface == InterfaceId(1)
1976 )));
1977 }
1978
1979 #[test]
1980 fn test_boundary_exempts_unresponsive() {
1981 let mut engine = TransportEngine::new(make_config(false));
1982 engine.register_interface(make_interface(1, constants::MODE_BOUNDARY));
1983 let dest = [0xB1; 16];
1984
1985 engine.mark_path_unresponsive(&dest, Some(InterfaceId(1)));
1987 assert!(!engine.path_is_unresponsive(&dest));
1988 }
1989
1990 #[test]
1991 fn test_non_boundary_marks_unresponsive() {
1992 let mut engine = TransportEngine::new(make_config(false));
1993 engine.register_interface(make_interface(1, constants::MODE_FULL));
1994 let dest = [0xB2; 16];
1995
1996 engine.mark_path_unresponsive(&dest, Some(InterfaceId(1)));
1998 assert!(engine.path_is_unresponsive(&dest));
1999 }
2000
2001 #[test]
2002 fn test_expire_path() {
2003 let mut engine = TransportEngine::new(make_config(false));
2004 let dest = [0x33; 16];
2005
2006 engine.path_table.insert(
2007 dest,
2008 PathSet::from_single(
2009 PathEntry {
2010 timestamp: 1000.0,
2011 next_hop: [0; 16],
2012 hops: 2,
2013 expires: 9999.0,
2014 random_blobs: Vec::new(),
2015 receiving_interface: InterfaceId(1),
2016 packet_hash: [0; 32],
2017 announce_raw: None,
2018 },
2019 1,
2020 ),
2021 );
2022
2023 assert!(engine.has_path(&dest));
2024 engine.expire_path(&dest);
2025 assert!(engine.has_path(&dest));
2027 assert_eq!(engine.path_table[&dest].primary().unwrap().expires, 0.0);
2028 }
2029
2030 #[test]
2031 fn test_link_table_operations() {
2032 let mut engine = TransportEngine::new(make_config(false));
2033 let link_id = [0x44; 16];
2034
2035 engine.register_link(
2036 link_id,
2037 LinkEntry {
2038 timestamp: 100.0,
2039 next_hop_transport_id: [0; 16],
2040 next_hop_interface: InterfaceId(1),
2041 remaining_hops: 3,
2042 received_interface: InterfaceId(2),
2043 taken_hops: 2,
2044 destination_hash: [0xAA; 16],
2045 validated: false,
2046 proof_timeout: 200.0,
2047 },
2048 );
2049
2050 assert!(engine.link_table.contains_key(&link_id));
2051 assert!(!engine.link_table[&link_id].validated);
2052
2053 engine.validate_link(&link_id);
2054 assert!(engine.link_table[&link_id].validated);
2055
2056 engine.remove_link(&link_id);
2057 assert!(!engine.link_table.contains_key(&link_id));
2058 }
2059
2060 #[test]
2061 fn test_lrproof_routes_from_originating_side_via_link_table() {
2062 let mut engine = TransportEngine::new(make_config(true));
2063 engine.register_interface(make_interface(1, constants::MODE_FULL));
2064 engine.register_interface(make_interface(2, constants::MODE_FULL));
2065
2066 let link_id = [0x44; 16];
2067 engine.register_link(
2068 link_id,
2069 LinkEntry {
2070 timestamp: 100.0,
2071 next_hop_transport_id: [0xAA; 16],
2072 next_hop_interface: InterfaceId(2),
2073 remaining_hops: 3,
2074 received_interface: InterfaceId(1),
2075 taken_hops: 1,
2076 destination_hash: [0xBB; 16],
2077 validated: false,
2078 proof_timeout: 200.0,
2079 },
2080 );
2081
2082 let flags = PacketFlags {
2083 header_type: constants::HEADER_1,
2084 context_flag: constants::FLAG_UNSET,
2085 transport_type: constants::TRANSPORT_BROADCAST,
2086 destination_type: constants::DESTINATION_LINK,
2087 packet_type: constants::PACKET_TYPE_PROOF,
2088 };
2089 let packet = RawPacket::pack(
2090 flags,
2091 0,
2092 &link_id,
2093 None,
2094 constants::CONTEXT_LRPROOF,
2095 &[0xCC; 64],
2096 )
2097 .unwrap();
2098 let mut rng = rns_crypto::FixedRng::new(&[0x33; 32]);
2099
2100 let actions = engine.handle_inbound(
2101 InboundFrame {
2102 raw: &packet.raw,
2103 iface: InterfaceId(1),
2104 now: 101.0,
2105 rx: RxMetadata {
2106 rssi: None,
2107 snr: None,
2108 },
2109 },
2110 &mut rng,
2111 );
2112
2113 assert!(matches!(
2114 engine
2115 .link_table_ref()
2116 .get(&link_id)
2117 .map(|entry| entry.validated),
2118 Some(true)
2119 ));
2120 assert!(actions.iter().any(|action| matches!(
2121 action,
2122 TransportAction::LinkEstablished {
2123 link_id: established,
2124 interface: InterfaceId(2),
2125 } if *established == link_id
2126 )));
2127 assert!(actions.iter().any(|action| matches!(
2128 action,
2129 TransportAction::SendOnInterface {
2130 interface: InterfaceId(2),
2131 ..
2132 }
2133 )));
2134 }
2135
2136 #[test]
2137 fn test_packet_filter_drops_plain_announce() {
2138 let engine = TransportEngine::new(make_config(false));
2139 let flags = PacketFlags {
2140 header_type: constants::HEADER_1,
2141 context_flag: constants::FLAG_UNSET,
2142 transport_type: constants::TRANSPORT_BROADCAST,
2143 destination_type: constants::DESTINATION_PLAIN,
2144 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2145 };
2146 let packet =
2147 RawPacket::pack(flags, 0, &[0; 16], None, constants::CONTEXT_NONE, b"test").unwrap();
2148 assert!(!engine.packet_filter(&packet));
2149 }
2150
2151 #[test]
2152 fn test_packet_filter_allows_keepalive() {
2153 let engine = TransportEngine::new(make_config(false));
2154 let flags = PacketFlags {
2155 header_type: constants::HEADER_1,
2156 context_flag: constants::FLAG_UNSET,
2157 transport_type: constants::TRANSPORT_BROADCAST,
2158 destination_type: constants::DESTINATION_SINGLE,
2159 packet_type: constants::PACKET_TYPE_DATA,
2160 };
2161 let packet = RawPacket::pack(
2162 flags,
2163 0,
2164 &[0; 16],
2165 None,
2166 constants::CONTEXT_KEEPALIVE,
2167 b"test",
2168 )
2169 .unwrap();
2170 assert!(engine.packet_filter(&packet));
2171 }
2172
2173 #[test]
2174 fn test_packet_filter_drops_high_hop_plain() {
2175 let engine = TransportEngine::new(make_config(false));
2176 let flags = PacketFlags {
2177 header_type: constants::HEADER_1,
2178 context_flag: constants::FLAG_UNSET,
2179 transport_type: constants::TRANSPORT_BROADCAST,
2180 destination_type: constants::DESTINATION_PLAIN,
2181 packet_type: constants::PACKET_TYPE_DATA,
2182 };
2183 let mut packet =
2184 RawPacket::pack(flags, 0, &[0; 16], None, constants::CONTEXT_NONE, b"test").unwrap();
2185 packet.hops = 2;
2186 assert!(!engine.packet_filter(&packet));
2187 }
2188
2189 #[test]
2190 fn test_packet_filter_allows_duplicate_single_announce() {
2191 let mut engine = TransportEngine::new(make_config(false));
2192 let flags = PacketFlags {
2193 header_type: constants::HEADER_1,
2194 context_flag: constants::FLAG_UNSET,
2195 transport_type: constants::TRANSPORT_BROADCAST,
2196 destination_type: constants::DESTINATION_SINGLE,
2197 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2198 };
2199 let packet = RawPacket::pack(
2200 flags,
2201 0,
2202 &[0; 16],
2203 None,
2204 constants::CONTEXT_NONE,
2205 &[0xAA; 64],
2206 )
2207 .unwrap();
2208
2209 engine.packet_hashlist.add(packet.packet_hash);
2211
2212 assert!(engine.packet_filter(&packet));
2214 }
2215
2216 #[test]
2217 fn test_packet_filter_fifo_eviction_allows_oldest_hash_again() {
2218 let mut engine = TransportEngine::new(make_config(false));
2219 engine.packet_hashlist = PacketHashlist::new(2);
2220
2221 let make_packet = |seed: u8| {
2222 let flags = PacketFlags {
2223 header_type: constants::HEADER_1,
2224 context_flag: constants::FLAG_UNSET,
2225 transport_type: constants::TRANSPORT_BROADCAST,
2226 destination_type: constants::DESTINATION_SINGLE,
2227 packet_type: constants::PACKET_TYPE_DATA,
2228 };
2229 RawPacket::pack(
2230 flags,
2231 0,
2232 &[seed; 16],
2233 None,
2234 constants::CONTEXT_NONE,
2235 &[seed; 4],
2236 )
2237 .unwrap()
2238 };
2239
2240 let packet1 = make_packet(1);
2241 let packet2 = make_packet(2);
2242 let packet3 = make_packet(3);
2243
2244 engine.packet_hashlist.add(packet1.packet_hash);
2245 engine.packet_hashlist.add(packet2.packet_hash);
2246 assert!(!engine.packet_filter(&packet1));
2247
2248 engine.packet_hashlist.add(packet3.packet_hash);
2249
2250 assert!(engine.packet_filter(&packet1));
2251 assert!(!engine.packet_filter(&packet2));
2252 assert!(!engine.packet_filter(&packet3));
2253 }
2254
2255 #[test]
2256 fn test_packet_filter_duplicate_does_not_refresh_recency() {
2257 let mut engine = TransportEngine::new(make_config(false));
2258 engine.packet_hashlist = PacketHashlist::new(2);
2259
2260 let make_packet = |seed: u8| {
2261 let flags = PacketFlags {
2262 header_type: constants::HEADER_1,
2263 context_flag: constants::FLAG_UNSET,
2264 transport_type: constants::TRANSPORT_BROADCAST,
2265 destination_type: constants::DESTINATION_SINGLE,
2266 packet_type: constants::PACKET_TYPE_DATA,
2267 };
2268 RawPacket::pack(
2269 flags,
2270 0,
2271 &[seed; 16],
2272 None,
2273 constants::CONTEXT_NONE,
2274 &[seed; 4],
2275 )
2276 .unwrap()
2277 };
2278
2279 let packet1 = make_packet(1);
2280 let packet2 = make_packet(2);
2281 let packet3 = make_packet(3);
2282
2283 engine.packet_hashlist.add(packet1.packet_hash);
2284 engine.packet_hashlist.add(packet2.packet_hash);
2285 engine.packet_hashlist.add(packet2.packet_hash);
2286 engine.packet_hashlist.add(packet3.packet_hash);
2287
2288 assert!(engine.packet_filter(&packet1));
2289 assert!(!engine.packet_filter(&packet2));
2290 assert!(!engine.packet_filter(&packet3));
2291 }
2292
2293 #[test]
2294 fn test_tick_retransmits_announce() {
2295 let mut engine = TransportEngine::new(make_config(true));
2296 engine.register_interface(make_interface(1, constants::MODE_FULL));
2297
2298 let dest = [0x55; 16];
2299 engine.insert_announce_entry(
2300 dest,
2301 AnnounceEntry {
2302 timestamp: 190.0,
2303 retransmit_timeout: 100.0, retries: 0,
2305 received_from: [0xAA; 16],
2306 hops: 2,
2307 packet_raw: vec![0x01, 0x02],
2308 packet_data: vec![0xCC; 10],
2309 destination_hash: dest,
2310 context_flag: 0,
2311 local_rebroadcasts: 0,
2312 block_rebroadcasts: false,
2313 attached_interface: None,
2314 },
2315 190.0,
2316 );
2317
2318 let mut rng = rns_crypto::FixedRng::new(&[0x42; 32]);
2319 let actions = engine.tick(200.0, &mut rng);
2320
2321 assert!(!actions.is_empty());
2324 assert!(matches!(
2325 &actions[0],
2326 TransportAction::SendOnInterface { .. }
2327 ));
2328
2329 assert_eq!(engine.announce_table[&dest].retries, 1);
2331 }
2332
2333 #[test]
2334 fn test_gate_retransmit_actions_expands_broadcast_to_matching_interfaces() {
2335 let mut engine = TransportEngine::new(make_config(false));
2336 engine.register_interface(make_interface(1, constants::MODE_FULL));
2337 engine.register_interface(make_interface(2, constants::MODE_FULL));
2338 engine.register_interface(make_interface(3, constants::MODE_ACCESS_POINT));
2339
2340 let dest = [0x56; 16];
2341 let raw = make_announce_raw(&dest, &[0xAB; 32]);
2342 let actions = engine.gate_retransmit_actions(
2343 vec![TransportAction::BroadcastOnAllInterfaces {
2344 raw: raw.clone().into(),
2345 exclude: None,
2346 }],
2347 1000.0,
2348 );
2349
2350 assert_eq!(actions.len(), 2);
2351 for action in &actions {
2352 match action {
2353 TransportAction::SendOnInterface {
2354 interface,
2355 raw: sent,
2356 } => {
2357 assert!(*interface == InterfaceId(1) || *interface == InterfaceId(2));
2358 assert_eq!(&**sent, raw.as_slice());
2359 }
2360 other => panic!("expected SendOnInterface, got {:?}", other),
2361 }
2362 }
2363 }
2364
2365 #[test]
2366 fn test_tick_culls_expired_announce_entries() {
2367 let mut config = make_config(true);
2368 config.announce_table_ttl_secs = 10.0;
2369 let mut engine = TransportEngine::new(config);
2370
2371 let dest1 = [0x61; 16];
2372 let dest2 = [0x62; 16];
2373 assert!(engine.insert_announce_entry(dest1, make_announce_entry(dest1, 100.0, 8), 100.0));
2374 assert!(engine.insert_held_announce(dest2, make_announce_entry(dest2, 100.0, 8), 100.0));
2375
2376 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
2377 let _ = engine.tick(111.0, &mut rng);
2378
2379 assert!(!engine.announce_table().contains_key(&dest1));
2380 assert!(!engine.held_announces().contains_key(&dest2));
2381 }
2382
2383 #[test]
2384 fn test_announce_retention_cap_evicts_oldest_and_prefers_held_on_tie() {
2385 let sample_entry = make_announce_entry([0x70; 16], 100.0, 32);
2386 let mut config = make_config(true);
2387 config.announce_table_max_bytes = TransportEngine::announce_entry_size_bytes(&sample_entry)
2388 * 2
2389 + TransportEngine::announce_entry_size_bytes(&sample_entry) / 2;
2390 let max_bytes = config.announce_table_max_bytes;
2391 let mut engine = TransportEngine::new(config);
2392
2393 let held_dest = [0x71; 16];
2394 let active_dest = [0x72; 16];
2395 let newest_dest = [0x73; 16];
2396
2397 assert!(engine.insert_held_announce(
2398 held_dest,
2399 make_announce_entry(held_dest, 100.0, 32),
2400 100.0,
2401 ));
2402 assert!(engine.insert_announce_entry(
2403 active_dest,
2404 make_announce_entry(active_dest, 100.0, 32),
2405 100.0,
2406 ));
2407 assert!(engine.insert_announce_entry(
2408 newest_dest,
2409 make_announce_entry(newest_dest, 101.0, 32),
2410 101.0,
2411 ));
2412
2413 assert!(!engine.held_announces().contains_key(&held_dest));
2414 assert!(engine.announce_table().contains_key(&active_dest));
2415 assert!(engine.announce_table().contains_key(&newest_dest));
2416 assert!(engine.announce_retained_bytes() <= max_bytes);
2417 }
2418
2419 #[test]
2420 fn test_oversized_announce_entry_is_not_retained() {
2421 let mut config = make_config(true);
2422 config.announce_table_max_bytes = 200;
2423 let mut engine = TransportEngine::new(config);
2424 let dest = [0x81; 16];
2425
2426 assert!(!engine.insert_announce_entry(dest, make_announce_entry(dest, 100.0, 256), 100.0));
2427 assert!(!engine.announce_table().contains_key(&dest));
2428 assert_eq!(engine.announce_retained_bytes(), 0);
2429 }
2430
2431 #[test]
2432 fn test_void_queues_clears_shutdown_transients() {
2433 let mut engine = TransportEngine::new(make_config(true));
2434 engine.register_interface(make_interface(1, constants::MODE_FULL));
2435
2436 let active_dest = [0x91; 16];
2437 let held_dest = [0x92; 16];
2438 assert!(engine.insert_announce_entry(
2439 active_dest,
2440 make_announce_entry(active_dest, 100.0, 16),
2441 100.0,
2442 ));
2443 assert!(engine.insert_held_announce(
2444 held_dest,
2445 make_announce_entry(held_dest, 100.0, 16),
2446 100.0,
2447 ));
2448 engine.reverse_table.insert(
2449 [0x93; 16],
2450 tables::ReverseEntry {
2451 receiving_interface: InterfaceId(1),
2452 outbound_interface: InterfaceId(2),
2453 timestamp: 100.0,
2454 },
2455 );
2456 let _ = engine.announce_queues.gate_announce(
2457 InterfaceId(1),
2458 vec![0xAA; 32].into(),
2459 [0x94; 16],
2460 2,
2461 100.0,
2462 100.0,
2463 Some(1000),
2464 None,
2465 constants::ANNOUNCE_CAP,
2466 );
2467 let _ = engine.announce_queues.gate_announce(
2468 InterfaceId(1),
2469 vec![0xBB; 32].into(),
2470 [0x95; 16],
2471 3,
2472 100.0,
2473 100.0,
2474 Some(1000),
2475 None,
2476 constants::ANNOUNCE_CAP,
2477 );
2478
2479 assert_eq!(engine.announce_table_count(), 1);
2480 assert_eq!(engine.held_announces_count(), 1);
2481 assert_eq!(engine.reverse_table_count(), 1);
2482 assert_eq!(engine.queued_announce_count(), 1);
2483
2484 engine.void_queues();
2485
2486 assert_eq!(engine.announce_table_count(), 0);
2487 assert_eq!(engine.held_announces_count(), 0);
2488 assert_eq!(engine.reverse_table_count(), 0);
2489 assert_eq!(engine.queued_announce_count(), 0);
2490 assert_eq!(engine.nonempty_announce_queue_count(), 0);
2491 assert_eq!(engine.announce_retained_bytes(), 0);
2492 }
2493
2494 #[test]
2495 fn test_blackhole_identity() {
2496 let mut engine = TransportEngine::new(make_config(false));
2497 let hash = [0xAA; 16];
2498 let now = 1000.0;
2499
2500 assert!(!engine.is_blackholed(&hash, now));
2501
2502 engine.blackhole_identity(hash, now, None, Some(String::from("test")));
2503 assert!(engine.is_blackholed(&hash, now));
2504 assert!(engine.is_blackholed(&hash, now + 999999.0)); assert!(engine.unblackhole_identity(&hash));
2507 assert!(!engine.is_blackholed(&hash, now));
2508 assert!(!engine.unblackhole_identity(&hash)); }
2510
2511 #[test]
2512 fn test_blackhole_with_duration() {
2513 let mut engine = TransportEngine::new(make_config(false));
2514 let hash = [0xBB; 16];
2515 let now = 1000.0;
2516
2517 engine.blackhole_identity(hash, now, Some(1.0), None); assert!(engine.is_blackholed(&hash, now));
2519 assert!(engine.is_blackholed(&hash, now + 3599.0)); assert!(!engine.is_blackholed(&hash, now + 3601.0)); }
2522
2523 #[test]
2524 fn test_cull_blackholed() {
2525 let mut engine = TransportEngine::new(make_config(false));
2526 let hash1 = [0xCC; 16];
2527 let hash2 = [0xDD; 16];
2528 let now = 1000.0;
2529
2530 engine.blackhole_identity(hash1, now, Some(1.0), None); engine.blackhole_identity(hash2, now, None, None); engine.cull_blackholed(now + 4000.0); assert!(!engine.blackholed_identities.contains_key(&hash1));
2536 assert!(engine.blackholed_identities.contains_key(&hash2));
2537 }
2538
2539 #[test]
2540 fn test_blackhole_blocks_announce() {
2541 use crate::announce::AnnounceData;
2542 use crate::destination::{destination_hash, name_hash};
2543
2544 let mut engine = TransportEngine::new(make_config(false));
2545 engine.register_interface(make_interface(1, constants::MODE_FULL));
2546
2547 let identity =
2548 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x55; 32]));
2549 let dest_hash = destination_hash("test", &["app"], Some(identity.hash()));
2550 let name_h = name_hash("test", &["app"]);
2551 let random_hash = [0x42u8; 10];
2552
2553 let (announce_data, _) =
2554 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
2555
2556 let flags = PacketFlags {
2557 header_type: constants::HEADER_1,
2558 context_flag: constants::FLAG_UNSET,
2559 transport_type: constants::TRANSPORT_BROADCAST,
2560 destination_type: constants::DESTINATION_SINGLE,
2561 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2562 };
2563 let packet = RawPacket::pack(
2564 flags,
2565 0,
2566 &dest_hash,
2567 None,
2568 constants::CONTEXT_NONE,
2569 &announce_data,
2570 )
2571 .unwrap();
2572
2573 let now = 1000.0;
2575 engine.blackhole_identity(*identity.hash(), now, None, None);
2576
2577 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
2578 let actions = engine.handle_inbound(
2579 InboundFrame {
2580 raw: &packet.raw,
2581 iface: InterfaceId(1),
2582 now,
2583 rx: RxMetadata {
2584 rssi: None,
2585 snr: None,
2586 },
2587 },
2588 &mut rng,
2589 );
2590
2591 assert!(actions
2593 .iter()
2594 .all(|a| !matches!(a, TransportAction::AnnounceReceived { .. })));
2595 assert!(actions
2596 .iter()
2597 .all(|a| !matches!(a, TransportAction::PathUpdated { .. })));
2598 }
2599
2600 #[test]
2601 fn test_async_announce_retransmit_cleanup_happens_before_queueing() {
2602 use crate::announce::AnnounceData;
2603 use crate::destination::{destination_hash, name_hash};
2604 use crate::transport::announce_verify_queue::AnnounceVerifyQueue;
2605
2606 let mut engine = TransportEngine::new(make_config(true));
2607 engine.register_interface(make_interface(1, constants::MODE_FULL));
2608
2609 let identity =
2610 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x31; 32]));
2611 let dest_hash = destination_hash("async", &["announce"], Some(identity.hash()));
2612 let name_h = name_hash("async", &["announce"]);
2613 let random_hash = [0x44u8; 10];
2614 let (announce_data, _) =
2615 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
2616
2617 let packet = RawPacket::pack(
2618 PacketFlags {
2619 header_type: constants::HEADER_2,
2620 context_flag: constants::FLAG_UNSET,
2621 transport_type: constants::TRANSPORT_TRANSPORT,
2622 destination_type: constants::DESTINATION_SINGLE,
2623 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2624 },
2625 3,
2626 &dest_hash,
2627 Some(&[0xBB; 16]),
2628 constants::CONTEXT_NONE,
2629 &announce_data,
2630 )
2631 .unwrap();
2632
2633 engine.announce_table.insert(
2634 dest_hash,
2635 AnnounceEntry {
2636 timestamp: 1000.0,
2637 retransmit_timeout: 2000.0,
2638 retries: constants::PATHFINDER_R,
2639 received_from: [0xBB; 16],
2640 hops: 2,
2641 packet_raw: packet.raw.clone(),
2642 packet_data: packet.data.clone(),
2643 destination_hash: dest_hash,
2644 context_flag: constants::FLAG_UNSET,
2645 local_rebroadcasts: 0,
2646 block_rebroadcasts: false,
2647 attached_interface: None,
2648 },
2649 );
2650
2651 let mut queue = AnnounceVerifyQueue::new(8);
2652 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
2653 let actions = engine.handle_inbound_with_announce_queue(
2654 InboundFrame {
2655 raw: &packet.raw,
2656 iface: InterfaceId(1),
2657 now: 1000.0,
2658 rx: RxMetadata {
2659 rssi: None,
2660 snr: None,
2661 },
2662 },
2663 &mut rng,
2664 Some(&mut queue),
2665 );
2666
2667 assert!(actions.is_empty());
2668 assert_eq!(queue.len(), 1);
2669 assert!(
2670 !engine.announce_table.contains_key(&dest_hash),
2671 "retransmit completion should clear announce_table before queueing"
2672 );
2673 }
2674
2675 #[test]
2676 fn test_async_announce_completion_inserts_sig_cache_and_prevents_requeue() {
2677 use crate::announce::AnnounceData;
2678 use crate::destination::{destination_hash, name_hash};
2679 use crate::transport::announce_verify_queue::AnnounceVerifyQueue;
2680
2681 let mut engine = TransportEngine::new(make_config(false));
2682 engine.register_interface(make_interface(1, constants::MODE_FULL));
2683
2684 let identity =
2685 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x52; 32]));
2686 let dest_hash = destination_hash("async", &["cache"], Some(identity.hash()));
2687 let name_h = name_hash("async", &["cache"]);
2688 let random_hash = [0x55u8; 10];
2689 let (announce_data, _) =
2690 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
2691
2692 let packet = RawPacket::pack(
2693 PacketFlags {
2694 header_type: constants::HEADER_1,
2695 context_flag: constants::FLAG_UNSET,
2696 transport_type: constants::TRANSPORT_BROADCAST,
2697 destination_type: constants::DESTINATION_SINGLE,
2698 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2699 },
2700 0,
2701 &dest_hash,
2702 None,
2703 constants::CONTEXT_NONE,
2704 &announce_data,
2705 )
2706 .unwrap();
2707
2708 let mut queue = AnnounceVerifyQueue::new(8);
2709 let mut rng = rns_crypto::FixedRng::new(&[0x77; 32]);
2710 let actions = engine.handle_inbound_with_announce_queue(
2711 InboundFrame {
2712 raw: &packet.raw,
2713 iface: InterfaceId(1),
2714 now: 1000.0,
2715 rx: RxMetadata {
2716 rssi: None,
2717 snr: None,
2718 },
2719 },
2720 &mut rng,
2721 Some(&mut queue),
2722 );
2723 assert!(actions.is_empty());
2724 assert_eq!(queue.len(), 1);
2725
2726 let mut batch = queue.take_pending(1000.0);
2727 assert_eq!(batch.len(), 1);
2728 let (key, pending) = batch.pop().unwrap();
2729
2730 let announce = AnnounceData::unpack(&pending.packet.data, false).unwrap();
2731 let validated = announce.validate(&pending.packet.destination_hash).unwrap();
2732 let mut material = [0u8; 80];
2733 material[..16].copy_from_slice(&pending.packet.destination_hash);
2734 material[16..].copy_from_slice(&announce.signature);
2735 let sig_cache_key = hash::full_hash(&material);
2736
2737 let pending = queue.complete_success(&key).unwrap();
2738 let actions =
2739 engine.complete_verified_announce(pending, validated, sig_cache_key, 1000.0, &mut rng);
2740 assert!(actions
2741 .iter()
2742 .any(|action| matches!(action, TransportAction::AnnounceReceived { .. })));
2743 assert!(engine.announce_sig_cache_contains(&sig_cache_key));
2744
2745 let actions = engine.handle_inbound_with_announce_queue(
2746 InboundFrame {
2747 raw: &packet.raw,
2748 iface: InterfaceId(1),
2749 now: 1001.0,
2750 rx: RxMetadata {
2751 rssi: None,
2752 snr: None,
2753 },
2754 },
2755 &mut rng,
2756 Some(&mut queue),
2757 );
2758 assert!(actions.is_empty());
2759 assert_eq!(queue.len(), 0);
2760 }
2761
2762 #[test]
2763 fn test_tick_culls_expired_path() {
2764 let mut engine = TransportEngine::new(make_config(false));
2765 engine.register_interface(make_interface(1, constants::MODE_FULL));
2766
2767 let dest = [0x66; 16];
2768 engine.path_table.insert(
2769 dest,
2770 PathSet::from_single(
2771 PathEntry {
2772 timestamp: 100.0,
2773 next_hop: [0; 16],
2774 hops: 2,
2775 expires: 200.0,
2776 random_blobs: Vec::new(),
2777 receiving_interface: InterfaceId(1),
2778 packet_hash: [0; 32],
2779 announce_raw: None,
2780 },
2781 1,
2782 ),
2783 );
2784
2785 assert!(engine.has_path(&dest));
2786
2787 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
2788 engine.tick(300.0, &mut rng);
2790
2791 assert!(!engine.has_path(&dest));
2792 }
2793
2794 fn make_local_client_interface(id: u64) -> InterfaceInfo {
2799 InterfaceInfo {
2800 id: InterfaceId(id),
2801 name: String::from("local_client"),
2802 mode: constants::MODE_FULL,
2803 out_capable: true,
2804 in_capable: true,
2805 bitrate: None,
2806 airtime_profile: None,
2807 announce_rate_target: None,
2808 announce_rate_grace: 0,
2809 announce_rate_penalty: 0.0,
2810 announce_cap: constants::ANNOUNCE_CAP,
2811 is_local_client: true,
2812 wants_tunnel: false,
2813 tunnel_id: None,
2814 mtu: constants::MTU as u32,
2815 ingress_control: crate::transport::types::IngressControlConfig::disabled(),
2816 ia_freq: 0.0,
2817 ip_freq: 0.0,
2818 op_freq: 0.0,
2819 op_samples: 0,
2820 started: 0.0,
2821 }
2822 }
2823
2824 #[test]
2825 fn test_has_local_clients() {
2826 let mut engine = TransportEngine::new(make_config(false));
2827 assert!(!engine.has_local_clients());
2828
2829 engine.register_interface(make_interface(1, constants::MODE_FULL));
2830 assert!(!engine.has_local_clients());
2831
2832 engine.register_interface(make_local_client_interface(2));
2833 assert!(engine.has_local_clients());
2834
2835 engine.deregister_interface(InterfaceId(2));
2836 assert!(!engine.has_local_clients());
2837 }
2838
2839 #[test]
2840 fn test_local_client_hop_decrement() {
2841 let mut engine = TransportEngine::new(make_config(false));
2844 engine.register_interface(make_local_client_interface(1));
2845 engine.register_interface(make_interface(2, constants::MODE_FULL));
2846
2847 let dest = [0xAA; 16];
2849 engine.register_destination(dest, constants::DESTINATION_PLAIN);
2850
2851 let flags = PacketFlags {
2852 header_type: constants::HEADER_1,
2853 context_flag: constants::FLAG_UNSET,
2854 transport_type: constants::TRANSPORT_BROADCAST,
2855 destination_type: constants::DESTINATION_PLAIN,
2856 packet_type: constants::PACKET_TYPE_DATA,
2857 };
2858 let packet =
2860 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"hello").unwrap();
2861
2862 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
2863 let actions = engine.handle_inbound(
2864 InboundFrame {
2865 raw: &packet.raw,
2866 iface: InterfaceId(1),
2867 now: 1000.0,
2868 rx: RxMetadata {
2869 rssi: None,
2870 snr: None,
2871 },
2872 },
2873 &mut rng,
2874 );
2875
2876 let deliver = actions
2879 .iter()
2880 .find(|a| matches!(a, TransportAction::DeliverLocal { .. }));
2881 assert!(deliver.is_some(), "Should deliver locally");
2882 }
2883
2884 #[test]
2885 fn test_prepare_inbound_packet_only_retains_original_raw_for_announces() {
2886 let engine = TransportEngine::new(make_config(false));
2887 let dest = [0xAB; 16];
2888 let flags = PacketFlags {
2889 header_type: constants::HEADER_1,
2890 context_flag: constants::FLAG_UNSET,
2891 transport_type: constants::TRANSPORT_BROADCAST,
2892 destination_type: constants::DESTINATION_SINGLE,
2893 packet_type: constants::PACKET_TYPE_DATA,
2894 };
2895 let packet =
2896 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"hello").unwrap();
2897
2898 let ctx = engine
2899 .prepare_inbound_packet(InboundFrame {
2900 raw: &packet.raw,
2901 iface: InterfaceId(9),
2902 now: 1000.0,
2903 rx: RxMetadata {
2904 rssi: None,
2905 snr: None,
2906 },
2907 })
2908 .expect("packet should parse and pass filter");
2909
2910 assert!(ctx.original_raw.is_none());
2911 assert_eq!(ctx.packet.raw, packet.raw);
2912 assert_eq!(ctx.packet.hops, 1);
2913 assert_eq!(ctx.iface, InterfaceId(9));
2914
2915 let announce_flags = PacketFlags {
2916 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2917 ..flags
2918 };
2919 let announce = RawPacket::pack(
2920 announce_flags,
2921 0,
2922 &dest,
2923 None,
2924 constants::CONTEXT_NONE,
2925 &[0u8; 91],
2926 )
2927 .unwrap();
2928 let announce_ctx = engine
2929 .prepare_inbound_packet(InboundFrame {
2930 raw: &announce.raw,
2931 iface: InterfaceId(9),
2932 now: 1000.0,
2933 rx: RxMetadata {
2934 rssi: None,
2935 snr: None,
2936 },
2937 })
2938 .expect("announce should parse and pass filter");
2939 assert_eq!(
2940 announce_ctx.original_raw.as_deref(),
2941 Some(announce.raw.as_slice())
2942 );
2943 }
2944
2945 #[test]
2946 fn test_deliver_local_preserves_original_raw_and_metadata() {
2947 let mut engine = TransportEngine::new(make_config(false));
2948 engine.register_interface(make_interface(1, constants::MODE_FULL));
2949
2950 let dest = [0xAC; 16];
2951 engine.register_destination(dest, constants::DESTINATION_SINGLE);
2952
2953 let flags = PacketFlags {
2954 header_type: constants::HEADER_1,
2955 context_flag: constants::FLAG_UNSET,
2956 transport_type: constants::TRANSPORT_BROADCAST,
2957 destination_type: constants::DESTINATION_SINGLE,
2958 packet_type: constants::PACKET_TYPE_DATA,
2959 };
2960 let packet =
2961 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"deliver").unwrap();
2962
2963 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
2964 let actions = engine.handle_inbound(
2965 InboundFrame {
2966 raw: &packet.raw,
2967 iface: InterfaceId(1),
2968 now: 1000.0,
2969 rx: RxMetadata {
2970 rssi: None,
2971 snr: None,
2972 },
2973 },
2974 &mut rng,
2975 );
2976
2977 let deliver = actions
2978 .iter()
2979 .find_map(|action| match action {
2980 TransportAction::DeliverLocal {
2981 destination_hash,
2982 raw,
2983 packet_hash,
2984 receiving_interface,
2985 } => Some((destination_hash, raw, packet_hash, receiving_interface)),
2986 _ => None,
2987 })
2988 .expect("should produce DeliverLocal");
2989
2990 assert_eq!(*deliver.0, dest);
2991 assert_eq!(&**deliver.1, packet.raw.as_slice());
2992 assert_eq!(*deliver.2, packet.packet_hash);
2993 assert_eq!(*deliver.3, InterfaceId(1));
2994 }
2995
2996 #[test]
2997 fn test_plain_broadcast_from_local_client() {
2998 let mut engine = TransportEngine::new(make_config(false));
3000 engine.register_interface(make_local_client_interface(1));
3001 engine.register_interface(make_interface(2, constants::MODE_FULL));
3002
3003 let dest = [0xBB; 16];
3004 let flags = PacketFlags {
3005 header_type: constants::HEADER_1,
3006 context_flag: constants::FLAG_UNSET,
3007 transport_type: constants::TRANSPORT_BROADCAST,
3008 destination_type: constants::DESTINATION_PLAIN,
3009 packet_type: constants::PACKET_TYPE_DATA,
3010 };
3011 let packet =
3012 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"test").unwrap();
3013
3014 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3015 let actions = engine.handle_inbound(
3016 InboundFrame {
3017 raw: &packet.raw,
3018 iface: InterfaceId(1),
3019 now: 1000.0,
3020 rx: RxMetadata {
3021 rssi: None,
3022 snr: None,
3023 },
3024 },
3025 &mut rng,
3026 );
3027
3028 let forward = actions.iter().find(|a| {
3030 matches!(
3031 a,
3032 TransportAction::ForwardPlainBroadcast {
3033 to_local: false,
3034 ..
3035 }
3036 )
3037 });
3038 assert!(forward.is_some(), "Should forward to external interfaces");
3039 }
3040
3041 #[test]
3042 fn test_plain_broadcast_from_external() {
3043 let mut engine = TransportEngine::new(make_config(false));
3045 engine.register_interface(make_local_client_interface(1));
3046 engine.register_interface(make_interface(2, constants::MODE_FULL));
3047
3048 let dest = [0xCC; 16];
3049 let flags = PacketFlags {
3050 header_type: constants::HEADER_1,
3051 context_flag: constants::FLAG_UNSET,
3052 transport_type: constants::TRANSPORT_BROADCAST,
3053 destination_type: constants::DESTINATION_PLAIN,
3054 packet_type: constants::PACKET_TYPE_DATA,
3055 };
3056 let packet =
3057 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"test").unwrap();
3058
3059 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3060 let actions = engine.handle_inbound(
3061 InboundFrame {
3062 raw: &packet.raw,
3063 iface: InterfaceId(2),
3064 now: 1000.0,
3065 rx: RxMetadata {
3066 rssi: None,
3067 snr: None,
3068 },
3069 },
3070 &mut rng,
3071 );
3072
3073 let forward = actions.iter().find(|a| {
3075 matches!(
3076 a,
3077 TransportAction::ForwardPlainBroadcast { to_local: true, .. }
3078 )
3079 });
3080 assert!(forward.is_some(), "Should forward to local clients");
3081 }
3082
3083 #[test]
3084 fn test_no_plain_broadcast_bridging_without_local_clients() {
3085 let mut engine = TransportEngine::new(make_config(false));
3087 engine.register_interface(make_interface(1, constants::MODE_FULL));
3088 engine.register_interface(make_interface(2, constants::MODE_FULL));
3089
3090 let dest = [0xDD; 16];
3091 let flags = PacketFlags {
3092 header_type: constants::HEADER_1,
3093 context_flag: constants::FLAG_UNSET,
3094 transport_type: constants::TRANSPORT_BROADCAST,
3095 destination_type: constants::DESTINATION_PLAIN,
3096 packet_type: constants::PACKET_TYPE_DATA,
3097 };
3098 let packet =
3099 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"test").unwrap();
3100
3101 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3102 let actions = engine.handle_inbound(
3103 InboundFrame {
3104 raw: &packet.raw,
3105 iface: InterfaceId(1),
3106 now: 1000.0,
3107 rx: RxMetadata {
3108 rssi: None,
3109 snr: None,
3110 },
3111 },
3112 &mut rng,
3113 );
3114
3115 let has_forward = actions
3117 .iter()
3118 .any(|a| matches!(a, TransportAction::ForwardPlainBroadcast { .. }));
3119 assert!(!has_forward, "No bridging without local clients");
3120 }
3121
3122 #[test]
3123 fn test_announce_forwarded_to_local_clients() {
3124 use crate::announce::AnnounceData;
3125 use crate::destination::{destination_hash, name_hash};
3126
3127 let mut engine = TransportEngine::new(make_config(false));
3128 engine.register_interface(make_interface(1, constants::MODE_FULL));
3129 engine.register_interface(make_local_client_interface(2));
3130
3131 let identity =
3132 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x77; 32]));
3133 let dest_hash = destination_hash("test", &["fwd"], Some(identity.hash()));
3134 let name_h = name_hash("test", &["fwd"]);
3135 let random_hash = [0x42u8; 10];
3136
3137 let (announce_data, _) =
3138 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
3139
3140 let flags = PacketFlags {
3141 header_type: constants::HEADER_1,
3142 context_flag: constants::FLAG_UNSET,
3143 transport_type: constants::TRANSPORT_BROADCAST,
3144 destination_type: constants::DESTINATION_SINGLE,
3145 packet_type: constants::PACKET_TYPE_ANNOUNCE,
3146 };
3147 let packet = RawPacket::pack(
3148 flags,
3149 0,
3150 &dest_hash,
3151 None,
3152 constants::CONTEXT_NONE,
3153 &announce_data,
3154 )
3155 .unwrap();
3156
3157 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
3158 let actions = engine.handle_inbound(
3159 InboundFrame {
3160 raw: &packet.raw,
3161 iface: InterfaceId(1),
3162 now: 1000.0,
3163 rx: RxMetadata {
3164 rssi: None,
3165 snr: None,
3166 },
3167 },
3168 &mut rng,
3169 );
3170
3171 let forward = actions
3173 .iter()
3174 .find(|a| matches!(a, TransportAction::ForwardToLocalClients { .. }));
3175 assert!(
3176 forward.is_some(),
3177 "Should forward announce to local clients"
3178 );
3179
3180 match forward.unwrap() {
3182 TransportAction::ForwardToLocalClients { exclude, .. } => {
3183 assert_eq!(*exclude, Some(InterfaceId(1)));
3184 }
3185 _ => unreachable!(),
3186 }
3187 }
3188
3189 #[test]
3190 fn test_no_announce_forward_without_local_clients() {
3191 use crate::announce::AnnounceData;
3192 use crate::destination::{destination_hash, name_hash};
3193
3194 let mut engine = TransportEngine::new(make_config(false));
3195 engine.register_interface(make_interface(1, constants::MODE_FULL));
3196
3197 let identity =
3198 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x88; 32]));
3199 let dest_hash = destination_hash("test", &["nofwd"], Some(identity.hash()));
3200 let name_h = name_hash("test", &["nofwd"]);
3201 let random_hash = [0x42u8; 10];
3202
3203 let (announce_data, _) =
3204 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
3205
3206 let flags = PacketFlags {
3207 header_type: constants::HEADER_1,
3208 context_flag: constants::FLAG_UNSET,
3209 transport_type: constants::TRANSPORT_BROADCAST,
3210 destination_type: constants::DESTINATION_SINGLE,
3211 packet_type: constants::PACKET_TYPE_ANNOUNCE,
3212 };
3213 let packet = RawPacket::pack(
3214 flags,
3215 0,
3216 &dest_hash,
3217 None,
3218 constants::CONTEXT_NONE,
3219 &announce_data,
3220 )
3221 .unwrap();
3222
3223 let mut rng = rns_crypto::FixedRng::new(&[0x22; 32]);
3224 let actions = engine.handle_inbound(
3225 InboundFrame {
3226 raw: &packet.raw,
3227 iface: InterfaceId(1),
3228 now: 1000.0,
3229 rx: RxMetadata {
3230 rssi: None,
3231 snr: None,
3232 },
3233 },
3234 &mut rng,
3235 );
3236
3237 let has_forward = actions
3239 .iter()
3240 .any(|a| matches!(a, TransportAction::ForwardToLocalClients { .. }));
3241 assert!(!has_forward, "No forward without local clients");
3242 }
3243
3244 #[test]
3245 fn test_local_client_exclude_from_forward() {
3246 use crate::announce::AnnounceData;
3247 use crate::destination::{destination_hash, name_hash};
3248
3249 let mut engine = TransportEngine::new(make_config(false));
3250 engine.register_interface(make_local_client_interface(1));
3251 engine.register_interface(make_local_client_interface(2));
3252
3253 let identity =
3254 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
3255 let dest_hash = destination_hash("test", &["excl"], Some(identity.hash()));
3256 let name_h = name_hash("test", &["excl"]);
3257 let random_hash = [0x42u8; 10];
3258
3259 let (announce_data, _) =
3260 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
3261
3262 let flags = PacketFlags {
3263 header_type: constants::HEADER_1,
3264 context_flag: constants::FLAG_UNSET,
3265 transport_type: constants::TRANSPORT_BROADCAST,
3266 destination_type: constants::DESTINATION_SINGLE,
3267 packet_type: constants::PACKET_TYPE_ANNOUNCE,
3268 };
3269 let packet = RawPacket::pack(
3270 flags,
3271 0,
3272 &dest_hash,
3273 None,
3274 constants::CONTEXT_NONE,
3275 &announce_data,
3276 )
3277 .unwrap();
3278
3279 let mut rng = rns_crypto::FixedRng::new(&[0x33; 32]);
3280 let actions = engine.handle_inbound(
3282 InboundFrame {
3283 raw: &packet.raw,
3284 iface: InterfaceId(1),
3285 now: 1000.0,
3286 rx: RxMetadata {
3287 rssi: None,
3288 snr: None,
3289 },
3290 },
3291 &mut rng,
3292 );
3293
3294 let forward = actions
3296 .iter()
3297 .find(|a| matches!(a, TransportAction::ForwardToLocalClients { .. }));
3298 assert!(forward.is_some());
3299 match forward.unwrap() {
3300 TransportAction::ForwardToLocalClients { exclude, .. } => {
3301 assert_eq!(*exclude, Some(InterfaceId(1)));
3302 }
3303 _ => unreachable!(),
3304 }
3305 }
3306
3307 fn make_tunnel_interface(id: u64) -> InterfaceInfo {
3312 InterfaceInfo {
3313 id: InterfaceId(id),
3314 name: String::from("tunnel_iface"),
3315 mode: constants::MODE_FULL,
3316 out_capable: true,
3317 in_capable: true,
3318 bitrate: None,
3319 airtime_profile: None,
3320 announce_rate_target: None,
3321 announce_rate_grace: 0,
3322 announce_rate_penalty: 0.0,
3323 announce_cap: constants::ANNOUNCE_CAP,
3324 is_local_client: false,
3325 wants_tunnel: true,
3326 tunnel_id: None,
3327 mtu: constants::MTU as u32,
3328 ingress_control: crate::transport::types::IngressControlConfig::disabled(),
3329 ia_freq: 0.0,
3330 ip_freq: 0.0,
3331 op_freq: 0.0,
3332 op_samples: 0,
3333 started: 0.0,
3334 }
3335 }
3336
3337 #[test]
3338 fn test_handle_tunnel_new() {
3339 let mut engine = TransportEngine::new(make_config(true));
3340 engine.register_interface(make_tunnel_interface(1));
3341
3342 let tunnel_id = [0xAA; 32];
3343 let actions = engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
3344
3345 assert!(actions
3347 .iter()
3348 .any(|a| matches!(a, TransportAction::TunnelEstablished { .. })));
3349
3350 let info = engine.interface_info(&InterfaceId(1)).unwrap();
3352 assert_eq!(info.tunnel_id, Some(tunnel_id));
3353
3354 assert_eq!(engine.tunnel_table().len(), 1);
3356 }
3357
3358 #[test]
3359 fn test_announce_stores_tunnel_path() {
3360 use crate::announce::AnnounceData;
3361 use crate::destination::{destination_hash, name_hash};
3362
3363 let mut engine = TransportEngine::new(make_config(false));
3364 let mut iface = make_tunnel_interface(1);
3365 let tunnel_id = [0xBB; 32];
3366 iface.tunnel_id = Some(tunnel_id);
3367 engine.register_interface(iface);
3368
3369 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
3371
3372 let identity =
3374 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xCC; 32]));
3375 let dest_hash = destination_hash("test", &["tunnel"], Some(identity.hash()));
3376 let name_h = name_hash("test", &["tunnel"]);
3377 let random_hash = [0x42u8; 10];
3378
3379 let (announce_data, _) =
3380 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
3381
3382 let flags = PacketFlags {
3383 header_type: constants::HEADER_1,
3384 context_flag: constants::FLAG_UNSET,
3385 transport_type: constants::TRANSPORT_BROADCAST,
3386 destination_type: constants::DESTINATION_SINGLE,
3387 packet_type: constants::PACKET_TYPE_ANNOUNCE,
3388 };
3389 let packet = RawPacket::pack(
3390 flags,
3391 0,
3392 &dest_hash,
3393 None,
3394 constants::CONTEXT_NONE,
3395 &announce_data,
3396 )
3397 .unwrap();
3398
3399 let mut rng = rns_crypto::FixedRng::new(&[0xDD; 32]);
3400 engine.handle_inbound(
3401 InboundFrame {
3402 raw: &packet.raw,
3403 iface: InterfaceId(1),
3404 now: 1000.0,
3405 rx: RxMetadata {
3406 rssi: None,
3407 snr: None,
3408 },
3409 },
3410 &mut rng,
3411 );
3412
3413 assert!(engine.has_path(&dest_hash));
3415
3416 let tunnel = engine.tunnel_table().get(&tunnel_id).unwrap();
3418 assert_eq!(tunnel.paths.len(), 1);
3419 assert!(tunnel.paths.contains_key(&dest_hash));
3420 }
3421
3422 #[test]
3423 fn test_tunnel_reattach_restores_paths() {
3424 let mut engine = TransportEngine::new(make_config(true));
3425 engine.register_interface(make_tunnel_interface(1));
3426
3427 let tunnel_id = [0xCC; 32];
3428 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
3429
3430 let dest = [0xDD; 16];
3432 engine.tunnel_table.store_tunnel_path(
3433 &tunnel_id,
3434 dest,
3435 tunnel::TunnelPath {
3436 timestamp: 1000.0,
3437 received_from: [0xEE; 16],
3438 hops: 3,
3439 expires: 1000.0 + constants::DESTINATION_TIMEOUT,
3440 random_blobs: Vec::new(),
3441 packet_hash: [0xFF; 32],
3442 },
3443 1000.0,
3444 constants::DESTINATION_TIMEOUT,
3445 usize::MAX,
3446 );
3447
3448 engine.void_tunnel_interface(&tunnel_id);
3450
3451 engine.path_table.remove(&dest);
3453 assert!(!engine.has_path(&dest));
3454
3455 engine.register_interface(make_interface(2, constants::MODE_FULL));
3457 let actions = engine.handle_tunnel(tunnel_id, InterfaceId(2), 2000.0);
3458
3459 assert!(engine.has_path(&dest));
3461 let path = engine.path_table.get(&dest).unwrap().primary().unwrap();
3462 assert_eq!(path.hops, 3);
3463 assert_eq!(path.receiving_interface, InterfaceId(2));
3464
3465 assert!(actions
3467 .iter()
3468 .any(|a| matches!(a, TransportAction::TunnelEstablished { .. })));
3469 }
3470
3471 #[test]
3472 fn test_tunnel_reattach_does_not_overwrite_newer_path() {
3473 let mut engine = TransportEngine::new(make_config(true));
3474 engine.register_interface(make_tunnel_interface(1));
3475
3476 let tunnel_id = [0xCD; 32];
3477 let dest = [0xDE; 16];
3478 let older_blob = make_random_blob(100);
3479 let newer_blob = make_random_blob(200);
3480
3481 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
3482 engine.tunnel_table.store_tunnel_path(
3483 &tunnel_id,
3484 dest,
3485 tunnel::TunnelPath {
3486 timestamp: 1000.0,
3487 received_from: [0xEE; 16],
3488 hops: 2,
3489 expires: 1000.0 + constants::DESTINATION_TIMEOUT,
3490 random_blobs: vec![older_blob],
3491 packet_hash: [0x11; 32],
3492 },
3493 1000.0,
3494 constants::DESTINATION_TIMEOUT,
3495 usize::MAX,
3496 );
3497 engine.void_tunnel_interface(&tunnel_id);
3498
3499 engine.path_table.insert(
3500 dest,
3501 PathSet::from_single(
3502 PathEntry {
3503 timestamp: 1500.0,
3504 next_hop: [0xAB; 16],
3505 hops: 3,
3506 expires: 1500.0 + constants::DESTINATION_TIMEOUT,
3507 random_blobs: vec![newer_blob],
3508 receiving_interface: InterfaceId(3),
3509 packet_hash: [0x22; 32],
3510 announce_raw: None,
3511 },
3512 1,
3513 ),
3514 );
3515
3516 engine.register_interface(make_interface(2, constants::MODE_FULL));
3517 engine.handle_tunnel(tunnel_id, InterfaceId(2), 2000.0);
3518
3519 let path = engine.path_table.get(&dest).unwrap().primary().unwrap();
3520 assert_eq!(path.next_hop, [0xAB; 16]);
3521 assert_eq!(path.hops, 3);
3522 assert_eq!(path.receiving_interface, InterfaceId(3));
3523 assert_eq!(path.random_blobs, vec![newer_blob]);
3524 }
3525
3526 #[test]
3527 fn test_void_tunnel_interface() {
3528 let mut engine = TransportEngine::new(make_config(true));
3529 engine.register_interface(make_tunnel_interface(1));
3530
3531 let tunnel_id = [0xDD; 32];
3532 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
3533
3534 assert_eq!(
3536 engine.tunnel_table().get(&tunnel_id).unwrap().interface,
3537 Some(InterfaceId(1))
3538 );
3539
3540 engine.void_tunnel_interface(&tunnel_id);
3541
3542 assert_eq!(engine.tunnel_table().len(), 1);
3544 assert_eq!(
3545 engine.tunnel_table().get(&tunnel_id).unwrap().interface,
3546 None
3547 );
3548 }
3549
3550 #[test]
3551 fn test_tick_culls_tunnels() {
3552 let mut engine = TransportEngine::new(make_config(true));
3553 engine.register_interface(make_tunnel_interface(1));
3554
3555 let tunnel_id = [0xEE; 32];
3556 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
3557 assert_eq!(engine.tunnel_table().len(), 1);
3558
3559 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3560
3561 engine.tick(
3563 1000.0 + constants::DESTINATION_TIMEOUT + constants::TABLES_CULL_INTERVAL + 1.0,
3564 &mut rng,
3565 );
3566
3567 assert_eq!(engine.tunnel_table().len(), 0);
3568 }
3569
3570 #[test]
3571 fn test_synthesize_tunnel() {
3572 let mut engine = TransportEngine::new(make_config(true));
3573 engine.register_interface(make_tunnel_interface(1));
3574
3575 let identity =
3576 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xFF; 32]));
3577 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
3578
3579 let actions = engine.synthesize_tunnel(&identity, InterfaceId(1), &mut rng);
3580
3581 assert_eq!(actions.len(), 1);
3583 match &actions[0] {
3584 TransportAction::TunnelSynthesize {
3585 interface,
3586 data,
3587 dest_hash,
3588 } => {
3589 assert_eq!(*interface, InterfaceId(1));
3590 assert_eq!(data.len(), tunnel::TUNNEL_SYNTH_LENGTH);
3591 let expected_dest = crate::destination::destination_hash(
3593 "rnstransport",
3594 &["tunnel", "synthesize"],
3595 None,
3596 );
3597 assert_eq!(*dest_hash, expected_dest);
3598 }
3599 _ => panic!("Expected TunnelSynthesize"),
3600 }
3601 }
3602
3603 #[test]
3604 fn test_synthesize_tunnel_missing_interface_is_dropped() {
3605 let engine = TransportEngine::new(make_config(true));
3606
3607 let identity =
3608 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xFF; 32]));
3609 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
3610
3611 let actions = engine.synthesize_tunnel(&identity, InterfaceId(99), &mut rng);
3612
3613 assert!(actions.is_empty());
3614 }
3615
3616 #[test]
3617 fn test_synthesize_tunnel_public_only_identity_is_dropped() {
3618 let mut engine = TransportEngine::new(make_config(true));
3619 engine.register_interface(make_tunnel_interface(1));
3620
3621 let identity =
3622 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xFF; 32]));
3623 let public_key = identity.get_public_key().unwrap();
3624 let public_only_identity = rns_crypto::identity::Identity::from_public_key(&public_key);
3625 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
3626
3627 let actions = engine.synthesize_tunnel(&public_only_identity, InterfaceId(1), &mut rng);
3628
3629 assert!(actions.is_empty());
3630 }
3631
3632 fn make_path_request_data(dest_hash: &[u8; 16], tag: &[u8]) -> Vec<u8> {
3637 let mut data = Vec::new();
3638 data.extend_from_slice(dest_hash);
3639 data.extend_from_slice(tag);
3640 data
3641 }
3642
3643 #[test]
3644 fn test_path_request_forwarded_on_ap() {
3645 let mut engine = TransportEngine::new(make_config(true));
3646 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
3647 engine.register_interface(make_interface(2, constants::MODE_FULL));
3648
3649 let dest = [0xD1; 16];
3650 let tag = [0x01; 16];
3651 let data = make_path_request_data(&dest, &tag);
3652
3653 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
3654
3655 assert_eq!(actions.len(), 1);
3657 match &actions[0] {
3658 TransportAction::SendOnInterface { interface, .. } => {
3659 assert_eq!(*interface, InterfaceId(2));
3660 }
3661 _ => panic!("Expected SendOnInterface for forwarded path request"),
3662 }
3663 assert!(engine.discovery_path_requests.contains_key(&dest));
3665 }
3666
3667 #[test]
3668 fn test_path_request_not_forwarded_on_full() {
3669 let mut engine = TransportEngine::new(make_config(true));
3670 engine.register_interface(make_interface(1, constants::MODE_FULL));
3671 engine.register_interface(make_interface(2, constants::MODE_FULL));
3672
3673 let dest = [0xD2; 16];
3674 let tag = [0x02; 16];
3675 let data = make_path_request_data(&dest, &tag);
3676
3677 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
3678
3679 assert!(actions.is_empty());
3681 assert!(!engine.discovery_path_requests.contains_key(&dest));
3682 }
3683
3684 #[test]
3685 fn test_duplicate_discovery_path_request_is_suppressed() {
3686 let mut engine = TransportEngine::new(make_config(true));
3687 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
3688 engine.register_interface(make_interface(2, constants::MODE_FULL));
3689
3690 let dest = [0xD7; 16];
3691 let tag = [0x07; 16];
3692 let data = make_path_request_data(&dest, &tag);
3693
3694 let first = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
3695 let second = engine.handle_path_request(&data, InterfaceId(1), 1001.0);
3696
3697 assert_eq!(first.len(), 1);
3698 assert!(
3699 second.is_empty(),
3700 "duplicate discovery request should be dropped"
3701 );
3702 assert_eq!(engine.discovery_pr_tags_count(), 1);
3703 }
3704
3705 #[test]
3706 fn test_path_request_ingress_burst_suppresses_recursive_discovery() {
3707 let mut engine = TransportEngine::new(make_config(true));
3708 let mut ingress = make_interface(1, constants::MODE_ACCESS_POINT);
3709 ingress.ingress_control.enabled = true;
3710 ingress.ip_freq = constants::IC_PR_BURST_FREQ + 1.0;
3711 engine.register_interface(ingress);
3712 engine.register_interface(make_interface(2, constants::MODE_FULL));
3713
3714 let dest = [0xE1; 16];
3715 let tag = [0x11; 16];
3716 let data = make_path_request_data(&dest, &tag);
3717
3718 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
3719
3720 assert!(actions.is_empty());
3721 assert!(!engine.discovery_path_requests.contains_key(&dest));
3722 }
3723
3724 #[test]
3725 fn test_path_request_egress_limit_skips_only_limited_interface() {
3726 let mut engine = TransportEngine::new(make_config(true));
3727 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
3728
3729 let mut limited = make_interface(2, constants::MODE_FULL);
3730 limited.ingress_control.egress_enabled = true;
3731 limited.op_freq = constants::EC_PR_FREQ + 1.0;
3732 limited.op_samples = constants::IC_BURST_MIN_SAMPLES;
3733 engine.register_interface(limited);
3734
3735 let mut allowed = make_interface(3, constants::MODE_FULL);
3736 allowed.ingress_control.egress_enabled = true;
3737 allowed.op_freq = constants::EC_PR_FREQ - 1.0;
3738 allowed.op_samples = constants::IC_BURST_MIN_SAMPLES;
3739 engine.register_interface(allowed);
3740
3741 let dest = [0xE2; 16];
3742 let tag = [0x12; 16];
3743 let data = make_path_request_data(&dest, &tag);
3744
3745 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
3746
3747 assert_eq!(actions.len(), 1);
3748 match &actions[0] {
3749 TransportAction::SendOnInterface { interface, .. } => {
3750 assert_eq!(*interface, InterfaceId(3))
3751 }
3752 _ => panic!("expected SendOnInterface for the unlimited egress interface"),
3753 }
3754 assert!(engine.discovery_path_requests.contains_key(&dest));
3755 }
3756
3757 #[test]
3758 fn test_recursive_path_request_skips_interface_with_queued_announces() {
3759 let mut engine = TransportEngine::new(make_config(true));
3760 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
3761 let mut blocked = make_interface(2, constants::MODE_FULL);
3762 blocked.bitrate = Some(1_000);
3763 engine.register_interface(blocked);
3764 engine.register_interface(make_interface(3, constants::MODE_FULL));
3765
3766 let _ = engine.announce_queues.gate_announce(
3767 InterfaceId(2),
3768 vec![0xAA; 100].into(),
3769 [0xA0; 16],
3770 1,
3771 900.0,
3772 900.0,
3773 Some(1_000),
3774 None,
3775 constants::ANNOUNCE_CAP,
3776 );
3777 let _ = engine.announce_queues.gate_announce(
3778 InterfaceId(2),
3779 vec![0xBB; 100].into(),
3780 [0xB0; 16],
3781 1,
3782 901.0,
3783 901.0,
3784 Some(1_000),
3785 None,
3786 constants::ANNOUNCE_CAP,
3787 );
3788
3789 let dest = [0xE3; 16];
3790 let tag = [0x13; 16];
3791 let data = make_path_request_data(&dest, &tag);
3792 let actions = engine.handle_path_request(&data, InterfaceId(1), 902.0);
3793
3794 assert_eq!(actions.len(), 1);
3795 match &actions[0] {
3796 TransportAction::SendOnInterface { interface, .. } => {
3797 assert_eq!(*interface, InterfaceId(3));
3798 }
3799 _ => panic!("expected SendOnInterface for the unqueued egress interface"),
3800 }
3801 assert!(engine.discovery_path_requests.contains_key(&dest));
3802 }
3803
3804 #[test]
3805 fn test_recursive_path_request_skips_interface_with_active_announce_cap() {
3806 let mut engine = TransportEngine::new(make_config(true));
3807 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
3808 let mut blocked = make_interface(2, constants::MODE_FULL);
3809 blocked.bitrate = Some(1_000);
3810 engine.register_interface(blocked);
3811
3812 let _ = engine.announce_queues.gate_announce(
3813 InterfaceId(2),
3814 vec![0xAA; 100].into(),
3815 [0xA0; 16],
3816 1,
3817 900.0,
3818 900.0,
3819 Some(1_000),
3820 None,
3821 constants::ANNOUNCE_CAP,
3822 );
3823
3824 let dest = [0xE4; 16];
3825 let tag = [0x14; 16];
3826 let data = make_path_request_data(&dest, &tag);
3827 let actions = engine.handle_path_request(&data, InterfaceId(1), 901.0);
3828
3829 assert!(actions.is_empty());
3830 assert!(!engine.discovery_path_requests.contains_key(&dest));
3831 }
3832
3833 #[test]
3834 fn test_recursive_path_request_reserves_announce_cap_on_sent_interface() {
3835 let mut engine = TransportEngine::new(make_config(true));
3836 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
3837 let mut egress = make_interface(2, constants::MODE_FULL);
3838 egress.bitrate = Some(1_000);
3839 engine.register_interface(egress);
3840
3841 let dest = [0xE5; 16];
3842 let tag = [0x15; 16];
3843 let data = make_path_request_data(&dest, &tag);
3844 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
3845
3846 assert_eq!(actions.len(), 1);
3847 let queue = engine
3848 .announce_queues
3849 .queue_for(&InterfaceId(2))
3850 .expect("sent recursive PR should create announce-cap state");
3851 assert!(
3852 queue.announce_allowed_at > 1000.0,
3853 "recursive PR should reserve announce-cap airtime"
3854 );
3855 assert!(queue.entries.is_empty());
3856 }
3857
3858 #[test]
3859 fn test_discovery_pr_tags_fifo_eviction() {
3860 let mut config = make_config(true);
3861 config.max_discovery_pr_tags = 2;
3862 let mut engine = TransportEngine::new(config);
3863
3864 let dest1 = [0xA1; 16];
3865 let dest2 = [0xA2; 16];
3866 let dest3 = [0xA3; 16];
3867 let tag1 = [0x01; 16];
3868 let tag2 = [0x02; 16];
3869 let tag3 = [0x03; 16];
3870
3871 engine.handle_path_request(
3872 &make_path_request_data(&dest1, &tag1),
3873 InterfaceId(1),
3874 1000.0,
3875 );
3876 engine.handle_path_request(
3877 &make_path_request_data(&dest2, &tag2),
3878 InterfaceId(1),
3879 1001.0,
3880 );
3881 assert_eq!(engine.discovery_pr_tags_count(), 2);
3882
3883 let unique1 = make_unique_tag(dest1, &tag1);
3884 let unique2 = make_unique_tag(dest2, &tag2);
3885 assert!(engine.has_discovery_pr_tag(&unique1));
3886 assert!(engine.has_discovery_pr_tag(&unique2));
3887
3888 engine.handle_path_request(
3889 &make_path_request_data(&dest3, &tag3),
3890 InterfaceId(1),
3891 1002.0,
3892 );
3893 assert_eq!(engine.discovery_pr_tags_count(), 2);
3894 assert!(!engine.has_discovery_pr_tag(&unique1));
3895 assert!(engine.has_discovery_pr_tag(&unique2));
3896
3897 engine.handle_path_request(
3898 &make_path_request_data(&dest1, &tag1),
3899 InterfaceId(1),
3900 1003.0,
3901 );
3902 assert_eq!(engine.discovery_pr_tags_count(), 2);
3903 assert!(engine.has_discovery_pr_tag(&unique1));
3904 }
3905
3906 #[test]
3907 fn test_path_destination_cap_evicts_oldest_and_clears_state() {
3908 let mut config = make_config(false);
3909 config.max_path_destinations = 2;
3910 let mut engine = TransportEngine::new(config);
3911 engine.register_interface(make_interface(1, constants::MODE_FULL));
3912
3913 let dest1 = [0xB1; 16];
3914 let dest2 = [0xB2; 16];
3915 let dest3 = [0xB3; 16];
3916
3917 engine.upsert_path_destination(
3918 dest1,
3919 make_path_entry(1000.0, 1, InterfaceId(1), [0x11; 16]),
3920 1000.0,
3921 );
3922 engine.upsert_path_destination(
3923 dest2,
3924 make_path_entry(1001.0, 1, InterfaceId(1), [0x22; 16]),
3925 1001.0,
3926 );
3927 engine
3928 .path_states
3929 .insert(dest1, constants::STATE_UNRESPONSIVE);
3930
3931 engine.upsert_path_destination(
3932 dest3,
3933 make_path_entry(1002.0, 1, InterfaceId(1), [0x33; 16]),
3934 1002.0,
3935 );
3936
3937 assert_eq!(engine.path_table_count(), 2);
3938 assert!(!engine.has_path(&dest1));
3939 assert!(engine.has_path(&dest2));
3940 assert!(engine.has_path(&dest3));
3941 assert!(!engine.path_states.contains_key(&dest1));
3942 assert_eq!(engine.path_destination_cap_evict_count(), 1);
3943 }
3944
3945 #[test]
3946 fn test_existing_path_destination_update_does_not_trigger_cap_eviction() {
3947 let mut config = make_config(false);
3948 config.max_path_destinations = 2;
3949 config.max_paths_per_destination = 2;
3950 let mut engine = TransportEngine::new(config);
3951 engine.register_interface(make_interface(1, constants::MODE_FULL));
3952
3953 let dest1 = [0xC1; 16];
3954 let dest2 = [0xC2; 16];
3955
3956 engine.upsert_path_destination(
3957 dest1,
3958 make_path_entry(1000.0, 2, InterfaceId(1), [0x11; 16]),
3959 1000.0,
3960 );
3961 engine.upsert_path_destination(
3962 dest2,
3963 make_path_entry(1001.0, 2, InterfaceId(1), [0x22; 16]),
3964 1001.0,
3965 );
3966
3967 engine.upsert_path_destination(
3968 dest2,
3969 make_path_entry(1002.0, 1, InterfaceId(1), [0x23; 16]),
3970 1002.0,
3971 );
3972
3973 assert_eq!(engine.path_table_count(), 2);
3974 assert!(engine.has_path(&dest1));
3975 assert!(engine.has_path(&dest2));
3976 }
3977
3978 #[test]
3979 fn test_roaming_loop_prevention() {
3980 let mut engine = TransportEngine::new(make_config(true));
3981 engine.register_interface(make_interface(1, constants::MODE_ROAMING));
3982
3983 let dest = [0xD3; 16];
3984 engine.path_table.insert(
3986 dest,
3987 PathSet::from_single(
3988 PathEntry {
3989 timestamp: 900.0,
3990 next_hop: [0xAA; 16],
3991 hops: 2,
3992 expires: 9999.0,
3993 random_blobs: Vec::new(),
3994 receiving_interface: InterfaceId(1),
3995 packet_hash: [0; 32],
3996 announce_raw: None,
3997 },
3998 1,
3999 ),
4000 );
4001
4002 let tag = [0x03; 16];
4003 let data = make_path_request_data(&dest, &tag);
4004
4005 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4006
4007 assert!(actions.is_empty());
4009 assert!(!engine.announce_table.contains_key(&dest));
4010 }
4011
4012 fn make_announce_raw(dest_hash: &[u8; 16], payload: &[u8]) -> Vec<u8> {
4014 let flags: u8 = 0x01; let mut raw = Vec::new();
4018 raw.push(flags);
4019 raw.push(0x02); raw.extend_from_slice(dest_hash);
4021 raw.push(constants::CONTEXT_NONE);
4022 raw.extend_from_slice(payload);
4023 raw
4024 }
4025
4026 #[test]
4027 fn test_path_request_populates_announce_entry_from_raw() {
4028 let mut engine = TransportEngine::new(make_config(true));
4029 engine.register_interface(make_interface(1, constants::MODE_FULL));
4030 engine.register_interface(make_interface(2, constants::MODE_FULL));
4031
4032 let dest = [0xD5; 16];
4033 let payload = vec![0xAB; 32]; let announce_raw = make_announce_raw(&dest, &payload);
4035
4036 engine.path_table.insert(
4037 dest,
4038 PathSet::from_single(
4039 PathEntry {
4040 timestamp: 900.0,
4041 next_hop: [0xBB; 16],
4042 hops: 2,
4043 expires: 9999.0,
4044 random_blobs: Vec::new(),
4045 receiving_interface: InterfaceId(2),
4046 packet_hash: [0; 32],
4047 announce_raw: Some(announce_raw.clone()),
4048 },
4049 1,
4050 ),
4051 );
4052
4053 let tag = [0x05; 16];
4054 let data = make_path_request_data(&dest, &tag);
4055 let _actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4056
4057 let entry = engine
4059 .announce_table
4060 .get(&dest)
4061 .expect("announce entry must exist");
4062 assert_eq!(entry.packet_raw, announce_raw);
4063 assert_eq!(entry.packet_data, payload);
4064 assert!(entry.block_rebroadcasts);
4065 }
4066
4067 #[test]
4068 fn test_path_request_skips_when_no_announce_raw() {
4069 let mut engine = TransportEngine::new(make_config(true));
4070 engine.register_interface(make_interface(1, constants::MODE_FULL));
4071 engine.register_interface(make_interface(2, constants::MODE_FULL));
4072
4073 let dest = [0xD6; 16];
4074
4075 engine.path_table.insert(
4076 dest,
4077 PathSet::from_single(
4078 PathEntry {
4079 timestamp: 900.0,
4080 next_hop: [0xCC; 16],
4081 hops: 1,
4082 expires: 9999.0,
4083 random_blobs: Vec::new(),
4084 receiving_interface: InterfaceId(2),
4085 packet_hash: [0; 32],
4086 announce_raw: None, },
4088 1,
4089 ),
4090 );
4091
4092 let tag = [0x06; 16];
4093 let data = make_path_request_data(&dest, &tag);
4094 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4095
4096 assert!(actions.is_empty());
4098 assert!(!engine.announce_table.contains_key(&dest));
4099 }
4100
4101 #[test]
4102 fn test_discovery_request_consumed_on_announce() {
4103 let mut engine = TransportEngine::new(make_config(true));
4104 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
4105
4106 let dest = [0xD4; 16];
4107
4108 engine.discovery_path_requests.insert(
4110 dest,
4111 DiscoveryPathRequest {
4112 timestamp: 900.0,
4113 requesting_interface: InterfaceId(1),
4114 },
4115 );
4116
4117 let iface = engine.discovery_path_requests_waiting(&dest);
4119 assert_eq!(iface, Some(InterfaceId(1)));
4120
4121 assert!(!engine.discovery_path_requests.contains_key(&dest));
4123 assert_eq!(engine.discovery_path_requests_waiting(&dest), None);
4124 }
4125
4126 #[test]
4127 fn test_pending_path_request_announce_bypasses_ingress_control() {
4128 let mut engine = TransportEngine::new(make_config(true));
4129 let mut inbound = make_interface(1, constants::MODE_FULL);
4130 inbound.ingress_control = crate::transport::types::IngressControlConfig::enabled();
4131 inbound.ia_freq = 10_000.0;
4132 inbound.started = 0.0;
4133 engine.register_interface(inbound);
4134 engine.register_interface(make_interface(2, constants::MODE_ACCESS_POINT));
4135
4136 let identity =
4137 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
4138 let dest_hash = crate::destination::destination_hash(
4139 "ingress",
4140 &["path-request"],
4141 Some(identity.hash()),
4142 );
4143 let name_hash = crate::destination::name_hash("ingress", &["path-request"]);
4144 let announce_raw = build_announce_for_issue4(&dest_hash, &name_hash);
4145
4146 engine.discovery_path_requests.insert(
4147 dest_hash,
4148 DiscoveryPathRequest {
4149 timestamp: 999.0,
4150 requesting_interface: InterfaceId(2),
4151 },
4152 );
4153
4154 let mut rng = rns_crypto::FixedRng::new(&[0x88; 32]);
4155 let actions = engine.handle_inbound(
4156 InboundFrame {
4157 raw: &announce_raw,
4158 iface: InterfaceId(1),
4159 now: 1000.0,
4160 rx: RxMetadata {
4161 rssi: None,
4162 snr: None,
4163 },
4164 },
4165 &mut rng,
4166 );
4167
4168 assert_eq!(engine.held_announce_count(&InterfaceId(1)), 0);
4169 assert!(engine.has_path(&dest_hash));
4170 assert!(!engine.discovery_path_requests.contains_key(&dest_hash));
4171 assert!(actions.iter().any(|a| {
4172 matches!(
4173 a,
4174 TransportAction::AnnounceReceived {
4175 destination_hash,
4176 receiving_interface: InterfaceId(1),
4177 ..
4178 } if *destination_hash == dest_hash
4179 )
4180 }));
4181
4182 let entry = engine
4183 .announce_table
4184 .get(&dest_hash)
4185 .expect("path response announce should be queued");
4186 assert!(entry.block_rebroadcasts);
4187 assert_eq!(entry.attached_interface, Some(InterfaceId(2)));
4188 }
4189
4190 fn build_announce_for_issue4(dest_hash: &[u8; 16], name_hash: &[u8; 10]) -> Vec<u8> {
4196 let identity =
4197 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
4198 let random_hash = [0x42u8; 10];
4199 let (announce_data, _) = crate::announce::AnnounceData::pack(
4200 &identity,
4201 dest_hash,
4202 name_hash,
4203 &random_hash,
4204 None,
4205 None,
4206 )
4207 .unwrap();
4208 let flags = PacketFlags {
4209 header_type: constants::HEADER_1,
4210 context_flag: constants::FLAG_UNSET,
4211 transport_type: constants::TRANSPORT_BROADCAST,
4212 destination_type: constants::DESTINATION_SINGLE,
4213 packet_type: constants::PACKET_TYPE_ANNOUNCE,
4214 };
4215 RawPacket::pack(
4216 flags,
4217 0,
4218 dest_hash,
4219 None,
4220 constants::CONTEXT_NONE,
4221 &announce_data,
4222 )
4223 .unwrap()
4224 .raw
4225 }
4226
4227 #[test]
4228 fn test_ingress_held_announce_preserves_rx_metadata_on_release() {
4229 let mut engine = TransportEngine::new(make_config(true));
4230 let mut inbound = make_interface(1, constants::MODE_FULL);
4231 inbound.ingress_control = crate::transport::types::IngressControlConfig::enabled();
4232 inbound.ia_freq = constants::IC_BURST_FREQ + 1.0;
4233 inbound.started = 0.0;
4234 engine.register_interface(inbound);
4235
4236 let identity =
4237 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
4238 let dest_hash =
4239 crate::destination::destination_hash("ingress", &["rx"], Some(identity.hash()));
4240 let name_hash = crate::destination::name_hash("ingress", &["rx"]);
4241 let announce_raw = build_announce_for_issue4(&dest_hash, &name_hash);
4242 let rx = RxMetadata {
4243 rssi: Some(-91),
4244 snr: Some(5.5),
4245 };
4246
4247 let mut rng = rns_crypto::FixedRng::new(&[0x88; 32]);
4248 let held_actions = engine.handle_inbound(
4249 InboundFrame::new(&announce_raw, InterfaceId(1), 10000.0).with_rx(rx),
4250 &mut rng,
4251 );
4252
4253 assert!(held_actions.is_empty());
4254 assert_eq!(engine.held_announce_count(&InterfaceId(1)), 1);
4255 assert!(!engine.has_path(&dest_hash));
4256
4257 engine
4258 .interfaces
4259 .get_mut(&InterfaceId(1))
4260 .expect("interface must exist")
4261 .ia_freq = 0.0;
4262
4263 let released_actions = engine.tick(10000.0 + constants::IC_BURST_PENALTY + 1.0, &mut rng);
4264
4265 let released_rx = released_actions.iter().find_map(|action| match action {
4266 TransportAction::AnnounceReceived {
4267 destination_hash,
4268 rx: action_rx,
4269 ..
4270 } if *destination_hash == dest_hash => Some(*action_rx),
4271 _ => None,
4272 });
4273
4274 assert_eq!(released_rx, Some(rx));
4275 assert_eq!(engine.held_announce_count(&InterfaceId(1)), 0);
4276 assert!(engine.has_path(&dest_hash));
4277 }
4278
4279 #[test]
4280 fn test_issue4_local_client_single_data_to_1hop_rewrites_on_outbound() {
4281 let mut engine = TransportEngine::new(make_config(false));
4286 engine.register_interface(make_local_client_interface(1));
4287
4288 let identity =
4289 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
4290 let dest_hash =
4291 crate::destination::destination_hash("issue4", &["test"], Some(identity.hash()));
4292 let name_hash = crate::destination::name_hash("issue4", &["test"]);
4293 let announce_raw = build_announce_for_issue4(&dest_hash, &name_hash);
4294
4295 let mut announce_packet = RawPacket::unpack(&announce_raw).unwrap();
4299 announce_packet.raw[1] = 1;
4300 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
4301 engine.handle_inbound(
4302 InboundFrame {
4303 raw: &announce_packet.raw,
4304 iface: InterfaceId(1),
4305 now: 1000.0,
4306 rx: RxMetadata {
4307 rssi: None,
4308 snr: None,
4309 },
4310 },
4311 &mut rng,
4312 );
4313 assert!(engine.has_path(&dest_hash));
4314 assert_eq!(engine.hops_to(&dest_hash), Some(1));
4315
4316 let data_flags = PacketFlags {
4318 header_type: constants::HEADER_1,
4319 context_flag: constants::FLAG_UNSET,
4320 transport_type: constants::TRANSPORT_BROADCAST,
4321 destination_type: constants::DESTINATION_SINGLE,
4322 packet_type: constants::PACKET_TYPE_DATA,
4323 };
4324 let data_packet = RawPacket::pack(
4325 data_flags,
4326 0,
4327 &dest_hash,
4328 None,
4329 constants::CONTEXT_NONE,
4330 b"hello",
4331 )
4332 .unwrap();
4333
4334 let actions =
4335 engine.handle_outbound(&data_packet, constants::DESTINATION_SINGLE, None, 1001.0);
4336
4337 let send = actions.iter().find_map(|a| match a {
4338 TransportAction::SendOnInterface { interface, raw } => Some((interface, raw)),
4339 _ => None,
4340 });
4341 let (interface, raw) = send.expect("shared client should emit a transport-injected packet");
4342 assert_eq!(*interface, InterfaceId(1));
4343 let flags = PacketFlags::unpack(raw[0]);
4344 assert_eq!(flags.header_type, constants::HEADER_2);
4345 assert_eq!(flags.transport_type, constants::TRANSPORT_TRANSPORT);
4346 }
4347
4348 #[test]
4349 fn test_issue4_external_data_to_1hop_via_transport_works() {
4350 let daemon_id = [0x42; 16];
4356 let mut engine = TransportEngine::new(TransportConfig {
4357 transport_enabled: true,
4358 identity_hash: Some(daemon_id),
4359 prefer_shorter_path: false,
4360 max_paths_per_destination: 1,
4361 packet_hashlist_max_entries: constants::HASHLIST_MAXSIZE,
4362 max_discovery_pr_tags: constants::MAX_PR_TAGS,
4363 max_path_destinations: usize::MAX,
4364 max_tunnel_destinations_total: usize::MAX,
4365 destination_timeout_secs: constants::DESTINATION_TIMEOUT,
4366 announce_table_ttl_secs: constants::ANNOUNCE_TABLE_TTL,
4367 announce_table_max_bytes: constants::ANNOUNCE_TABLE_MAX_BYTES,
4368 announce_sig_cache_enabled: true,
4369 announce_sig_cache_max_entries: constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4370 announce_sig_cache_ttl_secs: constants::ANNOUNCE_SIG_CACHE_TTL,
4371 announce_queue_max_entries: 256,
4372 announce_queue_max_interfaces: 1024,
4373 });
4374 engine.register_interface(make_interface(1, constants::MODE_FULL)); engine.register_interface(make_interface(2, constants::MODE_FULL)); let identity =
4378 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
4379 let dest_hash =
4380 crate::destination::destination_hash("issue4", &["ctrl"], Some(identity.hash()));
4381 let name_hash = crate::destination::name_hash("issue4", &["ctrl"]);
4382 let announce_raw = build_announce_for_issue4(&dest_hash, &name_hash);
4383
4384 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
4386 engine.handle_inbound(
4387 InboundFrame {
4388 raw: &announce_raw,
4389 iface: InterfaceId(2),
4390 now: 1000.0,
4391 rx: RxMetadata {
4392 rssi: None,
4393 snr: None,
4394 },
4395 },
4396 &mut rng,
4397 );
4398 assert_eq!(engine.hops_to(&dest_hash), Some(1));
4399
4400 let h2_flags = PacketFlags {
4403 header_type: constants::HEADER_2,
4404 context_flag: constants::FLAG_UNSET,
4405 transport_type: constants::TRANSPORT_TRANSPORT,
4406 destination_type: constants::DESTINATION_SINGLE,
4407 packet_type: constants::PACKET_TYPE_DATA,
4408 };
4409 let mut h2_raw = Vec::new();
4411 h2_raw.push(h2_flags.pack());
4412 h2_raw.push(0); h2_raw.extend_from_slice(&daemon_id); h2_raw.extend_from_slice(&dest_hash);
4415 h2_raw.push(constants::CONTEXT_NONE);
4416 h2_raw.extend_from_slice(b"hello via transport");
4417
4418 let mut rng2 = rns_crypto::FixedRng::new(&[0x22; 32]);
4419 let actions = engine.handle_inbound(
4420 InboundFrame {
4421 raw: &h2_raw,
4422 iface: InterfaceId(1),
4423 now: 1001.0,
4424 rx: RxMetadata {
4425 rssi: None,
4426 snr: None,
4427 },
4428 },
4429 &mut rng2,
4430 );
4431
4432 let has_send = actions.iter().any(|a| {
4434 matches!(
4435 a,
4436 TransportAction::SendOnInterface { interface, .. } if *interface == InterfaceId(2)
4437 )
4438 });
4439 assert!(
4440 has_send,
4441 "HEADER_2 transport packet should be forwarded (control test)"
4442 );
4443 }
4444}