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