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