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 persistence;
12pub mod queries;
13pub mod rate_limit;
14pub mod retention;
15pub mod tables;
16pub mod tunnel;
17pub mod types;
18
19use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
20use alloc::string::String;
21use alloc::vec::Vec;
22use core::mem::size_of;
23
24use rns_crypto::Rng;
25
26use crate::announce::AnnounceData;
27use crate::constants;
28use crate::hash;
29use crate::packet::RawPacket;
30
31use self::announce_proc::compute_path_expires;
32use self::announce_queue::AnnounceQueues;
33use self::announce_verify_queue::{AnnounceVerifyKey, AnnounceVerifyQueue, PendingAnnounce};
34use self::dedup::{AnnounceSignatureCache, PacketHashlist};
35use self::inbound::{
36 create_link_entry, create_reverse_entry, forward_transport_packet, route_proof_via_reverse,
37 route_via_link_table, LocalHopRewrite,
38};
39use self::ingress_control::IngressControl;
40use self::outbound::{route_outbound_with_options, should_transmit_announce, OutboundRouteOptions};
41use self::pathfinder::{
42 extract_random_blob, timebase_from_random_blob, 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
54fn lrproof_hop_mismatch_diagnostic(packet_hops: u8, entry: &LinkEntry) -> String {
55 alloc::format!(
56 "Received link request proof with hop mismatch ({}/{}:{}->{}), not transporting it",
57 packet_hops,
58 entry.remaining_hops,
59 entry.next_hop_interface.0,
60 entry.received_interface.0,
61 )
62}
63
64fn link_route_hops_match(
65 packet_hops: u8,
66 entry: &LinkEntry,
67 receiving_interface: InterfaceId,
68) -> bool {
69 if entry.next_hop_interface == entry.received_interface {
70 packet_hops == entry.remaining_hops || packet_hops == entry.taken_hops
71 } else if receiving_interface == entry.next_hop_interface {
72 packet_hops == entry.remaining_hops
73 } else if receiving_interface == entry.received_interface {
74 packet_hops == entry.taken_hops
75 } else {
76 true
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Default)]
82pub struct RxMetadata {
83 pub rssi: Option<i16>,
84 pub snr: Option<f32>,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq)]
88pub struct InboundFrame<'a> {
89 pub raw: &'a [u8],
90 pub iface: InterfaceId,
91 pub now: f64,
92 pub rx: RxMetadata,
93}
94
95impl<'a> InboundFrame<'a> {
96 pub fn new(raw: &'a [u8], iface: InterfaceId, now: f64) -> Self {
97 Self {
98 raw,
99 iface,
100 now,
101 rx: RxMetadata::default(),
102 }
103 }
104
105 pub fn with_rx(mut self, rx: RxMetadata) -> Self {
106 self.rx = rx;
107 self
108 }
109}
110
111struct InboundPacketCtx {
112 packet: RawPacket,
113 original_raw: Option<Vec<u8>>,
114 iface: InterfaceId,
115 now: f64,
116 from_local_client: bool,
117}
118
119struct VerifiedAnnounceCtx<'a> {
120 packet: &'a RawPacket,
121 original_raw: &'a [u8],
122 iface: InterfaceId,
123 now: f64,
124 validated: crate::announce::ValidatedAnnounce,
125 received_from: [u8; 16],
126 random_blob: [u8; 10],
127 announce_emitted: u64,
128}
129
130struct TickCtx<'a> {
131 now: f64,
132 rng: &'a mut dyn Rng,
133 actions: Vec<TransportAction>,
134}
135
136struct PathRequestCtx<'a> {
137 tag: &'a [u8],
138 interface_id: InterfaceId,
139 now: f64,
140 destination_hash: [u8; 16],
141}
142
143pub struct TransportEngine {
148 config: TransportConfig,
149 path_table: BTreeMap<[u8; 16], PathSet>,
150 announce_table: BTreeMap<[u8; 16], AnnounceEntry>,
151 reverse_table: BTreeMap<[u8; 16], tables::ReverseEntry>,
152 link_table: BTreeMap<[u8; 16], LinkEntry>,
153 held_announces: BTreeMap<[u8; 16], AnnounceEntry>,
154 packet_hashlist: PacketHashlist,
155 announce_sig_cache: AnnounceSignatureCache,
156 rate_limiter: AnnounceRateLimiter,
157 path_states: BTreeMap<[u8; 16], u8>,
158 interfaces: BTreeMap<InterfaceId, InterfaceInfo>,
159 interface_hashes: BTreeMap<InterfaceId, [u8; 32]>,
160 local_destinations: BTreeMap<[u8; 16], u8>,
161 blackholed_identities: BTreeMap<[u8; 16], BlackholeEntry>,
162 announce_queues: AnnounceQueues,
163 ingress_control: IngressControl,
164 tunnel_table: TunnelTable,
165 discovery_pr_tags: VecDeque<[u8; 32]>,
166 discovery_pr_tag_set: BTreeSet<[u8; 32]>,
167 discovery_path_requests: BTreeMap<[u8; 16], DiscoveryPathRequest>,
168 path_destination_cap_evict_count: usize,
169 announces_last_checked: f64,
171 tables_last_culled: f64,
172}
173
174impl TransportEngine {
175 pub fn new(config: TransportConfig) -> Self {
176 let packet_hashlist_max_entries = config.packet_hashlist_max_entries;
177 let sig_cache_max = if config.announce_sig_cache_enabled {
178 config.announce_sig_cache_max_entries
179 } else {
180 0
181 };
182 let sig_cache_ttl = config.announce_sig_cache_ttl_secs;
183 let announce_queue_max_interfaces = config.announce_queue_max_interfaces;
184 TransportEngine {
185 config,
186 path_table: BTreeMap::new(),
187 announce_table: BTreeMap::new(),
188 reverse_table: BTreeMap::new(),
189 link_table: BTreeMap::new(),
190 held_announces: BTreeMap::new(),
191 packet_hashlist: PacketHashlist::new(packet_hashlist_max_entries),
192 announce_sig_cache: AnnounceSignatureCache::new(sig_cache_max, sig_cache_ttl),
193 rate_limiter: AnnounceRateLimiter::new(),
194 path_states: BTreeMap::new(),
195 interfaces: BTreeMap::new(),
196 interface_hashes: BTreeMap::new(),
197 local_destinations: BTreeMap::new(),
198 blackholed_identities: BTreeMap::new(),
199 announce_queues: AnnounceQueues::new(announce_queue_max_interfaces),
200 ingress_control: IngressControl::new(),
201 tunnel_table: TunnelTable::new(),
202 discovery_pr_tags: VecDeque::new(),
203 discovery_pr_tag_set: BTreeSet::new(),
204 discovery_path_requests: BTreeMap::new(),
205 path_destination_cap_evict_count: 0,
206 announces_last_checked: 0.0,
207 tables_last_culled: 0.0,
208 }
209 }
210
211 pub fn register_interface(&mut self, info: InterfaceInfo) {
216 self.interface_hashes
217 .insert(info.id, hash::full_hash(info.name.as_bytes()));
218 self.interfaces.insert(info.id, info);
219 }
220
221 pub fn deregister_interface(&mut self, id: InterfaceId) {
222 self.interfaces.remove(&id);
223 self.interface_hashes.remove(&id);
224 self.drop_paths_for_interface(id);
225 self.drop_reverse_for_interface(id);
226 self.drop_links_for_interface(id);
227 self.announce_queues.remove_interface(id);
228 self.ingress_control.remove_interface(&id);
229 }
230
231 pub fn register_destination(&mut self, dest_hash: [u8; 16], dest_type: u8) {
236 self.local_destinations.insert(dest_hash, dest_type);
237 }
238
239 pub fn deregister_destination(&mut self, dest_hash: &[u8; 16]) {
240 self.local_destinations.remove(dest_hash);
241 }
242
243 pub fn has_path(&self, dest_hash: &[u8; 16]) -> bool {
248 self.path_table
249 .get(dest_hash)
250 .is_some_and(|ps| !ps.is_empty())
251 }
252
253 pub fn hops_to(&self, dest_hash: &[u8; 16]) -> Option<u8> {
254 self.path_table
255 .get(dest_hash)
256 .and_then(|ps| ps.primary())
257 .map(|e| e.hops)
258 }
259
260 pub fn next_hop(&self, dest_hash: &[u8; 16]) -> Option<[u8; 16]> {
261 self.path_table
262 .get(dest_hash)
263 .and_then(|ps| ps.primary())
264 .map(|e| e.next_hop)
265 }
266
267 pub fn next_hop_interface(&self, dest_hash: &[u8; 16]) -> Option<InterfaceId> {
268 self.path_table
269 .get(dest_hash)
270 .and_then(|ps| ps.primary())
271 .map(|e| e.receiving_interface)
272 }
273
274 pub fn mark_path_unresponsive(
284 &mut self,
285 dest_hash: &[u8; 16],
286 receiving_interface: Option<InterfaceId>,
287 ) {
288 if let Some(iface_id) = receiving_interface {
289 if let Some(info) = self.interfaces.get(&iface_id) {
290 if info.mode == constants::MODE_BOUNDARY {
291 return;
292 }
293 }
294 }
295
296 if let Some(ps) = self.path_table.get_mut(dest_hash) {
298 if ps.len() > 1 {
299 ps.failover(false); self.path_states.remove(dest_hash);
302 return;
303 }
304 }
305
306 self.path_states
307 .insert(*dest_hash, constants::STATE_UNRESPONSIVE);
308 }
309
310 pub fn mark_path_responsive(&mut self, dest_hash: &[u8; 16]) {
311 self.path_states
312 .insert(*dest_hash, constants::STATE_RESPONSIVE);
313 }
314
315 pub fn path_is_unresponsive(&self, dest_hash: &[u8; 16]) -> bool {
316 self.path_states.get(dest_hash) == Some(&constants::STATE_UNRESPONSIVE)
317 }
318
319 pub fn expire_path(&mut self, dest_hash: &[u8; 16]) {
320 if let Some(ps) = self.path_table.get_mut(dest_hash) {
321 ps.expire_all();
322 }
323 }
324
325 pub fn register_link(&mut self, link_id: [u8; 16], entry: LinkEntry) {
330 self.link_table.insert(link_id, entry);
331 }
332
333 pub fn validate_link(&mut self, link_id: &[u8; 16]) {
334 if let Some(entry) = self.link_table.get_mut(link_id) {
335 entry.validated = true;
336 }
337 }
338
339 pub fn remove_link(&mut self, link_id: &[u8; 16]) {
340 self.link_table.remove(link_id);
341 }
342
343 pub fn link_rebalance_destination(
346 &self,
347 link_id: &[u8; 16],
348 packet_hops: u8,
349 receiving_interface: InterfaceId,
350 ) -> Option<[u8; 16]> {
351 let entry = self.link_table.get(link_id)?;
352 (self.config.transport_enabled
353 && !entry.validated
354 && packet_hops != entry.remaining_hops
355 && receiving_interface == entry.next_hop_interface)
356 .then_some(entry.destination_hash)
357 }
358
359 pub fn inbound_lrproof_rebalance_candidate(
363 &self,
364 raw: &[u8],
365 receiving_interface: InterfaceId,
366 ) -> Option<([u8; 16], [u8; 16], u8, Vec<u8>)> {
367 let ctx = self.prepare_inbound_packet(InboundFrame {
368 raw,
369 iface: receiving_interface,
370 now: 0.0,
371 rx: RxMetadata::default(),
372 })?;
373 if ctx.packet.flags.packet_type != constants::PACKET_TYPE_PROOF
374 || ctx.packet.context != constants::CONTEXT_LRPROOF
375 {
376 return None;
377 }
378 let link_id = ctx.packet.destination_hash;
379 let destination_hash =
380 self.link_rebalance_destination(&link_id, ctx.packet.hops, receiving_interface)?;
381 Some((link_id, destination_hash, ctx.packet.hops, ctx.packet.data))
382 }
383
384 pub fn rebalance_link_path_from_lrproof(
387 &mut self,
388 link_id: &[u8; 16],
389 packet_hops: u8,
390 receiving_interface: InterfaceId,
391 proof_data: &[u8],
392 destination_sig_pub_bytes: &[u8; 32],
393 ) -> bool {
394 if self
395 .link_rebalance_destination(link_id, packet_hops, receiving_interface)
396 .is_none()
397 {
398 return false;
399 }
400
401 let destination_sig_pub =
402 rns_crypto::ed25519::Ed25519PublicKey::from_bytes(destination_sig_pub_bytes);
403 if crate::link::handshake::validate_lrproof(
404 proof_data,
405 link_id,
406 &destination_sig_pub,
407 destination_sig_pub_bytes,
408 )
409 .is_err()
410 {
411 return false;
412 }
413
414 let destination_hash = match self.link_table.get_mut(link_id) {
415 Some(entry) if !entry.validated => {
416 entry.remaining_hops = packet_hops;
417 entry.destination_hash
418 }
419 _ => return false,
420 };
421 if let Some(paths) = self.path_table.get_mut(&destination_hash) {
422 paths.update_primary_hops(packet_hops);
423 }
424 true
425 }
426
427 pub fn rebalance_destination_path_hops(
429 &mut self,
430 destination_hash: &[u8; 16],
431 packet_hops: u8,
432 ) -> bool {
433 self.path_table
434 .get_mut(destination_hash)
435 .is_some_and(|paths| paths.update_primary_hops(packet_hops))
436 }
437
438 pub fn blackhole_identity(
449 &mut self,
450 identity_hash: [u8; 16],
451 now: f64,
452 duration_hours: Option<f64>,
453 reason: Option<String>,
454 ) {
455 let expires = match duration_hours {
456 Some(h) if h > 0.0 => now + h * 3600.0,
457 _ => 0.0, };
459 self.blackholed_identities.insert(
460 identity_hash,
461 BlackholeEntry {
462 created: now,
463 expires,
464 reason,
465 },
466 );
467 }
468
469 pub fn unblackhole_identity(&mut self, identity_hash: &[u8; 16]) -> bool {
474 self.blackholed_identities.remove(identity_hash).is_some()
475 }
476
477 pub fn is_blackholed(&self, identity_hash: &[u8; 16], now: f64) -> bool {
479 if let Some(entry) = self.blackholed_identities.get(identity_hash) {
480 if entry.expires == 0.0 || entry.expires > now {
481 return true;
482 }
483 }
484 false
485 }
486
487 pub fn blackholed_entries(&self) -> impl Iterator<Item = (&[u8; 16], &BlackholeEntry)> {
489 self.blackholed_identities.iter()
490 }
491
492 fn cull_blackholed(&mut self, now: f64) {
494 self.blackholed_identities
495 .retain(|_, entry| entry.expires == 0.0 || entry.expires > now);
496 }
497
498 pub fn handle_tunnel(
506 &mut self,
507 tunnel_id: [u8; 32],
508 interface: InterfaceId,
509 now: f64,
510 ) -> Vec<TransportAction> {
511 let mut actions = Vec::new();
512 let reattaching = self.tunnel_table.get(&tunnel_id).is_some();
513 if reattaching {
514 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
515 "Tunnel endpoint {:02x?} reappeared on interface {}; restoring paths",
516 &tunnel_id[..4],
517 interface.0,
518 );
519 } else {
520 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
521 "Tunnel endpoint {:02x?} established on interface {}",
522 &tunnel_id[..4],
523 interface.0,
524 );
525 }
526
527 if let Some(info) = self.interfaces.get_mut(&interface) {
529 info.tunnel_id = Some(tunnel_id);
530 }
531
532 let restored_paths = self.tunnel_table.handle_tunnel(
533 tunnel_id,
534 interface,
535 now,
536 self.config.destination_timeout_secs,
537 );
538
539 for (dest_hash, tunnel_path) in &restored_paths {
541 let should_restore = match self.path_table.get(dest_hash).and_then(|ps| ps.primary()) {
542 Some(existing) => {
543 if tunnel_path.hops <= existing.hops || existing.expires < now {
546 let existing_timebase = timebase_from_random_blobs(&existing.random_blobs);
547 let tunnel_timebase = timebase_from_random_blobs(&tunnel_path.random_blobs);
548 tunnel_timebase >= existing_timebase
549 } else {
550 false
551 }
552 }
553 None => now < tunnel_path.expires,
554 };
555
556 if should_restore {
557 let entry = PathEntry {
558 timestamp: tunnel_path.timestamp,
559 next_hop: tunnel_path.received_from,
560 hops: tunnel_path.hops,
561 expires: tunnel_path.expires,
562 random_blobs: tunnel_path.random_blobs.clone(),
563 receiving_interface: interface,
564 packet_hash: tunnel_path.packet_hash,
565 announce_raw: None,
566 };
567 self.upsert_path_destination(*dest_hash, entry, now);
568 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
569 "Restored tunnel path to {:02x?}: hops={} via={:02x?} interface={}",
570 &dest_hash[..4],
571 tunnel_path.hops,
572 &tunnel_path.received_from[..4],
573 interface.0,
574 );
575 } else {
576 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
577 "Did not restore tunnel path to {:02x?}: existing path is preferred or tunnel path expired",
578 &dest_hash[..4],
579 );
580 }
581 }
582
583 actions.push(TransportAction::TunnelEstablished {
584 tunnel_id,
585 interface,
586 });
587
588 actions
589 }
590
591 pub fn synthesize_tunnel(
599 &self,
600 identity: &rns_crypto::identity::Identity,
601 interface_id: InterfaceId,
602 rng: &mut dyn Rng,
603 ) -> Vec<TransportAction> {
604 let mut actions = Vec::new();
605
606 let interface_hash = if let Some(interface_hash) = self.interface_hashes.get(&interface_id)
607 {
608 *interface_hash
609 } else {
610 log::warn!(
611 "Cannot synthesize tunnel on {:?}: unknown interface or missing cached hash",
612 interface_id
613 );
614 return actions;
615 };
616
617 match tunnel::build_tunnel_synthesize_data(identity, &interface_hash, rng) {
618 Ok((data, _tunnel_id)) => {
619 let dest_hash = crate::destination::destination_hash(
620 "rnstransport",
621 &["tunnel", "synthesize"],
622 None,
623 );
624 actions.push(TransportAction::TunnelSynthesize {
625 interface: interface_id,
626 data,
627 dest_hash,
628 });
629 }
630 Err(e) => {
631 log::warn!("Cannot synthesize tunnel on {:?}: {}", interface_id, e);
632 }
633 }
634
635 actions
636 }
637
638 pub fn void_tunnel_interface(&mut self, tunnel_id: &[u8; 32]) {
640 self.tunnel_table.void_tunnel_interface(tunnel_id);
641 }
642
643 pub fn tunnel_table(&self) -> &TunnelTable {
645 &self.tunnel_table
646 }
647
648 fn has_local_clients(&self) -> bool {
654 self.interfaces.values().any(|i| i.is_local_client)
655 }
656
657 fn interface_is_local_client(&self, iface: InterfaceId) -> bool {
658 self.interfaces
659 .get(&iface)
660 .map(|i| i.is_local_client)
661 .unwrap_or(false)
662 }
663
664 fn packet_filter(&self, packet: &RawPacket) -> bool {
668 if packet.transport_id.is_some()
670 && packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE
671 {
672 if let Some(ref identity_hash) = self.config.identity_hash {
673 if packet.transport_id.as_ref() != Some(identity_hash) {
674 return false;
675 }
676 }
677 }
678
679 match packet.context {
681 constants::CONTEXT_KEEPALIVE
682 | constants::CONTEXT_RESOURCE_REQ
683 | constants::CONTEXT_RESOURCE_PRF
684 | constants::CONTEXT_RESOURCE
685 | constants::CONTEXT_CACHE_REQUEST
686 | constants::CONTEXT_CHANNEL => return true,
687 _ => {}
688 }
689
690 if packet.flags.destination_type == constants::DESTINATION_PLAIN
692 || packet.flags.destination_type == constants::DESTINATION_GROUP
693 {
694 if packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE {
695 return packet.hops <= 1;
696 } else {
697 return false;
699 }
700 }
701
702 if !self.packet_hashlist.is_duplicate(&packet.packet_hash) {
704 return true;
705 }
706
707 if packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
709 && packet.flags.destination_type == constants::DESTINATION_SINGLE
710 {
711 return true;
712 }
713
714 false
715 }
716
717 pub fn handle_inbound(
725 &mut self,
726 frame: InboundFrame<'_>,
727 rng: &mut dyn Rng,
728 ) -> Vec<TransportAction> {
729 self.handle_inbound_with_announce_queue(frame, rng, None)
730 }
731
732 pub fn handle_inbound_with_announce_queue(
733 &mut self,
734 frame: InboundFrame<'_>,
735 rng: &mut dyn Rng,
736 announce_queue: Option<&mut AnnounceVerifyQueue>,
737 ) -> Vec<TransportAction> {
738 let Some(ctx) = self.prepare_inbound_packet(frame) else {
739 return Vec::new();
740 };
741 let mut actions = Vec::new();
742
743 self.remember_inbound_packet_hash(&ctx.packet);
744 self.bridge_plain_broadcast(&ctx, &mut actions);
745 self.handle_transport_forwarding(&ctx, &mut actions);
746 self.handle_link_table_routing(&ctx, &mut actions);
747 self.handle_inbound_announce(&ctx, rng, announce_queue, &mut actions);
748
749 if ctx.packet.flags.packet_type == constants::PACKET_TYPE_PROOF {
750 self.process_inbound_proof(&ctx, &mut actions);
751 }
752
753 self.handle_inbound_local_delivery(&ctx, &mut actions);
754 actions
755 }
756
757 fn prepare_inbound_packet(&self, frame: InboundFrame<'_>) -> Option<InboundPacketCtx> {
758 let mut packet = RawPacket::unpack(frame.raw).ok()?;
759 let from_local_client = self
760 .interfaces
761 .get(&frame.iface)
762 .map(|i| i.is_local_client)
763 .unwrap_or(false);
764 packet.hops = packet.hops.checked_add(1)?;
765 packet.rssi = frame.rx.rssi;
766 packet.snr = frame.rx.snr;
767 if from_local_client {
768 packet.hops = packet.hops.saturating_sub(1);
769 }
770 if !self.packet_filter(&packet) {
771 return None;
772 }
773 let retain_original_raw = packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE;
774 Some(InboundPacketCtx {
775 packet,
776 original_raw: if retain_original_raw {
777 Some(frame.raw.to_vec())
778 } else {
779 None
780 },
781 iface: frame.iface,
782 now: frame.now,
783 from_local_client,
784 })
785 }
786
787 fn remember_inbound_packet_hash(&mut self, packet: &RawPacket) {
788 let remember_hash = !(self.link_table.contains_key(&packet.destination_hash)
789 || (packet.flags.packet_type == constants::PACKET_TYPE_PROOF
790 && packet.context == constants::CONTEXT_LRPROOF));
791 if remember_hash {
792 self.packet_hashlist.add(packet.packet_hash);
793 }
794 }
795
796 fn bridge_plain_broadcast(&self, ctx: &InboundPacketCtx, actions: &mut Vec<TransportAction>) {
797 if ctx.packet.flags.destination_type != constants::DESTINATION_PLAIN
798 || ctx.packet.flags.transport_type != constants::TRANSPORT_BROADCAST
799 || !self.has_local_clients()
800 {
801 return;
802 }
803
804 if ctx.from_local_client {
805 actions.push(TransportAction::ForwardPlainBroadcast {
806 raw: PacketBytes::from(ctx.packet.raw.clone()),
807 to_local: false,
808 exclude: Some(ctx.iface),
809 });
810 } else {
811 actions.push(TransportAction::ForwardPlainBroadcast {
812 raw: PacketBytes::from(ctx.packet.raw.clone()),
813 to_local: true,
814 exclude: None,
815 });
816 }
817 }
818
819 fn handle_transport_forwarding(
820 &mut self,
821 ctx: &InboundPacketCtx,
822 actions: &mut Vec<TransportAction>,
823 ) {
824 if !(self.config.transport_enabled || self.config.identity_hash.is_some()) {
825 return;
826 }
827 if ctx.packet.transport_id.is_none()
828 || ctx.packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
829 {
830 if ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA {
831 log::debug!(
832 "TransportForward: DATA dest={:02x}{:02x}{:02x}{:02x}.. not transport-addressed header={} iface={}",
833 ctx.packet.destination_hash[0],
834 ctx.packet.destination_hash[1],
835 ctx.packet.destination_hash[2],
836 ctx.packet.destination_hash[3],
837 ctx.packet.flags.header_type,
838 ctx.iface.0
839 );
840 }
841 return;
842 }
843
844 let Some(identity_hash) = self.config.identity_hash else {
845 return;
846 };
847 if ctx.packet.transport_id != Some(identity_hash) {
848 if ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA {
849 log::debug!(
850 "TransportForward: DATA dest={:02x}{:02x}{:02x}{:02x}.. transport mismatch got={:02x?} own={:02x?} iface={}",
851 ctx.packet.destination_hash[0],
852 ctx.packet.destination_hash[1],
853 ctx.packet.destination_hash[2],
854 ctx.packet.destination_hash[3],
855 ctx.packet.transport_id.as_ref().map(|id| &id[..4]),
856 &identity_hash[..4],
857 ctx.iface.0
858 );
859 }
860 return;
861 }
862
863 let Some(path_entry) = self
864 .path_table
865 .get(&ctx.packet.destination_hash)
866 .and_then(|ps| ps.primary())
867 else {
868 if ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA {
869 log::debug!(
870 "TransportForward: DATA dest={:02x}{:02x}{:02x}{:02x}.. addressed to us but no path iface={}",
871 ctx.packet.destination_hash[0],
872 ctx.packet.destination_hash[1],
873 ctx.packet.destination_hash[2],
874 ctx.packet.destination_hash[3],
875 ctx.iface.0
876 );
877 }
878 return;
879 };
880
881 let next_hop = path_entry.next_hop;
882 let remaining_hops = path_entry.hops;
883 let outbound_interface = path_entry.receiving_interface;
884 let outbound_is_local_client = self
885 .interfaces
886 .get(&outbound_interface)
887 .map(|info| info.is_local_client)
888 .unwrap_or(false);
889 let forwarded_remaining_hops = if outbound_is_local_client {
890 0
891 } else {
892 remaining_hops
893 };
894 if ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA {
895 log::debug!(
896 "TransportForward: DATA dest={:02x}{:02x}{:02x}{:02x}.. remaining_hops={} out_iface={} next_hop={:02x?}",
897 ctx.packet.destination_hash[0],
898 ctx.packet.destination_hash[1],
899 ctx.packet.destination_hash[2],
900 ctx.packet.destination_hash[3],
901 remaining_hops,
902 outbound_interface.0,
903 &next_hop[..4]
904 );
905 }
906 let mut new_raw = forward_transport_packet(
907 &ctx.packet,
908 next_hop,
909 forwarded_remaining_hops,
910 outbound_interface,
911 );
912 if self.config.local_hops_delta != 0
913 && ctx.from_local_client
914 && !outbound_is_local_client
915 && ctx.packet.hops == 0
916 && ctx.packet.flags.destination_type != constants::DESTINATION_PLAIN
917 && ctx.packet.flags.destination_type != constants::DESTINATION_GROUP
918 && new_raw.len() > 1
919 {
920 new_raw[1] = self.config.local_hops_delta;
921 }
922
923 if ctx.packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST {
924 let proof_timeout = ctx.now
925 + constants::LINK_ESTABLISHMENT_TIMEOUT_PER_HOP * (remaining_hops.max(1) as f64);
926 let (link_id, link_entry) = create_link_entry(
927 &ctx.packet,
928 next_hop,
929 outbound_interface,
930 remaining_hops,
931 ctx.iface,
932 ctx.now,
933 proof_timeout,
934 );
935 self.link_table.insert(link_id, link_entry);
936 actions.push(TransportAction::LinkRequestReceived {
937 link_id,
938 destination_hash: ctx.packet.destination_hash,
939 receiving_interface: ctx.iface,
940 });
941 } else {
942 let (trunc_hash, reverse_entry) =
943 create_reverse_entry(&ctx.packet, outbound_interface, ctx.iface, ctx.now);
944 self.reverse_table.insert(trunc_hash, reverse_entry);
945 }
946
947 actions.push(TransportAction::SendOnInterface {
948 interface: outbound_interface,
949 raw: new_raw.into(),
950 });
951
952 if let Some(entry) = self
953 .path_table
954 .get_mut(&ctx.packet.destination_hash)
955 .and_then(|ps| ps.primary_mut())
956 {
957 entry.timestamp = ctx.now;
958 }
959 }
960
961 fn handle_link_table_routing(
962 &mut self,
963 ctx: &InboundPacketCtx,
964 actions: &mut Vec<TransportAction>,
965 ) {
966 if !self.config.transport_enabled && self.config.identity_hash.is_none() {
967 return;
968 }
969 if ctx.packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
970 || ctx.packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST
971 || ctx.packet.context == constants::CONTEXT_LRPROOF
972 {
973 return;
974 }
975
976 let Some(link_entry) = self.link_table.get(&ctx.packet.destination_hash).cloned() else {
977 return;
978 };
979 let instance_local_link = self.interface_is_local_client(link_entry.next_hop_interface)
980 && self.interface_is_local_client(link_entry.received_interface);
981 let Some((outbound_iface, new_raw)) = route_via_link_table(
982 &ctx.packet,
983 &link_entry,
984 ctx.iface,
985 LocalHopRewrite {
986 local_hops_delta: self.config.local_hops_delta,
987 from_local_client: ctx.from_local_client,
988 skip_local_hops_delta: instance_local_link,
989 },
990 ) else {
991 return;
992 };
993
994 self.packet_hashlist.add(ctx.packet.packet_hash);
995 actions.push(TransportAction::SendOnInterface {
996 interface: outbound_iface,
997 raw: new_raw.into(),
998 });
999
1000 if let Some(entry) = self.link_table.get_mut(&ctx.packet.destination_hash) {
1001 entry.timestamp = ctx.now;
1002 }
1003 }
1004
1005 fn handle_inbound_announce(
1006 &mut self,
1007 ctx: &InboundPacketCtx,
1008 rng: &mut dyn Rng,
1009 announce_queue: Option<&mut AnnounceVerifyQueue>,
1010 actions: &mut Vec<TransportAction>,
1011 ) {
1012 if ctx.packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE {
1013 return;
1014 }
1015
1016 if let Some(queue) = announce_queue {
1017 self.try_enqueue_announce(ctx, rng, queue, actions);
1018 } else {
1019 let original_raw = ctx
1020 .original_raw
1021 .as_deref()
1022 .expect("announce packets retain original raw bytes");
1023 self.process_inbound_announce(
1024 &ctx.packet,
1025 original_raw,
1026 ctx.iface,
1027 ctx.now,
1028 rng,
1029 actions,
1030 );
1031 }
1032 }
1033
1034 fn handle_inbound_local_delivery(
1035 &self,
1036 ctx: &InboundPacketCtx,
1037 actions: &mut Vec<TransportAction>,
1038 ) {
1039 if (ctx.packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST
1040 || ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA)
1041 && self
1042 .local_destinations
1043 .contains_key(&ctx.packet.destination_hash)
1044 {
1045 let mut delivery_raw = ctx.packet.raw.clone();
1046 if ctx.packet.context == constants::CONTEXT_LRRTT && delivery_raw.len() >= 2 {
1050 delivery_raw[1] = ctx.packet.hops;
1051 }
1052 actions.push(TransportAction::DeliverLocal {
1053 destination_hash: ctx.packet.destination_hash,
1054 raw: PacketBytes::from(delivery_raw),
1055 packet_hash: ctx.packet.packet_hash,
1056 receiving_interface: ctx.iface,
1057 });
1058 }
1059 }
1060
1061 fn process_inbound_announce(
1066 &mut self,
1067 packet: &RawPacket,
1068 original_raw: &[u8],
1069 iface: InterfaceId,
1070 now: f64,
1071 rng: &mut dyn Rng,
1072 actions: &mut Vec<TransportAction>,
1073 ) {
1074 if packet.flags.destination_type != constants::DESTINATION_SINGLE {
1075 return;
1076 }
1077
1078 let has_ratchet = packet.flags.context_flag == constants::FLAG_SET;
1079
1080 let announce = match AnnounceData::unpack(&packet.data, has_ratchet) {
1082 Ok(a) => a,
1083 Err(_) => return,
1084 };
1085
1086 if self.should_hold_announce(packet, original_raw, iface, now) {
1087 return;
1088 }
1089
1090 let sig_cache_key =
1091 Self::announce_sig_cache_key(packet.destination_hash, &announce.signature);
1092
1093 let validated = if self.announce_sig_cache.contains(&sig_cache_key) {
1094 announce.to_validated_unchecked()
1095 } else {
1096 match announce.validate(&packet.destination_hash) {
1097 Ok(v) => {
1098 self.announce_sig_cache.insert(sig_cache_key, now);
1099 v
1100 }
1101 Err(_) => return,
1102 }
1103 };
1104
1105 let received_from = self.announce_received_from(packet, now);
1106 let random_blob = match extract_random_blob(&packet.data) {
1107 Some(b) => b,
1108 None => return,
1109 };
1110 let announce_emitted = timebase_from_random_blob(&random_blob);
1111
1112 self.process_verified_announce(
1113 VerifiedAnnounceCtx {
1114 packet,
1115 original_raw,
1116 iface,
1117 now,
1118 validated,
1119 received_from,
1120 random_blob,
1121 announce_emitted,
1122 },
1123 rng,
1124 actions,
1125 );
1126 }
1127
1128 fn announce_raw_for_local_clients(&self, packet: &RawPacket) -> PacketBytes {
1129 let Some(identity_hash) = self.config.identity_hash else {
1130 return PacketBytes::from(packet.raw.clone());
1131 };
1132
1133 if packet.raw.len() < 2 {
1134 return PacketBytes::from(packet.raw.clone());
1135 }
1136
1137 let payload_start = if packet.flags.header_type == constants::HEADER_2 {
1138 18usize
1139 } else {
1140 2usize
1141 };
1142 if packet.raw.len() < payload_start {
1143 return PacketBytes::from(packet.raw.clone());
1144 }
1145
1146 let flags = (constants::HEADER_2 << 6)
1147 | (constants::TRANSPORT_TRANSPORT << 4)
1148 | (packet.raw[0] & 0x0F);
1149 let mut raw = Vec::with_capacity(18 + packet.raw.len() - payload_start);
1150 raw.push(flags);
1151 raw.push(packet.hops);
1152 raw.extend_from_slice(&identity_hash);
1153 raw.extend_from_slice(&packet.raw[payload_start..]);
1154 PacketBytes::from(raw)
1155 }
1156
1157 fn announce_sig_cache_key(destination_hash: [u8; 16], signature: &[u8; 64]) -> [u8; 32] {
1158 let mut material = [0u8; 80];
1159 material[..16].copy_from_slice(&destination_hash);
1160 material[16..].copy_from_slice(signature);
1161 hash::full_hash(&material)
1162 }
1163
1164 fn announce_received_from(&mut self, packet: &RawPacket, now: f64) -> [u8; 16] {
1165 if let Some(transport_id) = packet.transport_id {
1166 if self.config.transport_enabled {
1167 if let Some(announce_entry) = self.announce_table.get_mut(&packet.destination_hash)
1168 {
1169 if packet.hops.checked_sub(1) == Some(announce_entry.hops) {
1170 announce_entry.local_rebroadcasts += 1;
1171 if announce_entry.retries > 0
1172 && announce_entry.local_rebroadcasts
1173 >= constants::LOCAL_REBROADCASTS_MAX
1174 {
1175 self.announce_table.remove(&packet.destination_hash);
1176 }
1177 }
1178 if let Some(announce_entry) = self.announce_table.get(&packet.destination_hash)
1179 {
1180 if packet.hops.checked_sub(1) == Some(announce_entry.hops + 1)
1181 && announce_entry.retries > 0
1182 && now < announce_entry.retransmit_timeout
1183 {
1184 self.announce_table.remove(&packet.destination_hash);
1185 }
1186 }
1187 }
1188 }
1189 transport_id
1190 } else {
1191 packet.destination_hash
1192 }
1193 }
1194
1195 fn should_hold_announce(
1196 &mut self,
1197 packet: &RawPacket,
1198 original_raw: &[u8],
1199 iface: InterfaceId,
1200 now: f64,
1201 ) -> bool {
1202 if self.has_path(&packet.destination_hash) {
1203 return false;
1204 }
1205 if self
1206 .discovery_path_requests
1207 .contains_key(&packet.destination_hash)
1208 {
1209 return false;
1210 }
1211 let Some(info) = self.interfaces.get(&iface) else {
1212 return false;
1213 };
1214 if packet.context == constants::CONTEXT_PATH_RESPONSE
1215 || !self.ingress_control.should_ingress_limit(
1216 iface,
1217 &info.ingress_control,
1218 info.ia_freq,
1219 info.started,
1220 now,
1221 )
1222 {
1223 return false;
1224 }
1225 self.ingress_control.hold_announce(
1226 iface,
1227 &info.ingress_control,
1228 packet.destination_hash,
1229 ingress_control::HeldAnnounce {
1230 raw: original_raw.to_vec(),
1231 hops: packet.hops,
1232 receiving_interface: iface,
1233 rx: RxMetadata {
1234 rssi: packet.rssi,
1235 snr: packet.snr,
1236 },
1237 timestamp: now,
1238 },
1239 );
1240 true
1241 }
1242
1243 fn try_enqueue_announce(
1244 &mut self,
1245 ctx: &InboundPacketCtx,
1246 rng: &mut dyn Rng,
1247 announce_queue: &mut AnnounceVerifyQueue,
1248 actions: &mut Vec<TransportAction>,
1249 ) {
1250 if ctx.packet.flags.destination_type != constants::DESTINATION_SINGLE {
1251 return;
1252 }
1253
1254 let has_ratchet = ctx.packet.flags.context_flag == constants::FLAG_SET;
1255 let announce = match AnnounceData::unpack(&ctx.packet.data, has_ratchet) {
1256 Ok(a) => a,
1257 Err(_) => return,
1258 };
1259
1260 let received_from = self.announce_received_from(&ctx.packet, ctx.now);
1261
1262 if self
1263 .local_destinations
1264 .contains_key(&ctx.packet.destination_hash)
1265 {
1266 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
1267 "Announce:skipping local destination {:02x}{:02x}{:02x}{:02x}..",
1268 ctx.packet.destination_hash[0],
1269 ctx.packet.destination_hash[1],
1270 ctx.packet.destination_hash[2],
1271 ctx.packet.destination_hash[3],
1272 );
1273 return;
1274 }
1275
1276 let original_raw = ctx
1277 .original_raw
1278 .as_deref()
1279 .expect("announce packets retain original raw bytes");
1280 if self.should_hold_announce(&ctx.packet, original_raw, ctx.iface, ctx.now) {
1281 return;
1282 }
1283
1284 let sig_cache_key =
1285 Self::announce_sig_cache_key(ctx.packet.destination_hash, &announce.signature);
1286 if self.announce_sig_cache.contains(&sig_cache_key) {
1287 let validated = announce.to_validated_unchecked();
1288 let random_blob = match extract_random_blob(&ctx.packet.data) {
1289 Some(b) => b,
1290 None => return,
1291 };
1292 let announce_emitted = timebase_from_random_blob(&random_blob);
1293 self.process_verified_announce(
1294 VerifiedAnnounceCtx {
1295 packet: &ctx.packet,
1296 original_raw,
1297 iface: ctx.iface,
1298 now: ctx.now,
1299 validated,
1300 received_from,
1301 random_blob,
1302 announce_emitted,
1303 },
1304 rng,
1305 actions,
1306 );
1307 return;
1308 }
1309
1310 if ctx.packet.context == constants::CONTEXT_PATH_RESPONSE {
1311 let Ok(validated) = announce.validate(&ctx.packet.destination_hash) else {
1312 return;
1313 };
1314 self.announce_sig_cache.insert(sig_cache_key, ctx.now);
1315 let random_blob = match extract_random_blob(&ctx.packet.data) {
1316 Some(b) => b,
1317 None => return,
1318 };
1319 let announce_emitted = timebase_from_random_blob(&random_blob);
1320 self.process_verified_announce(
1321 VerifiedAnnounceCtx {
1322 packet: &ctx.packet,
1323 original_raw,
1324 iface: ctx.iface,
1325 now: ctx.now,
1326 validated,
1327 received_from,
1328 random_blob,
1329 announce_emitted,
1330 },
1331 rng,
1332 actions,
1333 );
1334 return;
1335 }
1336
1337 let random_blob = match extract_random_blob(&ctx.packet.data) {
1338 Some(b) => b,
1339 None => return,
1340 };
1341 let announce_emitted = timebase_from_random_blob(&random_blob);
1342 let key = AnnounceVerifyKey {
1343 destination_hash: ctx.packet.destination_hash,
1344 random_blob,
1345 received_from,
1346 };
1347 let pending = PendingAnnounce {
1348 original_raw: original_raw.to_vec(),
1349 packet: ctx.packet.clone(),
1350 interface: ctx.iface,
1351 received_from,
1352 queued_at: ctx.now,
1353 best_hops: ctx.packet.hops,
1354 emission_ts: announce_emitted,
1355 random_blob,
1356 };
1357 let _ = announce_queue.enqueue(key, pending);
1358 }
1359
1360 pub fn complete_verified_announce(
1361 &mut self,
1362 pending: PendingAnnounce,
1363 validated: crate::announce::ValidatedAnnounce,
1364 sig_cache_key: [u8; 32],
1365 now: f64,
1366 rng: &mut dyn Rng,
1367 ) -> Vec<TransportAction> {
1368 self.announce_sig_cache.insert(sig_cache_key, now);
1369 let mut actions = Vec::new();
1370 self.process_verified_announce(
1371 VerifiedAnnounceCtx {
1372 packet: &pending.packet,
1373 original_raw: &pending.original_raw,
1374 iface: pending.interface,
1375 now,
1376 validated,
1377 received_from: pending.received_from,
1378 random_blob: pending.random_blob,
1379 announce_emitted: pending.emission_ts,
1380 },
1381 rng,
1382 &mut actions,
1383 );
1384 actions
1385 }
1386
1387 pub fn clear_failed_verified_announce(&mut self, _sig_cache_key: [u8; 32], _now: f64) {}
1388
1389 fn process_verified_announce(
1390 &mut self,
1391 ctx: VerifiedAnnounceCtx<'_>,
1392 rng: &mut dyn Rng,
1393 actions: &mut Vec<TransportAction>,
1394 ) {
1395 if self.is_blackholed(&ctx.validated.identity_hash, ctx.now) {
1396 return;
1397 }
1398 if ctx.packet.hops > constants::PATHFINDER_M {
1399 return;
1400 }
1401
1402 let existing_set = self.path_table.get(&ctx.packet.destination_hash);
1403 let was_unknown_destination = existing_set.is_none_or(|ps| ps.is_empty());
1404
1405 if was_unknown_destination {
1408 self.path_states.remove(&ctx.packet.destination_hash);
1409 }
1410
1411 let is_unresponsive = self.path_is_unresponsive(&ctx.packet.destination_hash);
1413
1414 let current_gravity = existing_set
1415 .and_then(|path_set| path_set.primary())
1416 .and_then(|path| self.interfaces.get(&path.receiving_interface))
1417 .map(|interface| interface.gravity);
1418 let announce_gravity = self
1419 .interfaces
1420 .get(&ctx.iface)
1421 .map(|interface| interface.gravity);
1422 let higher_gravity_replacement = existing_set.is_some_and(|path_set| {
1423 pathfinder::is_higher_gravity_replacement(
1424 path_set,
1425 ctx.packet.hops,
1426 ctx.announce_emitted,
1427 current_gravity,
1428 announce_gravity,
1429 )
1430 });
1431 let mp_decision = pathfinder::decide_announce_multipath_with_gravity(
1432 existing_set,
1433 ctx.packet.hops,
1434 ctx.announce_emitted,
1435 &ctx.random_blob,
1436 &ctx.received_from,
1437 is_unresponsive,
1438 ctx.now,
1439 self.config.prefer_shorter_path,
1440 current_gravity,
1441 announce_gravity,
1442 );
1443
1444 if mp_decision == MultiPathDecision::Reject {
1445 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
1446 "Announce:path decision REJECT for dest={:02x}{:02x}{:02x}{:02x}..",
1447 ctx.packet.destination_hash[0],
1448 ctx.packet.destination_hash[1],
1449 ctx.packet.destination_hash[2],
1450 ctx.packet.destination_hash[3],
1451 );
1452 return;
1453 }
1454 if higher_gravity_replacement {
1455 log::log!(
1456 target: crate::logging::PATHING_LOG_TARGET,
1457 crate::logging::GRAVITY_UPDATE_LOG_LEVEL,
1458 "Replacing path table entry for {:02x}{:02x}{:02x}{:02x}.. due to higher gravity ({:?}->{:?})",
1459 ctx.packet.destination_hash[0],
1460 ctx.packet.destination_hash[1],
1461 ctx.packet.destination_hash[2],
1462 ctx.packet.destination_hash[3],
1463 current_gravity,
1464 announce_gravity,
1465 );
1466 }
1467
1468 let rate_blocked = if ctx.packet.context != constants::CONTEXT_PATH_RESPONSE {
1470 if let Some(iface_info) = self.interfaces.get(&ctx.iface) {
1471 self.rate_limiter.check_and_update(
1472 &ctx.packet.destination_hash,
1473 ctx.now,
1474 iface_info.announce_rate_target,
1475 iface_info.announce_rate_grace,
1476 iface_info.announce_rate_penalty,
1477 )
1478 } else {
1479 false
1480 }
1481 } else {
1482 false
1483 };
1484
1485 let interface_mode = self
1487 .interfaces
1488 .get(&ctx.iface)
1489 .map(|i| i.mode)
1490 .unwrap_or(constants::MODE_FULL);
1491
1492 let expires = compute_path_expires(ctx.now, interface_mode);
1493
1494 let existing_blobs = self
1496 .path_table
1497 .get(&ctx.packet.destination_hash)
1498 .and_then(|ps| ps.find_by_next_hop(&ctx.received_from))
1499 .map(|e| e.random_blobs.clone())
1500 .unwrap_or_default();
1501
1502 let mut rng_bytes = [0u8; 8];
1504 rng.fill_bytes(&mut rng_bytes);
1505 let rng_value = (u64::from_le_bytes(rng_bytes) as f64) / (u64::MAX as f64);
1506
1507 let is_path_response = ctx.packet.context == constants::CONTEXT_PATH_RESPONSE;
1508
1509 let (path_entry, announce_entry) = announce_proc::process_validated_announce(
1510 ctx.packet.destination_hash,
1511 ctx.packet.hops,
1512 &ctx.packet.data,
1513 &ctx.packet.raw,
1514 ctx.packet.packet_hash,
1515 ctx.packet.flags.context_flag,
1516 ctx.received_from,
1517 ctx.iface,
1518 ctx.now,
1519 existing_blobs,
1520 ctx.random_blob,
1521 expires,
1522 rng_value,
1523 self.config.transport_enabled,
1524 is_path_response,
1525 rate_blocked,
1526 Some(ctx.original_raw.to_vec()),
1527 );
1528
1529 actions.push(TransportAction::CacheAnnounce {
1531 packet_hash: ctx.packet.packet_hash,
1532 raw: ctx.original_raw.to_vec().into(),
1533 });
1534
1535 match mp_decision {
1537 MultiPathDecision::ReplacePrimary => self.upsert_primary_path_destination(
1538 ctx.packet.destination_hash,
1539 path_entry,
1540 ctx.now,
1541 ),
1542 MultiPathDecision::AddAlternative => {
1543 self.upsert_path_destination(ctx.packet.destination_hash, path_entry, ctx.now)
1544 }
1545 MultiPathDecision::Reject => unreachable!("rejected decisions returned above"),
1546 }
1547
1548 if let Some(tunnel_id) = self.interfaces.get(&ctx.iface).and_then(|i| i.tunnel_id) {
1550 let blobs = self
1551 .path_table
1552 .get(&ctx.packet.destination_hash)
1553 .and_then(|ps| ps.find_by_next_hop(&ctx.received_from))
1554 .map(|e| e.random_blobs.clone())
1555 .unwrap_or_default();
1556 self.tunnel_table.store_tunnel_path(
1557 &tunnel_id,
1558 ctx.packet.destination_hash,
1559 tunnel::TunnelPath {
1560 timestamp: ctx.now,
1561 received_from: ctx.received_from,
1562 hops: ctx.packet.hops,
1563 expires,
1564 random_blobs: blobs,
1565 packet_hash: ctx.packet.packet_hash,
1566 },
1567 ctx.now,
1568 self.config.destination_timeout_secs,
1569 self.config.max_tunnel_destinations_total,
1570 );
1571 }
1572
1573 self.path_states.remove(&ctx.packet.destination_hash);
1576
1577 if let Some(ann) = announce_entry {
1579 self.insert_announce_entry(ctx.packet.destination_hash, ann, ctx.now);
1580 }
1581
1582 actions.push(TransportAction::AnnounceReceived {
1584 destination_hash: ctx.packet.destination_hash,
1585 identity_hash: ctx.validated.identity_hash,
1586 public_key: ctx.validated.public_key,
1587 name_hash: ctx.validated.name_hash,
1588 random_hash: ctx.validated.random_hash,
1589 ratchet: ctx.validated.ratchet,
1590 app_data: ctx.validated.app_data,
1591 hops: ctx.packet.hops,
1592 receiving_interface: ctx.iface,
1593 rx: RxMetadata {
1594 rssi: ctx.packet.rssi,
1595 snr: ctx.packet.snr,
1596 },
1597 });
1598
1599 actions.push(TransportAction::PathUpdated {
1600 destination_hash: ctx.packet.destination_hash,
1601 hops: ctx.packet.hops,
1602 next_hop: ctx.received_from,
1603 interface: ctx.iface,
1604 });
1605
1606 if self.has_local_clients() {
1608 actions.push(TransportAction::ForwardToLocalClients {
1609 raw: self.announce_raw_for_local_clients(ctx.packet),
1610 exclude: Some(ctx.iface),
1611 });
1612 }
1613
1614 if let Some(pr_entry) = self.discovery_path_requests_waiting(&ctx.packet.destination_hash) {
1616 let entry = AnnounceEntry {
1618 timestamp: ctx.now,
1619 retransmit_timeout: ctx.now,
1620 retries: constants::PATHFINDER_R,
1621 received_from: ctx.received_from,
1622 hops: ctx.packet.hops,
1623 packet_raw: ctx.packet.raw.clone(),
1624 packet_data: ctx.packet.data.clone(),
1625 destination_hash: ctx.packet.destination_hash,
1626 context_flag: ctx.packet.flags.context_flag,
1627 local_rebroadcasts: 0,
1628 block_rebroadcasts: true,
1629 attached_interface: Some(pr_entry),
1630 };
1631 self.insert_announce_entry(ctx.packet.destination_hash, entry, ctx.now);
1632 }
1633 }
1634
1635 pub fn announce_sig_cache_contains(&self, sig_cache_key: &[u8; 32]) -> bool {
1636 self.announce_sig_cache.contains(sig_cache_key)
1637 }
1638
1639 fn discovery_path_requests_waiting(&mut self, dest_hash: &[u8; 16]) -> Option<InterfaceId> {
1642 self.discovery_path_requests
1643 .remove(dest_hash)
1644 .map(|req| req.requesting_interface)
1645 }
1646
1647 fn process_inbound_proof(
1652 &mut self,
1653 ctx: &InboundPacketCtx,
1654 actions: &mut Vec<TransportAction>,
1655 ) {
1656 let packet = &ctx.packet;
1657 if packet.context == constants::CONTEXT_LRPROOF {
1658 if (self.config.transport_enabled)
1660 && self.link_table.contains_key(&packet.destination_hash)
1661 {
1662 let link_entry = self.link_table.get(&packet.destination_hash).cloned();
1663 if let Some(entry) = link_entry {
1664 let instance_local_link = self
1665 .interface_is_local_client(entry.next_hop_interface)
1666 && self.interface_is_local_client(entry.received_interface);
1667 if let Some((outbound_interface, new_raw)) = route_via_link_table(
1668 packet,
1669 &entry,
1670 ctx.iface,
1671 LocalHopRewrite {
1672 local_hops_delta: self.config.local_hops_delta,
1673 from_local_client: ctx.from_local_client,
1674 skip_local_hops_delta: instance_local_link,
1675 },
1676 ) {
1677 if let Some(le) = self.link_table.get_mut(&packet.destination_hash) {
1682 le.validated = true;
1683 }
1684
1685 actions.push(TransportAction::LinkEstablished {
1686 link_id: packet.destination_hash,
1687 interface: outbound_interface,
1688 });
1689
1690 actions.push(TransportAction::SendOnInterface {
1691 interface: outbound_interface,
1692 raw: new_raw.into(),
1693 });
1694 } else if link_route_hops_match(packet.hops, &entry, ctx.iface) {
1695 log::debug!(
1696 "Link request proof received on wrong interface {}, not transporting it (expected {} or {})",
1697 ctx.iface.0,
1698 entry.next_hop_interface.0,
1699 entry.received_interface.0,
1700 );
1701 } else {
1702 log::debug!("{}", lrproof_hop_mismatch_diagnostic(packet.hops, &entry));
1703 }
1704 }
1705 } else {
1706 let mut delivery_raw = packet.raw.clone();
1708 if delivery_raw.len() >= 2 {
1711 delivery_raw[1] = packet.hops;
1712 }
1713 actions.push(TransportAction::DeliverLocal {
1714 destination_hash: packet.destination_hash,
1715 raw: PacketBytes::from(delivery_raw),
1716 packet_hash: packet.packet_hash,
1717 receiving_interface: ctx.iface,
1718 });
1719 }
1720 } else {
1721 if self.config.transport_enabled {
1723 if let Some(reverse_entry) = self.reverse_table.remove(&packet.destination_hash) {
1724 let proof_for_local_client =
1725 self.interface_is_local_client(reverse_entry.receiving_interface);
1726 if let Some(action) = route_proof_via_reverse(
1727 packet,
1728 &reverse_entry,
1729 ctx.iface,
1730 LocalHopRewrite {
1731 local_hops_delta: self.config.local_hops_delta,
1732 from_local_client: ctx.from_local_client,
1733 skip_local_hops_delta: proof_for_local_client,
1734 },
1735 ) {
1736 actions.push(action);
1737 }
1738 }
1739 }
1740
1741 actions.push(TransportAction::DeliverLocal {
1743 destination_hash: packet.destination_hash,
1744 raw: PacketBytes::from(packet.raw.clone()),
1745 packet_hash: packet.packet_hash,
1746 receiving_interface: ctx.iface,
1747 });
1748 }
1749 }
1750
1751 pub fn handle_outbound(
1757 &mut self,
1758 packet: &RawPacket,
1759 dest_type: u8,
1760 attached_interface: Option<InterfaceId>,
1761 now: f64,
1762 ) -> Vec<TransportAction> {
1763 let actions = route_outbound_with_options(
1764 &self.path_table,
1765 &self.interfaces,
1766 &self.local_destinations,
1767 packet,
1768 dest_type,
1769 attached_interface,
1770 now,
1771 OutboundRouteOptions {
1772 identity_hash: self.config.identity_hash,
1773 local_hops_delta: self.config.local_hops_delta,
1774 },
1775 );
1776
1777 self.packet_hashlist.add(packet.packet_hash);
1779
1780 if packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE && packet.hops > 0 {
1782 self.gate_announce_actions(actions, &packet.destination_hash, packet.hops, now)
1783 } else {
1784 actions
1785 }
1786 }
1787
1788 fn gate_announce_actions(
1790 &mut self,
1791 actions: Vec<TransportAction>,
1792 dest_hash: &[u8; 16],
1793 hops: u8,
1794 now: f64,
1795 ) -> Vec<TransportAction> {
1796 let mut result = Vec::new();
1797 for action in actions {
1798 match action {
1799 TransportAction::SendOnInterface { interface, raw } => {
1800 let (bitrate, airtime_profile, announce_cap) =
1801 if let Some(info) = self.interfaces.get(&interface) {
1802 (info.bitrate, info.airtime_profile, info.announce_cap)
1803 } else {
1804 (None, None, constants::ANNOUNCE_CAP)
1805 };
1806 if let Some(send_action) = self.announce_queues.gate_announce(
1807 interface,
1808 raw,
1809 *dest_hash,
1810 hops,
1811 now,
1812 now,
1813 bitrate,
1814 airtime_profile,
1815 announce_cap,
1816 ) {
1817 result.push(send_action);
1818 }
1819 }
1821 other => result.push(other),
1822 }
1823 }
1824 result
1825 }
1826
1827 pub fn tick(&mut self, now: f64, rng: &mut dyn Rng) -> Vec<TransportAction> {
1833 let mut ctx = TickCtx {
1834 now,
1835 rng,
1836 actions: Vec::new(),
1837 };
1838 self.process_tick_pending_announces(&mut ctx);
1839
1840 let mut queue_actions = self.announce_queues.process_queues(now, &self.interfaces);
1841 ctx.actions.append(&mut queue_actions);
1842
1843 self.process_tick_ingress_release(&mut ctx);
1844 self.cull_tick_tables(&mut ctx);
1845 ctx.actions
1846 }
1847
1848 fn process_tick_pending_announces(&mut self, ctx: &mut TickCtx<'_>) {
1849 if ctx.now <= self.announces_last_checked + constants::ANNOUNCES_CHECK_INTERVAL {
1850 return;
1851 }
1852
1853 self.cull_expired_announce_entries(ctx.now);
1854 self.enforce_announce_retention_cap(ctx.now);
1855 if let Some(identity_hash) = self.config.identity_hash {
1856 let announce_actions = jobs::process_pending_announces(
1857 &mut self.announce_table,
1858 &mut self.held_announces,
1859 &identity_hash,
1860 ctx.now,
1861 );
1862 let gated = self.gate_retransmit_actions(announce_actions, ctx.now);
1863 ctx.actions.extend(gated);
1864 }
1865 self.cull_expired_announce_entries(ctx.now);
1866 self.enforce_announce_retention_cap(ctx.now);
1867 self.announces_last_checked = ctx.now;
1868 }
1869
1870 fn process_tick_ingress_release(&mut self, ctx: &mut TickCtx<'_>) {
1871 let ic_interfaces = self.ingress_control.interfaces_with_held();
1872 for iface_id in ic_interfaces {
1873 let (ia_freq, started, ingress_config) = match self.interfaces.get(&iface_id) {
1874 Some(info) => (info.ia_freq, info.started, info.ingress_control),
1875 None => continue,
1876 };
1877 if !ingress_config.enabled {
1878 continue;
1879 }
1880 if let Some(held) = self.ingress_control.process_held_announces(
1881 iface_id,
1882 &ingress_config,
1883 ia_freq,
1884 started,
1885 ctx.now,
1886 ) {
1887 let released_actions = self.handle_inbound(
1888 InboundFrame {
1889 raw: &held.raw,
1890 iface: held.receiving_interface,
1891 now: ctx.now,
1892 rx: held.rx,
1893 },
1894 ctx.rng,
1895 );
1896 ctx.actions.extend(released_actions);
1897 }
1898 }
1899 }
1900
1901 fn cull_tick_tables(&mut self, ctx: &mut TickCtx<'_>) {
1902 if ctx.now <= self.tables_last_culled + constants::TABLES_CULL_INTERVAL {
1903 return;
1904 }
1905
1906 jobs::cull_path_table(&mut self.path_table, &self.interfaces, ctx.now);
1907 jobs::cull_reverse_table(&mut self.reverse_table, &self.interfaces, ctx.now);
1908 let (_culled, link_closed_actions) =
1909 jobs::cull_link_table(&mut self.link_table, &self.interfaces, ctx.now);
1910 ctx.actions.extend(link_closed_actions);
1911 jobs::cull_path_states(&mut self.path_states, &self.path_table);
1912 self.cull_blackholed(ctx.now);
1913 self.discovery_path_requests
1914 .retain(|_, req| ctx.now - req.timestamp < constants::DISCOVERY_PATH_REQUEST_TIMEOUT);
1915 self.tunnel_table
1916 .void_missing_interfaces(|id| self.interfaces.contains_key(id));
1917 self.tunnel_table.cull(ctx.now);
1918 self.announce_sig_cache.cull(ctx.now);
1919 self.tables_last_culled = ctx.now;
1920 }
1921
1922 fn gate_retransmit_actions(
1927 &mut self,
1928 actions: Vec<TransportAction>,
1929 now: f64,
1930 ) -> Vec<TransportAction> {
1931 let mut result = Vec::new();
1932 for action in actions {
1933 match action {
1934 TransportAction::SendOnInterface { interface, raw } => {
1935 let (dest_hash, hops) = Self::extract_announce_info(&raw);
1937 let (bitrate, airtime_profile, announce_cap) =
1938 if let Some(info) = self.interfaces.get(&interface) {
1939 (info.bitrate, info.airtime_profile, info.announce_cap)
1940 } else {
1941 (None, None, constants::ANNOUNCE_CAP)
1942 };
1943 if let Some(send_action) = self.announce_queues.gate_announce(
1944 interface,
1945 raw,
1946 dest_hash,
1947 hops,
1948 now,
1949 now,
1950 bitrate,
1951 airtime_profile,
1952 announce_cap,
1953 ) {
1954 result.push(send_action);
1955 }
1956 }
1957 TransportAction::BroadcastOnAllInterfaces { raw, exclude } => {
1958 let (dest_hash, hops) = Self::extract_announce_info(&raw);
1959 let iface_ids: Vec<(
1962 InterfaceId,
1963 Option<u64>,
1964 Option<types::AirtimeProfile>,
1965 f64,
1966 )> = self
1967 .interfaces
1968 .iter()
1969 .filter(|(_, info)| info.out_capable)
1970 .filter(|(id, _)| {
1971 if let Some(ref ex) = exclude {
1972 **id != *ex
1973 } else {
1974 true
1975 }
1976 })
1977 .filter(|(_, info)| {
1978 should_transmit_announce(
1979 info,
1980 &dest_hash,
1981 hops,
1982 &self.local_destinations,
1983 &self.path_table,
1984 &self.interfaces,
1985 )
1986 })
1987 .map(|(id, info)| {
1988 (*id, info.bitrate, info.airtime_profile, info.announce_cap)
1989 })
1990 .collect();
1991
1992 for (iface_id, bitrate, airtime_profile, announce_cap) in iface_ids {
1993 if let Some(send_action) = self.announce_queues.gate_announce(
1994 iface_id,
1995 raw.clone(),
1996 dest_hash,
1997 hops,
1998 now,
1999 now,
2000 bitrate,
2001 airtime_profile,
2002 announce_cap,
2003 ) {
2004 result.push(send_action);
2005 }
2006 }
2007 }
2008 other => result.push(other),
2009 }
2010 }
2011 result
2012 }
2013
2014 fn extract_announce_info(raw: &[u8]) -> ([u8; 16], u8) {
2016 if raw.len() < 18 {
2017 return ([0; 16], 0);
2018 }
2019 let header_type = (raw[0] >> 6) & 0x03;
2020 let hops = raw[1];
2021 if header_type == constants::HEADER_2 && raw.len() >= 34 {
2022 let mut dest = [0u8; 16];
2024 dest.copy_from_slice(&raw[18..34]);
2025 (dest, hops)
2026 } else {
2027 let mut dest = [0u8; 16];
2029 dest.copy_from_slice(&raw[2..18]);
2030 (dest, hops)
2031 }
2032 }
2033
2034 #[cfg(test)]
2035 #[allow(dead_code)]
2036 pub(crate) fn link_table_ref(&self) -> &BTreeMap<[u8; 16], LinkEntry> {
2037 &self.link_table
2038 }
2039}
2040
2041#[cfg(test)]
2042mod tests {
2043 use super::*;
2044 use crate::packet::PacketFlags;
2045
2046 fn make_config(transport_enabled: bool) -> TransportConfig {
2047 TransportConfig {
2048 transport_enabled,
2049 identity_hash: if transport_enabled {
2050 Some([0x42; 16])
2051 } else {
2052 None
2053 },
2054 local_hops_delta: 0,
2055 prefer_shorter_path: false,
2056 max_paths_per_destination: 1,
2057 packet_hashlist_max_entries: constants::HASHLIST_MAXSIZE,
2058 max_discovery_pr_tags: constants::MAX_PR_TAGS,
2059 max_path_destinations: usize::MAX,
2060 max_tunnel_destinations_total: usize::MAX,
2061 destination_timeout_secs: constants::DESTINATION_TIMEOUT,
2062 announce_table_ttl_secs: constants::ANNOUNCE_TABLE_TTL,
2063 announce_table_max_bytes: constants::ANNOUNCE_TABLE_MAX_BYTES,
2064 announce_sig_cache_enabled: true,
2065 announce_sig_cache_max_entries: constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
2066 announce_sig_cache_ttl_secs: constants::ANNOUNCE_SIG_CACHE_TTL,
2067 announce_queue_max_entries: 256,
2068 announce_queue_max_interfaces: 1024,
2069 }
2070 }
2071
2072 fn make_interface(id: u64, mode: u8) -> InterfaceInfo {
2073 InterfaceInfo {
2074 id: InterfaceId(id),
2075 name: String::from("test"),
2076 mode,
2077 gravity: 0,
2078 recursive_prs: false,
2079 announces_from_internal: true,
2080 announces_to_internal: None,
2081 out_capable: true,
2082 in_capable: true,
2083 bitrate: None,
2084 airtime_profile: None,
2085 announce_rate_target: None,
2086 announce_rate_grace: 0,
2087 announce_rate_penalty: 0.0,
2088 announce_cap: constants::ANNOUNCE_CAP,
2089 is_local_client: false,
2090 wants_tunnel: false,
2091 tunnel_id: None,
2092 mtu: constants::MTU as u32,
2093 ingress_control: crate::transport::types::IngressControlConfig::disabled(),
2094 ia_freq: 0.0,
2095 ip_freq: 0.0,
2096 op_freq: 0.0,
2097 op_samples: 0,
2098 started: 0.0,
2099 }
2100 }
2101
2102 fn make_announce_entry(dest_hash: [u8; 16], timestamp: f64, fill_len: usize) -> AnnounceEntry {
2103 AnnounceEntry {
2104 timestamp,
2105 retransmit_timeout: timestamp,
2106 retries: 0,
2107 received_from: [0xAA; 16],
2108 hops: 2,
2109 packet_raw: vec![0x01; fill_len],
2110 packet_data: vec![0x02; fill_len],
2111 destination_hash: dest_hash,
2112 context_flag: 0,
2113 local_rebroadcasts: 0,
2114 block_rebroadcasts: false,
2115 attached_interface: None,
2116 }
2117 }
2118
2119 fn make_path_entry(
2120 timestamp: f64,
2121 hops: u8,
2122 receiving_interface: InterfaceId,
2123 next_hop: [u8; 16],
2124 ) -> PathEntry {
2125 PathEntry {
2126 timestamp,
2127 next_hop,
2128 hops,
2129 expires: timestamp + 10_000.0,
2130 random_blobs: Vec::new(),
2131 receiving_interface,
2132 packet_hash: [0; 32],
2133 announce_raw: None,
2134 }
2135 }
2136
2137 fn make_unique_tag(dest_hash: [u8; 16], tag: &[u8]) -> [u8; 32] {
2138 let mut unique_tag = [0u8; 32];
2139 let tag_len = tag.len().min(16);
2140 unique_tag[..16].copy_from_slice(&dest_hash);
2141 unique_tag[16..16 + tag_len].copy_from_slice(&tag[..tag_len]);
2142 unique_tag
2143 }
2144
2145 fn make_random_blob(timebase: u64) -> [u8; 10] {
2146 let mut blob = [0u8; 10];
2147 let bytes = timebase.to_be_bytes();
2148 blob[5..10].copy_from_slice(&bytes[3..8]);
2149 blob
2150 }
2151
2152 #[test]
2153 fn test_empty_engine() {
2154 let engine = TransportEngine::new(make_config(false));
2155 assert!(!engine.has_path(&[0; 16]));
2156 assert!(engine.hops_to(&[0; 16]).is_none());
2157 assert!(engine.next_hop(&[0; 16]).is_none());
2158 }
2159
2160 #[test]
2161 fn test_register_deregister_interface() {
2162 let mut engine = TransportEngine::new(make_config(false));
2163 engine.register_interface(make_interface(1, constants::MODE_FULL));
2164 assert!(engine.interfaces.contains_key(&InterfaceId(1)));
2165
2166 engine.deregister_interface(InterfaceId(1));
2167 assert!(!engine.interfaces.contains_key(&InterfaceId(1)));
2168 }
2169
2170 #[test]
2171 fn test_deregister_interface_removes_announce_queue_state() {
2172 let mut engine = TransportEngine::new(make_config(false));
2173 engine.register_interface(make_interface(1, constants::MODE_FULL));
2174
2175 let _ = engine.announce_queues.gate_announce(
2176 InterfaceId(1),
2177 vec![0x01; 100].into(),
2178 [0xAA; 16],
2179 2,
2180 0.0,
2181 0.0,
2182 Some(1000),
2183 None,
2184 constants::ANNOUNCE_CAP,
2185 );
2186 let _ = engine.announce_queues.gate_announce(
2187 InterfaceId(1),
2188 vec![0x02; 100].into(),
2189 [0xBB; 16],
2190 3,
2191 0.0,
2192 0.0,
2193 Some(1000),
2194 None,
2195 constants::ANNOUNCE_CAP,
2196 );
2197 assert_eq!(engine.announce_queue_count(), 1);
2198
2199 engine.deregister_interface(InterfaceId(1));
2200 assert_eq!(engine.announce_queue_count(), 0);
2201 }
2202
2203 #[test]
2204 fn test_deregister_interface_removes_transport_state() {
2205 let mut engine = TransportEngine::new(make_config(true));
2206 engine.register_interface(make_interface(1, constants::MODE_FULL));
2207 engine.register_interface(make_interface(2, constants::MODE_FULL));
2208
2209 let destination_hash = [0x11; 16];
2210 engine.inject_path(
2211 destination_hash,
2212 PathEntry {
2213 timestamp: 1000.0,
2214 next_hop: [0x22; 16],
2215 hops: 2,
2216 expires: 2000.0,
2217 random_blobs: Vec::new(),
2218 receiving_interface: InterfaceId(1),
2219 packet_hash: [0x33; 32],
2220 announce_raw: None,
2221 },
2222 );
2223 engine.reverse_table.insert(
2224 [0x44; 16],
2225 tables::ReverseEntry {
2226 receiving_interface: InterfaceId(2),
2227 outbound_interface: InterfaceId(1),
2228 timestamp: 1000.0,
2229 },
2230 );
2231 engine.register_link(
2232 [0x55; 16],
2233 LinkEntry {
2234 timestamp: 1000.0,
2235 next_hop_transport_id: [0x66; 16],
2236 next_hop_interface: InterfaceId(1),
2237 remaining_hops: 1,
2238 received_interface: InterfaceId(2),
2239 taken_hops: 1,
2240 destination_hash,
2241 validated: true,
2242 proof_timeout: 1100.0,
2243 },
2244 );
2245
2246 assert_eq!(engine.path_table_count(), 1);
2247 assert_eq!(engine.reverse_table_count(), 1);
2248 assert_eq!(engine.link_table_count(), 1);
2249
2250 engine.deregister_interface(InterfaceId(1));
2251
2252 assert_eq!(engine.path_table_count(), 0);
2253 assert_eq!(engine.reverse_table_count(), 0);
2254 assert_eq!(engine.link_table_count(), 0);
2255 }
2256
2257 #[test]
2258 fn test_deregister_interface_preserves_other_announce_queues() {
2259 let mut engine = TransportEngine::new(make_config(false));
2260 engine.register_interface(make_interface(1, constants::MODE_FULL));
2261 engine.register_interface(make_interface(2, constants::MODE_FULL));
2262
2263 let _ = engine.announce_queues.gate_announce(
2264 InterfaceId(1),
2265 vec![0x01; 100].into(),
2266 [0xAA; 16],
2267 2,
2268 0.0,
2269 0.0,
2270 Some(1000),
2271 None,
2272 constants::ANNOUNCE_CAP,
2273 );
2274 let _ = engine.announce_queues.gate_announce(
2275 InterfaceId(1),
2276 vec![0x02; 100].into(),
2277 [0xAB; 16],
2278 3,
2279 0.0,
2280 0.0,
2281 Some(1000),
2282 None,
2283 constants::ANNOUNCE_CAP,
2284 );
2285 let _ = engine.announce_queues.gate_announce(
2286 InterfaceId(2),
2287 vec![0x03; 100].into(),
2288 [0xBA; 16],
2289 2,
2290 0.0,
2291 0.0,
2292 Some(1000),
2293 None,
2294 constants::ANNOUNCE_CAP,
2295 );
2296 let _ = engine.announce_queues.gate_announce(
2297 InterfaceId(2),
2298 vec![0x04; 100].into(),
2299 [0xBB; 16],
2300 3,
2301 0.0,
2302 0.0,
2303 Some(1000),
2304 None,
2305 constants::ANNOUNCE_CAP,
2306 );
2307
2308 engine.deregister_interface(InterfaceId(1));
2309 assert_eq!(engine.announce_queue_count(), 1);
2310 assert_eq!(engine.nonempty_announce_queue_count(), 1);
2311 }
2312
2313 #[test]
2314 fn test_register_deregister_destination() {
2315 let mut engine = TransportEngine::new(make_config(false));
2316 let dest = [0x11; 16];
2317 engine.register_destination(dest, constants::DESTINATION_SINGLE);
2318 assert!(engine.local_destinations.contains_key(&dest));
2319
2320 engine.deregister_destination(&dest);
2321 assert!(!engine.local_destinations.contains_key(&dest));
2322 }
2323
2324 #[test]
2325 fn test_path_state() {
2326 let mut engine = TransportEngine::new(make_config(false));
2327 let dest = [0x22; 16];
2328
2329 assert!(!engine.path_is_unresponsive(&dest));
2330
2331 engine.mark_path_unresponsive(&dest, None);
2332 assert!(engine.path_is_unresponsive(&dest));
2333
2334 engine.mark_path_responsive(&dest);
2335 assert!(!engine.path_is_unresponsive(&dest));
2336 }
2337
2338 #[test]
2339 fn test_announce_clears_stale_path_state_for_unknown_destination() {
2340 use crate::announce::AnnounceData;
2341 use crate::destination::{destination_hash, name_hash};
2342
2343 let mut engine = TransportEngine::new(make_config(false));
2344 engine.register_interface(make_interface(1, constants::MODE_FULL));
2345
2346 let identity =
2347 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x61; 32]));
2348 let dest_hash = destination_hash("pathfix", &["announce"], Some(identity.hash()));
2349 let name_h = name_hash("pathfix", &["announce"]);
2350 let random_hash = [0x24u8; 10];
2351
2352 let (announce_data, _) =
2353 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
2354
2355 let packet = RawPacket::pack(
2356 PacketFlags {
2357 header_type: constants::HEADER_1,
2358 context_flag: constants::FLAG_UNSET,
2359 transport_type: constants::TRANSPORT_BROADCAST,
2360 destination_type: constants::DESTINATION_SINGLE,
2361 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2362 },
2363 0,
2364 &dest_hash,
2365 None,
2366 constants::CONTEXT_NONE,
2367 &announce_data,
2368 )
2369 .unwrap();
2370
2371 engine.mark_path_unresponsive(&dest_hash, None);
2372 assert!(engine.path_is_unresponsive(&dest_hash));
2373 assert!(!engine.has_path(&dest_hash));
2374
2375 let mut rng = rns_crypto::FixedRng::new(&[0x62; 32]);
2376 let actions = engine.handle_inbound(
2377 InboundFrame {
2378 raw: &packet.raw,
2379 iface: InterfaceId(1),
2380 now: 1000.0,
2381 rx: RxMetadata {
2382 rssi: None,
2383 snr: None,
2384 },
2385 },
2386 &mut rng,
2387 );
2388
2389 assert!(engine.has_path(&dest_hash));
2390 assert!(
2391 !engine.path_is_unresponsive(&dest_hash),
2392 "stale path state should be cleared for newly installed paths"
2393 );
2394 assert!(actions.iter().any(|action| matches!(
2395 action,
2396 TransportAction::PathUpdated {
2397 destination_hash,
2398 interface,
2399 ..
2400 } if *destination_hash == dest_hash && *interface == InterfaceId(1)
2401 )));
2402 }
2403
2404 #[test]
2405 fn test_duplicate_announce_from_second_interface_uses_existing_path() {
2406 use crate::announce::AnnounceData;
2407 use crate::destination::{destination_hash, name_hash};
2408
2409 let mut engine = TransportEngine::new(make_config(false));
2410 engine.register_interface(make_interface(1, constants::MODE_FULL));
2411 engine.register_interface(make_interface(2, constants::MODE_FULL));
2412
2413 let identity =
2414 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x63; 32]));
2415 let dest_hash = destination_hash("dedup", &["announce"], Some(identity.hash()));
2416 let name_h = name_hash("dedup", &["announce"]);
2417 let random_hash = [0x25u8; 10];
2418
2419 let (announce_data, _) =
2420 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
2421 let packet = RawPacket::pack(
2422 PacketFlags {
2423 header_type: constants::HEADER_1,
2424 context_flag: constants::FLAG_UNSET,
2425 transport_type: constants::TRANSPORT_BROADCAST,
2426 destination_type: constants::DESTINATION_SINGLE,
2427 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2428 },
2429 0,
2430 &dest_hash,
2431 None,
2432 constants::CONTEXT_NONE,
2433 &announce_data,
2434 )
2435 .unwrap();
2436
2437 let mut rng = rns_crypto::FixedRng::new(&[0x64; 32]);
2438 let first_actions = engine.handle_inbound(
2439 InboundFrame {
2440 raw: &packet.raw,
2441 iface: InterfaceId(1),
2442 now: 1000.0,
2443 rx: RxMetadata::default(),
2444 },
2445 &mut rng,
2446 );
2447 assert!(first_actions.iter().any(|action| matches!(
2448 action,
2449 TransportAction::PathUpdated {
2450 destination_hash,
2451 interface,
2452 ..
2453 } if *destination_hash == dest_hash && *interface == InterfaceId(1)
2454 )));
2455
2456 let second_actions = engine.handle_inbound(
2457 InboundFrame {
2458 raw: &packet.raw,
2459 iface: InterfaceId(2),
2460 now: 1000.1,
2461 rx: RxMetadata::default(),
2462 },
2463 &mut rng,
2464 );
2465
2466 assert!(!second_actions.iter().any(|action| matches!(
2467 action,
2468 TransportAction::PathUpdated {
2469 destination_hash,
2470 interface,
2471 ..
2472 } if *destination_hash == dest_hash && *interface == InterfaceId(2)
2473 )));
2474 let path = engine
2475 .path_table
2476 .get(&dest_hash)
2477 .and_then(|set| set.primary())
2478 .expect("first announce should install a path");
2479 assert_eq!(path.receiving_interface, InterfaceId(1));
2480
2481 let mut higher_gravity = make_interface(2, constants::MODE_FULL);
2482 higher_gravity.gravity = 1;
2483 engine.register_interface(higher_gravity);
2484 let third_actions = engine.handle_inbound(
2485 InboundFrame {
2486 raw: &packet.raw,
2487 iface: InterfaceId(2),
2488 now: 1000.2,
2489 rx: RxMetadata::default(),
2490 },
2491 &mut rng,
2492 );
2493
2494 assert!(third_actions.iter().any(|action| matches!(
2495 action,
2496 TransportAction::PathUpdated {
2497 destination_hash,
2498 interface,
2499 ..
2500 } if *destination_hash == dest_hash && *interface == InterfaceId(2)
2501 )));
2502 let path = engine
2503 .path_table
2504 .get(&dest_hash)
2505 .and_then(|set| set.primary())
2506 .expect("higher-gravity reception should become primary");
2507 assert_eq!(path.receiving_interface, InterfaceId(2));
2508 }
2509
2510 #[test]
2511 fn test_boundary_exempts_unresponsive() {
2512 let mut engine = TransportEngine::new(make_config(false));
2513 engine.register_interface(make_interface(1, constants::MODE_BOUNDARY));
2514 let dest = [0xB1; 16];
2515
2516 engine.mark_path_unresponsive(&dest, Some(InterfaceId(1)));
2518 assert!(!engine.path_is_unresponsive(&dest));
2519 }
2520
2521 #[test]
2522 fn test_non_boundary_marks_unresponsive() {
2523 let mut engine = TransportEngine::new(make_config(false));
2524 engine.register_interface(make_interface(1, constants::MODE_FULL));
2525 let dest = [0xB2; 16];
2526
2527 engine.mark_path_unresponsive(&dest, Some(InterfaceId(1)));
2529 assert!(engine.path_is_unresponsive(&dest));
2530 }
2531
2532 #[test]
2533 fn test_expire_path() {
2534 let mut engine = TransportEngine::new(make_config(false));
2535 let dest = [0x33; 16];
2536
2537 engine.path_table.insert(
2538 dest,
2539 PathSet::from_single(
2540 PathEntry {
2541 timestamp: 1000.0,
2542 next_hop: [0; 16],
2543 hops: 2,
2544 expires: 9999.0,
2545 random_blobs: Vec::new(),
2546 receiving_interface: InterfaceId(1),
2547 packet_hash: [0; 32],
2548 announce_raw: None,
2549 },
2550 1,
2551 ),
2552 );
2553
2554 assert!(engine.has_path(&dest));
2555 engine.expire_path(&dest);
2556 assert!(engine.has_path(&dest));
2558 assert_eq!(engine.path_table[&dest].primary().unwrap().expires, 0.0);
2559 }
2560
2561 #[test]
2562 fn test_link_table_operations() {
2563 let mut engine = TransportEngine::new(make_config(false));
2564 let link_id = [0x44; 16];
2565
2566 engine.register_link(
2567 link_id,
2568 LinkEntry {
2569 timestamp: 100.0,
2570 next_hop_transport_id: [0; 16],
2571 next_hop_interface: InterfaceId(1),
2572 remaining_hops: 3,
2573 received_interface: InterfaceId(2),
2574 taken_hops: 2,
2575 destination_hash: [0xAA; 16],
2576 validated: false,
2577 proof_timeout: 200.0,
2578 },
2579 );
2580
2581 assert!(engine.link_table.contains_key(&link_id));
2582 assert!(!engine.link_table[&link_id].validated);
2583
2584 engine.validate_link(&link_id);
2585 assert!(engine.link_table[&link_id].validated);
2586
2587 engine.remove_link(&link_id);
2588 assert!(!engine.link_table.contains_key(&link_id));
2589 }
2590
2591 #[test]
2592 fn test_lrproof_routes_from_originating_side_via_link_table() {
2593 let mut engine = TransportEngine::new(make_config(true));
2594 engine.register_interface(make_interface(1, constants::MODE_FULL));
2595 engine.register_interface(make_interface(2, constants::MODE_FULL));
2596
2597 let link_id = [0x44; 16];
2598 engine.register_link(
2599 link_id,
2600 LinkEntry {
2601 timestamp: 100.0,
2602 next_hop_transport_id: [0xAA; 16],
2603 next_hop_interface: InterfaceId(2),
2604 remaining_hops: 3,
2605 received_interface: InterfaceId(1),
2606 taken_hops: 1,
2607 destination_hash: [0xBB; 16],
2608 validated: false,
2609 proof_timeout: 200.0,
2610 },
2611 );
2612
2613 let flags = PacketFlags {
2614 header_type: constants::HEADER_1,
2615 context_flag: constants::FLAG_UNSET,
2616 transport_type: constants::TRANSPORT_BROADCAST,
2617 destination_type: constants::DESTINATION_LINK,
2618 packet_type: constants::PACKET_TYPE_PROOF,
2619 };
2620 let packet = RawPacket::pack(
2621 flags,
2622 0,
2623 &link_id,
2624 None,
2625 constants::CONTEXT_LRPROOF,
2626 &[0xCC; 64],
2627 )
2628 .unwrap();
2629 let mut rng = rns_crypto::FixedRng::new(&[0x33; 32]);
2630
2631 let actions = engine.handle_inbound(
2632 InboundFrame {
2633 raw: &packet.raw,
2634 iface: InterfaceId(1),
2635 now: 101.0,
2636 rx: RxMetadata {
2637 rssi: None,
2638 snr: None,
2639 },
2640 },
2641 &mut rng,
2642 );
2643
2644 assert!(matches!(
2645 engine
2646 .link_table_ref()
2647 .get(&link_id)
2648 .map(|entry| entry.validated),
2649 Some(true)
2650 ));
2651 assert!(actions.iter().any(|action| matches!(
2652 action,
2653 TransportAction::LinkEstablished {
2654 link_id: established,
2655 interface: InterfaceId(2),
2656 } if *established == link_id
2657 )));
2658 assert!(actions.iter().any(|action| matches!(
2659 action,
2660 TransportAction::SendOnInterface {
2661 interface: InterfaceId(2),
2662 ..
2663 }
2664 )));
2665 }
2666
2667 fn lrproof_rebalance_fixture() -> (TransportEngine, [u8; 16], [u8; 16], [u8; 32], Vec<u8>) {
2668 use rns_crypto::ed25519::Ed25519PrivateKey;
2669
2670 let mut engine = TransportEngine::new(make_config(true));
2671 engine.register_interface(make_interface(1, constants::MODE_FULL));
2672 engine.register_interface(make_interface(2, constants::MODE_FULL));
2673 let link_id = [0x44; 16];
2674 let destination_hash = [0xBB; 16];
2675 engine.register_link(
2676 link_id,
2677 LinkEntry {
2678 timestamp: 100.0,
2679 next_hop_transport_id: [0xAA; 16],
2680 next_hop_interface: InterfaceId(2),
2681 remaining_hops: 3,
2682 received_interface: InterfaceId(1),
2683 taken_hops: 1,
2684 destination_hash,
2685 validated: false,
2686 proof_timeout: 200.0,
2687 },
2688 );
2689 engine.inject_path(
2690 destination_hash,
2691 PathEntry {
2692 timestamp: 99.0,
2693 next_hop: [0xAA; 16],
2694 hops: 3,
2695 expires: 999.0,
2696 random_blobs: Vec::new(),
2697 receiving_interface: InterfaceId(2),
2698 packet_hash: [0xCC; 32],
2699 announce_raw: None,
2700 },
2701 );
2702
2703 let mut key_rng = rns_crypto::FixedRng::new(&[0x51; 128]);
2704 let signing_key = Ed25519PrivateKey::generate(&mut key_rng);
2705 let signing_public = signing_key.public_key().public_bytes();
2706 let proof = crate::link::handshake::build_lrproof(
2707 &link_id,
2708 &[0x22; 32],
2709 &signing_public,
2710 &signing_key,
2711 None,
2712 crate::link::LinkMode::Aes256Cbc,
2713 );
2714 (engine, link_id, destination_hash, signing_public, proof)
2715 }
2716
2717 #[test]
2718 fn valid_lrproof_rebalances_relay_link_and_destination_path() {
2719 let (mut engine, link_id, destination_hash, signing_public, proof) =
2720 lrproof_rebalance_fixture();
2721
2722 assert!(engine.rebalance_link_path_from_lrproof(
2723 &link_id,
2724 5,
2725 InterfaceId(2),
2726 &proof,
2727 &signing_public,
2728 ));
2729
2730 assert_eq!(engine.link_table[&link_id].remaining_hops, 5);
2731 assert_eq!(engine.hops_to(&destination_hash), Some(5));
2732
2733 let packet = RawPacket::pack(
2734 PacketFlags {
2735 header_type: constants::HEADER_1,
2736 context_flag: constants::FLAG_UNSET,
2737 transport_type: constants::TRANSPORT_BROADCAST,
2738 destination_type: constants::DESTINATION_LINK,
2739 packet_type: constants::PACKET_TYPE_PROOF,
2740 },
2741 4,
2742 &link_id,
2743 None,
2744 constants::CONTEXT_LRPROOF,
2745 &proof,
2746 )
2747 .unwrap();
2748 let mut inbound_rng = rns_crypto::FixedRng::new(&[0x61; 32]);
2749 let actions = engine.handle_inbound(
2750 InboundFrame {
2751 raw: &packet.raw,
2752 iface: InterfaceId(2),
2753 now: 101.0,
2754 rx: RxMetadata::default(),
2755 },
2756 &mut inbound_rng,
2757 );
2758 assert!(engine.link_table[&link_id].validated);
2759 assert!(actions.iter().any(|action| matches!(
2760 action,
2761 TransportAction::SendOnInterface {
2762 interface: InterfaceId(1),
2763 ..
2764 }
2765 )));
2766 }
2767
2768 #[test]
2769 fn relay_rebalance_rejects_invalid_signature_wrong_interface_and_validated_link() {
2770 let (mut engine, link_id, destination_hash, signing_public, mut proof) =
2771 lrproof_rebalance_fixture();
2772 proof[0] ^= 0x01;
2773 assert!(!engine.rebalance_link_path_from_lrproof(
2774 &link_id,
2775 5,
2776 InterfaceId(2),
2777 &proof,
2778 &signing_public,
2779 ));
2780 assert_eq!(engine.link_table[&link_id].remaining_hops, 3);
2781 assert_eq!(engine.hops_to(&destination_hash), Some(3));
2782
2783 let (_, _, _, _, valid_proof) = lrproof_rebalance_fixture();
2784 assert!(!engine.rebalance_link_path_from_lrproof(
2785 &link_id,
2786 5,
2787 InterfaceId(1),
2788 &valid_proof,
2789 &signing_public,
2790 ));
2791 engine.validate_link(&link_id);
2792 assert!(!engine.rebalance_link_path_from_lrproof(
2793 &link_id,
2794 5,
2795 InterfaceId(2),
2796 &valid_proof,
2797 &signing_public,
2798 ));
2799 }
2800
2801 #[test]
2802 fn relay_rebalance_candidate_rejects_proof_addressed_to_another_transport() {
2803 let (engine, link_id, _, _, proof) = lrproof_rebalance_fixture();
2804 let packet = RawPacket::pack(
2805 PacketFlags {
2806 header_type: constants::HEADER_2,
2807 context_flag: constants::FLAG_UNSET,
2808 transport_type: constants::TRANSPORT_TRANSPORT,
2809 destination_type: constants::DESTINATION_LINK,
2810 packet_type: constants::PACKET_TYPE_PROOF,
2811 },
2812 4,
2813 &link_id,
2814 Some(&[0x43; 16]),
2815 constants::CONTEXT_LRPROOF,
2816 &proof,
2817 )
2818 .unwrap();
2819
2820 assert!(engine
2821 .inbound_lrproof_rebalance_candidate(&packet.raw, InterfaceId(2))
2822 .is_none());
2823 }
2824
2825 #[test]
2826 fn lrproof_hop_mismatch_diagnostic_contains_complete_route_context() {
2827 let entry = LinkEntry {
2828 timestamp: 100.0,
2829 next_hop_transport_id: [0; 16],
2830 next_hop_interface: InterfaceId(17),
2831 remaining_hops: 3,
2832 received_interface: InterfaceId(29),
2833 taken_hops: 5,
2834 destination_hash: [0xAA; 16],
2835 validated: false,
2836 proof_timeout: 200.0,
2837 };
2838
2839 assert_eq!(
2840 lrproof_hop_mismatch_diagnostic(9, &entry),
2841 "Received link request proof with hop mismatch (9/3:17->29), not transporting it"
2842 );
2843 }
2844
2845 #[test]
2846 fn lrproof_hop_mismatch_diagnostic_does_not_confuse_hops_with_interface_ids() {
2847 let entry = LinkEntry {
2848 timestamp: 100.0,
2849 next_hop_transport_id: [0; 16],
2850 next_hop_interface: InterfaceId(u64::MAX - 1),
2851 remaining_hops: u8::MAX,
2852 received_interface: InterfaceId(u64::MAX),
2853 taken_hops: 0,
2854 destination_hash: [0xAA; 16],
2855 validated: false,
2856 proof_timeout: 200.0,
2857 };
2858
2859 assert_eq!(
2860 lrproof_hop_mismatch_diagnostic(0, &entry),
2861 alloc::format!(
2862 "Received link request proof with hop mismatch (0/255:{}->{}), not transporting it",
2863 u64::MAX - 1,
2864 u64::MAX
2865 )
2866 );
2867 }
2868
2869 #[test]
2870 fn test_packet_filter_drops_plain_announce() {
2871 let engine = TransportEngine::new(make_config(false));
2872 let flags = PacketFlags {
2873 header_type: constants::HEADER_1,
2874 context_flag: constants::FLAG_UNSET,
2875 transport_type: constants::TRANSPORT_BROADCAST,
2876 destination_type: constants::DESTINATION_PLAIN,
2877 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2878 };
2879 let packet =
2880 RawPacket::pack(flags, 0, &[0; 16], None, constants::CONTEXT_NONE, b"test").unwrap();
2881 assert!(!engine.packet_filter(&packet));
2882 }
2883
2884 #[test]
2885 fn test_packet_filter_allows_keepalive() {
2886 let engine = TransportEngine::new(make_config(false));
2887 let flags = PacketFlags {
2888 header_type: constants::HEADER_1,
2889 context_flag: constants::FLAG_UNSET,
2890 transport_type: constants::TRANSPORT_BROADCAST,
2891 destination_type: constants::DESTINATION_SINGLE,
2892 packet_type: constants::PACKET_TYPE_DATA,
2893 };
2894 let packet = RawPacket::pack(
2895 flags,
2896 0,
2897 &[0; 16],
2898 None,
2899 constants::CONTEXT_KEEPALIVE,
2900 b"test",
2901 )
2902 .unwrap();
2903 assert!(engine.packet_filter(&packet));
2904 }
2905
2906 #[test]
2907 fn test_packet_filter_drops_high_hop_plain() {
2908 let engine = TransportEngine::new(make_config(false));
2909 let flags = PacketFlags {
2910 header_type: constants::HEADER_1,
2911 context_flag: constants::FLAG_UNSET,
2912 transport_type: constants::TRANSPORT_BROADCAST,
2913 destination_type: constants::DESTINATION_PLAIN,
2914 packet_type: constants::PACKET_TYPE_DATA,
2915 };
2916 let mut packet =
2917 RawPacket::pack(flags, 0, &[0; 16], None, constants::CONTEXT_NONE, b"test").unwrap();
2918 packet.hops = 2;
2919 assert!(!engine.packet_filter(&packet));
2920 }
2921
2922 #[test]
2923 fn test_packet_filter_allows_duplicate_single_announce() {
2924 let mut engine = TransportEngine::new(make_config(false));
2925 let flags = PacketFlags {
2926 header_type: constants::HEADER_1,
2927 context_flag: constants::FLAG_UNSET,
2928 transport_type: constants::TRANSPORT_BROADCAST,
2929 destination_type: constants::DESTINATION_SINGLE,
2930 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2931 };
2932 let packet = RawPacket::pack(
2933 flags,
2934 0,
2935 &[0; 16],
2936 None,
2937 constants::CONTEXT_NONE,
2938 &[0xAA; 64],
2939 )
2940 .unwrap();
2941
2942 engine.packet_hashlist.add(packet.packet_hash);
2944
2945 assert!(engine.packet_filter(&packet));
2947 }
2948
2949 #[test]
2950 fn test_packet_filter_fifo_eviction_allows_oldest_hash_again() {
2951 let mut engine = TransportEngine::new(make_config(false));
2952 engine.packet_hashlist = PacketHashlist::new(2);
2953
2954 let make_packet = |seed: u8| {
2955 let flags = PacketFlags {
2956 header_type: constants::HEADER_1,
2957 context_flag: constants::FLAG_UNSET,
2958 transport_type: constants::TRANSPORT_BROADCAST,
2959 destination_type: constants::DESTINATION_SINGLE,
2960 packet_type: constants::PACKET_TYPE_DATA,
2961 };
2962 RawPacket::pack(
2963 flags,
2964 0,
2965 &[seed; 16],
2966 None,
2967 constants::CONTEXT_NONE,
2968 &[seed; 4],
2969 )
2970 .unwrap()
2971 };
2972
2973 let packet1 = make_packet(1);
2974 let packet2 = make_packet(2);
2975 let packet3 = make_packet(3);
2976
2977 engine.packet_hashlist.add(packet1.packet_hash);
2978 engine.packet_hashlist.add(packet2.packet_hash);
2979 assert!(!engine.packet_filter(&packet1));
2980
2981 engine.packet_hashlist.add(packet3.packet_hash);
2982
2983 assert!(engine.packet_filter(&packet1));
2984 assert!(!engine.packet_filter(&packet2));
2985 assert!(!engine.packet_filter(&packet3));
2986 }
2987
2988 #[test]
2989 fn test_packet_filter_duplicate_does_not_refresh_recency() {
2990 let mut engine = TransportEngine::new(make_config(false));
2991 engine.packet_hashlist = PacketHashlist::new(2);
2992
2993 let make_packet = |seed: u8| {
2994 let flags = PacketFlags {
2995 header_type: constants::HEADER_1,
2996 context_flag: constants::FLAG_UNSET,
2997 transport_type: constants::TRANSPORT_BROADCAST,
2998 destination_type: constants::DESTINATION_SINGLE,
2999 packet_type: constants::PACKET_TYPE_DATA,
3000 };
3001 RawPacket::pack(
3002 flags,
3003 0,
3004 &[seed; 16],
3005 None,
3006 constants::CONTEXT_NONE,
3007 &[seed; 4],
3008 )
3009 .unwrap()
3010 };
3011
3012 let packet1 = make_packet(1);
3013 let packet2 = make_packet(2);
3014 let packet3 = make_packet(3);
3015
3016 engine.packet_hashlist.add(packet1.packet_hash);
3017 engine.packet_hashlist.add(packet2.packet_hash);
3018 engine.packet_hashlist.add(packet2.packet_hash);
3019 engine.packet_hashlist.add(packet3.packet_hash);
3020
3021 assert!(engine.packet_filter(&packet1));
3022 assert!(!engine.packet_filter(&packet2));
3023 assert!(!engine.packet_filter(&packet3));
3024 }
3025
3026 #[test]
3027 fn test_tick_retransmits_announce() {
3028 let mut engine = TransportEngine::new(make_config(true));
3029 engine.register_interface(make_interface(1, constants::MODE_FULL));
3030
3031 let dest = [0x55; 16];
3032 engine.register_destination(dest, constants::DESTINATION_SINGLE);
3033 engine.insert_announce_entry(
3034 dest,
3035 AnnounceEntry {
3036 timestamp: 190.0,
3037 retransmit_timeout: 100.0, retries: 0,
3039 received_from: [0xAA; 16],
3040 hops: 2,
3041 packet_raw: vec![0x01, 0x02],
3042 packet_data: vec![0xCC; 10],
3043 destination_hash: dest,
3044 context_flag: 0,
3045 local_rebroadcasts: 0,
3046 block_rebroadcasts: false,
3047 attached_interface: None,
3048 },
3049 190.0,
3050 );
3051
3052 let mut rng = rns_crypto::FixedRng::new(&[0x42; 32]);
3053 let actions = engine.tick(200.0, &mut rng);
3054
3055 assert!(!actions.is_empty());
3058 assert!(matches!(
3059 &actions[0],
3060 TransportAction::SendOnInterface { .. }
3061 ));
3062
3063 assert_eq!(engine.announce_table[&dest].retries, 1);
3065 }
3066
3067 #[test]
3068 fn test_gate_retransmit_actions_expands_broadcast_to_matching_interfaces() {
3069 let mut engine = TransportEngine::new(make_config(false));
3070 engine.register_interface(make_interface(1, constants::MODE_FULL));
3071 engine.register_interface(make_interface(2, constants::MODE_FULL));
3072 engine.register_interface(make_interface(3, constants::MODE_ACCESS_POINT));
3073
3074 let dest = [0x56; 16];
3075 engine.register_destination(dest, constants::DESTINATION_SINGLE);
3076 let raw = make_announce_raw(&dest, &[0xAB; 32]);
3077 let actions = engine.gate_retransmit_actions(
3078 vec![TransportAction::BroadcastOnAllInterfaces {
3079 raw: raw.clone().into(),
3080 exclude: None,
3081 }],
3082 1000.0,
3083 );
3084
3085 assert_eq!(actions.len(), 2);
3086 for action in &actions {
3087 match action {
3088 TransportAction::SendOnInterface {
3089 interface,
3090 raw: sent,
3091 } => {
3092 assert!(*interface == InterfaceId(1) || *interface == InterfaceId(2));
3093 assert_eq!(&**sent, raw.as_slice());
3094 }
3095 other => panic!("expected SendOnInterface, got {:?}", other),
3096 }
3097 }
3098 }
3099
3100 #[test]
3101 fn test_tick_culls_expired_announce_entries() {
3102 let mut config = make_config(true);
3103 config.announce_table_ttl_secs = 10.0;
3104 let mut engine = TransportEngine::new(config);
3105
3106 let dest1 = [0x61; 16];
3107 let dest2 = [0x62; 16];
3108 assert!(engine.insert_announce_entry(dest1, make_announce_entry(dest1, 100.0, 8), 100.0));
3109 assert!(engine.insert_held_announce(dest2, make_announce_entry(dest2, 100.0, 8), 100.0));
3110
3111 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
3112 let _ = engine.tick(111.0, &mut rng);
3113
3114 assert!(!engine.announce_table().contains_key(&dest1));
3115 assert!(!engine.held_announces().contains_key(&dest2));
3116 }
3117
3118 #[test]
3119 fn test_announce_retention_cap_evicts_oldest_and_prefers_held_on_tie() {
3120 let sample_entry = make_announce_entry([0x70; 16], 100.0, 32);
3121 let mut config = make_config(true);
3122 config.announce_table_max_bytes = TransportEngine::announce_entry_size_bytes(&sample_entry)
3123 * 2
3124 + TransportEngine::announce_entry_size_bytes(&sample_entry) / 2;
3125 let max_bytes = config.announce_table_max_bytes;
3126 let mut engine = TransportEngine::new(config);
3127
3128 let held_dest = [0x71; 16];
3129 let active_dest = [0x72; 16];
3130 let newest_dest = [0x73; 16];
3131
3132 assert!(engine.insert_held_announce(
3133 held_dest,
3134 make_announce_entry(held_dest, 100.0, 32),
3135 100.0,
3136 ));
3137 assert!(engine.insert_announce_entry(
3138 active_dest,
3139 make_announce_entry(active_dest, 100.0, 32),
3140 100.0,
3141 ));
3142 assert!(engine.insert_announce_entry(
3143 newest_dest,
3144 make_announce_entry(newest_dest, 101.0, 32),
3145 101.0,
3146 ));
3147
3148 assert!(!engine.held_announces().contains_key(&held_dest));
3149 assert!(engine.announce_table().contains_key(&active_dest));
3150 assert!(engine.announce_table().contains_key(&newest_dest));
3151 assert!(engine.announce_retained_bytes() <= max_bytes);
3152 }
3153
3154 #[test]
3155 fn test_oversized_announce_entry_is_not_retained() {
3156 let mut config = make_config(true);
3157 config.announce_table_max_bytes = 200;
3158 let mut engine = TransportEngine::new(config);
3159 let dest = [0x81; 16];
3160
3161 assert!(!engine.insert_announce_entry(dest, make_announce_entry(dest, 100.0, 256), 100.0));
3162 assert!(!engine.announce_table().contains_key(&dest));
3163 assert_eq!(engine.announce_retained_bytes(), 0);
3164 }
3165
3166 #[test]
3167 fn test_void_queues_clears_shutdown_transients() {
3168 let mut engine = TransportEngine::new(make_config(true));
3169 engine.register_interface(make_interface(1, constants::MODE_FULL));
3170
3171 let active_dest = [0x91; 16];
3172 let held_dest = [0x92; 16];
3173 assert!(engine.insert_announce_entry(
3174 active_dest,
3175 make_announce_entry(active_dest, 100.0, 16),
3176 100.0,
3177 ));
3178 assert!(engine.insert_held_announce(
3179 held_dest,
3180 make_announce_entry(held_dest, 100.0, 16),
3181 100.0,
3182 ));
3183 engine.reverse_table.insert(
3184 [0x93; 16],
3185 tables::ReverseEntry {
3186 receiving_interface: InterfaceId(1),
3187 outbound_interface: InterfaceId(2),
3188 timestamp: 100.0,
3189 },
3190 );
3191 let _ = engine.announce_queues.gate_announce(
3192 InterfaceId(1),
3193 vec![0xAA; 32].into(),
3194 [0x94; 16],
3195 2,
3196 100.0,
3197 100.0,
3198 Some(1000),
3199 None,
3200 constants::ANNOUNCE_CAP,
3201 );
3202 let _ = engine.announce_queues.gate_announce(
3203 InterfaceId(1),
3204 vec![0xBB; 32].into(),
3205 [0x95; 16],
3206 3,
3207 100.0,
3208 100.0,
3209 Some(1000),
3210 None,
3211 constants::ANNOUNCE_CAP,
3212 );
3213
3214 assert_eq!(engine.announce_table_count(), 1);
3215 assert_eq!(engine.held_announces_count(), 1);
3216 assert_eq!(engine.reverse_table_count(), 1);
3217 assert_eq!(engine.queued_announce_count(), 1);
3218
3219 engine.void_queues();
3220
3221 assert_eq!(engine.announce_table_count(), 0);
3222 assert_eq!(engine.held_announces_count(), 0);
3223 assert_eq!(engine.reverse_table_count(), 0);
3224 assert_eq!(engine.queued_announce_count(), 0);
3225 assert_eq!(engine.nonempty_announce_queue_count(), 0);
3226 assert_eq!(engine.announce_retained_bytes(), 0);
3227 }
3228
3229 #[test]
3230 fn test_blackhole_identity() {
3231 let mut engine = TransportEngine::new(make_config(false));
3232 let hash = [0xAA; 16];
3233 let now = 1000.0;
3234
3235 assert!(!engine.is_blackholed(&hash, now));
3236
3237 engine.blackhole_identity(hash, now, None, Some(String::from("test")));
3238 assert!(engine.is_blackholed(&hash, now));
3239 assert!(engine.is_blackholed(&hash, now + 999999.0)); assert!(engine.unblackhole_identity(&hash));
3242 assert!(!engine.is_blackholed(&hash, now));
3243 assert!(!engine.unblackhole_identity(&hash)); }
3245
3246 #[test]
3247 fn test_blackhole_with_duration() {
3248 let mut engine = TransportEngine::new(make_config(false));
3249 let hash = [0xBB; 16];
3250 let now = 1000.0;
3251
3252 engine.blackhole_identity(hash, now, Some(1.0), None); assert!(engine.is_blackholed(&hash, now));
3254 assert!(engine.is_blackholed(&hash, now + 3599.0)); assert!(!engine.is_blackholed(&hash, now + 3601.0)); }
3257
3258 #[test]
3259 fn test_cull_blackholed() {
3260 let mut engine = TransportEngine::new(make_config(false));
3261 let hash1 = [0xCC; 16];
3262 let hash2 = [0xDD; 16];
3263 let now = 1000.0;
3264
3265 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));
3271 assert!(engine.blackholed_identities.contains_key(&hash2));
3272 }
3273
3274 #[test]
3275 fn test_blackhole_blocks_announce() {
3276 use crate::announce::AnnounceData;
3277 use crate::destination::{destination_hash, name_hash};
3278
3279 let mut engine = TransportEngine::new(make_config(false));
3280 engine.register_interface(make_interface(1, constants::MODE_FULL));
3281
3282 let identity =
3283 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x55; 32]));
3284 let dest_hash = destination_hash("test", &["app"], Some(identity.hash()));
3285 let name_h = name_hash("test", &["app"]);
3286 let random_hash = [0x42u8; 10];
3287
3288 let (announce_data, _) =
3289 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
3290
3291 let flags = PacketFlags {
3292 header_type: constants::HEADER_1,
3293 context_flag: constants::FLAG_UNSET,
3294 transport_type: constants::TRANSPORT_BROADCAST,
3295 destination_type: constants::DESTINATION_SINGLE,
3296 packet_type: constants::PACKET_TYPE_ANNOUNCE,
3297 };
3298 let packet = RawPacket::pack(
3299 flags,
3300 0,
3301 &dest_hash,
3302 None,
3303 constants::CONTEXT_NONE,
3304 &announce_data,
3305 )
3306 .unwrap();
3307
3308 let now = 1000.0;
3310 engine.blackhole_identity(*identity.hash(), now, None, None);
3311
3312 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
3313 let actions = engine.handle_inbound(
3314 InboundFrame {
3315 raw: &packet.raw,
3316 iface: InterfaceId(1),
3317 now,
3318 rx: RxMetadata {
3319 rssi: None,
3320 snr: None,
3321 },
3322 },
3323 &mut rng,
3324 );
3325
3326 assert!(actions
3328 .iter()
3329 .all(|a| !matches!(a, TransportAction::AnnounceReceived { .. })));
3330 assert!(actions
3331 .iter()
3332 .all(|a| !matches!(a, TransportAction::PathUpdated { .. })));
3333 }
3334
3335 #[test]
3336 fn test_async_announce_retransmit_cleanup_happens_before_queueing() {
3337 use crate::announce::AnnounceData;
3338 use crate::destination::{destination_hash, name_hash};
3339 use crate::transport::announce_verify_queue::AnnounceVerifyQueue;
3340
3341 let mut engine = TransportEngine::new(make_config(true));
3342 engine.register_interface(make_interface(1, constants::MODE_FULL));
3343
3344 let identity =
3345 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x31; 32]));
3346 let dest_hash = destination_hash("async", &["announce"], Some(identity.hash()));
3347 let name_h = name_hash("async", &["announce"]);
3348 let random_hash = [0x44u8; 10];
3349 let (announce_data, _) =
3350 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
3351
3352 let packet = RawPacket::pack(
3353 PacketFlags {
3354 header_type: constants::HEADER_2,
3355 context_flag: constants::FLAG_UNSET,
3356 transport_type: constants::TRANSPORT_TRANSPORT,
3357 destination_type: constants::DESTINATION_SINGLE,
3358 packet_type: constants::PACKET_TYPE_ANNOUNCE,
3359 },
3360 3,
3361 &dest_hash,
3362 Some(&[0xBB; 16]),
3363 constants::CONTEXT_NONE,
3364 &announce_data,
3365 )
3366 .unwrap();
3367
3368 engine.announce_table.insert(
3369 dest_hash,
3370 AnnounceEntry {
3371 timestamp: 1000.0,
3372 retransmit_timeout: 2000.0,
3373 retries: constants::PATHFINDER_R,
3374 received_from: [0xBB; 16],
3375 hops: 2,
3376 packet_raw: packet.raw.clone(),
3377 packet_data: packet.data.clone(),
3378 destination_hash: dest_hash,
3379 context_flag: constants::FLAG_UNSET,
3380 local_rebroadcasts: 0,
3381 block_rebroadcasts: false,
3382 attached_interface: None,
3383 },
3384 );
3385
3386 let mut queue = AnnounceVerifyQueue::new(8);
3387 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
3388 let actions = engine.handle_inbound_with_announce_queue(
3389 InboundFrame {
3390 raw: &packet.raw,
3391 iface: InterfaceId(1),
3392 now: 1000.0,
3393 rx: RxMetadata {
3394 rssi: None,
3395 snr: None,
3396 },
3397 },
3398 &mut rng,
3399 Some(&mut queue),
3400 );
3401
3402 assert!(actions.is_empty());
3403 assert_eq!(queue.len(), 1);
3404 assert!(
3405 !engine.announce_table.contains_key(&dest_hash),
3406 "retransmit completion should clear announce_table before queueing"
3407 );
3408 }
3409
3410 #[test]
3411 fn test_async_announce_completion_inserts_sig_cache_and_prevents_requeue() {
3412 use crate::announce::AnnounceData;
3413 use crate::destination::{destination_hash, name_hash};
3414 use crate::transport::announce_verify_queue::AnnounceVerifyQueue;
3415
3416 let mut engine = TransportEngine::new(make_config(false));
3417 engine.register_interface(make_interface(1, constants::MODE_FULL));
3418
3419 let identity =
3420 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x52; 32]));
3421 let dest_hash = destination_hash("async", &["cache"], Some(identity.hash()));
3422 let name_h = name_hash("async", &["cache"]);
3423 let random_hash = [0x55u8; 10];
3424 let (announce_data, _) =
3425 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
3426
3427 let packet = RawPacket::pack(
3428 PacketFlags {
3429 header_type: constants::HEADER_1,
3430 context_flag: constants::FLAG_UNSET,
3431 transport_type: constants::TRANSPORT_BROADCAST,
3432 destination_type: constants::DESTINATION_SINGLE,
3433 packet_type: constants::PACKET_TYPE_ANNOUNCE,
3434 },
3435 0,
3436 &dest_hash,
3437 None,
3438 constants::CONTEXT_NONE,
3439 &announce_data,
3440 )
3441 .unwrap();
3442
3443 let mut queue = AnnounceVerifyQueue::new(8);
3444 let mut rng = rns_crypto::FixedRng::new(&[0x77; 32]);
3445 let actions = engine.handle_inbound_with_announce_queue(
3446 InboundFrame {
3447 raw: &packet.raw,
3448 iface: InterfaceId(1),
3449 now: 1000.0,
3450 rx: RxMetadata {
3451 rssi: None,
3452 snr: None,
3453 },
3454 },
3455 &mut rng,
3456 Some(&mut queue),
3457 );
3458 assert!(actions.is_empty());
3459 assert_eq!(queue.len(), 1);
3460
3461 let mut batch = queue.take_pending(1000.0);
3462 assert_eq!(batch.len(), 1);
3463 let (key, pending) = batch.pop().unwrap();
3464
3465 let announce = AnnounceData::unpack(&pending.packet.data, false).unwrap();
3466 let validated = announce.validate(&pending.packet.destination_hash).unwrap();
3467 let mut material = [0u8; 80];
3468 material[..16].copy_from_slice(&pending.packet.destination_hash);
3469 material[16..].copy_from_slice(&announce.signature);
3470 let sig_cache_key = hash::full_hash(&material);
3471
3472 let pending = queue.complete_success(&key).unwrap();
3473 let actions =
3474 engine.complete_verified_announce(pending, validated, sig_cache_key, 1000.0, &mut rng);
3475 assert!(actions
3476 .iter()
3477 .any(|action| matches!(action, TransportAction::AnnounceReceived { .. })));
3478 assert!(engine.announce_sig_cache_contains(&sig_cache_key));
3479
3480 let actions = engine.handle_inbound_with_announce_queue(
3481 InboundFrame {
3482 raw: &packet.raw,
3483 iface: InterfaceId(1),
3484 now: 1001.0,
3485 rx: RxMetadata {
3486 rssi: None,
3487 snr: None,
3488 },
3489 },
3490 &mut rng,
3491 Some(&mut queue),
3492 );
3493 assert!(actions.is_empty());
3494 assert_eq!(queue.len(), 0);
3495 }
3496
3497 #[test]
3498 fn test_tick_culls_expired_path() {
3499 let mut engine = TransportEngine::new(make_config(false));
3500 engine.register_interface(make_interface(1, constants::MODE_FULL));
3501
3502 let dest = [0x66; 16];
3503 engine.path_table.insert(
3504 dest,
3505 PathSet::from_single(
3506 PathEntry {
3507 timestamp: 100.0,
3508 next_hop: [0; 16],
3509 hops: 2,
3510 expires: 200.0,
3511 random_blobs: Vec::new(),
3512 receiving_interface: InterfaceId(1),
3513 packet_hash: [0; 32],
3514 announce_raw: None,
3515 },
3516 1,
3517 ),
3518 );
3519
3520 assert!(engine.has_path(&dest));
3521
3522 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3523 engine.tick(300.0, &mut rng);
3525
3526 assert!(!engine.has_path(&dest));
3527 }
3528
3529 fn make_local_client_interface(id: u64) -> InterfaceInfo {
3534 InterfaceInfo {
3535 id: InterfaceId(id),
3536 name: String::from("local_client"),
3537 mode: constants::MODE_FULL,
3538 gravity: 0,
3539 recursive_prs: false,
3540 announces_from_internal: true,
3541 announces_to_internal: None,
3542 out_capable: true,
3543 in_capable: true,
3544 bitrate: None,
3545 airtime_profile: None,
3546 announce_rate_target: None,
3547 announce_rate_grace: 0,
3548 announce_rate_penalty: 0.0,
3549 announce_cap: constants::ANNOUNCE_CAP,
3550 is_local_client: true,
3551 wants_tunnel: false,
3552 tunnel_id: None,
3553 mtu: constants::MTU as u32,
3554 ingress_control: crate::transport::types::IngressControlConfig::disabled(),
3555 ia_freq: 0.0,
3556 ip_freq: 0.0,
3557 op_freq: 0.0,
3558 op_samples: 0,
3559 started: 0.0,
3560 }
3561 }
3562
3563 #[test]
3564 fn test_has_local_clients() {
3565 let mut engine = TransportEngine::new(make_config(false));
3566 assert!(!engine.has_local_clients());
3567
3568 engine.register_interface(make_interface(1, constants::MODE_FULL));
3569 assert!(!engine.has_local_clients());
3570
3571 engine.register_interface(make_local_client_interface(2));
3572 assert!(engine.has_local_clients());
3573
3574 engine.deregister_interface(InterfaceId(2));
3575 assert!(!engine.has_local_clients());
3576 }
3577
3578 #[test]
3579 fn test_local_client_hop_decrement() {
3580 let mut engine = TransportEngine::new(make_config(false));
3583 engine.register_interface(make_local_client_interface(1));
3584 engine.register_interface(make_interface(2, constants::MODE_FULL));
3585
3586 let dest = [0xAA; 16];
3588 engine.register_destination(dest, constants::DESTINATION_PLAIN);
3589
3590 let flags = PacketFlags {
3591 header_type: constants::HEADER_1,
3592 context_flag: constants::FLAG_UNSET,
3593 transport_type: constants::TRANSPORT_BROADCAST,
3594 destination_type: constants::DESTINATION_PLAIN,
3595 packet_type: constants::PACKET_TYPE_DATA,
3596 };
3597 let packet =
3599 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"hello").unwrap();
3600
3601 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3602 let actions = engine.handle_inbound(
3603 InboundFrame {
3604 raw: &packet.raw,
3605 iface: InterfaceId(1),
3606 now: 1000.0,
3607 rx: RxMetadata {
3608 rssi: None,
3609 snr: None,
3610 },
3611 },
3612 &mut rng,
3613 );
3614
3615 let deliver = actions
3618 .iter()
3619 .find(|a| matches!(a, TransportAction::DeliverLocal { .. }));
3620 assert!(deliver.is_some(), "Should deliver locally");
3621 }
3622
3623 #[test]
3624 fn lrrtt_local_delivery_preserves_post_ingress_hops() {
3625 let mut engine = TransportEngine::new(make_config(false));
3626 engine.register_interface(make_interface(1, constants::MODE_FULL));
3627 let link_id = [0x4c; 16];
3628 engine.register_destination(link_id, constants::DESTINATION_LINK);
3629 let packet = RawPacket::pack(
3630 PacketFlags {
3631 header_type: constants::HEADER_1,
3632 context_flag: constants::FLAG_UNSET,
3633 transport_type: constants::TRANSPORT_BROADCAST,
3634 destination_type: constants::DESTINATION_LINK,
3635 packet_type: constants::PACKET_TYPE_DATA,
3636 },
3637 0,
3638 &link_id,
3639 None,
3640 constants::CONTEXT_LRRTT,
3641 b"authenticated ciphertext",
3642 )
3643 .unwrap();
3644 let actions = engine.handle_inbound(
3645 InboundFrame {
3646 raw: &packet.raw,
3647 iface: InterfaceId(1),
3648 now: 1.0,
3649 rx: RxMetadata::default(),
3650 },
3651 &mut rns_crypto::FixedRng::new(&[0; 32]),
3652 );
3653 let raw = actions
3654 .iter()
3655 .find_map(|action| match action {
3656 TransportAction::DeliverLocal { raw, .. } => Some(raw),
3657 _ => None,
3658 })
3659 .expect("LRRTT should be delivered locally");
3660 assert_eq!(RawPacket::unpack(raw).unwrap().hops, 1);
3661 }
3662
3663 #[test]
3664 fn test_prepare_inbound_packet_only_retains_original_raw_for_announces() {
3665 let engine = TransportEngine::new(make_config(false));
3666 let dest = [0xAB; 16];
3667 let flags = PacketFlags {
3668 header_type: constants::HEADER_1,
3669 context_flag: constants::FLAG_UNSET,
3670 transport_type: constants::TRANSPORT_BROADCAST,
3671 destination_type: constants::DESTINATION_SINGLE,
3672 packet_type: constants::PACKET_TYPE_DATA,
3673 };
3674 let packet =
3675 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"hello").unwrap();
3676
3677 let ctx = engine
3678 .prepare_inbound_packet(InboundFrame {
3679 raw: &packet.raw,
3680 iface: InterfaceId(9),
3681 now: 1000.0,
3682 rx: RxMetadata {
3683 rssi: None,
3684 snr: None,
3685 },
3686 })
3687 .expect("packet should parse and pass filter");
3688
3689 assert!(ctx.original_raw.is_none());
3690 assert_eq!(ctx.packet.raw, packet.raw);
3691 assert_eq!(ctx.packet.hops, 1);
3692 assert_eq!(ctx.iface, InterfaceId(9));
3693
3694 let announce_flags = PacketFlags {
3695 packet_type: constants::PACKET_TYPE_ANNOUNCE,
3696 ..flags
3697 };
3698 let announce = RawPacket::pack(
3699 announce_flags,
3700 0,
3701 &dest,
3702 None,
3703 constants::CONTEXT_NONE,
3704 &[0u8; 91],
3705 )
3706 .unwrap();
3707 let announce_ctx = engine
3708 .prepare_inbound_packet(InboundFrame {
3709 raw: &announce.raw,
3710 iface: InterfaceId(9),
3711 now: 1000.0,
3712 rx: RxMetadata {
3713 rssi: None,
3714 snr: None,
3715 },
3716 })
3717 .expect("announce should parse and pass filter");
3718 assert_eq!(
3719 announce_ctx.original_raw.as_deref(),
3720 Some(announce.raw.as_slice())
3721 );
3722 }
3723
3724 #[test]
3725 fn test_deliver_local_preserves_original_raw_and_metadata() {
3726 let mut engine = TransportEngine::new(make_config(false));
3727 engine.register_interface(make_interface(1, constants::MODE_FULL));
3728
3729 let dest = [0xAC; 16];
3730 engine.register_destination(dest, constants::DESTINATION_SINGLE);
3731
3732 let flags = PacketFlags {
3733 header_type: constants::HEADER_1,
3734 context_flag: constants::FLAG_UNSET,
3735 transport_type: constants::TRANSPORT_BROADCAST,
3736 destination_type: constants::DESTINATION_SINGLE,
3737 packet_type: constants::PACKET_TYPE_DATA,
3738 };
3739 let packet =
3740 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"deliver").unwrap();
3741
3742 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3743 let actions = engine.handle_inbound(
3744 InboundFrame {
3745 raw: &packet.raw,
3746 iface: InterfaceId(1),
3747 now: 1000.0,
3748 rx: RxMetadata {
3749 rssi: None,
3750 snr: None,
3751 },
3752 },
3753 &mut rng,
3754 );
3755
3756 let deliver = actions
3757 .iter()
3758 .find_map(|action| match action {
3759 TransportAction::DeliverLocal {
3760 destination_hash,
3761 raw,
3762 packet_hash,
3763 receiving_interface,
3764 } => Some((destination_hash, raw, packet_hash, receiving_interface)),
3765 _ => None,
3766 })
3767 .expect("should produce DeliverLocal");
3768
3769 assert_eq!(*deliver.0, dest);
3770 assert_eq!(&**deliver.1, packet.raw.as_slice());
3771 assert_eq!(*deliver.2, packet.packet_hash);
3772 assert_eq!(*deliver.3, InterfaceId(1));
3773 }
3774
3775 #[test]
3776 fn local_lrproof_delivery_carries_post_ingress_hops() {
3777 let mut engine = TransportEngine::new(make_config(false));
3778 engine.register_interface(make_interface(1, constants::MODE_FULL));
3779 let link_id = [0x4C; 16];
3780 let packet = RawPacket::pack(
3781 PacketFlags {
3782 header_type: constants::HEADER_1,
3783 context_flag: constants::FLAG_UNSET,
3784 transport_type: constants::TRANSPORT_BROADCAST,
3785 destination_type: constants::DESTINATION_LINK,
3786 packet_type: constants::PACKET_TYPE_PROOF,
3787 },
3788 4,
3789 &link_id,
3790 None,
3791 constants::CONTEXT_LRPROOF,
3792 &[0xAA; 96],
3793 )
3794 .unwrap();
3795 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
3796
3797 let actions = engine.handle_inbound(
3798 InboundFrame {
3799 raw: &packet.raw,
3800 iface: InterfaceId(1),
3801 now: 1000.0,
3802 rx: RxMetadata::default(),
3803 },
3804 &mut rng,
3805 );
3806 let delivered_raw = actions
3807 .iter()
3808 .find_map(|action| match action {
3809 TransportAction::DeliverLocal { raw, .. } => Some(&**raw),
3810 _ => None,
3811 })
3812 .expect("LRPROOF should be delivered to the pending link manager");
3813 assert_eq!(RawPacket::unpack(delivered_raw).unwrap().hops, 5);
3814 }
3815
3816 #[test]
3817 fn test_plain_broadcast_from_local_client() {
3818 let mut engine = TransportEngine::new(make_config(false));
3820 engine.register_interface(make_local_client_interface(1));
3821 engine.register_interface(make_interface(2, constants::MODE_FULL));
3822
3823 let dest = [0xBB; 16];
3824 let flags = PacketFlags {
3825 header_type: constants::HEADER_1,
3826 context_flag: constants::FLAG_UNSET,
3827 transport_type: constants::TRANSPORT_BROADCAST,
3828 destination_type: constants::DESTINATION_PLAIN,
3829 packet_type: constants::PACKET_TYPE_DATA,
3830 };
3831 let packet =
3832 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"test").unwrap();
3833
3834 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3835 let actions = engine.handle_inbound(
3836 InboundFrame {
3837 raw: &packet.raw,
3838 iface: InterfaceId(1),
3839 now: 1000.0,
3840 rx: RxMetadata {
3841 rssi: None,
3842 snr: None,
3843 },
3844 },
3845 &mut rng,
3846 );
3847
3848 let forward = actions.iter().find(|a| {
3850 matches!(
3851 a,
3852 TransportAction::ForwardPlainBroadcast {
3853 to_local: false,
3854 ..
3855 }
3856 )
3857 });
3858 assert!(forward.is_some(), "Should forward to external interfaces");
3859 }
3860
3861 #[test]
3862 fn test_plain_broadcast_from_external() {
3863 let mut engine = TransportEngine::new(make_config(false));
3865 engine.register_interface(make_local_client_interface(1));
3866 engine.register_interface(make_interface(2, constants::MODE_FULL));
3867
3868 let dest = [0xCC; 16];
3869 let flags = PacketFlags {
3870 header_type: constants::HEADER_1,
3871 context_flag: constants::FLAG_UNSET,
3872 transport_type: constants::TRANSPORT_BROADCAST,
3873 destination_type: constants::DESTINATION_PLAIN,
3874 packet_type: constants::PACKET_TYPE_DATA,
3875 };
3876 let packet =
3877 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"test").unwrap();
3878
3879 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3880 let actions = engine.handle_inbound(
3881 InboundFrame {
3882 raw: &packet.raw,
3883 iface: InterfaceId(2),
3884 now: 1000.0,
3885 rx: RxMetadata {
3886 rssi: None,
3887 snr: None,
3888 },
3889 },
3890 &mut rng,
3891 );
3892
3893 let forward = actions.iter().find(|a| {
3895 matches!(
3896 a,
3897 TransportAction::ForwardPlainBroadcast { to_local: true, .. }
3898 )
3899 });
3900 assert!(forward.is_some(), "Should forward to local clients");
3901 }
3902
3903 #[test]
3904 fn test_no_plain_broadcast_bridging_without_local_clients() {
3905 let mut engine = TransportEngine::new(make_config(false));
3907 engine.register_interface(make_interface(1, constants::MODE_FULL));
3908 engine.register_interface(make_interface(2, constants::MODE_FULL));
3909
3910 let dest = [0xDD; 16];
3911 let flags = PacketFlags {
3912 header_type: constants::HEADER_1,
3913 context_flag: constants::FLAG_UNSET,
3914 transport_type: constants::TRANSPORT_BROADCAST,
3915 destination_type: constants::DESTINATION_PLAIN,
3916 packet_type: constants::PACKET_TYPE_DATA,
3917 };
3918 let packet =
3919 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"test").unwrap();
3920
3921 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3922 let actions = engine.handle_inbound(
3923 InboundFrame {
3924 raw: &packet.raw,
3925 iface: InterfaceId(1),
3926 now: 1000.0,
3927 rx: RxMetadata {
3928 rssi: None,
3929 snr: None,
3930 },
3931 },
3932 &mut rng,
3933 );
3934
3935 let has_forward = actions
3937 .iter()
3938 .any(|a| matches!(a, TransportAction::ForwardPlainBroadcast { .. }));
3939 assert!(!has_forward, "No bridging without local clients");
3940 }
3941
3942 #[test]
3943 fn test_announce_forwarded_to_local_clients() {
3944 use crate::announce::AnnounceData;
3945 use crate::destination::{destination_hash, name_hash};
3946
3947 let mut engine = TransportEngine::new(make_config(true));
3948 engine.register_interface(make_interface(1, constants::MODE_FULL));
3949 engine.register_interface(make_local_client_interface(2));
3950
3951 let identity =
3952 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x77; 32]));
3953 let dest_hash = destination_hash("test", &["fwd"], Some(identity.hash()));
3954 let name_h = name_hash("test", &["fwd"]);
3955 let random_hash = [0x42u8; 10];
3956
3957 let (announce_data, _) =
3958 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
3959
3960 let flags = PacketFlags {
3961 header_type: constants::HEADER_1,
3962 context_flag: constants::FLAG_UNSET,
3963 transport_type: constants::TRANSPORT_BROADCAST,
3964 destination_type: constants::DESTINATION_SINGLE,
3965 packet_type: constants::PACKET_TYPE_ANNOUNCE,
3966 };
3967 let packet = RawPacket::pack(
3968 flags,
3969 0,
3970 &dest_hash,
3971 None,
3972 constants::CONTEXT_NONE,
3973 &announce_data,
3974 )
3975 .unwrap();
3976
3977 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
3978 let actions = engine.handle_inbound(
3979 InboundFrame {
3980 raw: &packet.raw,
3981 iface: InterfaceId(1),
3982 now: 1000.0,
3983 rx: RxMetadata {
3984 rssi: None,
3985 snr: None,
3986 },
3987 },
3988 &mut rng,
3989 );
3990
3991 let forward = actions
3993 .iter()
3994 .find(|a| matches!(a, TransportAction::ForwardToLocalClients { .. }));
3995 assert!(
3996 forward.is_some(),
3997 "Should forward announce to local clients"
3998 );
3999
4000 match forward.unwrap() {
4002 TransportAction::ForwardToLocalClients { exclude, raw } => {
4003 assert_eq!(*exclude, Some(InterfaceId(1)));
4004 let flags = PacketFlags::unpack(raw[0]);
4005 assert_eq!(flags.header_type, constants::HEADER_2);
4006 assert_eq!(flags.transport_type, constants::TRANSPORT_TRANSPORT);
4007 assert_eq!(&raw[2..18], &[0x42; 16]);
4008 }
4009 _ => unreachable!(),
4010 }
4011 }
4012
4013 #[test]
4014 fn test_no_announce_forward_without_local_clients() {
4015 use crate::announce::AnnounceData;
4016 use crate::destination::{destination_hash, name_hash};
4017
4018 let mut engine = TransportEngine::new(make_config(false));
4019 engine.register_interface(make_interface(1, constants::MODE_FULL));
4020
4021 let identity =
4022 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x88; 32]));
4023 let dest_hash = destination_hash("test", &["nofwd"], Some(identity.hash()));
4024 let name_h = name_hash("test", &["nofwd"]);
4025 let random_hash = [0x42u8; 10];
4026
4027 let (announce_data, _) =
4028 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
4029
4030 let flags = PacketFlags {
4031 header_type: constants::HEADER_1,
4032 context_flag: constants::FLAG_UNSET,
4033 transport_type: constants::TRANSPORT_BROADCAST,
4034 destination_type: constants::DESTINATION_SINGLE,
4035 packet_type: constants::PACKET_TYPE_ANNOUNCE,
4036 };
4037 let packet = RawPacket::pack(
4038 flags,
4039 0,
4040 &dest_hash,
4041 None,
4042 constants::CONTEXT_NONE,
4043 &announce_data,
4044 )
4045 .unwrap();
4046
4047 let mut rng = rns_crypto::FixedRng::new(&[0x22; 32]);
4048 let actions = engine.handle_inbound(
4049 InboundFrame {
4050 raw: &packet.raw,
4051 iface: InterfaceId(1),
4052 now: 1000.0,
4053 rx: RxMetadata {
4054 rssi: None,
4055 snr: None,
4056 },
4057 },
4058 &mut rng,
4059 );
4060
4061 let has_forward = actions
4063 .iter()
4064 .any(|a| matches!(a, TransportAction::ForwardToLocalClients { .. }));
4065 assert!(!has_forward, "No forward without local clients");
4066 }
4067
4068 #[test]
4069 fn test_local_client_exclude_from_forward() {
4070 use crate::announce::AnnounceData;
4071 use crate::destination::{destination_hash, name_hash};
4072
4073 let mut engine = TransportEngine::new(make_config(false));
4074 engine.register_interface(make_local_client_interface(1));
4075 engine.register_interface(make_local_client_interface(2));
4076
4077 let identity =
4078 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
4079 let dest_hash = destination_hash("test", &["excl"], Some(identity.hash()));
4080 let name_h = name_hash("test", &["excl"]);
4081 let random_hash = [0x42u8; 10];
4082
4083 let (announce_data, _) =
4084 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
4085
4086 let flags = PacketFlags {
4087 header_type: constants::HEADER_1,
4088 context_flag: constants::FLAG_UNSET,
4089 transport_type: constants::TRANSPORT_BROADCAST,
4090 destination_type: constants::DESTINATION_SINGLE,
4091 packet_type: constants::PACKET_TYPE_ANNOUNCE,
4092 };
4093 let packet = RawPacket::pack(
4094 flags,
4095 0,
4096 &dest_hash,
4097 None,
4098 constants::CONTEXT_NONE,
4099 &announce_data,
4100 )
4101 .unwrap();
4102
4103 let mut rng = rns_crypto::FixedRng::new(&[0x33; 32]);
4104 let actions = engine.handle_inbound(
4106 InboundFrame {
4107 raw: &packet.raw,
4108 iface: InterfaceId(1),
4109 now: 1000.0,
4110 rx: RxMetadata {
4111 rssi: None,
4112 snr: None,
4113 },
4114 },
4115 &mut rng,
4116 );
4117
4118 let forward = actions
4120 .iter()
4121 .find(|a| matches!(a, TransportAction::ForwardToLocalClients { .. }));
4122 assert!(forward.is_some());
4123 match forward.unwrap() {
4124 TransportAction::ForwardToLocalClients { exclude, .. } => {
4125 assert_eq!(*exclude, Some(InterfaceId(1)));
4126 }
4127 _ => unreachable!(),
4128 }
4129 }
4130
4131 fn make_tunnel_interface(id: u64) -> InterfaceInfo {
4136 InterfaceInfo {
4137 id: InterfaceId(id),
4138 name: String::from("tunnel_iface"),
4139 mode: constants::MODE_FULL,
4140 gravity: 0,
4141 recursive_prs: false,
4142 announces_from_internal: true,
4143 announces_to_internal: None,
4144 out_capable: true,
4145 in_capable: true,
4146 bitrate: None,
4147 airtime_profile: None,
4148 announce_rate_target: None,
4149 announce_rate_grace: 0,
4150 announce_rate_penalty: 0.0,
4151 announce_cap: constants::ANNOUNCE_CAP,
4152 is_local_client: false,
4153 wants_tunnel: true,
4154 tunnel_id: None,
4155 mtu: constants::MTU as u32,
4156 ingress_control: crate::transport::types::IngressControlConfig::disabled(),
4157 ia_freq: 0.0,
4158 ip_freq: 0.0,
4159 op_freq: 0.0,
4160 op_samples: 0,
4161 started: 0.0,
4162 }
4163 }
4164
4165 #[test]
4166 fn test_handle_tunnel_new() {
4167 let mut engine = TransportEngine::new(make_config(true));
4168 engine.register_interface(make_tunnel_interface(1));
4169
4170 let tunnel_id = [0xAA; 32];
4171 let actions = engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
4172
4173 assert!(actions
4175 .iter()
4176 .any(|a| matches!(a, TransportAction::TunnelEstablished { .. })));
4177
4178 let info = engine.interface_info(&InterfaceId(1)).unwrap();
4180 assert_eq!(info.tunnel_id, Some(tunnel_id));
4181
4182 assert_eq!(engine.tunnel_table().len(), 1);
4184 }
4185
4186 #[test]
4187 fn test_announce_stores_tunnel_path() {
4188 use crate::announce::AnnounceData;
4189 use crate::destination::{destination_hash, name_hash};
4190
4191 let mut engine = TransportEngine::new(make_config(false));
4192 let mut iface = make_tunnel_interface(1);
4193 let tunnel_id = [0xBB; 32];
4194 iface.tunnel_id = Some(tunnel_id);
4195 engine.register_interface(iface);
4196
4197 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
4199
4200 let identity =
4202 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xCC; 32]));
4203 let dest_hash = destination_hash("test", &["tunnel"], Some(identity.hash()));
4204 let name_h = name_hash("test", &["tunnel"]);
4205 let random_hash = [0x42u8; 10];
4206
4207 let (announce_data, _) =
4208 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
4209
4210 let flags = PacketFlags {
4211 header_type: constants::HEADER_1,
4212 context_flag: constants::FLAG_UNSET,
4213 transport_type: constants::TRANSPORT_BROADCAST,
4214 destination_type: constants::DESTINATION_SINGLE,
4215 packet_type: constants::PACKET_TYPE_ANNOUNCE,
4216 };
4217 let packet = RawPacket::pack(
4218 flags,
4219 0,
4220 &dest_hash,
4221 None,
4222 constants::CONTEXT_NONE,
4223 &announce_data,
4224 )
4225 .unwrap();
4226
4227 let mut rng = rns_crypto::FixedRng::new(&[0xDD; 32]);
4228 engine.handle_inbound(
4229 InboundFrame {
4230 raw: &packet.raw,
4231 iface: InterfaceId(1),
4232 now: 1000.0,
4233 rx: RxMetadata {
4234 rssi: None,
4235 snr: None,
4236 },
4237 },
4238 &mut rng,
4239 );
4240
4241 assert!(engine.has_path(&dest_hash));
4243
4244 let tunnel = engine.tunnel_table().get(&tunnel_id).unwrap();
4246 assert_eq!(tunnel.paths.len(), 1);
4247 assert!(tunnel.paths.contains_key(&dest_hash));
4248 }
4249
4250 #[test]
4251 fn test_tunnel_reattach_restores_paths() {
4252 let mut engine = TransportEngine::new(make_config(true));
4253 engine.register_interface(make_tunnel_interface(1));
4254
4255 let tunnel_id = [0xCC; 32];
4256 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
4257
4258 let dest = [0xDD; 16];
4260 engine.tunnel_table.store_tunnel_path(
4261 &tunnel_id,
4262 dest,
4263 tunnel::TunnelPath {
4264 timestamp: 1000.0,
4265 received_from: [0xEE; 16],
4266 hops: 3,
4267 expires: 1000.0 + constants::DESTINATION_TIMEOUT,
4268 random_blobs: Vec::new(),
4269 packet_hash: [0xFF; 32],
4270 },
4271 1000.0,
4272 constants::DESTINATION_TIMEOUT,
4273 usize::MAX,
4274 );
4275
4276 engine.void_tunnel_interface(&tunnel_id);
4278
4279 engine.path_table.remove(&dest);
4281 assert!(!engine.has_path(&dest));
4282
4283 engine.register_interface(make_interface(2, constants::MODE_FULL));
4285 let actions = engine.handle_tunnel(tunnel_id, InterfaceId(2), 2000.0);
4286
4287 assert!(engine.has_path(&dest));
4289 let path = engine.path_table.get(&dest).unwrap().primary().unwrap();
4290 assert_eq!(path.hops, 3);
4291 assert_eq!(path.receiving_interface, InterfaceId(2));
4292
4293 assert!(actions
4295 .iter()
4296 .any(|a| matches!(a, TransportAction::TunnelEstablished { .. })));
4297 }
4298
4299 #[test]
4300 fn test_active_packet_hashes_include_detached_tunnel_paths() {
4301 let mut engine = TransportEngine::new(make_config(true));
4302 engine.register_interface(make_tunnel_interface(1));
4303
4304 let tunnel_id = [0xCA; 32];
4305 let destination_hash = [0xDB; 16];
4306 let packet_hash = [0xEC; 32];
4307 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
4308 engine.tunnel_table.store_tunnel_path(
4309 &tunnel_id,
4310 destination_hash,
4311 tunnel::TunnelPath {
4312 timestamp: 1000.0,
4313 received_from: [0xFE; 16],
4314 hops: 2,
4315 expires: 1000.0 + constants::DESTINATION_TIMEOUT,
4316 random_blobs: Vec::new(),
4317 packet_hash,
4318 },
4319 1000.0,
4320 constants::DESTINATION_TIMEOUT,
4321 usize::MAX,
4322 );
4323 engine.void_tunnel_interface(&tunnel_id);
4324 engine.path_table.remove(&destination_hash);
4325
4326 let active_hashes = engine.active_packet_hashes();
4327 assert_eq!(active_hashes, vec![packet_hash]);
4328 }
4329
4330 #[test]
4331 fn test_tunnel_reattach_does_not_overwrite_newer_path() {
4332 let mut engine = TransportEngine::new(make_config(true));
4333 engine.register_interface(make_tunnel_interface(1));
4334
4335 let tunnel_id = [0xCD; 32];
4336 let dest = [0xDE; 16];
4337 let older_blob = make_random_blob(100);
4338 let newer_blob = make_random_blob(200);
4339
4340 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
4341 engine.tunnel_table.store_tunnel_path(
4342 &tunnel_id,
4343 dest,
4344 tunnel::TunnelPath {
4345 timestamp: 1000.0,
4346 received_from: [0xEE; 16],
4347 hops: 2,
4348 expires: 1000.0 + constants::DESTINATION_TIMEOUT,
4349 random_blobs: vec![older_blob],
4350 packet_hash: [0x11; 32],
4351 },
4352 1000.0,
4353 constants::DESTINATION_TIMEOUT,
4354 usize::MAX,
4355 );
4356 engine.void_tunnel_interface(&tunnel_id);
4357
4358 engine.path_table.insert(
4359 dest,
4360 PathSet::from_single(
4361 PathEntry {
4362 timestamp: 1500.0,
4363 next_hop: [0xAB; 16],
4364 hops: 3,
4365 expires: 1500.0 + constants::DESTINATION_TIMEOUT,
4366 random_blobs: vec![newer_blob],
4367 receiving_interface: InterfaceId(3),
4368 packet_hash: [0x22; 32],
4369 announce_raw: None,
4370 },
4371 1,
4372 ),
4373 );
4374
4375 engine.register_interface(make_interface(2, constants::MODE_FULL));
4376 engine.handle_tunnel(tunnel_id, InterfaceId(2), 2000.0);
4377
4378 let path = engine.path_table.get(&dest).unwrap().primary().unwrap();
4379 assert_eq!(path.next_hop, [0xAB; 16]);
4380 assert_eq!(path.hops, 3);
4381 assert_eq!(path.receiving_interface, InterfaceId(3));
4382 assert_eq!(path.random_blobs, vec![newer_blob]);
4383 }
4384
4385 #[test]
4386 fn test_void_tunnel_interface() {
4387 let mut engine = TransportEngine::new(make_config(true));
4388 engine.register_interface(make_tunnel_interface(1));
4389
4390 let tunnel_id = [0xDD; 32];
4391 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
4392
4393 assert_eq!(
4395 engine.tunnel_table().get(&tunnel_id).unwrap().interface,
4396 Some(InterfaceId(1))
4397 );
4398
4399 engine.void_tunnel_interface(&tunnel_id);
4400
4401 assert_eq!(engine.tunnel_table().len(), 1);
4403 assert_eq!(
4404 engine.tunnel_table().get(&tunnel_id).unwrap().interface,
4405 None
4406 );
4407 }
4408
4409 #[test]
4410 fn test_tick_culls_tunnels() {
4411 let mut engine = TransportEngine::new(make_config(true));
4412 engine.register_interface(make_tunnel_interface(1));
4413
4414 let tunnel_id = [0xEE; 32];
4415 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
4416 assert_eq!(engine.tunnel_table().len(), 1);
4417
4418 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
4419
4420 engine.tick(
4422 1000.0 + constants::DESTINATION_TIMEOUT + constants::TABLES_CULL_INTERVAL + 1.0,
4423 &mut rng,
4424 );
4425
4426 assert_eq!(engine.tunnel_table().len(), 0);
4427 }
4428
4429 #[test]
4430 fn test_synthesize_tunnel() {
4431 let mut engine = TransportEngine::new(make_config(true));
4432 engine.register_interface(make_tunnel_interface(1));
4433
4434 let identity =
4435 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xFF; 32]));
4436 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
4437
4438 let actions = engine.synthesize_tunnel(&identity, InterfaceId(1), &mut rng);
4439
4440 assert_eq!(actions.len(), 1);
4442 match &actions[0] {
4443 TransportAction::TunnelSynthesize {
4444 interface,
4445 data,
4446 dest_hash,
4447 } => {
4448 assert_eq!(*interface, InterfaceId(1));
4449 assert_eq!(data.len(), tunnel::TUNNEL_SYNTH_LENGTH);
4450 let expected_dest = crate::destination::destination_hash(
4452 "rnstransport",
4453 &["tunnel", "synthesize"],
4454 None,
4455 );
4456 assert_eq!(*dest_hash, expected_dest);
4457 }
4458 _ => panic!("Expected TunnelSynthesize"),
4459 }
4460 }
4461
4462 fn synthesized_interface_hash(actions: &[TransportAction]) -> [u8; 32] {
4463 let data = actions
4464 .iter()
4465 .find_map(|action| match action {
4466 TransportAction::TunnelSynthesize { data, .. } => Some(data),
4467 _ => None,
4468 })
4469 .expect("tunnel synthesis action");
4470 let mut interface_hash = [0u8; 32];
4471 interface_hash.copy_from_slice(&data[64..96]);
4472 interface_hash
4473 }
4474
4475 #[test]
4476 fn tunnel_synthesis_uses_hash_cached_at_interface_registration() {
4477 let mut engine = TransportEngine::new(make_config(true));
4478 let mut interface = make_tunnel_interface(1);
4479 interface.name = String::from("registered-name");
4480 engine.register_interface(interface);
4481
4482 engine.interfaces.get_mut(&InterfaceId(1)).unwrap().name = String::from("later-name");
4486
4487 let identity =
4488 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xFF; 32]));
4489 let actions = engine.synthesize_tunnel(
4490 &identity,
4491 InterfaceId(1),
4492 &mut rns_crypto::FixedRng::new(&[0x11; 32]),
4493 );
4494
4495 assert_eq!(
4496 synthesized_interface_hash(&actions),
4497 hash::full_hash(b"registered-name")
4498 );
4499 }
4500
4501 #[test]
4502 fn replacing_interface_id_refreshes_cached_tunnel_hash() {
4503 let mut engine = TransportEngine::new(make_config(true));
4504 let mut first = make_tunnel_interface(1);
4505 first.name = String::from("first-name");
4506 engine.register_interface(first);
4507 let mut replacement = make_tunnel_interface(1);
4508 replacement.name = String::from("replacement-name");
4509 engine.register_interface(replacement);
4510
4511 let identity =
4512 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xFF; 32]));
4513 let actions = engine.synthesize_tunnel(
4514 &identity,
4515 InterfaceId(1),
4516 &mut rns_crypto::FixedRng::new(&[0x11; 32]),
4517 );
4518
4519 assert_eq!(
4520 synthesized_interface_hash(&actions),
4521 hash::full_hash(b"replacement-name")
4522 );
4523 assert_ne!(
4524 synthesized_interface_hash(&actions),
4525 hash::full_hash(b"first-name")
4526 );
4527 }
4528
4529 #[test]
4530 fn test_synthesize_tunnel_missing_interface_is_dropped() {
4531 let engine = TransportEngine::new(make_config(true));
4532
4533 let identity =
4534 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xFF; 32]));
4535 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
4536
4537 let actions = engine.synthesize_tunnel(&identity, InterfaceId(99), &mut rng);
4538
4539 assert!(actions.is_empty());
4540 }
4541
4542 #[test]
4543 fn test_synthesize_tunnel_public_only_identity_is_dropped() {
4544 let mut engine = TransportEngine::new(make_config(true));
4545 engine.register_interface(make_tunnel_interface(1));
4546
4547 let identity =
4548 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xFF; 32]));
4549 let public_key = identity.get_public_key().unwrap();
4550 let public_only_identity = rns_crypto::identity::Identity::from_public_key(&public_key);
4551 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
4552
4553 let actions = engine.synthesize_tunnel(&public_only_identity, InterfaceId(1), &mut rng);
4554
4555 assert!(actions.is_empty());
4556 }
4557
4558 fn make_path_request_data(dest_hash: &[u8; 16], tag: &[u8]) -> Vec<u8> {
4563 let mut data = Vec::new();
4564 data.extend_from_slice(dest_hash);
4565 data.extend_from_slice(tag);
4566 data
4567 }
4568
4569 fn make_transport_path_request_data(
4570 dest_hash: &[u8; 16],
4571 requestor_transport_id: &[u8; 16],
4572 tag: &[u8],
4573 ) -> Vec<u8> {
4574 let mut data = Vec::new();
4575 data.extend_from_slice(dest_hash);
4576 data.extend_from_slice(requestor_transport_id);
4577 data.extend_from_slice(tag);
4578 data
4579 }
4580
4581 fn assert_recursive_path_request_packet(raw: &[u8], dest: &[u8; 16], tag: &[u8]) {
4582 let packet = RawPacket::unpack(raw).expect("recursive path request packet");
4583 let path_request_dest =
4584 crate::destination::destination_hash("rnstransport", &["path", "request"], None);
4585
4586 assert_eq!(packet.flags.header_type, constants::HEADER_1);
4587 assert_eq!(packet.flags.transport_type, constants::TRANSPORT_BROADCAST);
4588 assert_eq!(packet.flags.destination_type, constants::DESTINATION_PLAIN);
4589 assert_eq!(packet.flags.packet_type, constants::PACKET_TYPE_DATA);
4590 assert_eq!(packet.hops, 0);
4591 assert_eq!(packet.context, constants::CONTEXT_NONE);
4592 assert_eq!(packet.destination_hash, path_request_dest);
4593
4594 let mut expected_data = Vec::new();
4595 expected_data.extend_from_slice(dest);
4596 expected_data.extend_from_slice(&[0x42; 16]);
4597 expected_data.extend_from_slice(tag);
4598 assert_eq!(packet.data, expected_data);
4599 }
4600
4601 #[test]
4602 fn test_path_request_forwarded_on_ap() {
4603 let mut engine = TransportEngine::new(make_config(true));
4604 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
4605 engine.register_interface(make_interface(2, constants::MODE_FULL));
4606
4607 let dest = [0xD1; 16];
4608 let tag = [0x01; 16];
4609 let data = make_path_request_data(&dest, &tag);
4610
4611 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4612
4613 assert_eq!(actions.len(), 1);
4615 match &actions[0] {
4616 TransportAction::SendOnInterface { interface, .. } => {
4617 assert_eq!(*interface, InterfaceId(2));
4618 }
4619 _ => panic!("Expected SendOnInterface for forwarded path request"),
4620 }
4621 assert!(engine.discovery_path_requests.contains_key(&dest));
4623 }
4624
4625 #[test]
4626 fn test_recursive_path_request_rebuilds_transport_payload() {
4627 let mut engine = TransportEngine::new(make_config(true));
4628 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
4629 engine.register_interface(make_interface(2, constants::MODE_FULL));
4630
4631 let dest = [0xD8; 16];
4632 let original_requestor_transport_id = [0x99; 16];
4633 let tag = [0x08; 16];
4634 let data = make_transport_path_request_data(&dest, &original_requestor_transport_id, &tag);
4635
4636 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4637
4638 assert_eq!(actions.len(), 1);
4639 match &actions[0] {
4640 TransportAction::SendOnInterface { interface, raw } => {
4641 assert_eq!(*interface, InterfaceId(2));
4642 assert_recursive_path_request_packet(raw.as_ref(), &dest, &tag);
4643 }
4644 _ => panic!("expected SendOnInterface for recursive path request"),
4645 }
4646 assert!(engine.discovery_path_requests.contains_key(&dest));
4647 }
4648
4649 #[test]
4650 fn test_path_request_forwarded_on_internal() {
4651 let mut engine = TransportEngine::new(make_config(true));
4652 engine.register_interface(make_interface(1, constants::MODE_INTERNAL));
4653 engine.register_interface(make_interface(2, constants::MODE_FULL));
4654
4655 let dest = [0xDB; 16];
4656 let tag = [0x0B; 16];
4657 let data = make_path_request_data(&dest, &tag);
4658
4659 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4660
4661 assert_eq!(actions.len(), 1);
4662 match &actions[0] {
4663 TransportAction::SendOnInterface { interface, raw } => {
4664 assert_eq!(*interface, InterfaceId(2));
4665 assert_recursive_path_request_packet(raw.as_ref(), &dest, &tag);
4666 }
4667 _ => panic!("expected SendOnInterface for recursive path request"),
4668 }
4669 assert!(engine.discovery_path_requests.contains_key(&dest));
4670 }
4671
4672 #[test]
4673 fn boundary_path_request_searches_only_boundary_and_gateway_interfaces() {
4674 let mut engine = TransportEngine::new(make_config(true));
4675 engine.register_interface(make_interface(1, constants::MODE_BOUNDARY));
4676 engine.register_interface(make_interface(2, constants::MODE_BOUNDARY));
4677 engine.register_interface(make_interface(3, constants::MODE_GATEWAY));
4678 engine.register_interface(make_interface(4, constants::MODE_FULL));
4679 engine.register_interface(make_interface(5, constants::MODE_ACCESS_POINT));
4680 engine.register_interface(make_interface(6, constants::MODE_INTERNAL));
4681
4682 let dest = [0xBC; 16];
4683 let tag = [0x21; 16];
4684 let data = make_path_request_data(&dest, &tag);
4685
4686 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4687
4688 let interfaces: Vec<_> = actions
4689 .iter()
4690 .filter_map(|action| match action {
4691 TransportAction::SendOnInterface { interface, raw } => {
4692 assert_recursive_path_request_packet(raw.as_ref(), &dest, &tag);
4693 Some(*interface)
4694 }
4695 _ => None,
4696 })
4697 .collect();
4698 assert_eq!(interfaces, vec![InterfaceId(2), InterfaceId(3)]);
4699 assert!(engine.discovery_path_requests.contains_key(&dest));
4700 }
4701
4702 #[test]
4703 fn recursive_prs_on_boundary_keeps_unfiltered_egress_behavior() {
4704 let mut engine = TransportEngine::new(make_config(true));
4705 let mut ingress = make_interface(1, constants::MODE_BOUNDARY);
4706 ingress.recursive_prs = true;
4707 engine.register_interface(ingress);
4708 engine.register_interface(make_interface(2, constants::MODE_FULL));
4709
4710 let dest = [0xBD; 16];
4711 let tag = [0x22; 16];
4712 let data = make_path_request_data(&dest, &tag);
4713
4714 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4715
4716 assert_eq!(actions.len(), 1);
4717 match &actions[0] {
4718 TransportAction::SendOnInterface { interface, raw } => {
4719 assert_eq!(*interface, InterfaceId(2));
4720 assert_recursive_path_request_packet(raw.as_ref(), &dest, &tag);
4721 }
4722 _ => panic!("expected SendOnInterface for recursive path request"),
4723 }
4724 assert!(engine.discovery_path_requests.contains_key(&dest));
4725 }
4726
4727 #[test]
4728 fn test_path_request_not_forwarded_on_full() {
4729 let mut engine = TransportEngine::new(make_config(true));
4730 engine.register_interface(make_interface(1, constants::MODE_FULL));
4731 engine.register_interface(make_interface(2, constants::MODE_FULL));
4732
4733 let dest = [0xD2; 16];
4734 let tag = [0x02; 16];
4735 let data = make_path_request_data(&dest, &tag);
4736
4737 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4738
4739 assert!(actions.is_empty());
4741 assert!(!engine.discovery_path_requests.contains_key(&dest));
4742 }
4743
4744 #[test]
4745 fn test_path_request_forwarded_on_full_with_recursive_prs() {
4746 let mut engine = TransportEngine::new(make_config(true));
4747 let mut ingress = make_interface(1, constants::MODE_FULL);
4748 ingress.recursive_prs = true;
4749 engine.register_interface(ingress);
4750 engine.register_interface(make_interface(2, constants::MODE_FULL));
4751
4752 let dest = [0xD9; 16];
4753 let tag = [0x09; 16];
4754 let data = make_path_request_data(&dest, &tag);
4755
4756 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4757
4758 assert_eq!(actions.len(), 1);
4759 match &actions[0] {
4760 TransportAction::SendOnInterface { interface, raw } => {
4761 assert_eq!(*interface, InterfaceId(2));
4762 assert_recursive_path_request_packet(raw.as_ref(), &dest, &tag);
4763 }
4764 _ => panic!("expected SendOnInterface for recursive path request"),
4765 }
4766 assert!(engine.discovery_path_requests.contains_key(&dest));
4767 }
4768
4769 #[test]
4770 fn test_recursive_prs_still_obeys_ingress_control() {
4771 let mut engine = TransportEngine::new(make_config(true));
4772 let mut ingress = make_interface(1, constants::MODE_FULL);
4773 ingress.recursive_prs = true;
4774 let ingress_config = crate::transport::types::IngressControlConfig::enabled();
4775 ingress.ip_freq = ingress_config.pr_burst_freq_new + 1.0;
4776 ingress.ingress_control = ingress_config;
4777 ingress.started = 1000.0;
4778 engine.register_interface(ingress);
4779 engine.register_interface(make_interface(2, constants::MODE_FULL));
4780
4781 let dest = [0xDA; 16];
4782 let tag = [0x0A; 16];
4783 let data = make_path_request_data(&dest, &tag);
4784
4785 let actions = engine.handle_path_request(&data, InterfaceId(1), 1001.0);
4786
4787 assert!(actions.is_empty());
4788 assert!(!engine.discovery_path_requests.contains_key(&dest));
4789 }
4790
4791 #[test]
4792 fn test_duplicate_discovery_path_request_is_suppressed() {
4793 let mut engine = TransportEngine::new(make_config(true));
4794 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
4795 engine.register_interface(make_interface(2, constants::MODE_FULL));
4796
4797 let dest = [0xD7; 16];
4798 let tag = [0x07; 16];
4799 let data = make_path_request_data(&dest, &tag);
4800
4801 let first = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4802 let second = engine.handle_path_request(&data, InterfaceId(1), 1001.0);
4803
4804 assert_eq!(first.len(), 1);
4805 assert!(
4806 second.is_empty(),
4807 "duplicate discovery request should be dropped"
4808 );
4809 assert_eq!(engine.discovery_pr_tags_count(), 1);
4810 }
4811
4812 #[test]
4813 fn test_path_request_ingress_burst_suppresses_recursive_discovery() {
4814 let mut engine = TransportEngine::new(make_config(true));
4815 let mut ingress = make_interface(1, constants::MODE_ACCESS_POINT);
4816 ingress.ingress_control.enabled = true;
4817 ingress.ip_freq = constants::IC_PR_BURST_FREQ + 1.0;
4818 engine.register_interface(ingress);
4819 engine.register_interface(make_interface(2, constants::MODE_FULL));
4820
4821 let dest = [0xE1; 16];
4822 let tag = [0x11; 16];
4823 let data = make_path_request_data(&dest, &tag);
4824
4825 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4826
4827 assert!(actions.is_empty());
4828 assert!(!engine.discovery_path_requests.contains_key(&dest));
4829 }
4830
4831 #[test]
4832 fn test_path_request_egress_limit_skips_only_limited_interface() {
4833 let mut engine = TransportEngine::new(make_config(true));
4834 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
4835
4836 let mut limited = make_interface(2, constants::MODE_FULL);
4837 limited.ingress_control.egress_enabled = true;
4838 limited.op_freq = constants::EC_PR_FREQ + 1.0;
4839 limited.op_samples = constants::IC_BURST_MIN_SAMPLES;
4840 engine.register_interface(limited);
4841
4842 let mut allowed = make_interface(3, constants::MODE_FULL);
4843 allowed.ingress_control.egress_enabled = true;
4844 allowed.op_freq = constants::EC_PR_FREQ - 1.0;
4845 allowed.op_samples = constants::IC_BURST_MIN_SAMPLES;
4846 engine.register_interface(allowed);
4847
4848 let dest = [0xE2; 16];
4849 let tag = [0x12; 16];
4850 let data = make_path_request_data(&dest, &tag);
4851
4852 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4853
4854 assert_eq!(actions.len(), 1);
4855 match &actions[0] {
4856 TransportAction::SendOnInterface { interface, .. } => {
4857 assert_eq!(*interface, InterfaceId(3))
4858 }
4859 _ => panic!("expected SendOnInterface for the unlimited egress interface"),
4860 }
4861 assert!(engine.discovery_path_requests.contains_key(&dest));
4862 }
4863
4864 #[test]
4865 fn test_recursive_path_request_skips_interface_with_queued_announces() {
4866 let mut engine = TransportEngine::new(make_config(true));
4867 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
4868 let mut blocked = make_interface(2, constants::MODE_FULL);
4869 blocked.bitrate = Some(1_000);
4870 engine.register_interface(blocked);
4871 engine.register_interface(make_interface(3, constants::MODE_FULL));
4872
4873 let _ = engine.announce_queues.gate_announce(
4874 InterfaceId(2),
4875 vec![0xAA; 100].into(),
4876 [0xA0; 16],
4877 1,
4878 900.0,
4879 900.0,
4880 Some(1_000),
4881 None,
4882 constants::ANNOUNCE_CAP,
4883 );
4884 let _ = engine.announce_queues.gate_announce(
4885 InterfaceId(2),
4886 vec![0xBB; 100].into(),
4887 [0xB0; 16],
4888 1,
4889 901.0,
4890 901.0,
4891 Some(1_000),
4892 None,
4893 constants::ANNOUNCE_CAP,
4894 );
4895
4896 let dest = [0xE3; 16];
4897 let tag = [0x13; 16];
4898 let data = make_path_request_data(&dest, &tag);
4899 let actions = engine.handle_path_request(&data, InterfaceId(1), 902.0);
4900
4901 assert_eq!(actions.len(), 1);
4902 match &actions[0] {
4903 TransportAction::SendOnInterface { interface, .. } => {
4904 assert_eq!(*interface, InterfaceId(3));
4905 }
4906 _ => panic!("expected SendOnInterface for the unqueued egress interface"),
4907 }
4908 assert!(engine.discovery_path_requests.contains_key(&dest));
4909 }
4910
4911 #[test]
4912 fn test_recursive_path_request_skips_interface_with_active_announce_cap() {
4913 let mut engine = TransportEngine::new(make_config(true));
4914 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
4915 let mut blocked = make_interface(2, constants::MODE_FULL);
4916 blocked.bitrate = Some(1_000);
4917 engine.register_interface(blocked);
4918
4919 let _ = engine.announce_queues.gate_announce(
4920 InterfaceId(2),
4921 vec![0xAA; 100].into(),
4922 [0xA0; 16],
4923 1,
4924 900.0,
4925 900.0,
4926 Some(1_000),
4927 None,
4928 constants::ANNOUNCE_CAP,
4929 );
4930
4931 let dest = [0xE4; 16];
4932 let tag = [0x14; 16];
4933 let data = make_path_request_data(&dest, &tag);
4934 let actions = engine.handle_path_request(&data, InterfaceId(1), 901.0);
4935
4936 assert!(actions.is_empty());
4937 assert!(!engine.discovery_path_requests.contains_key(&dest));
4938 }
4939
4940 #[test]
4941 fn test_recursive_path_request_reserves_announce_cap_on_sent_interface() {
4942 let mut engine = TransportEngine::new(make_config(true));
4943 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
4944 let mut egress = make_interface(2, constants::MODE_FULL);
4945 egress.bitrate = Some(1_000);
4946 engine.register_interface(egress);
4947
4948 let dest = [0xE5; 16];
4949 let tag = [0x15; 16];
4950 let data = make_path_request_data(&dest, &tag);
4951 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
4952
4953 assert_eq!(actions.len(), 1);
4954 let queue = engine
4955 .announce_queues
4956 .queue_for(&InterfaceId(2))
4957 .expect("sent recursive PR should create announce-cap state");
4958 assert!(
4959 queue.announce_allowed_at > 1000.0,
4960 "recursive PR should reserve announce-cap airtime"
4961 );
4962 assert!(queue.entries.is_empty());
4963 }
4964
4965 #[test]
4966 fn test_discovery_pr_tags_fifo_eviction() {
4967 let mut config = make_config(true);
4968 config.max_discovery_pr_tags = 2;
4969 let mut engine = TransportEngine::new(config);
4970
4971 let dest1 = [0xA1; 16];
4972 let dest2 = [0xA2; 16];
4973 let dest3 = [0xA3; 16];
4974 let tag1 = [0x01; 16];
4975 let tag2 = [0x02; 16];
4976 let tag3 = [0x03; 16];
4977
4978 engine.handle_path_request(
4979 &make_path_request_data(&dest1, &tag1),
4980 InterfaceId(1),
4981 1000.0,
4982 );
4983 engine.handle_path_request(
4984 &make_path_request_data(&dest2, &tag2),
4985 InterfaceId(1),
4986 1001.0,
4987 );
4988 assert_eq!(engine.discovery_pr_tags_count(), 2);
4989
4990 let unique1 = make_unique_tag(dest1, &tag1);
4991 let unique2 = make_unique_tag(dest2, &tag2);
4992 assert!(engine.has_discovery_pr_tag(&unique1));
4993 assert!(engine.has_discovery_pr_tag(&unique2));
4994
4995 engine.handle_path_request(
4996 &make_path_request_data(&dest3, &tag3),
4997 InterfaceId(1),
4998 1002.0,
4999 );
5000 assert_eq!(engine.discovery_pr_tags_count(), 2);
5001 assert!(!engine.has_discovery_pr_tag(&unique1));
5002 assert!(engine.has_discovery_pr_tag(&unique2));
5003
5004 engine.handle_path_request(
5005 &make_path_request_data(&dest1, &tag1),
5006 InterfaceId(1),
5007 1003.0,
5008 );
5009 assert_eq!(engine.discovery_pr_tags_count(), 2);
5010 assert!(engine.has_discovery_pr_tag(&unique1));
5011 }
5012
5013 #[test]
5014 fn test_path_destination_cap_evicts_oldest_and_clears_state() {
5015 let mut config = make_config(false);
5016 config.max_path_destinations = 2;
5017 let mut engine = TransportEngine::new(config);
5018 engine.register_interface(make_interface(1, constants::MODE_FULL));
5019
5020 let dest1 = [0xB1; 16];
5021 let dest2 = [0xB2; 16];
5022 let dest3 = [0xB3; 16];
5023
5024 engine.upsert_path_destination(
5025 dest1,
5026 make_path_entry(1000.0, 1, InterfaceId(1), [0x11; 16]),
5027 1000.0,
5028 );
5029 engine.upsert_path_destination(
5030 dest2,
5031 make_path_entry(1001.0, 1, InterfaceId(1), [0x22; 16]),
5032 1001.0,
5033 );
5034 engine
5035 .path_states
5036 .insert(dest1, constants::STATE_UNRESPONSIVE);
5037
5038 engine.upsert_path_destination(
5039 dest3,
5040 make_path_entry(1002.0, 1, InterfaceId(1), [0x33; 16]),
5041 1002.0,
5042 );
5043
5044 assert_eq!(engine.path_table_count(), 2);
5045 assert!(!engine.has_path(&dest1));
5046 assert!(engine.has_path(&dest2));
5047 assert!(engine.has_path(&dest3));
5048 assert!(!engine.path_states.contains_key(&dest1));
5049 assert_eq!(engine.path_destination_cap_evict_count(), 1);
5050 }
5051
5052 #[test]
5053 fn test_existing_path_destination_update_does_not_trigger_cap_eviction() {
5054 let mut config = make_config(false);
5055 config.max_path_destinations = 2;
5056 config.max_paths_per_destination = 2;
5057 let mut engine = TransportEngine::new(config);
5058 engine.register_interface(make_interface(1, constants::MODE_FULL));
5059
5060 let dest1 = [0xC1; 16];
5061 let dest2 = [0xC2; 16];
5062
5063 engine.upsert_path_destination(
5064 dest1,
5065 make_path_entry(1000.0, 2, InterfaceId(1), [0x11; 16]),
5066 1000.0,
5067 );
5068 engine.upsert_path_destination(
5069 dest2,
5070 make_path_entry(1001.0, 2, InterfaceId(1), [0x22; 16]),
5071 1001.0,
5072 );
5073
5074 engine.upsert_path_destination(
5075 dest2,
5076 make_path_entry(1002.0, 1, InterfaceId(1), [0x23; 16]),
5077 1002.0,
5078 );
5079
5080 assert_eq!(engine.path_table_count(), 2);
5081 assert!(engine.has_path(&dest1));
5082 assert!(engine.has_path(&dest2));
5083 }
5084
5085 #[test]
5086 fn test_roaming_loop_prevention() {
5087 let mut engine = TransportEngine::new(make_config(true));
5088 engine.register_interface(make_interface(1, constants::MODE_ROAMING));
5089
5090 let dest = [0xD3; 16];
5091 engine.path_table.insert(
5093 dest,
5094 PathSet::from_single(
5095 PathEntry {
5096 timestamp: 900.0,
5097 next_hop: [0xAA; 16],
5098 hops: 2,
5099 expires: 9999.0,
5100 random_blobs: Vec::new(),
5101 receiving_interface: InterfaceId(1),
5102 packet_hash: [0; 32],
5103 announce_raw: None,
5104 },
5105 1,
5106 ),
5107 );
5108
5109 let tag = [0x03; 16];
5110 let data = make_path_request_data(&dest, &tag);
5111
5112 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
5113
5114 assert!(actions.is_empty());
5116 assert!(!engine.announce_table.contains_key(&dest));
5117 }
5118
5119 fn make_announce_raw(dest_hash: &[u8; 16], payload: &[u8]) -> Vec<u8> {
5121 let flags: u8 = 0x01; let mut raw = Vec::new();
5125 raw.push(flags);
5126 raw.push(0x02); raw.extend_from_slice(dest_hash);
5128 raw.push(constants::CONTEXT_NONE);
5129 raw.extend_from_slice(payload);
5130 raw
5131 }
5132
5133 #[test]
5134 fn test_path_request_populates_announce_entry_from_raw() {
5135 let mut engine = TransportEngine::new(make_config(true));
5136 engine.register_interface(make_interface(1, constants::MODE_FULL));
5137 engine.register_interface(make_interface(2, constants::MODE_FULL));
5138
5139 let dest = [0xD5; 16];
5140 let payload = vec![0xAB; 32]; let announce_raw = make_announce_raw(&dest, &payload);
5142
5143 engine.path_table.insert(
5144 dest,
5145 PathSet::from_single(
5146 PathEntry {
5147 timestamp: 900.0,
5148 next_hop: [0xBB; 16],
5149 hops: 2,
5150 expires: 9999.0,
5151 random_blobs: Vec::new(),
5152 receiving_interface: InterfaceId(2),
5153 packet_hash: [0; 32],
5154 announce_raw: Some(announce_raw.clone()),
5155 },
5156 1,
5157 ),
5158 );
5159
5160 let tag = [0x05; 16];
5161 let data = make_path_request_data(&dest, &tag);
5162 let _actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
5163
5164 let entry = engine
5166 .announce_table
5167 .get(&dest)
5168 .expect("announce entry must exist");
5169 assert_eq!(entry.packet_raw, announce_raw);
5170 assert_eq!(entry.packet_data, payload);
5171 assert!(entry.block_rebroadcasts);
5172 }
5173
5174 #[test]
5175 fn test_path_request_discovers_when_known_path_has_no_announce_raw() {
5176 let mut engine = TransportEngine::new(make_config(true));
5177 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
5178 engine.register_interface(make_interface(2, constants::MODE_FULL));
5179
5180 let dest = [0xD6; 16];
5181
5182 engine.path_table.insert(
5183 dest,
5184 PathSet::from_single(
5185 PathEntry {
5186 timestamp: 900.0,
5187 next_hop: [0xCC; 16],
5188 hops: 1,
5189 expires: 9999.0,
5190 random_blobs: Vec::new(),
5191 receiving_interface: InterfaceId(2),
5192 packet_hash: [0; 32],
5193 announce_raw: None, },
5195 1,
5196 ),
5197 );
5198
5199 let tag = [0x06; 16];
5200 let data = make_path_request_data(&dest, &tag);
5201 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
5202
5203 assert!(!engine.announce_table.contains_key(&dest));
5204 assert_eq!(actions.len(), 1);
5205 match &actions[0] {
5206 TransportAction::SendOnInterface { interface, raw } => {
5207 assert_eq!(*interface, InterfaceId(2));
5208 assert_recursive_path_request_packet(raw.as_ref(), &dest, &tag);
5209 }
5210 _ => panic!("expected SendOnInterface for recursive path request"),
5211 }
5212 assert!(engine.discovery_path_requests.contains_key(&dest));
5213 }
5214
5215 #[test]
5216 fn test_discovery_request_consumed_on_announce() {
5217 let mut engine = TransportEngine::new(make_config(true));
5218 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
5219
5220 let dest = [0xD4; 16];
5221
5222 engine.discovery_path_requests.insert(
5224 dest,
5225 DiscoveryPathRequest {
5226 timestamp: 900.0,
5227 requesting_interface: InterfaceId(1),
5228 },
5229 );
5230
5231 let iface = engine.discovery_path_requests_waiting(&dest);
5233 assert_eq!(iface, Some(InterfaceId(1)));
5234
5235 assert!(!engine.discovery_path_requests.contains_key(&dest));
5237 assert_eq!(engine.discovery_path_requests_waiting(&dest), None);
5238 }
5239
5240 #[test]
5241 fn test_pending_path_request_announce_bypasses_ingress_control() {
5242 let mut engine = TransportEngine::new(make_config(true));
5243 let mut inbound = make_interface(1, constants::MODE_FULL);
5244 inbound.ingress_control = crate::transport::types::IngressControlConfig::enabled();
5245 inbound.ia_freq = 10_000.0;
5246 inbound.started = 0.0;
5247 engine.register_interface(inbound);
5248 engine.register_interface(make_interface(2, constants::MODE_ACCESS_POINT));
5249
5250 let identity =
5251 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
5252 let dest_hash = crate::destination::destination_hash(
5253 "ingress",
5254 &["path-request"],
5255 Some(identity.hash()),
5256 );
5257 let name_hash = crate::destination::name_hash("ingress", &["path-request"]);
5258 let announce_raw = build_announce_for_issue4(&dest_hash, &name_hash);
5259
5260 engine.discovery_path_requests.insert(
5261 dest_hash,
5262 DiscoveryPathRequest {
5263 timestamp: 999.0,
5264 requesting_interface: InterfaceId(2),
5265 },
5266 );
5267
5268 let mut rng = rns_crypto::FixedRng::new(&[0x88; 32]);
5269 let actions = engine.handle_inbound(
5270 InboundFrame {
5271 raw: &announce_raw,
5272 iface: InterfaceId(1),
5273 now: 1000.0,
5274 rx: RxMetadata {
5275 rssi: None,
5276 snr: None,
5277 },
5278 },
5279 &mut rng,
5280 );
5281
5282 assert_eq!(engine.held_announce_count(&InterfaceId(1)), 0);
5283 assert!(engine.has_path(&dest_hash));
5284 assert!(!engine.discovery_path_requests.contains_key(&dest_hash));
5285 assert!(actions.iter().any(|a| {
5286 matches!(
5287 a,
5288 TransportAction::AnnounceReceived {
5289 destination_hash,
5290 receiving_interface: InterfaceId(1),
5291 ..
5292 } if *destination_hash == dest_hash
5293 )
5294 }));
5295
5296 let entry = engine
5297 .announce_table
5298 .get(&dest_hash)
5299 .expect("path response announce should be queued");
5300 assert!(entry.block_rebroadcasts);
5301 assert_eq!(entry.attached_interface, Some(InterfaceId(2)));
5302 }
5303
5304 fn build_announce_for_issue4(dest_hash: &[u8; 16], name_hash: &[u8; 10]) -> Vec<u8> {
5310 let identity =
5311 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
5312 let random_hash = [0x42u8; 10];
5313 let (announce_data, _) = crate::announce::AnnounceData::pack(
5314 &identity,
5315 dest_hash,
5316 name_hash,
5317 &random_hash,
5318 None,
5319 None,
5320 )
5321 .unwrap();
5322 let flags = PacketFlags {
5323 header_type: constants::HEADER_1,
5324 context_flag: constants::FLAG_UNSET,
5325 transport_type: constants::TRANSPORT_BROADCAST,
5326 destination_type: constants::DESTINATION_SINGLE,
5327 packet_type: constants::PACKET_TYPE_ANNOUNCE,
5328 };
5329 RawPacket::pack(
5330 flags,
5331 0,
5332 dest_hash,
5333 None,
5334 constants::CONTEXT_NONE,
5335 &announce_data,
5336 )
5337 .unwrap()
5338 .raw
5339 }
5340
5341 #[test]
5342 fn test_ingress_held_announce_preserves_rx_metadata_on_release() {
5343 let mut engine = TransportEngine::new(make_config(true));
5344 let mut inbound = make_interface(1, constants::MODE_FULL);
5345 inbound.ingress_control = crate::transport::types::IngressControlConfig::enabled();
5346 inbound.ia_freq = constants::IC_BURST_FREQ + 1.0;
5347 inbound.started = 0.0;
5348 engine.register_interface(inbound);
5349
5350 let identity =
5351 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
5352 let dest_hash =
5353 crate::destination::destination_hash("ingress", &["rx"], Some(identity.hash()));
5354 let name_hash = crate::destination::name_hash("ingress", &["rx"]);
5355 let announce_raw = build_announce_for_issue4(&dest_hash, &name_hash);
5356 let rx = RxMetadata {
5357 rssi: Some(-91),
5358 snr: Some(5.5),
5359 };
5360
5361 let mut rng = rns_crypto::FixedRng::new(&[0x88; 32]);
5362 let held_actions = engine.handle_inbound(
5363 InboundFrame::new(&announce_raw, InterfaceId(1), 10000.0).with_rx(rx),
5364 &mut rng,
5365 );
5366
5367 assert!(held_actions.is_empty());
5368 assert_eq!(engine.held_announce_count(&InterfaceId(1)), 1);
5369 assert!(!engine.has_path(&dest_hash));
5370
5371 engine
5372 .interfaces
5373 .get_mut(&InterfaceId(1))
5374 .expect("interface must exist")
5375 .ia_freq = 0.0;
5376
5377 let released_actions = engine.tick(10000.0 + constants::IC_BURST_PENALTY + 1.0, &mut rng);
5378
5379 let released_rx = released_actions.iter().find_map(|action| match action {
5380 TransportAction::AnnounceReceived {
5381 destination_hash,
5382 rx: action_rx,
5383 ..
5384 } if *destination_hash == dest_hash => Some(*action_rx),
5385 _ => None,
5386 });
5387
5388 assert_eq!(released_rx, Some(rx));
5389 assert_eq!(engine.held_announce_count(&InterfaceId(1)), 0);
5390 assert!(engine.has_path(&dest_hash));
5391 }
5392
5393 #[test]
5394 fn test_issue4_local_client_single_data_to_1hop_rewrites_on_outbound() {
5395 let mut engine = TransportEngine::new(make_config(false));
5400 engine.register_interface(make_local_client_interface(1));
5401
5402 let identity =
5403 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
5404 let dest_hash =
5405 crate::destination::destination_hash("issue4", &["test"], Some(identity.hash()));
5406 let name_hash = crate::destination::name_hash("issue4", &["test"]);
5407 let announce_raw = build_announce_for_issue4(&dest_hash, &name_hash);
5408
5409 let mut announce_packet = RawPacket::unpack(&announce_raw).unwrap();
5413 announce_packet.raw[1] = 1;
5414 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
5415 engine.handle_inbound(
5416 InboundFrame {
5417 raw: &announce_packet.raw,
5418 iface: InterfaceId(1),
5419 now: 1000.0,
5420 rx: RxMetadata {
5421 rssi: None,
5422 snr: None,
5423 },
5424 },
5425 &mut rng,
5426 );
5427 assert!(engine.has_path(&dest_hash));
5428 assert_eq!(engine.hops_to(&dest_hash), Some(1));
5429
5430 let data_flags = PacketFlags {
5432 header_type: constants::HEADER_1,
5433 context_flag: constants::FLAG_UNSET,
5434 transport_type: constants::TRANSPORT_BROADCAST,
5435 destination_type: constants::DESTINATION_SINGLE,
5436 packet_type: constants::PACKET_TYPE_DATA,
5437 };
5438 let data_packet = RawPacket::pack(
5439 data_flags,
5440 0,
5441 &dest_hash,
5442 None,
5443 constants::CONTEXT_NONE,
5444 b"hello",
5445 )
5446 .unwrap();
5447
5448 let actions =
5449 engine.handle_outbound(&data_packet, constants::DESTINATION_SINGLE, None, 1001.0);
5450
5451 let send = actions.iter().find_map(|a| match a {
5452 TransportAction::SendOnInterface { interface, raw } => Some((interface, raw)),
5453 _ => None,
5454 });
5455 let (interface, raw) = send.expect("shared client should emit a transport-injected packet");
5456 assert_eq!(*interface, InterfaceId(1));
5457 let flags = PacketFlags::unpack(raw[0]);
5458 assert_eq!(flags.header_type, constants::HEADER_2);
5459 assert_eq!(flags.transport_type, constants::TRANSPORT_TRANSPORT);
5460 }
5461
5462 #[test]
5463 fn test_local_client_forward_to_external_applies_local_hops_delta() {
5464 let daemon_id = [0x42; 16];
5465 let mut config = make_config(true);
5466 config.local_hops_delta = 5;
5467 let mut engine = TransportEngine::new(config);
5468 engine.register_interface(make_local_client_interface(1));
5469 engine.register_interface(make_interface(2, constants::MODE_FULL));
5470
5471 let dest_hash = [0xB7; 16];
5472 engine.upsert_path_destination(
5473 dest_hash,
5474 make_path_entry(1000.0, 1, InterfaceId(2), dest_hash),
5475 1000.0,
5476 );
5477
5478 let flags = PacketFlags {
5479 header_type: constants::HEADER_2,
5480 context_flag: constants::FLAG_UNSET,
5481 transport_type: constants::TRANSPORT_TRANSPORT,
5482 destination_type: constants::DESTINATION_SINGLE,
5483 packet_type: constants::PACKET_TYPE_DATA,
5484 };
5485 let packet = RawPacket::pack(
5486 flags,
5487 0,
5488 &dest_hash,
5489 Some(&daemon_id),
5490 constants::CONTEXT_NONE,
5491 b"from local client",
5492 )
5493 .unwrap();
5494
5495 let mut rng = rns_crypto::FixedRng::new(&[0x44; 32]);
5496 let actions = engine.handle_inbound(
5497 InboundFrame::new(&packet.raw, InterfaceId(1), 1001.0),
5498 &mut rng,
5499 );
5500
5501 let raw = actions.iter().find_map(|action| match action {
5502 TransportAction::SendOnInterface { interface, raw } if *interface == InterfaceId(2) => {
5503 Some(raw)
5504 }
5505 _ => None,
5506 });
5507 let raw = raw.expect("local-client DATA should be forwarded externally");
5508 assert_eq!(raw[1], 5);
5509 let forwarded_flags = PacketFlags::unpack(raw[0]);
5510 assert_eq!(forwarded_flags.header_type, constants::HEADER_1);
5511 assert_eq!(&raw[2..18], &dest_hash);
5512 }
5513
5514 #[test]
5515 fn test_local_client_link_routing_to_external_applies_local_hops_delta() {
5516 let mut config = make_config(true);
5517 config.local_hops_delta = 5;
5518 let mut engine = TransportEngine::new(config);
5519 engine.register_interface(make_local_client_interface(1));
5520 engine.register_interface(make_interface(2, constants::MODE_FULL));
5521
5522 let link_id = [0x4C; 16];
5523 engine.register_link(
5524 link_id,
5525 LinkEntry {
5526 timestamp: 1000.0,
5527 next_hop_transport_id: [0xAA; 16],
5528 next_hop_interface: InterfaceId(2),
5529 remaining_hops: 3,
5530 received_interface: InterfaceId(1),
5531 taken_hops: 0,
5532 destination_hash: [0xBB; 16],
5533 validated: true,
5534 proof_timeout: 1100.0,
5535 },
5536 );
5537
5538 let packet = RawPacket::pack(
5539 PacketFlags {
5540 header_type: constants::HEADER_1,
5541 context_flag: constants::FLAG_UNSET,
5542 transport_type: constants::TRANSPORT_BROADCAST,
5543 destination_type: constants::DESTINATION_LINK,
5544 packet_type: constants::PACKET_TYPE_DATA,
5545 },
5546 0,
5547 &link_id,
5548 None,
5549 constants::CONTEXT_CHANNEL,
5550 b"link data",
5551 )
5552 .unwrap();
5553
5554 let mut rng = rns_crypto::FixedRng::new(&[0x45; 32]);
5555 let actions = engine.handle_inbound(
5556 InboundFrame::new(&packet.raw, InterfaceId(1), 1001.0),
5557 &mut rng,
5558 );
5559
5560 let raw = actions.iter().find_map(|action| match action {
5561 TransportAction::SendOnInterface { interface, raw } if *interface == InterfaceId(2) => {
5562 Some(raw)
5563 }
5564 _ => None,
5565 });
5566 let raw = raw.expect("local-client link packet should be forwarded externally");
5567 assert_eq!(raw[1], 5);
5568 }
5569
5570 #[test]
5571 fn test_instance_local_link_routing_preserves_hops() {
5572 let mut config = make_config(true);
5573 config.local_hops_delta = 5;
5574 let mut engine = TransportEngine::new(config);
5575 engine.register_interface(make_local_client_interface(1));
5576 engine.register_interface(make_local_client_interface(2));
5577
5578 let link_id = [0x4D; 16];
5579 engine.register_link(
5580 link_id,
5581 LinkEntry {
5582 timestamp: 1000.0,
5583 next_hop_transport_id: [0xAA; 16],
5584 next_hop_interface: InterfaceId(2),
5585 remaining_hops: 0,
5586 received_interface: InterfaceId(1),
5587 taken_hops: 0,
5588 destination_hash: [0xBB; 16],
5589 validated: true,
5590 proof_timeout: 1100.0,
5591 },
5592 );
5593
5594 let packet = RawPacket::pack(
5595 PacketFlags {
5596 header_type: constants::HEADER_1,
5597 context_flag: constants::FLAG_UNSET,
5598 transport_type: constants::TRANSPORT_BROADCAST,
5599 destination_type: constants::DESTINATION_LINK,
5600 packet_type: constants::PACKET_TYPE_DATA,
5601 },
5602 0,
5603 &link_id,
5604 None,
5605 constants::CONTEXT_CHANNEL,
5606 b"local link data",
5607 )
5608 .unwrap();
5609
5610 let mut rng = rns_crypto::FixedRng::new(&[0x46; 32]);
5611 let actions = engine.handle_inbound(
5612 InboundFrame::new(&packet.raw, InterfaceId(1), 1001.0),
5613 &mut rng,
5614 );
5615
5616 let raw = actions.iter().find_map(|action| match action {
5617 TransportAction::SendOnInterface { interface, raw } if *interface == InterfaceId(2) => {
5618 Some(raw)
5619 }
5620 _ => None,
5621 });
5622 let raw = raw.expect("instance-local link packet should be forwarded");
5623 assert_eq!(raw[1], 0);
5624 }
5625
5626 #[test]
5627 fn test_local_client_proof_to_external_applies_local_hops_delta() {
5628 let mut config = make_config(true);
5629 config.local_hops_delta = 5;
5630 let mut engine = TransportEngine::new(config);
5631 engine.register_interface(make_local_client_interface(1));
5632 engine.register_interface(make_interface(2, constants::MODE_FULL));
5633
5634 let proof_dest = [0xA5; 16];
5635 engine.reverse_table.insert(
5636 proof_dest,
5637 tables::ReverseEntry {
5638 receiving_interface: InterfaceId(2),
5639 outbound_interface: InterfaceId(1),
5640 timestamp: 1000.0,
5641 },
5642 );
5643
5644 let packet = RawPacket::pack(
5645 PacketFlags {
5646 header_type: constants::HEADER_1,
5647 context_flag: constants::FLAG_UNSET,
5648 transport_type: constants::TRANSPORT_BROADCAST,
5649 destination_type: constants::DESTINATION_SINGLE,
5650 packet_type: constants::PACKET_TYPE_PROOF,
5651 },
5652 0,
5653 &proof_dest,
5654 None,
5655 constants::CONTEXT_NONE,
5656 &[0xCC; 32],
5657 )
5658 .unwrap();
5659
5660 let mut rng = rns_crypto::FixedRng::new(&[0x47; 32]);
5661 let actions = engine.handle_inbound(
5662 InboundFrame::new(&packet.raw, InterfaceId(1), 1001.0),
5663 &mut rng,
5664 );
5665
5666 let raw = actions.iter().find_map(|action| match action {
5667 TransportAction::SendOnInterface { interface, raw } if *interface == InterfaceId(2) => {
5668 Some(raw)
5669 }
5670 _ => None,
5671 });
5672 let raw = raw.expect("local-client proof should be forwarded externally");
5673 assert_eq!(raw[1], 5);
5674 }
5675
5676 #[test]
5677 fn test_proof_for_local_client_preserves_hops() {
5678 let mut config = make_config(true);
5679 config.local_hops_delta = 5;
5680 let mut engine = TransportEngine::new(config);
5681 engine.register_interface(make_local_client_interface(1));
5682 engine.register_interface(make_local_client_interface(2));
5683
5684 let proof_dest = [0xA6; 16];
5685 engine.reverse_table.insert(
5686 proof_dest,
5687 tables::ReverseEntry {
5688 receiving_interface: InterfaceId(2),
5689 outbound_interface: InterfaceId(1),
5690 timestamp: 1000.0,
5691 },
5692 );
5693
5694 let packet = RawPacket::pack(
5695 PacketFlags {
5696 header_type: constants::HEADER_1,
5697 context_flag: constants::FLAG_UNSET,
5698 transport_type: constants::TRANSPORT_BROADCAST,
5699 destination_type: constants::DESTINATION_SINGLE,
5700 packet_type: constants::PACKET_TYPE_PROOF,
5701 },
5702 0,
5703 &proof_dest,
5704 None,
5705 constants::CONTEXT_NONE,
5706 &[0xCD; 32],
5707 )
5708 .unwrap();
5709
5710 let mut rng = rns_crypto::FixedRng::new(&[0x48; 32]);
5711 let actions = engine.handle_inbound(
5712 InboundFrame::new(&packet.raw, InterfaceId(1), 1001.0),
5713 &mut rng,
5714 );
5715
5716 let raw = actions.iter().find_map(|action| match action {
5717 TransportAction::SendOnInterface { interface, raw } if *interface == InterfaceId(2) => {
5718 Some(raw)
5719 }
5720 _ => None,
5721 });
5722 let raw = raw.expect("proof for local client should be forwarded");
5723 assert_eq!(raw[1], 0);
5724 }
5725
5726 #[test]
5727 fn test_issue4_external_data_to_shared_client_strips_transport_header() {
5728 let daemon_id = [0x42; 16];
5729 let mut engine = TransportEngine::new(make_config(true));
5730 engine.register_interface(make_interface(1, constants::MODE_FULL));
5731 engine.register_interface(make_local_client_interface(2));
5732
5733 let dest_hash = [0x99; 16];
5734 engine.upsert_path_destination(
5735 dest_hash,
5736 make_path_entry(1000.0, 1, InterfaceId(2), daemon_id),
5737 1000.0,
5738 );
5739
5740 let h2_flags = PacketFlags {
5741 header_type: constants::HEADER_2,
5742 context_flag: constants::FLAG_UNSET,
5743 transport_type: constants::TRANSPORT_TRANSPORT,
5744 destination_type: constants::DESTINATION_SINGLE,
5745 packet_type: constants::PACKET_TYPE_DATA,
5746 };
5747 let mut h2_raw = Vec::new();
5748 h2_raw.push(h2_flags.pack());
5749 h2_raw.push(0);
5750 h2_raw.extend_from_slice(&daemon_id);
5751 h2_raw.extend_from_slice(&dest_hash);
5752 h2_raw.push(constants::CONTEXT_NONE);
5753 h2_raw.extend_from_slice(b"hello shared client");
5754
5755 let mut rng = rns_crypto::FixedRng::new(&[0x22; 32]);
5756 let actions = engine.handle_inbound(
5757 InboundFrame {
5758 raw: &h2_raw,
5759 iface: InterfaceId(1),
5760 now: 1001.0,
5761 rx: RxMetadata {
5762 rssi: None,
5763 snr: None,
5764 },
5765 },
5766 &mut rng,
5767 );
5768
5769 let raw = actions.iter().find_map(|a| match a {
5770 TransportAction::SendOnInterface { interface, raw } if *interface == InterfaceId(2) => {
5771 Some(raw)
5772 }
5773 _ => None,
5774 });
5775 let raw = raw.expect("daemon should forward external DATA to shared client");
5776 let flags = PacketFlags::unpack(raw[0]);
5777 assert_eq!(flags.header_type, constants::HEADER_1);
5778 assert_eq!(flags.transport_type, constants::TRANSPORT_BROADCAST);
5779 assert_eq!(&raw[2..18], &dest_hash);
5780 assert_eq!(&raw[19..], b"hello shared client");
5781 }
5782
5783 #[test]
5784 fn test_issue4_external_data_to_1hop_via_transport_works() {
5785 let daemon_id = [0x42; 16];
5791 let mut engine = TransportEngine::new(TransportConfig {
5792 transport_enabled: true,
5793 identity_hash: Some(daemon_id),
5794 local_hops_delta: 0,
5795 prefer_shorter_path: false,
5796 max_paths_per_destination: 1,
5797 packet_hashlist_max_entries: constants::HASHLIST_MAXSIZE,
5798 max_discovery_pr_tags: constants::MAX_PR_TAGS,
5799 max_path_destinations: usize::MAX,
5800 max_tunnel_destinations_total: usize::MAX,
5801 destination_timeout_secs: constants::DESTINATION_TIMEOUT,
5802 announce_table_ttl_secs: constants::ANNOUNCE_TABLE_TTL,
5803 announce_table_max_bytes: constants::ANNOUNCE_TABLE_MAX_BYTES,
5804 announce_sig_cache_enabled: true,
5805 announce_sig_cache_max_entries: constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
5806 announce_sig_cache_ttl_secs: constants::ANNOUNCE_SIG_CACHE_TTL,
5807 announce_queue_max_entries: 256,
5808 announce_queue_max_interfaces: 1024,
5809 });
5810 engine.register_interface(make_interface(1, constants::MODE_FULL)); engine.register_interface(make_interface(2, constants::MODE_FULL)); let identity =
5814 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
5815 let dest_hash =
5816 crate::destination::destination_hash("issue4", &["ctrl"], Some(identity.hash()));
5817 let name_hash = crate::destination::name_hash("issue4", &["ctrl"]);
5818 let announce_raw = build_announce_for_issue4(&dest_hash, &name_hash);
5819
5820 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
5822 engine.handle_inbound(
5823 InboundFrame {
5824 raw: &announce_raw,
5825 iface: InterfaceId(2),
5826 now: 1000.0,
5827 rx: RxMetadata {
5828 rssi: None,
5829 snr: None,
5830 },
5831 },
5832 &mut rng,
5833 );
5834 assert_eq!(engine.hops_to(&dest_hash), Some(1));
5835
5836 let h2_flags = PacketFlags {
5839 header_type: constants::HEADER_2,
5840 context_flag: constants::FLAG_UNSET,
5841 transport_type: constants::TRANSPORT_TRANSPORT,
5842 destination_type: constants::DESTINATION_SINGLE,
5843 packet_type: constants::PACKET_TYPE_DATA,
5844 };
5845 let mut h2_raw = Vec::new();
5847 h2_raw.push(h2_flags.pack());
5848 h2_raw.push(0); h2_raw.extend_from_slice(&daemon_id); h2_raw.extend_from_slice(&dest_hash);
5851 h2_raw.push(constants::CONTEXT_NONE);
5852 h2_raw.extend_from_slice(b"hello via transport");
5853
5854 let mut rng2 = rns_crypto::FixedRng::new(&[0x22; 32]);
5855 let actions = engine.handle_inbound(
5856 InboundFrame {
5857 raw: &h2_raw,
5858 iface: InterfaceId(1),
5859 now: 1001.0,
5860 rx: RxMetadata {
5861 rssi: None,
5862 snr: None,
5863 },
5864 },
5865 &mut rng2,
5866 );
5867
5868 let has_send = actions.iter().any(|a| {
5870 matches!(
5871 a,
5872 TransportAction::SendOnInterface { interface, .. } if *interface == InterfaceId(2)
5873 )
5874 });
5875 assert!(
5876 has_send,
5877 "HEADER_2 transport packet should be forwarded (control test)"
5878 );
5879 }
5880}