1pub mod announce_proc;
2pub mod announce_queue;
3pub mod dedup;
4pub mod inbound;
5pub mod ingress_control;
6pub mod jobs;
7pub mod outbound;
8pub mod pathfinder;
9pub mod rate_limit;
10pub mod tables;
11pub mod tunnel;
12pub mod types;
13
14use alloc::collections::BTreeMap;
15use alloc::string::String;
16use alloc::vec::Vec;
17
18use rns_crypto::Rng;
19
20use crate::announce::AnnounceData;
21use crate::constants;
22use crate::hash;
23use crate::packet::RawPacket;
24
25use self::announce_proc::compute_path_expires;
26use self::announce_queue::AnnounceQueues;
27use self::dedup::PacketHashlist;
28use self::inbound::{
29 create_link_entry, create_reverse_entry, forward_transport_packet, route_proof_via_reverse,
30 route_via_link_table,
31};
32use self::ingress_control::IngressControl;
33use self::outbound::{route_outbound, should_transmit_announce};
34use self::pathfinder::{
35 decide_announce_multipath, extract_random_blob, timebase_from_random_blob, MultiPathDecision,
36};
37use self::rate_limit::AnnounceRateLimiter;
38use self::tables::{AnnounceEntry, DiscoveryPathRequest, LinkEntry, PathEntry, PathSet};
39use self::tunnel::TunnelTable;
40use self::types::{BlackholeEntry, InterfaceId, InterfaceInfo, TransportAction, TransportConfig};
41
42pub type PathTableRow = ([u8; 16], f64, [u8; 16], u8, f64, String);
43pub type RateTableRow = ([u8; 16], f64, u32, f64, Vec<f64>);
44
45pub struct TransportEngine {
50 config: TransportConfig,
51 path_table: BTreeMap<[u8; 16], PathSet>,
52 announce_table: BTreeMap<[u8; 16], AnnounceEntry>,
53 reverse_table: BTreeMap<[u8; 16], tables::ReverseEntry>,
54 link_table: BTreeMap<[u8; 16], LinkEntry>,
55 held_announces: BTreeMap<[u8; 16], AnnounceEntry>,
56 packet_hashlist: PacketHashlist,
57 rate_limiter: AnnounceRateLimiter,
58 path_states: BTreeMap<[u8; 16], u8>,
59 interfaces: BTreeMap<InterfaceId, InterfaceInfo>,
60 local_destinations: BTreeMap<[u8; 16], u8>,
61 blackholed_identities: BTreeMap<[u8; 16], BlackholeEntry>,
62 announce_queues: AnnounceQueues,
63 ingress_control: IngressControl,
64 tunnel_table: TunnelTable,
65 discovery_pr_tags: Vec<[u8; 32]>,
66 discovery_path_requests: BTreeMap<[u8; 16], DiscoveryPathRequest>,
67 announces_last_checked: f64,
69 tables_last_culled: f64,
70}
71
72impl TransportEngine {
73 pub fn new(config: TransportConfig) -> Self {
74 let packet_hashlist_max_entries = config.packet_hashlist_max_entries;
75 TransportEngine {
76 config,
77 path_table: BTreeMap::new(),
78 announce_table: BTreeMap::new(),
79 reverse_table: BTreeMap::new(),
80 link_table: BTreeMap::new(),
81 held_announces: BTreeMap::new(),
82 packet_hashlist: PacketHashlist::new(packet_hashlist_max_entries),
83 rate_limiter: AnnounceRateLimiter::new(),
84 path_states: BTreeMap::new(),
85 interfaces: BTreeMap::new(),
86 local_destinations: BTreeMap::new(),
87 blackholed_identities: BTreeMap::new(),
88 announce_queues: AnnounceQueues::new(),
89 ingress_control: IngressControl::new(),
90 tunnel_table: TunnelTable::new(),
91 discovery_pr_tags: Vec::new(),
92 discovery_path_requests: BTreeMap::new(),
93 announces_last_checked: 0.0,
94 tables_last_culled: 0.0,
95 }
96 }
97
98 pub fn register_interface(&mut self, info: InterfaceInfo) {
103 self.interfaces.insert(info.id, info);
104 }
105
106 pub fn deregister_interface(&mut self, id: InterfaceId) {
107 self.interfaces.remove(&id);
108 self.ingress_control.remove_interface(&id);
109 }
110
111 pub fn register_destination(&mut self, dest_hash: [u8; 16], dest_type: u8) {
116 self.local_destinations.insert(dest_hash, dest_type);
117 }
118
119 pub fn deregister_destination(&mut self, dest_hash: &[u8; 16]) {
120 self.local_destinations.remove(dest_hash);
121 }
122
123 pub fn has_path(&self, dest_hash: &[u8; 16]) -> bool {
128 self.path_table
129 .get(dest_hash)
130 .is_some_and(|ps| !ps.is_empty())
131 }
132
133 pub fn hops_to(&self, dest_hash: &[u8; 16]) -> Option<u8> {
134 self.path_table
135 .get(dest_hash)
136 .and_then(|ps| ps.primary())
137 .map(|e| e.hops)
138 }
139
140 pub fn next_hop(&self, dest_hash: &[u8; 16]) -> Option<[u8; 16]> {
141 self.path_table
142 .get(dest_hash)
143 .and_then(|ps| ps.primary())
144 .map(|e| e.next_hop)
145 }
146
147 pub fn next_hop_interface(&self, dest_hash: &[u8; 16]) -> Option<InterfaceId> {
148 self.path_table
149 .get(dest_hash)
150 .and_then(|ps| ps.primary())
151 .map(|e| e.receiving_interface)
152 }
153
154 pub fn mark_path_unresponsive(
164 &mut self,
165 dest_hash: &[u8; 16],
166 receiving_interface: Option<InterfaceId>,
167 ) {
168 if let Some(iface_id) = receiving_interface {
169 if let Some(info) = self.interfaces.get(&iface_id) {
170 if info.mode == constants::MODE_BOUNDARY {
171 return;
172 }
173 }
174 }
175
176 if let Some(ps) = self.path_table.get_mut(dest_hash) {
178 if ps.len() > 1 {
179 ps.failover(false); self.path_states.remove(dest_hash);
182 return;
183 }
184 }
185
186 self.path_states
187 .insert(*dest_hash, constants::STATE_UNRESPONSIVE);
188 }
189
190 pub fn mark_path_responsive(&mut self, dest_hash: &[u8; 16]) {
191 self.path_states
192 .insert(*dest_hash, constants::STATE_RESPONSIVE);
193 }
194
195 pub fn path_is_unresponsive(&self, dest_hash: &[u8; 16]) -> bool {
196 self.path_states.get(dest_hash) == Some(&constants::STATE_UNRESPONSIVE)
197 }
198
199 pub fn expire_path(&mut self, dest_hash: &[u8; 16]) {
200 if let Some(ps) = self.path_table.get_mut(dest_hash) {
201 ps.expire_all();
202 }
203 }
204
205 pub fn register_link(&mut self, link_id: [u8; 16], entry: LinkEntry) {
210 self.link_table.insert(link_id, entry);
211 }
212
213 pub fn validate_link(&mut self, link_id: &[u8; 16]) {
214 if let Some(entry) = self.link_table.get_mut(link_id) {
215 entry.validated = true;
216 }
217 }
218
219 pub fn remove_link(&mut self, link_id: &[u8; 16]) {
220 self.link_table.remove(link_id);
221 }
222
223 pub fn blackhole_identity(
229 &mut self,
230 identity_hash: [u8; 16],
231 now: f64,
232 duration_hours: Option<f64>,
233 reason: Option<String>,
234 ) {
235 let expires = match duration_hours {
236 Some(h) if h > 0.0 => now + h * 3600.0,
237 _ => 0.0, };
239 self.blackholed_identities.insert(
240 identity_hash,
241 BlackholeEntry {
242 created: now,
243 expires,
244 reason,
245 },
246 );
247 }
248
249 pub fn unblackhole_identity(&mut self, identity_hash: &[u8; 16]) -> bool {
251 self.blackholed_identities.remove(identity_hash).is_some()
252 }
253
254 pub fn is_blackholed(&self, identity_hash: &[u8; 16], now: f64) -> bool {
256 if let Some(entry) = self.blackholed_identities.get(identity_hash) {
257 if entry.expires == 0.0 || entry.expires > now {
258 return true;
259 }
260 }
261 false
262 }
263
264 pub fn blackholed_entries(&self) -> impl Iterator<Item = (&[u8; 16], &BlackholeEntry)> {
266 self.blackholed_identities.iter()
267 }
268
269 fn cull_blackholed(&mut self, now: f64) {
271 self.blackholed_identities
272 .retain(|_, entry| entry.expires == 0.0 || entry.expires > now);
273 }
274
275 pub fn handle_tunnel(
283 &mut self,
284 tunnel_id: [u8; 32],
285 interface: InterfaceId,
286 now: f64,
287 ) -> Vec<TransportAction> {
288 let mut actions = Vec::new();
289
290 if let Some(info) = self.interfaces.get_mut(&interface) {
292 info.tunnel_id = Some(tunnel_id);
293 }
294
295 let restored_paths = self.tunnel_table.handle_tunnel(tunnel_id, interface, now);
296
297 let max_paths = self.config.max_paths_per_destination;
299 for (dest_hash, tunnel_path) in &restored_paths {
300 let should_restore = match self.path_table.get(dest_hash).and_then(|ps| ps.primary()) {
301 Some(existing) => {
302 tunnel_path.hops <= existing.hops || existing.expires < now
304 }
305 None => true,
306 };
307
308 if should_restore {
309 let entry = PathEntry {
310 timestamp: tunnel_path.timestamp,
311 next_hop: tunnel_path.received_from,
312 hops: tunnel_path.hops,
313 expires: tunnel_path.expires,
314 random_blobs: tunnel_path.random_blobs.clone(),
315 receiving_interface: interface,
316 packet_hash: tunnel_path.packet_hash,
317 announce_raw: None,
318 };
319 self.path_table
320 .insert(*dest_hash, PathSet::from_single(entry, max_paths));
321 }
322 }
323
324 actions.push(TransportAction::TunnelEstablished {
325 tunnel_id,
326 interface,
327 });
328
329 actions
330 }
331
332 pub fn synthesize_tunnel(
340 &self,
341 identity: &rns_crypto::identity::Identity,
342 interface_id: InterfaceId,
343 rng: &mut dyn Rng,
344 ) -> Vec<TransportAction> {
345 let mut actions = Vec::new();
346
347 let interface_hash = if let Some(info) = self.interfaces.get(&interface_id) {
349 hash::full_hash(info.name.as_bytes())
350 } else {
351 return actions;
352 };
353
354 match tunnel::build_tunnel_synthesize_data(identity, &interface_hash, rng) {
355 Ok((data, _tunnel_id)) => {
356 let dest_hash = crate::destination::destination_hash(
357 "rnstransport",
358 &["tunnel", "synthesize"],
359 None,
360 );
361 actions.push(TransportAction::TunnelSynthesize {
362 interface: interface_id,
363 data,
364 dest_hash,
365 });
366 }
367 Err(e) => {
368 let _ = e;
370 }
371 }
372
373 actions
374 }
375
376 pub fn void_tunnel_interface(&mut self, tunnel_id: &[u8; 32]) {
378 self.tunnel_table.void_tunnel_interface(tunnel_id);
379 }
380
381 pub fn tunnel_table(&self) -> &TunnelTable {
383 &self.tunnel_table
384 }
385
386 fn has_local_clients(&self) -> bool {
392 self.interfaces.values().any(|i| i.is_local_client)
393 }
394
395 fn packet_filter(&self, packet: &RawPacket) -> bool {
399 if packet.transport_id.is_some()
401 && packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE
402 {
403 if let Some(ref identity_hash) = self.config.identity_hash {
404 if packet.transport_id.as_ref() != Some(identity_hash) {
405 return false;
406 }
407 }
408 }
409
410 match packet.context {
412 constants::CONTEXT_KEEPALIVE
413 | constants::CONTEXT_RESOURCE_REQ
414 | constants::CONTEXT_RESOURCE_PRF
415 | constants::CONTEXT_RESOURCE
416 | constants::CONTEXT_CACHE_REQUEST
417 | constants::CONTEXT_CHANNEL => return true,
418 _ => {}
419 }
420
421 if packet.flags.destination_type == constants::DESTINATION_PLAIN
423 || packet.flags.destination_type == constants::DESTINATION_GROUP
424 {
425 if packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE {
426 return packet.hops <= 1;
427 } else {
428 return false;
430 }
431 }
432
433 if !self.packet_hashlist.is_duplicate(&packet.packet_hash) {
435 return true;
436 }
437
438 if packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
440 && packet.flags.destination_type == constants::DESTINATION_SINGLE
441 {
442 return true;
443 }
444
445 false
446 }
447
448 pub fn handle_inbound(
456 &mut self,
457 raw: &[u8],
458 iface: InterfaceId,
459 now: f64,
460 rng: &mut dyn Rng,
461 ) -> Vec<TransportAction> {
462 let mut actions = Vec::new();
463
464 let mut packet = match RawPacket::unpack(raw) {
466 Ok(p) => p,
467 Err(_) => return actions, };
469
470 let original_raw = raw.to_vec();
472
473 packet.hops += 1;
475
476 let from_local_client = self
479 .interfaces
480 .get(&iface)
481 .map(|i| i.is_local_client)
482 .unwrap_or(false);
483 if from_local_client {
484 packet.hops = packet.hops.saturating_sub(1);
485 }
486
487 if !self.packet_filter(&packet) {
489 return actions;
490 }
491
492 let mut remember_hash = true;
494
495 if self.link_table.contains_key(&packet.destination_hash) {
496 remember_hash = false;
497 }
498 if packet.flags.packet_type == constants::PACKET_TYPE_PROOF
499 && packet.context == constants::CONTEXT_LRPROOF
500 {
501 remember_hash = false;
502 }
503
504 if remember_hash {
505 self.packet_hashlist.add(packet.packet_hash);
506 }
507
508 if packet.flags.destination_type == constants::DESTINATION_PLAIN
510 && packet.flags.transport_type == constants::TRANSPORT_BROADCAST
511 && self.has_local_clients()
512 {
513 if from_local_client {
514 actions.push(TransportAction::ForwardPlainBroadcast {
516 raw: packet.raw.clone(),
517 to_local: false,
518 exclude: Some(iface),
519 });
520 } else {
521 actions.push(TransportAction::ForwardPlainBroadcast {
523 raw: packet.raw.clone(),
524 to_local: true,
525 exclude: None,
526 });
527 }
528 }
529
530 if self.config.transport_enabled || self.config.identity_hash.is_some() {
532 if packet.transport_id.is_some()
533 && packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE
534 {
535 if let Some(ref identity_hash) = self.config.identity_hash {
536 if packet.transport_id.as_ref() == Some(identity_hash) {
537 if let Some(path_entry) = self
538 .path_table
539 .get(&packet.destination_hash)
540 .and_then(|ps| ps.primary())
541 {
542 let next_hop = path_entry.next_hop;
543 let remaining_hops = path_entry.hops;
544 let outbound_interface = path_entry.receiving_interface;
545
546 let new_raw = forward_transport_packet(
547 &packet,
548 next_hop,
549 remaining_hops,
550 outbound_interface,
551 );
552
553 if packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST {
555 let proof_timeout = now
556 + constants::LINK_ESTABLISHMENT_TIMEOUT_PER_HOP
557 * (remaining_hops.max(1) as f64);
558
559 let (link_id, link_entry) = create_link_entry(
560 &packet,
561 next_hop,
562 outbound_interface,
563 remaining_hops,
564 iface,
565 now,
566 proof_timeout,
567 );
568 self.link_table.insert(link_id, link_entry);
569 actions.push(TransportAction::LinkRequestReceived {
570 link_id,
571 destination_hash: packet.destination_hash,
572 receiving_interface: iface,
573 });
574 } else {
575 let (trunc_hash, reverse_entry) =
576 create_reverse_entry(&packet, outbound_interface, iface, now);
577 self.reverse_table.insert(trunc_hash, reverse_entry);
578 }
579
580 actions.push(TransportAction::SendOnInterface {
581 interface: outbound_interface,
582 raw: new_raw,
583 });
584
585 if let Some(entry) = self
587 .path_table
588 .get_mut(&packet.destination_hash)
589 .and_then(|ps| ps.primary_mut())
590 {
591 entry.timestamp = now;
592 }
593 }
594 }
595 }
596 }
597
598 if packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE
600 && packet.flags.packet_type != constants::PACKET_TYPE_LINKREQUEST
601 && packet.context != constants::CONTEXT_LRPROOF
602 {
603 if let Some(link_entry) = self.link_table.get(&packet.destination_hash).cloned() {
604 if let Some((outbound_iface, new_raw)) =
605 route_via_link_table(&packet, &link_entry, iface)
606 {
607 self.packet_hashlist.add(packet.packet_hash);
609
610 actions.push(TransportAction::SendOnInterface {
611 interface: outbound_iface,
612 raw: new_raw,
613 });
614
615 if let Some(entry) = self.link_table.get_mut(&packet.destination_hash) {
617 entry.timestamp = now;
618 }
619 }
620 }
621 }
622 }
623
624 if packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE {
626 self.process_inbound_announce(&packet, &original_raw, iface, now, rng, &mut actions);
627 }
628
629 if packet.flags.packet_type == constants::PACKET_TYPE_PROOF {
631 self.process_inbound_proof(&packet, iface, now, &mut actions);
632 }
633
634 if (packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST
636 || packet.flags.packet_type == constants::PACKET_TYPE_DATA)
637 && self.local_destinations.contains_key(&packet.destination_hash)
638 {
639 actions.push(TransportAction::DeliverLocal {
640 destination_hash: packet.destination_hash,
641 raw: packet.raw.clone(),
642 packet_hash: packet.packet_hash,
643 receiving_interface: iface,
644 });
645 }
646
647 actions
648 }
649
650 fn process_inbound_announce(
655 &mut self,
656 packet: &RawPacket,
657 original_raw: &[u8],
658 iface: InterfaceId,
659 now: f64,
660 rng: &mut dyn Rng,
661 actions: &mut Vec<TransportAction>,
662 ) {
663 if packet.flags.destination_type != constants::DESTINATION_SINGLE {
664 return;
665 }
666
667 let has_ratchet = packet.flags.context_flag == constants::FLAG_SET;
668
669 let announce = match AnnounceData::unpack(&packet.data, has_ratchet) {
671 Ok(a) => a,
672 Err(_) => return,
673 };
674
675 let validated = match announce.validate(&packet.destination_hash) {
676 Ok(v) => v,
677 Err(_) => return,
678 };
679
680 if self.is_blackholed(&validated.identity_hash, now) {
682 return;
683 }
684
685 if self
687 .local_destinations
688 .contains_key(&packet.destination_hash)
689 {
690 log::debug!(
691 "Announce:skipping local destination {:02x}{:02x}{:02x}{:02x}..",
692 packet.destination_hash[0],
693 packet.destination_hash[1],
694 packet.destination_hash[2],
695 packet.destination_hash[3],
696 );
697 return;
698 }
699
700 if !self.has_path(&packet.destination_hash) {
702 if let Some(info) = self.interfaces.get(&iface) {
703 if info.ingress_control
704 && self.ingress_control.should_ingress_limit(
705 iface,
706 info.ia_freq,
707 info.started,
708 now,
709 )
710 {
711 self.ingress_control.hold_announce(
712 iface,
713 packet.destination_hash,
714 ingress_control::HeldAnnounce {
715 raw: original_raw.to_vec(),
716 hops: packet.hops,
717 receiving_interface: iface,
718 timestamp: now,
719 },
720 );
721 return;
722 }
723 }
724 }
725
726 let received_from = if let Some(transport_id) = packet.transport_id {
728 if self.config.transport_enabled {
730 if let Some(announce_entry) = self.announce_table.get_mut(&packet.destination_hash)
731 {
732 if packet.hops.checked_sub(1) == Some(announce_entry.hops) {
733 announce_entry.local_rebroadcasts += 1;
734 if announce_entry.retries > 0
735 && announce_entry.local_rebroadcasts
736 >= constants::LOCAL_REBROADCASTS_MAX
737 {
738 self.announce_table.remove(&packet.destination_hash);
739 }
740 }
741 if let Some(announce_entry) = self.announce_table.get(&packet.destination_hash)
743 {
744 if packet.hops.checked_sub(1) == Some(announce_entry.hops + 1)
745 && announce_entry.retries > 0
746 && now < announce_entry.retransmit_timeout
747 {
748 self.announce_table.remove(&packet.destination_hash);
749 }
750 }
751 }
752 }
753 transport_id
754 } else {
755 packet.destination_hash
756 };
757
758 let random_blob = match extract_random_blob(&packet.data) {
760 Some(b) => b,
761 None => return,
762 };
763
764 if packet.hops > constants::PATHFINDER_M {
766 return;
767 }
768
769 let announce_emitted = timebase_from_random_blob(&random_blob);
770
771 let existing_set = self.path_table.get(&packet.destination_hash);
773 let is_unresponsive = self.path_is_unresponsive(&packet.destination_hash);
774
775 let mp_decision = decide_announce_multipath(
776 existing_set,
777 packet.hops,
778 announce_emitted,
779 &random_blob,
780 &received_from,
781 is_unresponsive,
782 now,
783 self.config.prefer_shorter_path,
784 );
785
786 if mp_decision == MultiPathDecision::Reject {
787 log::debug!(
788 "Announce:path decision REJECT for dest={:02x}{:02x}{:02x}{:02x}..",
789 packet.destination_hash[0],
790 packet.destination_hash[1],
791 packet.destination_hash[2],
792 packet.destination_hash[3],
793 );
794 return;
795 }
796
797 let rate_blocked = if packet.context != constants::CONTEXT_PATH_RESPONSE {
799 if let Some(iface_info) = self.interfaces.get(&iface) {
800 self.rate_limiter.check_and_update(
801 &packet.destination_hash,
802 now,
803 iface_info.announce_rate_target,
804 iface_info.announce_rate_grace,
805 iface_info.announce_rate_penalty,
806 )
807 } else {
808 false
809 }
810 } else {
811 false
812 };
813
814 let interface_mode = self
816 .interfaces
817 .get(&iface)
818 .map(|i| i.mode)
819 .unwrap_or(constants::MODE_FULL);
820
821 let expires = compute_path_expires(now, interface_mode);
822
823 let existing_blobs = self
825 .path_table
826 .get(&packet.destination_hash)
827 .and_then(|ps| ps.find_by_next_hop(&received_from))
828 .map(|e| e.random_blobs.clone())
829 .unwrap_or_default();
830
831 let mut rng_bytes = [0u8; 8];
833 rng.fill_bytes(&mut rng_bytes);
834 let rng_value = (u64::from_le_bytes(rng_bytes) as f64) / (u64::MAX as f64);
835
836 let is_path_response = packet.context == constants::CONTEXT_PATH_RESPONSE;
837
838 let (path_entry, announce_entry) = announce_proc::process_validated_announce(
839 packet.destination_hash,
840 packet.hops,
841 &packet.data,
842 &packet.raw,
843 packet.packet_hash,
844 packet.flags.context_flag,
845 received_from,
846 iface,
847 now,
848 existing_blobs,
849 random_blob,
850 expires,
851 rng_value,
852 self.config.transport_enabled,
853 is_path_response,
854 rate_blocked,
855 Some(original_raw.to_vec()),
856 );
857
858 actions.push(TransportAction::CacheAnnounce {
860 packet_hash: packet.packet_hash,
861 raw: original_raw.to_vec(),
862 });
863
864 let max_paths = self.config.max_paths_per_destination;
866 if let Some(ps) = self.path_table.get_mut(&packet.destination_hash) {
867 ps.upsert(path_entry);
868 } else {
869 self.path_table.insert(
870 packet.destination_hash,
871 PathSet::from_single(path_entry, max_paths),
872 );
873 }
874
875 if let Some(tunnel_id) = self.interfaces.get(&iface).and_then(|i| i.tunnel_id) {
877 let blobs = self
878 .path_table
879 .get(&packet.destination_hash)
880 .and_then(|ps| ps.find_by_next_hop(&received_from))
881 .map(|e| e.random_blobs.clone())
882 .unwrap_or_default();
883 self.tunnel_table.store_tunnel_path(
884 &tunnel_id,
885 packet.destination_hash,
886 tunnel::TunnelPath {
887 timestamp: now,
888 received_from,
889 hops: packet.hops,
890 expires,
891 random_blobs: blobs,
892 packet_hash: packet.packet_hash,
893 },
894 now,
895 );
896 }
897
898 self.path_states.remove(&packet.destination_hash);
900
901 if let Some(ann) = announce_entry {
903 self.announce_table.insert(packet.destination_hash, ann);
904 }
905
906 actions.push(TransportAction::AnnounceReceived {
908 destination_hash: packet.destination_hash,
909 identity_hash: validated.identity_hash,
910 public_key: validated.public_key,
911 name_hash: validated.name_hash,
912 random_hash: validated.random_hash,
913 app_data: validated.app_data,
914 hops: packet.hops,
915 receiving_interface: iface,
916 });
917
918 actions.push(TransportAction::PathUpdated {
919 destination_hash: packet.destination_hash,
920 hops: packet.hops,
921 next_hop: received_from,
922 interface: iface,
923 });
924
925 if self.has_local_clients() {
927 actions.push(TransportAction::ForwardToLocalClients {
928 raw: packet.raw.clone(),
929 exclude: Some(iface),
930 });
931 }
932
933 if let Some(pr_entry) = self.discovery_path_requests_waiting(&packet.destination_hash) {
935 let entry = AnnounceEntry {
937 timestamp: now,
938 retransmit_timeout: now,
939 retries: constants::PATHFINDER_R,
940 received_from,
941 hops: packet.hops,
942 packet_raw: packet.raw.clone(),
943 packet_data: packet.data.clone(),
944 destination_hash: packet.destination_hash,
945 context_flag: packet.flags.context_flag,
946 local_rebroadcasts: 0,
947 block_rebroadcasts: true,
948 attached_interface: Some(pr_entry),
949 };
950 self.announce_table.insert(packet.destination_hash, entry);
951 }
952 }
953
954 fn discovery_path_requests_waiting(&mut self, dest_hash: &[u8; 16]) -> Option<InterfaceId> {
957 self.discovery_path_requests
958 .remove(dest_hash)
959 .map(|req| req.requesting_interface)
960 }
961
962 fn process_inbound_proof(
967 &mut self,
968 packet: &RawPacket,
969 iface: InterfaceId,
970 _now: f64,
971 actions: &mut Vec<TransportAction>,
972 ) {
973 if packet.context == constants::CONTEXT_LRPROOF {
974 if (self.config.transport_enabled)
976 && self.link_table.contains_key(&packet.destination_hash)
977 {
978 let link_entry = self.link_table.get(&packet.destination_hash).cloned();
979 if let Some(entry) = link_entry {
980 if packet.hops == entry.remaining_hops && iface == entry.next_hop_interface {
981 let mut new_raw = Vec::new();
984 new_raw.push(packet.raw[0]);
985 new_raw.push(packet.hops);
986 new_raw.extend_from_slice(&packet.raw[2..]);
987
988 if let Some(le) = self.link_table.get_mut(&packet.destination_hash) {
990 le.validated = true;
991 }
992
993 actions.push(TransportAction::LinkEstablished {
994 link_id: packet.destination_hash,
995 interface: entry.received_interface,
996 });
997
998 actions.push(TransportAction::SendOnInterface {
999 interface: entry.received_interface,
1000 raw: new_raw,
1001 });
1002 }
1003 }
1004 } else {
1005 actions.push(TransportAction::DeliverLocal {
1007 destination_hash: packet.destination_hash,
1008 raw: packet.raw.clone(),
1009 packet_hash: packet.packet_hash,
1010 receiving_interface: iface,
1011 });
1012 }
1013 } else {
1014 if self.config.transport_enabled {
1016 if let Some(reverse_entry) = self.reverse_table.remove(&packet.destination_hash) {
1017 if let Some(action) = route_proof_via_reverse(packet, &reverse_entry, iface) {
1018 actions.push(action);
1019 }
1020 }
1021 }
1022
1023 actions.push(TransportAction::DeliverLocal {
1025 destination_hash: packet.destination_hash,
1026 raw: packet.raw.clone(),
1027 packet_hash: packet.packet_hash,
1028 receiving_interface: iface,
1029 });
1030 }
1031 }
1032
1033 pub fn handle_outbound(
1039 &mut self,
1040 packet: &RawPacket,
1041 dest_type: u8,
1042 attached_interface: Option<InterfaceId>,
1043 now: f64,
1044 ) -> Vec<TransportAction> {
1045 let actions = route_outbound(
1046 &self.path_table,
1047 &self.interfaces,
1048 &self.local_destinations,
1049 packet,
1050 dest_type,
1051 attached_interface,
1052 now,
1053 );
1054
1055 self.packet_hashlist.add(packet.packet_hash);
1057
1058 if packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE && packet.hops > 0 {
1060 self.gate_announce_actions(actions, &packet.destination_hash, packet.hops, now)
1061 } else {
1062 actions
1063 }
1064 }
1065
1066 fn gate_announce_actions(
1068 &mut self,
1069 actions: Vec<TransportAction>,
1070 dest_hash: &[u8; 16],
1071 hops: u8,
1072 now: f64,
1073 ) -> Vec<TransportAction> {
1074 let mut result = Vec::new();
1075 for action in actions {
1076 match action {
1077 TransportAction::SendOnInterface { interface, raw } => {
1078 let (bitrate, announce_cap) =
1079 if let Some(info) = self.interfaces.get(&interface) {
1080 (info.bitrate, info.announce_cap)
1081 } else {
1082 (None, constants::ANNOUNCE_CAP)
1083 };
1084 if let Some(send_action) = self.announce_queues.gate_announce(
1085 interface,
1086 raw,
1087 *dest_hash,
1088 hops,
1089 now,
1090 now,
1091 bitrate,
1092 announce_cap,
1093 ) {
1094 result.push(send_action);
1095 }
1096 }
1098 other => result.push(other),
1099 }
1100 }
1101 result
1102 }
1103
1104 pub fn tick(&mut self, now: f64, rng: &mut dyn Rng) -> Vec<TransportAction> {
1110 let mut actions = Vec::new();
1111
1112 if now > self.announces_last_checked + constants::ANNOUNCES_CHECK_INTERVAL {
1114 if let Some(ref identity_hash) = self.config.identity_hash {
1115 let ih = *identity_hash;
1116 let announce_actions = jobs::process_pending_announces(
1117 &mut self.announce_table,
1118 &mut self.held_announces,
1119 &ih,
1120 now,
1121 );
1122 let gated = self.gate_retransmit_actions(announce_actions, now);
1124 actions.extend(gated);
1125 }
1126 self.announces_last_checked = now;
1127 }
1128
1129 let mut queue_actions = self.announce_queues.process_queues(now, &self.interfaces);
1131 actions.append(&mut queue_actions);
1132
1133 let ic_interfaces = self.ingress_control.interfaces_with_held();
1135 for iface_id in ic_interfaces {
1136 let (ia_freq, started, ic_enabled) = match self.interfaces.get(&iface_id) {
1137 Some(info) => (info.ia_freq, info.started, info.ingress_control),
1138 None => continue,
1139 };
1140 if !ic_enabled {
1141 continue;
1142 }
1143 if let Some(held) = self
1144 .ingress_control
1145 .process_held_announces(iface_id, ia_freq, started, now)
1146 {
1147 let released_actions =
1148 self.handle_inbound(&held.raw, held.receiving_interface, now, rng);
1149 actions.extend(released_actions);
1150 }
1151 }
1152
1153 if now > self.tables_last_culled + constants::TABLES_CULL_INTERVAL {
1155 jobs::cull_path_table(&mut self.path_table, &self.interfaces, now);
1156 jobs::cull_reverse_table(&mut self.reverse_table, &self.interfaces, now);
1157 let (_culled, link_closed_actions) =
1158 jobs::cull_link_table(&mut self.link_table, &self.interfaces, now);
1159 actions.extend(link_closed_actions);
1160 jobs::cull_path_states(&mut self.path_states, &self.path_table);
1161 self.cull_blackholed(now);
1162 self.discovery_path_requests
1164 .retain(|_, req| now - req.timestamp < constants::DISCOVERY_PATH_REQUEST_TIMEOUT);
1165 self.tunnel_table
1167 .void_missing_interfaces(|id| self.interfaces.contains_key(id));
1168 self.tunnel_table.cull(now);
1169 self.tables_last_culled = now;
1170 }
1171
1172 if self.discovery_pr_tags.len() > constants::MAX_PR_TAGS {
1174 let start = self.discovery_pr_tags.len() - constants::MAX_PR_TAGS;
1175 self.discovery_pr_tags = self.discovery_pr_tags[start..].to_vec();
1176 }
1177
1178 actions
1179 }
1180
1181 fn gate_retransmit_actions(
1186 &mut self,
1187 actions: Vec<TransportAction>,
1188 now: f64,
1189 ) -> Vec<TransportAction> {
1190 let mut result = Vec::new();
1191 for action in actions {
1192 match action {
1193 TransportAction::SendOnInterface { interface, raw } => {
1194 let (dest_hash, hops) = Self::extract_announce_info(&raw);
1196 let (bitrate, announce_cap) =
1197 if let Some(info) = self.interfaces.get(&interface) {
1198 (info.bitrate, info.announce_cap)
1199 } else {
1200 (None, constants::ANNOUNCE_CAP)
1201 };
1202 if let Some(send_action) = self.announce_queues.gate_announce(
1203 interface,
1204 raw,
1205 dest_hash,
1206 hops,
1207 now,
1208 now,
1209 bitrate,
1210 announce_cap,
1211 ) {
1212 result.push(send_action);
1213 }
1214 }
1215 TransportAction::BroadcastOnAllInterfaces { raw, exclude } => {
1216 let (dest_hash, hops) = Self::extract_announce_info(&raw);
1217 let iface_ids: Vec<(InterfaceId, Option<u64>, f64)> = self
1220 .interfaces
1221 .iter()
1222 .filter(|(_, info)| info.out_capable)
1223 .filter(|(id, _)| {
1224 if let Some(ref ex) = exclude {
1225 **id != *ex
1226 } else {
1227 true
1228 }
1229 })
1230 .filter(|(_, info)| {
1231 should_transmit_announce(
1232 info,
1233 &dest_hash,
1234 hops,
1235 &self.local_destinations,
1236 &self.path_table,
1237 &self.interfaces,
1238 )
1239 })
1240 .map(|(id, info)| (*id, info.bitrate, info.announce_cap))
1241 .collect();
1242
1243 for (iface_id, bitrate, announce_cap) in iface_ids {
1244 if let Some(send_action) = self.announce_queues.gate_announce(
1245 iface_id,
1246 raw.clone(),
1247 dest_hash,
1248 hops,
1249 now,
1250 now,
1251 bitrate,
1252 announce_cap,
1253 ) {
1254 result.push(send_action);
1255 }
1256 }
1257 }
1258 other => result.push(other),
1259 }
1260 }
1261 result
1262 }
1263
1264 fn extract_announce_info(raw: &[u8]) -> ([u8; 16], u8) {
1266 if raw.len() < 18 {
1267 return ([0; 16], 0);
1268 }
1269 let header_type = (raw[0] >> 6) & 0x03;
1270 let hops = raw[1];
1271 if header_type == constants::HEADER_2 && raw.len() >= 34 {
1272 let mut dest = [0u8; 16];
1274 dest.copy_from_slice(&raw[18..34]);
1275 (dest, hops)
1276 } else {
1277 let mut dest = [0u8; 16];
1279 dest.copy_from_slice(&raw[2..18]);
1280 (dest, hops)
1281 }
1282 }
1283
1284 pub fn handle_path_request(
1297 &mut self,
1298 data: &[u8],
1299 interface_id: InterfaceId,
1300 now: f64,
1301 ) -> Vec<TransportAction> {
1302 let mut actions = Vec::new();
1303
1304 if data.len() < 16 {
1305 return actions;
1306 }
1307
1308 let mut destination_hash = [0u8; 16];
1309 destination_hash.copy_from_slice(&data[..16]);
1310
1311 let _requesting_transport_id = if data.len() > 32 {
1313 let mut id = [0u8; 16];
1314 id.copy_from_slice(&data[16..32]);
1315 Some(id)
1316 } else {
1317 None
1318 };
1319
1320 let tag_bytes = if data.len() > 32 {
1322 Some(&data[32..])
1323 } else if data.len() > 16 {
1324 Some(&data[16..])
1325 } else {
1326 None
1327 };
1328
1329 if let Some(tag) = tag_bytes {
1330 let tag_len = tag.len().min(16);
1331 let mut unique_tag = [0u8; 32];
1332 unique_tag[..16].copy_from_slice(&destination_hash);
1333 unique_tag[16..16 + tag_len].copy_from_slice(&tag[..tag_len]);
1334
1335 if self.discovery_pr_tags.contains(&unique_tag) {
1336 return actions; }
1338 self.discovery_pr_tags.push(unique_tag);
1339 } else {
1340 return actions; }
1342
1343 if self.local_destinations.contains_key(&destination_hash) {
1345 return actions;
1346 }
1347
1348 if self.config.transport_enabled && self.has_path(&destination_hash) {
1350 let path = self
1351 .path_table
1352 .get(&destination_hash)
1353 .unwrap()
1354 .primary()
1355 .unwrap()
1356 .clone();
1357
1358 if let Some(recv_info) = self.interfaces.get(&interface_id) {
1362 if recv_info.mode == constants::MODE_ROAMING
1363 && path.receiving_interface == interface_id
1364 {
1365 return actions;
1366 }
1367 }
1368
1369 if let Some(ref raw) = path.announce_raw {
1373 if let Some(existing) = self.announce_table.remove(&destination_hash) {
1375 self.held_announces.insert(destination_hash, existing);
1376 }
1377 let retransmit_timeout =
1378 if let Some(iface_info) = self.interfaces.get(&interface_id) {
1379 let base = now + constants::PATH_REQUEST_GRACE;
1380 if iface_info.mode == constants::MODE_ROAMING {
1381 base + constants::PATH_REQUEST_RG
1382 } else {
1383 base
1384 }
1385 } else {
1386 now + constants::PATH_REQUEST_GRACE
1387 };
1388
1389 let (packet_data, context_flag) = match RawPacket::unpack(raw) {
1390 Ok(parsed) => (parsed.data, parsed.flags.context_flag),
1391 Err(_) => {
1392 return actions;
1393 }
1394 };
1395
1396 let entry = AnnounceEntry {
1397 timestamp: now,
1398 retransmit_timeout,
1399 retries: constants::PATHFINDER_R,
1400 received_from: path.next_hop,
1401 hops: path.hops,
1402 packet_raw: raw.clone(),
1403 packet_data,
1404 destination_hash,
1405 context_flag,
1406 local_rebroadcasts: 0,
1407 block_rebroadcasts: true,
1408 attached_interface: Some(interface_id),
1409 };
1410
1411 self.announce_table.insert(destination_hash, entry);
1412 }
1413 } else if self.config.transport_enabled {
1414 let should_discover = self
1416 .interfaces
1417 .get(&interface_id)
1418 .map(|info| constants::DISCOVER_PATHS_FOR.contains(&info.mode))
1419 .unwrap_or(false);
1420
1421 if should_discover {
1422 self.discovery_path_requests.insert(
1424 destination_hash,
1425 DiscoveryPathRequest {
1426 timestamp: now,
1427 requesting_interface: interface_id,
1428 },
1429 );
1430
1431 for (_, iface_info) in self.interfaces.iter() {
1433 if iface_info.id != interface_id && iface_info.out_capable {
1434 actions.push(TransportAction::SendOnInterface {
1435 interface: iface_info.id,
1436 raw: data.to_vec(),
1437 });
1438 }
1439 }
1440 }
1441 }
1442
1443 actions
1444 }
1445
1446 pub fn path_table_entries(&self) -> impl Iterator<Item = (&[u8; 16], &PathEntry)> {
1452 self.path_table
1453 .iter()
1454 .filter_map(|(k, ps)| ps.primary().map(|e| (k, e)))
1455 }
1456
1457 pub fn path_table_sets(&self) -> impl Iterator<Item = (&[u8; 16], &PathSet)> {
1459 self.path_table.iter()
1460 }
1461
1462 pub fn interface_count(&self) -> usize {
1464 self.interfaces.len()
1465 }
1466
1467 pub fn link_table_count(&self) -> usize {
1469 self.link_table.len()
1470 }
1471
1472 pub fn path_table_count(&self) -> usize {
1474 self.path_table.len()
1475 }
1476
1477 pub fn announce_table_count(&self) -> usize {
1479 self.announce_table.len()
1480 }
1481
1482 pub fn reverse_table_count(&self) -> usize {
1484 self.reverse_table.len()
1485 }
1486
1487 pub fn held_announces_count(&self) -> usize {
1489 self.held_announces.len()
1490 }
1491
1492 pub fn packet_hashlist_len(&self) -> usize {
1494 self.packet_hashlist.len()
1495 }
1496
1497 pub fn rate_limiter_count(&self) -> usize {
1499 self.rate_limiter.len()
1500 }
1501
1502 pub fn blackholed_count(&self) -> usize {
1504 self.blackholed_identities.len()
1505 }
1506
1507 pub fn tunnel_count(&self) -> usize {
1509 self.tunnel_table.len()
1510 }
1511
1512 pub fn discovery_pr_tags_count(&self) -> usize {
1514 self.discovery_pr_tags.len()
1515 }
1516
1517 pub fn discovery_path_requests_count(&self) -> usize {
1519 self.discovery_path_requests.len()
1520 }
1521
1522 pub fn local_destinations_count(&self) -> usize {
1524 self.local_destinations.len()
1525 }
1526
1527 pub fn rate_limiter(&self) -> &AnnounceRateLimiter {
1529 &self.rate_limiter
1530 }
1531
1532 pub fn interface_info(&self, id: &InterfaceId) -> Option<&InterfaceInfo> {
1534 self.interfaces.get(id)
1535 }
1536
1537 pub fn redirect_path(&mut self, dest_hash: &[u8; 16], interface: InterfaceId, now: f64) {
1540 if let Some(entry) = self
1541 .path_table
1542 .get_mut(dest_hash)
1543 .and_then(|ps| ps.primary_mut())
1544 {
1545 entry.receiving_interface = interface;
1546 entry.hops = 1;
1547 } else {
1548 let max_paths = self.config.max_paths_per_destination;
1549 self.path_table.insert(
1550 *dest_hash,
1551 PathSet::from_single(
1552 PathEntry {
1553 timestamp: now,
1554 next_hop: [0u8; 16],
1555 hops: 1,
1556 expires: now + 3600.0,
1557 random_blobs: Vec::new(),
1558 receiving_interface: interface,
1559 packet_hash: [0u8; 32],
1560 announce_raw: None,
1561 },
1562 max_paths,
1563 ),
1564 );
1565 }
1566 }
1567
1568 pub fn inject_path(&mut self, dest_hash: [u8; 16], entry: PathEntry) {
1570 let max_paths = self.config.max_paths_per_destination;
1571 self.path_table
1572 .insert(dest_hash, PathSet::from_single(entry, max_paths));
1573 }
1574
1575 pub fn drop_path(&mut self, dest_hash: &[u8; 16]) -> bool {
1577 self.path_table.remove(dest_hash).is_some()
1578 }
1579
1580 pub fn drop_all_via(&mut self, transport_hash: &[u8; 16]) -> usize {
1585 let mut removed = 0usize;
1586 for ps in self.path_table.values_mut() {
1587 let before = ps.len();
1588 ps.retain(|entry| &entry.next_hop != transport_hash);
1589 removed += before - ps.len();
1590 }
1591 self.path_table.retain(|_, ps| !ps.is_empty());
1592 removed
1593 }
1594
1595 pub fn drop_paths_for_interface(&mut self, interface: InterfaceId) -> usize {
1597 let mut removed = 0usize;
1598 let mut cleared_destinations = Vec::new();
1599 for (dest_hash, ps) in self.path_table.iter_mut() {
1600 let before = ps.len();
1601 ps.retain(|entry| entry.receiving_interface != interface);
1602 if ps.is_empty() {
1603 cleared_destinations.push(*dest_hash);
1604 }
1605 removed += before - ps.len();
1606 }
1607 self.path_table.retain(|_, ps| !ps.is_empty());
1608 for dest_hash in cleared_destinations {
1609 self.path_states.remove(&dest_hash);
1610 }
1611 removed
1612 }
1613
1614 pub fn drop_reverse_for_interface(&mut self, interface: InterfaceId) -> usize {
1616 let before = self.reverse_table.len();
1617 self.reverse_table.retain(|_, entry| {
1618 entry.receiving_interface != interface && entry.outbound_interface != interface
1619 });
1620 before - self.reverse_table.len()
1621 }
1622
1623 pub fn drop_links_for_interface(&mut self, interface: InterfaceId) -> usize {
1625 let before = self.link_table.len();
1626 self.link_table.retain(|_, entry| {
1627 entry.next_hop_interface != interface && entry.received_interface != interface
1628 });
1629 before - self.link_table.len()
1630 }
1631
1632 pub fn drop_announce_queues(&mut self) {
1634 self.announce_table.clear();
1635 self.held_announces.clear();
1636 self.announce_queues = AnnounceQueues::new();
1637 self.ingress_control.clear();
1638 }
1639
1640 pub fn identity_hash(&self) -> Option<&[u8; 16]> {
1642 self.config.identity_hash.as_ref()
1643 }
1644
1645 pub fn transport_enabled(&self) -> bool {
1647 self.config.transport_enabled
1648 }
1649
1650 pub fn config(&self) -> &TransportConfig {
1652 &self.config
1653 }
1654
1655 pub fn set_packet_hashlist_max_entries(&mut self, max_entries: usize) {
1656 self.config.packet_hashlist_max_entries = max_entries;
1657 self.packet_hashlist = PacketHashlist::new(max_entries);
1658 }
1659
1660 pub fn get_path_table(&self, max_hops: Option<u8>) -> Vec<PathTableRow> {
1664 let mut result = Vec::new();
1665 for (dest_hash, ps) in self.path_table.iter() {
1666 if let Some(entry) = ps.primary() {
1667 if let Some(max) = max_hops {
1668 if entry.hops > max {
1669 continue;
1670 }
1671 }
1672 let iface_name = self
1673 .interfaces
1674 .get(&entry.receiving_interface)
1675 .map(|i| i.name.clone())
1676 .unwrap_or_else(|| {
1677 alloc::format!("Interface({})", entry.receiving_interface.0)
1678 });
1679 result.push((
1680 *dest_hash,
1681 entry.timestamp,
1682 entry.next_hop,
1683 entry.hops,
1684 entry.expires,
1685 iface_name,
1686 ));
1687 }
1688 }
1689 result
1690 }
1691
1692 pub fn get_rate_table(&self) -> Vec<RateTableRow> {
1695 self.rate_limiter
1696 .entries()
1697 .map(|(hash, entry)| {
1698 (
1699 *hash,
1700 entry.last,
1701 entry.rate_violations,
1702 entry.blocked_until,
1703 entry.timestamps.clone(),
1704 )
1705 })
1706 .collect()
1707 }
1708
1709 pub fn get_blackholed(&self) -> Vec<([u8; 16], f64, f64, Option<alloc::string::String>)> {
1712 self.blackholed_entries()
1713 .map(|(hash, entry)| (*hash, entry.created, entry.expires, entry.reason.clone()))
1714 .collect()
1715 }
1716
1717 pub fn active_destination_hashes(&self) -> alloc::collections::BTreeSet<[u8; 16]> {
1723 self.path_table.keys().copied().collect()
1724 }
1725
1726 pub fn active_packet_hashes(&self) -> Vec<[u8; 32]> {
1728 self.path_table
1729 .values()
1730 .flat_map(|ps| ps.iter().map(|p| p.packet_hash))
1731 .collect()
1732 }
1733
1734 pub fn cull_rate_limiter(
1737 &mut self,
1738 active: &alloc::collections::BTreeSet<[u8; 16]>,
1739 now: f64,
1740 ttl_secs: f64,
1741 ) -> usize {
1742 self.rate_limiter.cull_stale(active, now, ttl_secs)
1743 }
1744
1745 pub fn update_interface_freq(&mut self, id: InterfaceId, ia_freq: f64) {
1751 if let Some(info) = self.interfaces.get_mut(&id) {
1752 info.ia_freq = ia_freq;
1753 }
1754 }
1755
1756 pub fn held_announce_count(&self, interface: &InterfaceId) -> usize {
1758 self.ingress_control.held_count(interface)
1759 }
1760
1761 #[cfg(test)]
1766 #[allow(dead_code)]
1767 pub(crate) fn path_table(&self) -> &BTreeMap<[u8; 16], PathSet> {
1768 &self.path_table
1769 }
1770
1771 #[cfg(test)]
1772 #[allow(dead_code)]
1773 pub(crate) fn announce_table(&self) -> &BTreeMap<[u8; 16], AnnounceEntry> {
1774 &self.announce_table
1775 }
1776
1777 #[cfg(test)]
1778 #[allow(dead_code)]
1779 pub(crate) fn reverse_table(&self) -> &BTreeMap<[u8; 16], tables::ReverseEntry> {
1780 &self.reverse_table
1781 }
1782
1783 #[cfg(test)]
1784 #[allow(dead_code)]
1785 pub(crate) fn link_table_ref(&self) -> &BTreeMap<[u8; 16], LinkEntry> {
1786 &self.link_table
1787 }
1788}
1789
1790#[cfg(test)]
1791mod tests {
1792 use super::*;
1793 use crate::packet::PacketFlags;
1794
1795 fn make_config(transport_enabled: bool) -> TransportConfig {
1796 TransportConfig {
1797 transport_enabled,
1798 identity_hash: if transport_enabled {
1799 Some([0x42; 16])
1800 } else {
1801 None
1802 },
1803 prefer_shorter_path: false,
1804 max_paths_per_destination: 1,
1805 packet_hashlist_max_entries: constants::HASHLIST_MAXSIZE,
1806 }
1807 }
1808
1809 fn make_interface(id: u64, mode: u8) -> InterfaceInfo {
1810 InterfaceInfo {
1811 id: InterfaceId(id),
1812 name: String::from("test"),
1813 mode,
1814 out_capable: true,
1815 in_capable: true,
1816 bitrate: None,
1817 announce_rate_target: None,
1818 announce_rate_grace: 0,
1819 announce_rate_penalty: 0.0,
1820 announce_cap: constants::ANNOUNCE_CAP,
1821 is_local_client: false,
1822 wants_tunnel: false,
1823 tunnel_id: None,
1824 mtu: constants::MTU as u32,
1825 ingress_control: false,
1826 ia_freq: 0.0,
1827 started: 0.0,
1828 }
1829 }
1830
1831 #[test]
1832 fn test_empty_engine() {
1833 let engine = TransportEngine::new(make_config(false));
1834 assert!(!engine.has_path(&[0; 16]));
1835 assert!(engine.hops_to(&[0; 16]).is_none());
1836 assert!(engine.next_hop(&[0; 16]).is_none());
1837 }
1838
1839 #[test]
1840 fn test_register_deregister_interface() {
1841 let mut engine = TransportEngine::new(make_config(false));
1842 engine.register_interface(make_interface(1, constants::MODE_FULL));
1843 assert!(engine.interfaces.contains_key(&InterfaceId(1)));
1844
1845 engine.deregister_interface(InterfaceId(1));
1846 assert!(!engine.interfaces.contains_key(&InterfaceId(1)));
1847 }
1848
1849 #[test]
1850 fn test_register_deregister_destination() {
1851 let mut engine = TransportEngine::new(make_config(false));
1852 let dest = [0x11; 16];
1853 engine.register_destination(dest, constants::DESTINATION_SINGLE);
1854 assert!(engine.local_destinations.contains_key(&dest));
1855
1856 engine.deregister_destination(&dest);
1857 assert!(!engine.local_destinations.contains_key(&dest));
1858 }
1859
1860 #[test]
1861 fn test_path_state() {
1862 let mut engine = TransportEngine::new(make_config(false));
1863 let dest = [0x22; 16];
1864
1865 assert!(!engine.path_is_unresponsive(&dest));
1866
1867 engine.mark_path_unresponsive(&dest, None);
1868 assert!(engine.path_is_unresponsive(&dest));
1869
1870 engine.mark_path_responsive(&dest);
1871 assert!(!engine.path_is_unresponsive(&dest));
1872 }
1873
1874 #[test]
1875 fn test_boundary_exempts_unresponsive() {
1876 let mut engine = TransportEngine::new(make_config(false));
1877 engine.register_interface(make_interface(1, constants::MODE_BOUNDARY));
1878 let dest = [0xB1; 16];
1879
1880 engine.mark_path_unresponsive(&dest, Some(InterfaceId(1)));
1882 assert!(!engine.path_is_unresponsive(&dest));
1883 }
1884
1885 #[test]
1886 fn test_non_boundary_marks_unresponsive() {
1887 let mut engine = TransportEngine::new(make_config(false));
1888 engine.register_interface(make_interface(1, constants::MODE_FULL));
1889 let dest = [0xB2; 16];
1890
1891 engine.mark_path_unresponsive(&dest, Some(InterfaceId(1)));
1893 assert!(engine.path_is_unresponsive(&dest));
1894 }
1895
1896 #[test]
1897 fn test_expire_path() {
1898 let mut engine = TransportEngine::new(make_config(false));
1899 let dest = [0x33; 16];
1900
1901 engine.path_table.insert(
1902 dest,
1903 PathSet::from_single(
1904 PathEntry {
1905 timestamp: 1000.0,
1906 next_hop: [0; 16],
1907 hops: 2,
1908 expires: 9999.0,
1909 random_blobs: Vec::new(),
1910 receiving_interface: InterfaceId(1),
1911 packet_hash: [0; 32],
1912 announce_raw: None,
1913 },
1914 1,
1915 ),
1916 );
1917
1918 assert!(engine.has_path(&dest));
1919 engine.expire_path(&dest);
1920 assert!(engine.has_path(&dest));
1922 assert_eq!(engine.path_table[&dest].primary().unwrap().expires, 0.0);
1923 }
1924
1925 #[test]
1926 fn test_link_table_operations() {
1927 let mut engine = TransportEngine::new(make_config(false));
1928 let link_id = [0x44; 16];
1929
1930 engine.register_link(
1931 link_id,
1932 LinkEntry {
1933 timestamp: 100.0,
1934 next_hop_transport_id: [0; 16],
1935 next_hop_interface: InterfaceId(1),
1936 remaining_hops: 3,
1937 received_interface: InterfaceId(2),
1938 taken_hops: 2,
1939 destination_hash: [0xAA; 16],
1940 validated: false,
1941 proof_timeout: 200.0,
1942 },
1943 );
1944
1945 assert!(engine.link_table.contains_key(&link_id));
1946 assert!(!engine.link_table[&link_id].validated);
1947
1948 engine.validate_link(&link_id);
1949 assert!(engine.link_table[&link_id].validated);
1950
1951 engine.remove_link(&link_id);
1952 assert!(!engine.link_table.contains_key(&link_id));
1953 }
1954
1955 #[test]
1956 fn test_packet_filter_drops_plain_announce() {
1957 let engine = TransportEngine::new(make_config(false));
1958 let flags = PacketFlags {
1959 header_type: constants::HEADER_1,
1960 context_flag: constants::FLAG_UNSET,
1961 transport_type: constants::TRANSPORT_BROADCAST,
1962 destination_type: constants::DESTINATION_PLAIN,
1963 packet_type: constants::PACKET_TYPE_ANNOUNCE,
1964 };
1965 let packet =
1966 RawPacket::pack(flags, 0, &[0; 16], None, constants::CONTEXT_NONE, b"test").unwrap();
1967 assert!(!engine.packet_filter(&packet));
1968 }
1969
1970 #[test]
1971 fn test_packet_filter_allows_keepalive() {
1972 let engine = TransportEngine::new(make_config(false));
1973 let flags = PacketFlags {
1974 header_type: constants::HEADER_1,
1975 context_flag: constants::FLAG_UNSET,
1976 transport_type: constants::TRANSPORT_BROADCAST,
1977 destination_type: constants::DESTINATION_SINGLE,
1978 packet_type: constants::PACKET_TYPE_DATA,
1979 };
1980 let packet = RawPacket::pack(
1981 flags,
1982 0,
1983 &[0; 16],
1984 None,
1985 constants::CONTEXT_KEEPALIVE,
1986 b"test",
1987 )
1988 .unwrap();
1989 assert!(engine.packet_filter(&packet));
1990 }
1991
1992 #[test]
1993 fn test_packet_filter_drops_high_hop_plain() {
1994 let engine = TransportEngine::new(make_config(false));
1995 let flags = PacketFlags {
1996 header_type: constants::HEADER_1,
1997 context_flag: constants::FLAG_UNSET,
1998 transport_type: constants::TRANSPORT_BROADCAST,
1999 destination_type: constants::DESTINATION_PLAIN,
2000 packet_type: constants::PACKET_TYPE_DATA,
2001 };
2002 let mut packet =
2003 RawPacket::pack(flags, 0, &[0; 16], None, constants::CONTEXT_NONE, b"test").unwrap();
2004 packet.hops = 2;
2005 assert!(!engine.packet_filter(&packet));
2006 }
2007
2008 #[test]
2009 fn test_packet_filter_allows_duplicate_single_announce() {
2010 let mut engine = TransportEngine::new(make_config(false));
2011 let flags = PacketFlags {
2012 header_type: constants::HEADER_1,
2013 context_flag: constants::FLAG_UNSET,
2014 transport_type: constants::TRANSPORT_BROADCAST,
2015 destination_type: constants::DESTINATION_SINGLE,
2016 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2017 };
2018 let packet = RawPacket::pack(
2019 flags,
2020 0,
2021 &[0; 16],
2022 None,
2023 constants::CONTEXT_NONE,
2024 &[0xAA; 64],
2025 )
2026 .unwrap();
2027
2028 engine.packet_hashlist.add(packet.packet_hash);
2030
2031 assert!(engine.packet_filter(&packet));
2033 }
2034
2035 #[test]
2036 fn test_packet_filter_fifo_eviction_allows_oldest_hash_again() {
2037 let mut engine = TransportEngine::new(make_config(false));
2038 engine.packet_hashlist = PacketHashlist::new(2);
2039
2040 let make_packet = |seed: u8| {
2041 let flags = PacketFlags {
2042 header_type: constants::HEADER_1,
2043 context_flag: constants::FLAG_UNSET,
2044 transport_type: constants::TRANSPORT_BROADCAST,
2045 destination_type: constants::DESTINATION_SINGLE,
2046 packet_type: constants::PACKET_TYPE_DATA,
2047 };
2048 RawPacket::pack(
2049 flags,
2050 0,
2051 &[seed; 16],
2052 None,
2053 constants::CONTEXT_NONE,
2054 &[seed; 4],
2055 )
2056 .unwrap()
2057 };
2058
2059 let packet1 = make_packet(1);
2060 let packet2 = make_packet(2);
2061 let packet3 = make_packet(3);
2062
2063 engine.packet_hashlist.add(packet1.packet_hash);
2064 engine.packet_hashlist.add(packet2.packet_hash);
2065 assert!(!engine.packet_filter(&packet1));
2066
2067 engine.packet_hashlist.add(packet3.packet_hash);
2068
2069 assert!(engine.packet_filter(&packet1));
2070 assert!(!engine.packet_filter(&packet2));
2071 assert!(!engine.packet_filter(&packet3));
2072 }
2073
2074 #[test]
2075 fn test_packet_filter_duplicate_does_not_refresh_recency() {
2076 let mut engine = TransportEngine::new(make_config(false));
2077 engine.packet_hashlist = PacketHashlist::new(2);
2078
2079 let make_packet = |seed: u8| {
2080 let flags = 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_DATA,
2086 };
2087 RawPacket::pack(
2088 flags,
2089 0,
2090 &[seed; 16],
2091 None,
2092 constants::CONTEXT_NONE,
2093 &[seed; 4],
2094 )
2095 .unwrap()
2096 };
2097
2098 let packet1 = make_packet(1);
2099 let packet2 = make_packet(2);
2100 let packet3 = make_packet(3);
2101
2102 engine.packet_hashlist.add(packet1.packet_hash);
2103 engine.packet_hashlist.add(packet2.packet_hash);
2104 engine.packet_hashlist.add(packet2.packet_hash);
2105 engine.packet_hashlist.add(packet3.packet_hash);
2106
2107 assert!(engine.packet_filter(&packet1));
2108 assert!(!engine.packet_filter(&packet2));
2109 assert!(!engine.packet_filter(&packet3));
2110 }
2111
2112 #[test]
2113 fn test_tick_retransmits_announce() {
2114 let mut engine = TransportEngine::new(make_config(true));
2115 engine.register_interface(make_interface(1, constants::MODE_FULL));
2116
2117 let dest = [0x55; 16];
2118 engine.announce_table.insert(
2119 dest,
2120 AnnounceEntry {
2121 timestamp: 100.0,
2122 retransmit_timeout: 100.0, retries: 0,
2124 received_from: [0xAA; 16],
2125 hops: 2,
2126 packet_raw: vec![0x01, 0x02],
2127 packet_data: vec![0xCC; 10],
2128 destination_hash: dest,
2129 context_flag: 0,
2130 local_rebroadcasts: 0,
2131 block_rebroadcasts: false,
2132 attached_interface: None,
2133 },
2134 );
2135
2136 let mut rng = rns_crypto::FixedRng::new(&[0x42; 32]);
2137 let actions = engine.tick(200.0, &mut rng);
2138
2139 assert!(!actions.is_empty());
2142 assert!(matches!(
2143 &actions[0],
2144 TransportAction::SendOnInterface { .. }
2145 ));
2146
2147 assert_eq!(engine.announce_table[&dest].retries, 1);
2149 }
2150
2151 #[test]
2152 fn test_blackhole_identity() {
2153 let mut engine = TransportEngine::new(make_config(false));
2154 let hash = [0xAA; 16];
2155 let now = 1000.0;
2156
2157 assert!(!engine.is_blackholed(&hash, now));
2158
2159 engine.blackhole_identity(hash, now, None, Some(String::from("test")));
2160 assert!(engine.is_blackholed(&hash, now));
2161 assert!(engine.is_blackholed(&hash, now + 999999.0)); assert!(engine.unblackhole_identity(&hash));
2164 assert!(!engine.is_blackholed(&hash, now));
2165 assert!(!engine.unblackhole_identity(&hash)); }
2167
2168 #[test]
2169 fn test_blackhole_with_duration() {
2170 let mut engine = TransportEngine::new(make_config(false));
2171 let hash = [0xBB; 16];
2172 let now = 1000.0;
2173
2174 engine.blackhole_identity(hash, now, Some(1.0), None); assert!(engine.is_blackholed(&hash, now));
2176 assert!(engine.is_blackholed(&hash, now + 3599.0)); assert!(!engine.is_blackholed(&hash, now + 3601.0)); }
2179
2180 #[test]
2181 fn test_cull_blackholed() {
2182 let mut engine = TransportEngine::new(make_config(false));
2183 let hash1 = [0xCC; 16];
2184 let hash2 = [0xDD; 16];
2185 let now = 1000.0;
2186
2187 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));
2193 assert!(engine.blackholed_identities.contains_key(&hash2));
2194 }
2195
2196 #[test]
2197 fn test_blackhole_blocks_announce() {
2198 use crate::announce::AnnounceData;
2199 use crate::destination::{destination_hash, name_hash};
2200
2201 let mut engine = TransportEngine::new(make_config(false));
2202 engine.register_interface(make_interface(1, constants::MODE_FULL));
2203
2204 let identity =
2205 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x55; 32]));
2206 let dest_hash = destination_hash("test", &["app"], Some(identity.hash()));
2207 let name_h = name_hash("test", &["app"]);
2208 let random_hash = [0x42u8; 10];
2209
2210 let (announce_data, _) =
2211 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
2212
2213 let flags = PacketFlags {
2214 header_type: constants::HEADER_1,
2215 context_flag: constants::FLAG_UNSET,
2216 transport_type: constants::TRANSPORT_BROADCAST,
2217 destination_type: constants::DESTINATION_SINGLE,
2218 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2219 };
2220 let packet = RawPacket::pack(
2221 flags,
2222 0,
2223 &dest_hash,
2224 None,
2225 constants::CONTEXT_NONE,
2226 &announce_data,
2227 )
2228 .unwrap();
2229
2230 let now = 1000.0;
2232 engine.blackhole_identity(*identity.hash(), now, None, None);
2233
2234 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
2235 let actions = engine.handle_inbound(&packet.raw, InterfaceId(1), now, &mut rng);
2236
2237 assert!(actions
2239 .iter()
2240 .all(|a| !matches!(a, TransportAction::AnnounceReceived { .. })));
2241 assert!(actions
2242 .iter()
2243 .all(|a| !matches!(a, TransportAction::PathUpdated { .. })));
2244 }
2245
2246 #[test]
2247 fn test_tick_culls_expired_path() {
2248 let mut engine = TransportEngine::new(make_config(false));
2249 engine.register_interface(make_interface(1, constants::MODE_FULL));
2250
2251 let dest = [0x66; 16];
2252 engine.path_table.insert(
2253 dest,
2254 PathSet::from_single(
2255 PathEntry {
2256 timestamp: 100.0,
2257 next_hop: [0; 16],
2258 hops: 2,
2259 expires: 200.0,
2260 random_blobs: Vec::new(),
2261 receiving_interface: InterfaceId(1),
2262 packet_hash: [0; 32],
2263 announce_raw: None,
2264 },
2265 1,
2266 ),
2267 );
2268
2269 assert!(engine.has_path(&dest));
2270
2271 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
2272 engine.tick(300.0, &mut rng);
2274
2275 assert!(!engine.has_path(&dest));
2276 }
2277
2278 fn make_local_client_interface(id: u64) -> InterfaceInfo {
2283 InterfaceInfo {
2284 id: InterfaceId(id),
2285 name: String::from("local_client"),
2286 mode: constants::MODE_FULL,
2287 out_capable: true,
2288 in_capable: true,
2289 bitrate: None,
2290 announce_rate_target: None,
2291 announce_rate_grace: 0,
2292 announce_rate_penalty: 0.0,
2293 announce_cap: constants::ANNOUNCE_CAP,
2294 is_local_client: true,
2295 wants_tunnel: false,
2296 tunnel_id: None,
2297 mtu: constants::MTU as u32,
2298 ingress_control: false,
2299 ia_freq: 0.0,
2300 started: 0.0,
2301 }
2302 }
2303
2304 #[test]
2305 fn test_has_local_clients() {
2306 let mut engine = TransportEngine::new(make_config(false));
2307 assert!(!engine.has_local_clients());
2308
2309 engine.register_interface(make_interface(1, constants::MODE_FULL));
2310 assert!(!engine.has_local_clients());
2311
2312 engine.register_interface(make_local_client_interface(2));
2313 assert!(engine.has_local_clients());
2314
2315 engine.deregister_interface(InterfaceId(2));
2316 assert!(!engine.has_local_clients());
2317 }
2318
2319 #[test]
2320 fn test_local_client_hop_decrement() {
2321 let mut engine = TransportEngine::new(make_config(false));
2324 engine.register_interface(make_local_client_interface(1));
2325 engine.register_interface(make_interface(2, constants::MODE_FULL));
2326
2327 let dest = [0xAA; 16];
2329 engine.register_destination(dest, constants::DESTINATION_PLAIN);
2330
2331 let flags = PacketFlags {
2332 header_type: constants::HEADER_1,
2333 context_flag: constants::FLAG_UNSET,
2334 transport_type: constants::TRANSPORT_BROADCAST,
2335 destination_type: constants::DESTINATION_PLAIN,
2336 packet_type: constants::PACKET_TYPE_DATA,
2337 };
2338 let packet =
2340 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"hello").unwrap();
2341
2342 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
2343 let actions = engine.handle_inbound(&packet.raw, InterfaceId(1), 1000.0, &mut rng);
2344
2345 let deliver = actions
2348 .iter()
2349 .find(|a| matches!(a, TransportAction::DeliverLocal { .. }));
2350 assert!(deliver.is_some(), "Should deliver locally");
2351 }
2352
2353 #[test]
2354 fn test_plain_broadcast_from_local_client() {
2355 let mut engine = TransportEngine::new(make_config(false));
2357 engine.register_interface(make_local_client_interface(1));
2358 engine.register_interface(make_interface(2, constants::MODE_FULL));
2359
2360 let dest = [0xBB; 16];
2361 let flags = PacketFlags {
2362 header_type: constants::HEADER_1,
2363 context_flag: constants::FLAG_UNSET,
2364 transport_type: constants::TRANSPORT_BROADCAST,
2365 destination_type: constants::DESTINATION_PLAIN,
2366 packet_type: constants::PACKET_TYPE_DATA,
2367 };
2368 let packet =
2369 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"test").unwrap();
2370
2371 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
2372 let actions = engine.handle_inbound(&packet.raw, InterfaceId(1), 1000.0, &mut rng);
2373
2374 let forward = actions.iter().find(|a| {
2376 matches!(
2377 a,
2378 TransportAction::ForwardPlainBroadcast {
2379 to_local: false,
2380 ..
2381 }
2382 )
2383 });
2384 assert!(forward.is_some(), "Should forward to external interfaces");
2385 }
2386
2387 #[test]
2388 fn test_plain_broadcast_from_external() {
2389 let mut engine = TransportEngine::new(make_config(false));
2391 engine.register_interface(make_local_client_interface(1));
2392 engine.register_interface(make_interface(2, constants::MODE_FULL));
2393
2394 let dest = [0xCC; 16];
2395 let flags = PacketFlags {
2396 header_type: constants::HEADER_1,
2397 context_flag: constants::FLAG_UNSET,
2398 transport_type: constants::TRANSPORT_BROADCAST,
2399 destination_type: constants::DESTINATION_PLAIN,
2400 packet_type: constants::PACKET_TYPE_DATA,
2401 };
2402 let packet =
2403 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"test").unwrap();
2404
2405 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
2406 let actions = engine.handle_inbound(&packet.raw, InterfaceId(2), 1000.0, &mut rng);
2407
2408 let forward = actions.iter().find(|a| {
2410 matches!(
2411 a,
2412 TransportAction::ForwardPlainBroadcast { to_local: true, .. }
2413 )
2414 });
2415 assert!(forward.is_some(), "Should forward to local clients");
2416 }
2417
2418 #[test]
2419 fn test_no_plain_broadcast_bridging_without_local_clients() {
2420 let mut engine = TransportEngine::new(make_config(false));
2422 engine.register_interface(make_interface(1, constants::MODE_FULL));
2423 engine.register_interface(make_interface(2, constants::MODE_FULL));
2424
2425 let dest = [0xDD; 16];
2426 let flags = PacketFlags {
2427 header_type: constants::HEADER_1,
2428 context_flag: constants::FLAG_UNSET,
2429 transport_type: constants::TRANSPORT_BROADCAST,
2430 destination_type: constants::DESTINATION_PLAIN,
2431 packet_type: constants::PACKET_TYPE_DATA,
2432 };
2433 let packet =
2434 RawPacket::pack(flags, 0, &dest, None, constants::CONTEXT_NONE, b"test").unwrap();
2435
2436 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
2437 let actions = engine.handle_inbound(&packet.raw, InterfaceId(1), 1000.0, &mut rng);
2438
2439 let has_forward = actions
2441 .iter()
2442 .any(|a| matches!(a, TransportAction::ForwardPlainBroadcast { .. }));
2443 assert!(!has_forward, "No bridging without local clients");
2444 }
2445
2446 #[test]
2447 fn test_announce_forwarded_to_local_clients() {
2448 use crate::announce::AnnounceData;
2449 use crate::destination::{destination_hash, name_hash};
2450
2451 let mut engine = TransportEngine::new(make_config(false));
2452 engine.register_interface(make_interface(1, constants::MODE_FULL));
2453 engine.register_interface(make_local_client_interface(2));
2454
2455 let identity =
2456 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x77; 32]));
2457 let dest_hash = destination_hash("test", &["fwd"], Some(identity.hash()));
2458 let name_h = name_hash("test", &["fwd"]);
2459 let random_hash = [0x42u8; 10];
2460
2461 let (announce_data, _) =
2462 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
2463
2464 let flags = PacketFlags {
2465 header_type: constants::HEADER_1,
2466 context_flag: constants::FLAG_UNSET,
2467 transport_type: constants::TRANSPORT_BROADCAST,
2468 destination_type: constants::DESTINATION_SINGLE,
2469 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2470 };
2471 let packet = RawPacket::pack(
2472 flags,
2473 0,
2474 &dest_hash,
2475 None,
2476 constants::CONTEXT_NONE,
2477 &announce_data,
2478 )
2479 .unwrap();
2480
2481 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
2482 let actions = engine.handle_inbound(&packet.raw, InterfaceId(1), 1000.0, &mut rng);
2483
2484 let forward = actions
2486 .iter()
2487 .find(|a| matches!(a, TransportAction::ForwardToLocalClients { .. }));
2488 assert!(
2489 forward.is_some(),
2490 "Should forward announce to local clients"
2491 );
2492
2493 match forward.unwrap() {
2495 TransportAction::ForwardToLocalClients { exclude, .. } => {
2496 assert_eq!(*exclude, Some(InterfaceId(1)));
2497 }
2498 _ => unreachable!(),
2499 }
2500 }
2501
2502 #[test]
2503 fn test_no_announce_forward_without_local_clients() {
2504 use crate::announce::AnnounceData;
2505 use crate::destination::{destination_hash, name_hash};
2506
2507 let mut engine = TransportEngine::new(make_config(false));
2508 engine.register_interface(make_interface(1, constants::MODE_FULL));
2509
2510 let identity =
2511 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x88; 32]));
2512 let dest_hash = destination_hash("test", &["nofwd"], Some(identity.hash()));
2513 let name_h = name_hash("test", &["nofwd"]);
2514 let random_hash = [0x42u8; 10];
2515
2516 let (announce_data, _) =
2517 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
2518
2519 let flags = PacketFlags {
2520 header_type: constants::HEADER_1,
2521 context_flag: constants::FLAG_UNSET,
2522 transport_type: constants::TRANSPORT_BROADCAST,
2523 destination_type: constants::DESTINATION_SINGLE,
2524 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2525 };
2526 let packet = RawPacket::pack(
2527 flags,
2528 0,
2529 &dest_hash,
2530 None,
2531 constants::CONTEXT_NONE,
2532 &announce_data,
2533 )
2534 .unwrap();
2535
2536 let mut rng = rns_crypto::FixedRng::new(&[0x22; 32]);
2537 let actions = engine.handle_inbound(&packet.raw, InterfaceId(1), 1000.0, &mut rng);
2538
2539 let has_forward = actions
2541 .iter()
2542 .any(|a| matches!(a, TransportAction::ForwardToLocalClients { .. }));
2543 assert!(!has_forward, "No forward without local clients");
2544 }
2545
2546 #[test]
2547 fn test_local_client_exclude_from_forward() {
2548 use crate::announce::AnnounceData;
2549 use crate::destination::{destination_hash, name_hash};
2550
2551 let mut engine = TransportEngine::new(make_config(false));
2552 engine.register_interface(make_local_client_interface(1));
2553 engine.register_interface(make_local_client_interface(2));
2554
2555 let identity =
2556 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
2557 let dest_hash = destination_hash("test", &["excl"], Some(identity.hash()));
2558 let name_h = name_hash("test", &["excl"]);
2559 let random_hash = [0x42u8; 10];
2560
2561 let (announce_data, _) =
2562 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
2563
2564 let flags = PacketFlags {
2565 header_type: constants::HEADER_1,
2566 context_flag: constants::FLAG_UNSET,
2567 transport_type: constants::TRANSPORT_BROADCAST,
2568 destination_type: constants::DESTINATION_SINGLE,
2569 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2570 };
2571 let packet = RawPacket::pack(
2572 flags,
2573 0,
2574 &dest_hash,
2575 None,
2576 constants::CONTEXT_NONE,
2577 &announce_data,
2578 )
2579 .unwrap();
2580
2581 let mut rng = rns_crypto::FixedRng::new(&[0x33; 32]);
2582 let actions = engine.handle_inbound(&packet.raw, InterfaceId(1), 1000.0, &mut rng);
2584
2585 let forward = actions
2587 .iter()
2588 .find(|a| matches!(a, TransportAction::ForwardToLocalClients { .. }));
2589 assert!(forward.is_some());
2590 match forward.unwrap() {
2591 TransportAction::ForwardToLocalClients { exclude, .. } => {
2592 assert_eq!(*exclude, Some(InterfaceId(1)));
2593 }
2594 _ => unreachable!(),
2595 }
2596 }
2597
2598 fn make_tunnel_interface(id: u64) -> InterfaceInfo {
2603 InterfaceInfo {
2604 id: InterfaceId(id),
2605 name: String::from("tunnel_iface"),
2606 mode: constants::MODE_FULL,
2607 out_capable: true,
2608 in_capable: true,
2609 bitrate: None,
2610 announce_rate_target: None,
2611 announce_rate_grace: 0,
2612 announce_rate_penalty: 0.0,
2613 announce_cap: constants::ANNOUNCE_CAP,
2614 is_local_client: false,
2615 wants_tunnel: true,
2616 tunnel_id: None,
2617 mtu: constants::MTU as u32,
2618 ingress_control: false,
2619 ia_freq: 0.0,
2620 started: 0.0,
2621 }
2622 }
2623
2624 #[test]
2625 fn test_handle_tunnel_new() {
2626 let mut engine = TransportEngine::new(make_config(true));
2627 engine.register_interface(make_tunnel_interface(1));
2628
2629 let tunnel_id = [0xAA; 32];
2630 let actions = engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
2631
2632 assert!(actions
2634 .iter()
2635 .any(|a| matches!(a, TransportAction::TunnelEstablished { .. })));
2636
2637 let info = engine.interface_info(&InterfaceId(1)).unwrap();
2639 assert_eq!(info.tunnel_id, Some(tunnel_id));
2640
2641 assert_eq!(engine.tunnel_table().len(), 1);
2643 }
2644
2645 #[test]
2646 fn test_announce_stores_tunnel_path() {
2647 use crate::announce::AnnounceData;
2648 use crate::destination::{destination_hash, name_hash};
2649
2650 let mut engine = TransportEngine::new(make_config(false));
2651 let mut iface = make_tunnel_interface(1);
2652 let tunnel_id = [0xBB; 32];
2653 iface.tunnel_id = Some(tunnel_id);
2654 engine.register_interface(iface);
2655
2656 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
2658
2659 let identity =
2661 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xCC; 32]));
2662 let dest_hash = destination_hash("test", &["tunnel"], Some(identity.hash()));
2663 let name_h = name_hash("test", &["tunnel"]);
2664 let random_hash = [0x42u8; 10];
2665
2666 let (announce_data, _) =
2667 AnnounceData::pack(&identity, &dest_hash, &name_h, &random_hash, None, None).unwrap();
2668
2669 let flags = PacketFlags {
2670 header_type: constants::HEADER_1,
2671 context_flag: constants::FLAG_UNSET,
2672 transport_type: constants::TRANSPORT_BROADCAST,
2673 destination_type: constants::DESTINATION_SINGLE,
2674 packet_type: constants::PACKET_TYPE_ANNOUNCE,
2675 };
2676 let packet = RawPacket::pack(
2677 flags,
2678 0,
2679 &dest_hash,
2680 None,
2681 constants::CONTEXT_NONE,
2682 &announce_data,
2683 )
2684 .unwrap();
2685
2686 let mut rng = rns_crypto::FixedRng::new(&[0xDD; 32]);
2687 engine.handle_inbound(&packet.raw, InterfaceId(1), 1000.0, &mut rng);
2688
2689 assert!(engine.has_path(&dest_hash));
2691
2692 let tunnel = engine.tunnel_table().get(&tunnel_id).unwrap();
2694 assert_eq!(tunnel.paths.len(), 1);
2695 assert!(tunnel.paths.contains_key(&dest_hash));
2696 }
2697
2698 #[test]
2699 fn test_tunnel_reattach_restores_paths() {
2700 let mut engine = TransportEngine::new(make_config(true));
2701 engine.register_interface(make_tunnel_interface(1));
2702
2703 let tunnel_id = [0xCC; 32];
2704 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
2705
2706 let dest = [0xDD; 16];
2708 engine.tunnel_table.store_tunnel_path(
2709 &tunnel_id,
2710 dest,
2711 tunnel::TunnelPath {
2712 timestamp: 1000.0,
2713 received_from: [0xEE; 16],
2714 hops: 3,
2715 expires: 1000.0 + constants::DESTINATION_TIMEOUT,
2716 random_blobs: Vec::new(),
2717 packet_hash: [0xFF; 32],
2718 },
2719 1000.0,
2720 );
2721
2722 engine.void_tunnel_interface(&tunnel_id);
2724
2725 engine.path_table.remove(&dest);
2727 assert!(!engine.has_path(&dest));
2728
2729 engine.register_interface(make_interface(2, constants::MODE_FULL));
2731 let actions = engine.handle_tunnel(tunnel_id, InterfaceId(2), 2000.0);
2732
2733 assert!(engine.has_path(&dest));
2735 let path = engine.path_table.get(&dest).unwrap().primary().unwrap();
2736 assert_eq!(path.hops, 3);
2737 assert_eq!(path.receiving_interface, InterfaceId(2));
2738
2739 assert!(actions
2741 .iter()
2742 .any(|a| matches!(a, TransportAction::TunnelEstablished { .. })));
2743 }
2744
2745 #[test]
2746 fn test_void_tunnel_interface() {
2747 let mut engine = TransportEngine::new(make_config(true));
2748 engine.register_interface(make_tunnel_interface(1));
2749
2750 let tunnel_id = [0xDD; 32];
2751 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
2752
2753 assert_eq!(
2755 engine.tunnel_table().get(&tunnel_id).unwrap().interface,
2756 Some(InterfaceId(1))
2757 );
2758
2759 engine.void_tunnel_interface(&tunnel_id);
2760
2761 assert_eq!(engine.tunnel_table().len(), 1);
2763 assert_eq!(
2764 engine.tunnel_table().get(&tunnel_id).unwrap().interface,
2765 None
2766 );
2767 }
2768
2769 #[test]
2770 fn test_tick_culls_tunnels() {
2771 let mut engine = TransportEngine::new(make_config(true));
2772 engine.register_interface(make_tunnel_interface(1));
2773
2774 let tunnel_id = [0xEE; 32];
2775 engine.handle_tunnel(tunnel_id, InterfaceId(1), 1000.0);
2776 assert_eq!(engine.tunnel_table().len(), 1);
2777
2778 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
2779
2780 engine.tick(
2782 1000.0 + constants::DESTINATION_TIMEOUT + constants::TABLES_CULL_INTERVAL + 1.0,
2783 &mut rng,
2784 );
2785
2786 assert_eq!(engine.tunnel_table().len(), 0);
2787 }
2788
2789 #[test]
2790 fn test_synthesize_tunnel() {
2791 let mut engine = TransportEngine::new(make_config(true));
2792 engine.register_interface(make_tunnel_interface(1));
2793
2794 let identity =
2795 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0xFF; 32]));
2796 let mut rng = rns_crypto::FixedRng::new(&[0x11; 32]);
2797
2798 let actions = engine.synthesize_tunnel(&identity, InterfaceId(1), &mut rng);
2799
2800 assert_eq!(actions.len(), 1);
2802 match &actions[0] {
2803 TransportAction::TunnelSynthesize {
2804 interface,
2805 data,
2806 dest_hash,
2807 } => {
2808 assert_eq!(*interface, InterfaceId(1));
2809 assert_eq!(data.len(), tunnel::TUNNEL_SYNTH_LENGTH);
2810 let expected_dest = crate::destination::destination_hash(
2812 "rnstransport",
2813 &["tunnel", "synthesize"],
2814 None,
2815 );
2816 assert_eq!(*dest_hash, expected_dest);
2817 }
2818 _ => panic!("Expected TunnelSynthesize"),
2819 }
2820 }
2821
2822 fn make_path_request_data(dest_hash: &[u8; 16], tag: &[u8]) -> Vec<u8> {
2827 let mut data = Vec::new();
2828 data.extend_from_slice(dest_hash);
2829 data.extend_from_slice(tag);
2830 data
2831 }
2832
2833 #[test]
2834 fn test_path_request_forwarded_on_ap() {
2835 let mut engine = TransportEngine::new(make_config(true));
2836 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
2837 engine.register_interface(make_interface(2, constants::MODE_FULL));
2838
2839 let dest = [0xD1; 16];
2840 let tag = [0x01; 16];
2841 let data = make_path_request_data(&dest, &tag);
2842
2843 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
2844
2845 assert_eq!(actions.len(), 1);
2847 match &actions[0] {
2848 TransportAction::SendOnInterface { interface, .. } => {
2849 assert_eq!(*interface, InterfaceId(2));
2850 }
2851 _ => panic!("Expected SendOnInterface for forwarded path request"),
2852 }
2853 assert!(engine.discovery_path_requests.contains_key(&dest));
2855 }
2856
2857 #[test]
2858 fn test_path_request_not_forwarded_on_full() {
2859 let mut engine = TransportEngine::new(make_config(true));
2860 engine.register_interface(make_interface(1, constants::MODE_FULL));
2861 engine.register_interface(make_interface(2, constants::MODE_FULL));
2862
2863 let dest = [0xD2; 16];
2864 let tag = [0x02; 16];
2865 let data = make_path_request_data(&dest, &tag);
2866
2867 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
2868
2869 assert!(actions.is_empty());
2871 assert!(!engine.discovery_path_requests.contains_key(&dest));
2872 }
2873
2874 #[test]
2875 fn test_roaming_loop_prevention() {
2876 let mut engine = TransportEngine::new(make_config(true));
2877 engine.register_interface(make_interface(1, constants::MODE_ROAMING));
2878
2879 let dest = [0xD3; 16];
2880 engine.path_table.insert(
2882 dest,
2883 PathSet::from_single(
2884 PathEntry {
2885 timestamp: 900.0,
2886 next_hop: [0xAA; 16],
2887 hops: 2,
2888 expires: 9999.0,
2889 random_blobs: Vec::new(),
2890 receiving_interface: InterfaceId(1),
2891 packet_hash: [0; 32],
2892 announce_raw: None,
2893 },
2894 1,
2895 ),
2896 );
2897
2898 let tag = [0x03; 16];
2899 let data = make_path_request_data(&dest, &tag);
2900
2901 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
2902
2903 assert!(actions.is_empty());
2905 assert!(!engine.announce_table.contains_key(&dest));
2906 }
2907
2908 fn make_announce_raw(dest_hash: &[u8; 16], payload: &[u8]) -> Vec<u8> {
2910 let flags: u8 = 0x01; let mut raw = Vec::new();
2914 raw.push(flags);
2915 raw.push(0x02); raw.extend_from_slice(dest_hash);
2917 raw.push(constants::CONTEXT_NONE);
2918 raw.extend_from_slice(payload);
2919 raw
2920 }
2921
2922 #[test]
2923 fn test_path_request_populates_announce_entry_from_raw() {
2924 let mut engine = TransportEngine::new(make_config(true));
2925 engine.register_interface(make_interface(1, constants::MODE_FULL));
2926 engine.register_interface(make_interface(2, constants::MODE_FULL));
2927
2928 let dest = [0xD5; 16];
2929 let payload = vec![0xAB; 32]; let announce_raw = make_announce_raw(&dest, &payload);
2931
2932 engine.path_table.insert(
2933 dest,
2934 PathSet::from_single(
2935 PathEntry {
2936 timestamp: 900.0,
2937 next_hop: [0xBB; 16],
2938 hops: 2,
2939 expires: 9999.0,
2940 random_blobs: Vec::new(),
2941 receiving_interface: InterfaceId(2),
2942 packet_hash: [0; 32],
2943 announce_raw: Some(announce_raw.clone()),
2944 },
2945 1,
2946 ),
2947 );
2948
2949 let tag = [0x05; 16];
2950 let data = make_path_request_data(&dest, &tag);
2951 let _actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
2952
2953 let entry = engine
2955 .announce_table
2956 .get(&dest)
2957 .expect("announce entry must exist");
2958 assert_eq!(entry.packet_raw, announce_raw);
2959 assert_eq!(entry.packet_data, payload);
2960 assert!(entry.block_rebroadcasts);
2961 }
2962
2963 #[test]
2964 fn test_path_request_skips_when_no_announce_raw() {
2965 let mut engine = TransportEngine::new(make_config(true));
2966 engine.register_interface(make_interface(1, constants::MODE_FULL));
2967 engine.register_interface(make_interface(2, constants::MODE_FULL));
2968
2969 let dest = [0xD6; 16];
2970
2971 engine.path_table.insert(
2972 dest,
2973 PathSet::from_single(
2974 PathEntry {
2975 timestamp: 900.0,
2976 next_hop: [0xCC; 16],
2977 hops: 1,
2978 expires: 9999.0,
2979 random_blobs: Vec::new(),
2980 receiving_interface: InterfaceId(2),
2981 packet_hash: [0; 32],
2982 announce_raw: None, },
2984 1,
2985 ),
2986 );
2987
2988 let tag = [0x06; 16];
2989 let data = make_path_request_data(&dest, &tag);
2990 let actions = engine.handle_path_request(&data, InterfaceId(1), 1000.0);
2991
2992 assert!(actions.is_empty());
2994 assert!(!engine.announce_table.contains_key(&dest));
2995 }
2996
2997 #[test]
2998 fn test_discovery_request_consumed_on_announce() {
2999 let mut engine = TransportEngine::new(make_config(true));
3000 engine.register_interface(make_interface(1, constants::MODE_ACCESS_POINT));
3001
3002 let dest = [0xD4; 16];
3003
3004 engine.discovery_path_requests.insert(
3006 dest,
3007 DiscoveryPathRequest {
3008 timestamp: 900.0,
3009 requesting_interface: InterfaceId(1),
3010 },
3011 );
3012
3013 let iface = engine.discovery_path_requests_waiting(&dest);
3015 assert_eq!(iface, Some(InterfaceId(1)));
3016
3017 assert!(!engine.discovery_path_requests.contains_key(&dest));
3019 assert_eq!(engine.discovery_path_requests_waiting(&dest), None);
3020 }
3021
3022 fn build_announce_for_issue4(dest_hash: &[u8; 16], name_hash: &[u8; 10]) -> Vec<u8> {
3028 let identity =
3029 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
3030 let random_hash = [0x42u8; 10];
3031 let (announce_data, _) = crate::announce::AnnounceData::pack(
3032 &identity,
3033 dest_hash,
3034 name_hash,
3035 &random_hash,
3036 None,
3037 None,
3038 )
3039 .unwrap();
3040 let flags = PacketFlags {
3041 header_type: constants::HEADER_1,
3042 context_flag: constants::FLAG_UNSET,
3043 transport_type: constants::TRANSPORT_BROADCAST,
3044 destination_type: constants::DESTINATION_SINGLE,
3045 packet_type: constants::PACKET_TYPE_ANNOUNCE,
3046 };
3047 RawPacket::pack(flags, 0, dest_hash, None, constants::CONTEXT_NONE, &announce_data)
3048 .unwrap()
3049 .raw
3050 }
3051
3052 #[test]
3053 fn test_issue4_local_client_single_data_to_1hop_rewrites_on_outbound() {
3054 let mut engine = TransportEngine::new(make_config(false));
3059 engine.register_interface(make_local_client_interface(1));
3060
3061 let identity =
3062 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
3063 let dest_hash = crate::destination::destination_hash(
3064 "issue4",
3065 &["test"],
3066 Some(identity.hash()),
3067 );
3068 let name_hash = crate::destination::name_hash("issue4", &["test"]);
3069 let announce_raw = build_announce_for_issue4(&dest_hash, &name_hash);
3070
3071 let mut announce_packet = RawPacket::unpack(&announce_raw).unwrap();
3075 announce_packet.raw[1] = 1;
3076 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3077 engine.handle_inbound(&announce_packet.raw, InterfaceId(1), 1000.0, &mut rng);
3078 assert!(engine.has_path(&dest_hash));
3079 assert_eq!(engine.hops_to(&dest_hash), Some(1));
3080
3081 let data_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_SINGLE,
3087 packet_type: constants::PACKET_TYPE_DATA,
3088 };
3089 let data_packet = RawPacket::pack(
3090 data_flags,
3091 0,
3092 &dest_hash,
3093 None,
3094 constants::CONTEXT_NONE,
3095 b"hello",
3096 )
3097 .unwrap();
3098
3099 let actions = engine.handle_outbound(
3100 &data_packet,
3101 constants::DESTINATION_SINGLE,
3102 None,
3103 1001.0,
3104 );
3105
3106 let send = actions.iter().find_map(|a| match a {
3107 TransportAction::SendOnInterface { interface, raw } => Some((interface, raw)),
3108 _ => None,
3109 });
3110 let (interface, raw) = send.expect("shared client should emit a transport-injected packet");
3111 assert_eq!(*interface, InterfaceId(1));
3112 let flags = PacketFlags::unpack(raw[0]);
3113 assert_eq!(flags.header_type, constants::HEADER_2);
3114 assert_eq!(flags.transport_type, constants::TRANSPORT_TRANSPORT);
3115 }
3116
3117 #[test]
3118 fn test_issue4_external_data_to_1hop_via_transport_works() {
3119 let daemon_id = [0x42; 16];
3125 let mut engine = TransportEngine::new(TransportConfig {
3126 transport_enabled: true,
3127 identity_hash: Some(daemon_id),
3128 prefer_shorter_path: false,
3129 max_paths_per_destination: 1,
3130 packet_hashlist_max_entries: constants::HASHLIST_MAXSIZE,
3131 });
3132 engine.register_interface(make_interface(1, constants::MODE_FULL)); engine.register_interface(make_interface(2, constants::MODE_FULL)); let identity =
3136 rns_crypto::identity::Identity::new(&mut rns_crypto::FixedRng::new(&[0x99; 32]));
3137 let dest_hash = crate::destination::destination_hash(
3138 "issue4",
3139 &["ctrl"],
3140 Some(identity.hash()),
3141 );
3142 let name_hash = crate::destination::name_hash("issue4", &["ctrl"]);
3143 let announce_raw = build_announce_for_issue4(&dest_hash, &name_hash);
3144
3145 let mut rng = rns_crypto::FixedRng::new(&[0; 32]);
3147 engine.handle_inbound(&announce_raw, InterfaceId(2), 1000.0, &mut rng);
3148 assert_eq!(engine.hops_to(&dest_hash), Some(1));
3149
3150 let h2_flags = PacketFlags {
3153 header_type: constants::HEADER_2,
3154 context_flag: constants::FLAG_UNSET,
3155 transport_type: constants::TRANSPORT_TRANSPORT,
3156 destination_type: constants::DESTINATION_SINGLE,
3157 packet_type: constants::PACKET_TYPE_DATA,
3158 };
3159 let mut h2_raw = Vec::new();
3161 h2_raw.push(h2_flags.pack());
3162 h2_raw.push(0); h2_raw.extend_from_slice(&daemon_id); h2_raw.extend_from_slice(&dest_hash);
3165 h2_raw.push(constants::CONTEXT_NONE);
3166 h2_raw.extend_from_slice(b"hello via transport");
3167
3168 let mut rng2 = rns_crypto::FixedRng::new(&[0x22; 32]);
3169 let actions = engine.handle_inbound(&h2_raw, InterfaceId(1), 1001.0, &mut rng2);
3170
3171 let has_send = actions.iter().any(|a| {
3173 matches!(
3174 a,
3175 TransportAction::SendOnInterface { interface, .. } if *interface == InterfaceId(2)
3176 )
3177 });
3178 assert!(
3179 has_send,
3180 "HEADER_2 transport packet should be forwarded (control test)"
3181 );
3182 }
3183}