@@ -34,16 +34,30 @@ pub(super) struct RemotePathState {
3434 pending_resolve_requests : VecDeque < oneshot:: Sender < Result < ( ) , DiscoveryError > > > ,
3535}
3636
37+ /// Describes the usability of this path, i.e. whether it has ever been opened,
38+ /// when it was closed, or if it has never been usable.
39+ #[ derive( Debug , Default ) ]
40+ pub ( super ) enum PathStatus {
41+ /// This path is open and active.
42+ Open ,
43+ /// This path was once opened, but was abandoned at the given [`Instant`].
44+ Inactive ( Instant ) ,
45+ /// This path was never usable and was abandoned at the given [`Instant`].
46+ Unusable ,
47+ /// We have not yet attempted holepunching, or holepunching is currently in
48+ /// progress, so we do not know the usability of this path.
49+ #[ default]
50+ Unknown ,
51+ }
52+
3753impl RemotePathState {
38- /// Insert a new address into our list of potential paths.
54+ /// Insert a new address of an open path into our list of paths.
3955 ///
40- /// This will emit pending resolve requests and trigger pruning paths..
41- pub ( super ) fn insert ( & mut self , addr : transports:: Addr , source : Source ) {
42- self . paths
43- . entry ( addr)
44- . or_default ( )
45- . sources
46- . insert ( source. clone ( ) , Instant :: now ( ) ) ;
56+ /// This will emit pending resolve requests and trigger pruning paths.
57+ pub ( super ) fn insert_open_path ( & mut self , addr : transports:: Addr , source : Source ) {
58+ let state = self . paths . entry ( addr) . or_default ( ) ;
59+ state. status = PathStatus :: Open ;
60+ state. sources . insert ( source. clone ( ) , Instant :: now ( ) ) ;
4761 self . emit_pending_resolve_requests ( None ) ;
4862 self . prune_paths ( ) ;
4963 }
@@ -54,22 +68,18 @@ impl RemotePathState {
5468 /// `RemotePathState`
5569 pub ( super ) fn abandoned_path ( & mut self , addr : & transports:: Addr ) {
5670 if let Some ( state) = self . paths . get_mut ( addr) {
57- state. abandoned = Some ( Instant :: now ( ) ) ;
58- }
59- }
60-
61- /// Mark a path as opened.
62- ///
63- /// If this path does not exist, it does nothing to the
64- /// `RemotePathState`
65- pub ( super ) fn opened_path ( & mut self , addr : & transports:: Addr ) {
66- if let Some ( state) = self . paths . get_mut ( addr) {
67- state. usable = true ;
68- state. abandoned = None ;
71+ match state. status {
72+ PathStatus :: Open | PathStatus :: Inactive ( _) => {
73+ state. status = PathStatus :: Inactive ( Instant :: now ( ) ) ;
74+ }
75+ PathStatus :: Unusable | PathStatus :: Unknown => {
76+ state. status = PathStatus :: Unusable ;
77+ }
78+ }
6979 }
7080 }
7181
72- /// Inserts multiple addresses into our list of potential paths.
82+ /// Inserts multiple addresses of unknown status into our list of potential paths.
7383 ///
7484 /// This will emit pending resolve requests and trigger pruning paths.
7585 pub ( super ) fn insert_multiple (
@@ -165,26 +175,15 @@ pub(super) struct PathState {
165175 /// We keep track of only the latest [`Instant`] for each [`Source`], keeping the size
166176 /// of the map of sources down to one entry per type of source.
167177 pub ( super ) sources : HashMap < Source , Instant > ,
168- /// The last time this path was proven usable.
169- ///
170- /// If this is `false` and the `abandoned` field is `Some`, than we attempted
171- /// to open this path, but it did not work.
172- ///
173- /// If this is `false` and the `abandoned` field is `None`, than we do not know
174- /// yet if this path is usable.
175- pub ( super ) usable : bool ,
176- /// The last time a path with this addr was abandoned.
177- ///
178- /// If this is `Some` and usable is `None`, than we attempted to use this path and it
179- /// did not work.
180- pub ( super ) abandoned : Option < Instant > ,
178+ /// The usability status of this path.
179+ pub ( super ) status : PathStatus ,
181180}
182181
183182/// Prunes the IP paths in the paths HashMap.
184183///
185184/// Only prunes if the number of IP paths is above [`MAX_IP_PATHS`].
186185///
187- /// Keeps paths that are active or have never been holepunched .
186+ /// Keeps paths that are open or of unknown status .
188187///
189188/// Always prunes paths that have unsuccessfully holepunched.
190189///
@@ -216,16 +215,18 @@ fn prune_ip_paths(paths: &mut FxHashMap<transports::Addr, PathState>) {
216215 let mut failed = Vec :: with_capacity ( ip_paths. len ( ) ) ;
217216
218217 for ( addr, state) in ip_paths {
219- // ignore paths that have never been abandoned, they are either currently
220- // open or were never dialed.
221- if let Some ( abandoned) = state. abandoned {
222- if state. usable {
218+ match state. status {
219+ PathStatus :: Inactive ( t) => {
223220 // paths where holepunching succeeded at one point, but the path was closed.
224- inactive. push ( ( addr. clone ( ) , abandoned) ) ;
225- } else {
221+ inactive. push ( ( addr. clone ( ) , t) ) ;
222+ }
223+ PathStatus :: Unusable => {
226224 // paths where holepunching has been attempted and failed.
227225 failed. push ( addr. clone ( ) ) ;
228226 }
227+ _ => {
228+ // ignore paths that are open or the status is unknown
229+ }
229230 }
230231 }
231232
@@ -268,19 +269,17 @@ mod tests {
268269 transports:: Addr :: Ip ( SocketAddrV4 :: new ( Ipv4Addr :: LOCALHOST , port) . into ( ) )
269270 }
270271
271- fn path_state_usable_abandoned ( abandoned : Instant ) -> PathState {
272+ fn path_state_inactive ( closed : Instant ) -> PathState {
272273 PathState {
273274 sources : HashMap :: new ( ) ,
274- usable : true ,
275- abandoned : Some ( abandoned) ,
275+ status : PathStatus :: Inactive ( closed) ,
276276 }
277277 }
278278
279- fn path_state_failed_abandoned ( abandoned : Instant ) -> PathState {
279+ fn path_state_unusable ( ) -> PathState {
280280 PathState {
281281 sources : HashMap :: new ( ) ,
282- usable : false ,
283- abandoned : Some ( abandoned) ,
282+ status : PathStatus :: Unusable ,
284283 }
285284 }
286285
@@ -310,7 +309,6 @@ mod tests {
310309 #[ test]
311310 fn test_prune_failed_holepunch ( ) {
312311 let mut paths = FxHashMap :: default ( ) ;
313- let now = Instant :: now ( ) ;
314312
315313 // Add 20 active paths
316314 for i in 0 ..20 {
@@ -319,7 +317,7 @@ mod tests {
319317
320318 // Add 15 failed holepunch paths (must_prune)
321319 for i in 20 ..35 {
322- paths. insert ( ip_addr ( i) , path_state_failed_abandoned ( now ) ) ;
320+ paths. insert ( ip_addr ( i) , path_state_unusable ( ) ) ;
323321 }
324322
325323 prune_ip_paths ( & mut paths) ;
@@ -344,14 +342,11 @@ mod tests {
344342 paths. insert ( ip_addr ( i) , PathState :: default ( ) ) ;
345343 }
346344
347- // Add 20 usable but abandoned paths with different abandon times
345+ // Add 20 inactive paths with different abandon times
348346 // Ports 15-34, with port 34 being most recently abandoned
349347 for i in 0 ..20 {
350348 let abandoned_time = now - Duration :: from_secs ( ( 20 - i) as u64 ) ;
351- paths. insert (
352- ip_addr ( 15 + i as u16 ) ,
353- path_state_usable_abandoned ( abandoned_time) ,
354- ) ;
349+ paths. insert ( ip_addr ( 15 + i as u16 ) , path_state_inactive ( abandoned_time) ) ;
355350 }
356351
357352 assert_eq ! ( 35 , paths. len( ) ) ;
@@ -392,16 +387,13 @@ mod tests {
392387
393388 // Add 5 failed holepunch paths
394389 for i in 15 ..20 {
395- paths. insert ( ip_addr ( i) , path_state_failed_abandoned ( now ) ) ;
390+ paths. insert ( ip_addr ( i) , path_state_unusable ( ) ) ;
396391 }
397392
398393 // Add 15 usable but abandoned paths
399394 for i in 0 ..15 {
400395 let abandoned_time = now - Duration :: from_secs ( ( 15 - i) as u64 ) ;
401- paths. insert (
402- ip_addr ( 20 + i as u16 ) ,
403- path_state_usable_abandoned ( abandoned_time) ,
404- ) ;
396+ paths. insert ( ip_addr ( 20 + i as u16 ) , path_state_inactive ( abandoned_time) ) ;
405397 }
406398
407399 assert_eq ! ( 35 , paths. len( ) ) ;
@@ -430,11 +422,10 @@ mod tests {
430422 #[ test]
431423 fn test_prune_non_ip_paths_not_counted ( ) {
432424 let mut paths = FxHashMap :: default ( ) ;
433- let now = Instant :: now ( ) ;
434425
435426 // Add 25 IP paths (under MAX_IP_PATHS)
436427 for i in 0 ..25 {
437- paths. insert ( ip_addr ( i) , path_state_failed_abandoned ( now ) ) ;
428+ paths. insert ( ip_addr ( i) , path_state_unusable ( ) ) ;
438429 }
439430
440431 let mut rng = rand_chacha:: ChaCha8Rng :: seed_from_u64 ( 0u64 ) ;
@@ -458,16 +449,15 @@ mod tests {
458449 #[ test]
459450 fn test_prune_preserves_never_dialed ( ) {
460451 let mut paths = FxHashMap :: default ( ) ;
461- let now = Instant :: now ( ) ;
462452
463- // Add 20 never-dialed paths (abandoned = None, usable = false )
453+ // Add 20 never-dialed paths (PathStatus::Unknown )
464454 for i in 0 ..20 {
465455 paths. insert ( ip_addr ( i) , PathState :: default ( ) ) ;
466456 }
467457
468458 // Add 15 failed paths to trigger pruning
469459 for i in 20 ..35 {
470- paths. insert ( ip_addr ( i) , path_state_failed_abandoned ( now ) ) ;
460+ paths. insert ( ip_addr ( i) , path_state_unusable ( ) ) ;
471461 }
472462
473463 prune_ip_paths ( & mut paths) ;
@@ -481,11 +471,10 @@ mod tests {
481471 #[ test]
482472 fn test_prune_all_paths_failed ( ) {
483473 let mut paths = FxHashMap :: default ( ) ;
484- let now = Instant :: now ( ) ;
485474
486475 // Add 40 failed holepunch paths (all paths have failed)
487476 for i in 0 ..40 {
488- paths. insert ( ip_addr ( i) , path_state_failed_abandoned ( now ) ) ;
477+ paths. insert ( ip_addr ( i) , path_state_unusable ( ) ) ;
489478 }
490479
491480 assert_eq ! ( 40 , paths. len( ) ) ;
@@ -499,4 +488,66 @@ mod tests {
499488 "should keep MAX_IP_PATHS when all paths failed"
500489 ) ;
501490 }
491+
492+ #[ test]
493+ fn test_insert_open_path ( ) {
494+ let mut state = RemotePathState :: default ( ) ;
495+ let addr = ip_addr ( 1000 ) ;
496+ let source = Source :: Udp ;
497+
498+ assert ! ( state. is_empty( ) ) ;
499+
500+ state. insert_open_path ( addr. clone ( ) , source. clone ( ) ) ;
501+
502+ assert ! ( !state. is_empty( ) ) ;
503+ assert ! ( state. paths. contains_key( & addr) ) ;
504+ let path = & state. paths [ & addr] ;
505+ assert ! ( matches!( path. status, PathStatus :: Open ) ) ;
506+ assert_eq ! ( path. sources. len( ) , 1 ) ;
507+ assert ! ( path. sources. contains_key( & source) ) ;
508+ }
509+
510+ #[ test]
511+ fn test_abandoned_path ( ) {
512+ let mut state = RemotePathState :: default ( ) ;
513+
514+ // Test: Open goes to Inactive
515+ let addr_open = ip_addr ( 1000 ) ;
516+ state. insert_open_path ( addr_open. clone ( ) , Source :: Udp ) ;
517+ assert ! ( matches!( state. paths[ & addr_open] . status, PathStatus :: Open ) ) ;
518+
519+ state. abandoned_path ( & addr_open) ;
520+ assert ! ( matches!(
521+ state. paths[ & addr_open] . status,
522+ PathStatus :: Inactive ( _)
523+ ) ) ;
524+
525+ // Test: Inactive stays Inactive
526+ state. abandoned_path ( & addr_open) ;
527+ assert ! ( matches!(
528+ state. paths[ & addr_open] . status,
529+ PathStatus :: Inactive ( _)
530+ ) ) ;
531+
532+ // Test: Unknown goes to Unusable
533+ let addr_unknown = ip_addr ( 2000 ) ;
534+ state. insert_multiple ( [ addr_unknown. clone ( ) ] . into_iter ( ) , Source :: Relay ) ;
535+ assert ! ( matches!(
536+ state. paths[ & addr_unknown] . status,
537+ PathStatus :: Unknown
538+ ) ) ;
539+
540+ state. abandoned_path ( & addr_unknown) ;
541+ assert ! ( matches!(
542+ state. paths[ & addr_unknown] . status,
543+ PathStatus :: Unusable
544+ ) ) ;
545+
546+ // Test: Unusable stays Unusable
547+ state. abandoned_path ( & addr_unknown) ;
548+ assert ! ( matches!(
549+ state. paths[ & addr_unknown] . status,
550+ PathStatus :: Unusable
551+ ) ) ;
552+ }
502553}
0 commit comments