Skip to main content

connexa/behaviour/
autorelay.rs

1// TODO: Replace with builtin autorelay behaviour from libp2p. See https://github.com/libp2p/rust-libp2p/pull/6156
2use crate::behaviour::autorelay::handler::Out;
3use crate::multiaddr_ext::MultiaddrExt;
4use crate::prelude::swarm::derive_prelude::{ListenerId, PortUse};
5use crate::prelude::swarm::{
6    ExternalAddresses, ListenOpts, NewListenAddr, NotifyHandler,
7    derive_prelude::{
8        AddressChange, ConnectionClosed, ConnectionDenied, ConnectionEstablished, ConnectionId,
9        DialFailure, ExpiredListenAddr, FromSwarm, ListenerClosed, ListenerError, Multiaddr,
10        NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm,
11    },
12    dial_opts::DialOpts,
13    dummy,
14};
15use crate::prelude::transport::Endpoint;
16use crate::prelude::{PeerId, Protocol};
17use either::Either;
18use std::collections::BTreeMap;
19use std::{
20    collections::{HashMap, HashSet, VecDeque},
21    num::NonZeroU8,
22    task::{Context, Poll, Waker},
23    time::Duration,
24};
25use web_time::{Instant, SystemTime};
26
27mod handler;
28
29#[derive(Debug)]
30pub struct Behaviour {
31    config: Config,
32    status: Status,
33    auto_status_change: bool,
34    external_addresses: ExternalAddresses,
35    events: VecDeque<ToSwarm<<Self as NetworkBehaviour>::ToSwarm, THandlerInEvent<Self>>>,
36
37    connections: HashMap<(PeerId, ConnectionId), Connection>,
38
39    reservations: HashMap<ListenerId, (PeerId, ConnectionId)>,
40
41    external_reservations: HashMap<ListenerId, PeerId>,
42
43    // placeholder for reservations.
44    // TODO: Removed once https://github.com/libp2p/rust-libp2p/pull/3222 is published or backported.
45    reservation_addrs: HashMap<ListenerId, HashSet<Multiaddr>>,
46
47    static_relays: HashMap<PeerId, Vec<Multiaddr>>,
48
49    static_dial_cooldowns: HashMap<PeerId, Instant>,
50
51    failure_counts: HashMap<PeerId, u32>,
52
53    previous_relays: VecDeque<(PeerId, Multiaddr, SystemTime)>,
54
55    relays_available: bool,
56
57    waker: Option<Waker>,
58}
59
60impl Default for Behaviour {
61    fn default() -> Self {
62        Self {
63            config: Config::default(),
64            status: Status::Enable,
65            auto_status_change: true,
66            external_addresses: ExternalAddresses::default(),
67            events: VecDeque::new(),
68            connections: HashMap::new(),
69            reservations: HashMap::new(),
70            external_reservations: HashMap::new(),
71            reservation_addrs: HashMap::new(),
72            static_relays: HashMap::new(),
73            static_dial_cooldowns: HashMap::new(),
74            failure_counts: HashMap::new(),
75            previous_relays: VecDeque::new(),
76            relays_available: false,
77            waker: None,
78        }
79    }
80}
81
82#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
83pub enum Status {
84    #[default]
85    Enable,
86    Disable,
87}
88
89#[derive(Debug)]
90struct Connection {
91    address: Multiaddr,
92    relay_status: RelayStatus,
93}
94
95impl Connection {
96    /// Mark relayed connection as not supported
97    pub(crate) fn disqualify_connection_if_relayed(&mut self) {
98        if self.address.is_relayed() {
99            self.relay_status = RelayStatus::NotSupported;
100        }
101    }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105enum RelayStatus {
106    Supported { status: ReservationStatus },
107    NotSupported,
108    Pending,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112enum ReservationStatus {
113    Idle,
114    Pending { id: ListenerId },
115    Active { id: ListenerId },
116    Blacklisted,
117}
118
119#[derive(Debug)]
120pub struct Config {
121    max_reservations: NonZeroU8,
122    failure_cooldown: Duration,
123    failure_cooldown_max: Duration,
124    max_previous_relays: usize,
125    static_relays: HashMap<PeerId, Vec<Multiaddr>>,
126}
127
128impl Default for Config {
129    fn default() -> Self {
130        Self {
131            max_reservations: NonZeroU8::new(2).unwrap(),
132            failure_cooldown: Duration::from_secs(30),
133            failure_cooldown_max: Duration::from_secs(10 * 60),
134            max_previous_relays: 16,
135            static_relays: HashMap::new(),
136        }
137    }
138}
139
140impl Config {
141    pub fn set_max_reservations(mut self, max_reservations: NonZeroU8) -> Self {
142        self.max_reservations = max_reservations;
143        self
144    }
145
146    pub fn set_failure_cooldown(mut self, duration: Duration) -> Self {
147        self.failure_cooldown = duration;
148        self
149    }
150
151    pub fn set_failure_cooldown_max(mut self, duration: Duration) -> Self {
152        self.failure_cooldown_max = duration;
153        self
154    }
155
156    pub fn set_max_previous_relays(mut self, max: usize) -> Self {
157        self.max_previous_relays = max;
158        self
159    }
160
161    pub fn add_static_relay(mut self, peer_id: PeerId, addresses: Vec<Multiaddr>) -> Self {
162        let entry = self.static_relays.entry(peer_id).or_default();
163        for addr in addresses {
164            if !entry.contains(&addr) {
165                entry.push(addr);
166            }
167        }
168        self
169    }
170}
171
172#[derive(Debug)]
173#[non_exhaustive]
174pub enum Event {
175    /// The status of the local node has changed.
176    StatusChanged { status: Status },
177    /// No connected peer supports the HOP protocol.
178    NoRelaysAvailable,
179    /// At least one connected peer supports the HOP protocol.
180    RelaysAvailable,
181}
182
183impl Behaviour {
184    pub fn new_with_config(mut config: Config) -> Self {
185        let initial_static_relays = std::mem::take(&mut config.static_relays);
186        let mut behaviour = Self {
187            config,
188            ..Default::default()
189        };
190        for (peer_id, addresses) in initial_static_relays {
191            for address in addresses {
192                behaviour.add_static_relay(peer_id, address);
193            }
194        }
195        behaviour
196    }
197
198    /// Sets the autorelay status.
199    pub fn set_status(&mut self, status: Option<Status>) {
200        match status {
201            Some(status) => {
202                self.auto_status_change = false;
203                if self.status != status {
204                    self.status = status;
205                    self.events
206                        .push_back(ToSwarm::GenerateEvent(Event::StatusChanged { status }));
207                    if status == Status::Enable {
208                        self.meet_reservation_target();
209                    }
210                }
211            }
212            None => {
213                self.auto_status_change = true;
214                self.determine_status_from_external_addresses();
215            }
216        }
217
218        if let Some(waker) = self.waker.take() {
219            waker.wake();
220        }
221    }
222
223    /// Register a peer as a static relay.
224    ///
225    /// This will dial and establish a connection to the peer if it doesn't already have a direct
226    /// connection.
227    /// Note that peers that are through a relay cannot be used as a static peer
228    pub fn add_static_relay(&mut self, peer_id: PeerId, address: Multiaddr) -> bool {
229        if address.is_relayed() {
230            tracing::warn!(%peer_id, %address, "static relay address is relayed. ignoring.");
231            return false;
232        }
233
234        let entry = self.static_relays.entry(peer_id).or_default();
235        if entry.contains(&address) {
236            tracing::warn!(%peer_id, %address, "static relay address already exist");
237        } else {
238            entry.push(address);
239        }
240        let combined = entry.clone();
241
242        if self.is_peer_idle(&peer_id) {
243            self.evict_for_static_peer(peer_id);
244        }
245
246        if !self.queue_static_dial(peer_id, combined) {
247            self.meet_reservation_target();
248        }
249
250        if let Some(waker) = self.waker.take() {
251            waker.wake();
252        }
253
254        true
255    }
256
257    /// Remove peer as a static relay.
258    /// This will not close any connections or terminate any existing reservation with the relay
259    pub fn remove_static_relay(&mut self, peer_id: &PeerId) -> bool {
260        self.static_dial_cooldowns.remove(peer_id);
261        self.static_relays.remove(peer_id).is_some()
262    }
263
264    pub fn static_relays(&self) -> impl Iterator<Item = (&PeerId, &[Multiaddr])> {
265        self.static_relays
266            .iter()
267            .map(|(peer, addrs)| (peer, addrs.as_slice()))
268    }
269
270    pub fn previous_relays(&self) -> impl Iterator<Item = (&PeerId, &Multiaddr, &SystemTime)> {
271        self.previous_relays
272            .iter()
273            .map(|(peer, addr, ts)| (peer, addr, ts))
274    }
275
276    fn static_dial_in_cooldown(&self, peer_id: &PeerId) -> bool {
277        self.static_dial_cooldowns
278            .get(peer_id)
279            .is_some_and(|deadline| *deadline > Instant::now())
280    }
281
282    fn queue_static_dial(&mut self, peer_id: PeerId, addresses: Vec<Multiaddr>) -> bool {
283        if addresses.is_empty()
284            || self.has_direct_connection(&peer_id)
285            || self.static_dial_in_cooldown(&peer_id)
286        {
287            return false;
288        }
289        let opts = DialOpts::peer_id(peer_id).addresses(addresses).build();
290        self.events.push_back(ToSwarm::Dial { opts });
291        true
292    }
293
294    fn record_previous_relay(&mut self, peer_id: PeerId, address: Multiaddr) {
295        let max = self.config.max_previous_relays;
296        if max == 0 {
297            return;
298        }
299        self.previous_relays.retain(|(p, _, _)| *p != peer_id);
300        if self.previous_relays.len() >= max {
301            self.previous_relays.pop_front();
302        }
303        self.previous_relays
304            .push_back((peer_id, address, SystemTime::now()));
305    }
306
307    fn forget_previous_relay(&mut self, peer_id: &PeerId) {
308        self.previous_relays.retain(|(p, _, _)| p != peer_id);
309    }
310
311    fn record_failure(&mut self, peer_id: PeerId) -> Duration {
312        let attempts = self.failure_counts.entry(peer_id).or_insert(0);
313        *attempts = attempts.saturating_add(1);
314        let exponent = attempts.saturating_sub(1).min(20);
315        let scale = 1u32 << exponent;
316        self.config
317            .failure_cooldown
318            .saturating_mul(scale)
319            .min(self.config.failure_cooldown_max)
320    }
321
322    fn clear_failure(&mut self, peer_id: &PeerId) {
323        self.failure_counts.remove(peer_id);
324    }
325
326    fn determine_status_from_external_addresses(&mut self) {
327        let has_public_addr = self
328            .external_addresses
329            .iter()
330            .any(|addr| !addr.is_relayed());
331
332        let new_status = match has_public_addr {
333            true => Status::Disable,
334            false => Status::Enable,
335        };
336        if new_status != self.status {
337            self.status = new_status;
338            self.events
339                .push_back(ToSwarm::GenerateEvent(Event::StatusChanged {
340                    status: new_status,
341                }));
342            match new_status {
343                Status::Enable => self.meet_reservation_target(),
344                Status::Disable => self.remove_all_reservations(),
345            }
346        }
347    }
348
349    fn is_peer_idle(&self, peer_id: &PeerId) -> bool {
350        self.connections.iter().any(|((pid, _), info)| {
351            pid == peer_id
352                && info.relay_status
353                    == RelayStatus::Supported {
354                        status: ReservationStatus::Idle,
355                    }
356        })
357    }
358
359    fn has_direct_connection(&self, peer_id: &PeerId) -> bool {
360        self.connections
361            .iter()
362            .any(|((pid, _), info)| pid == peer_id && !info.address.is_relayed())
363    }
364
365    fn evict_for_static_peer(&mut self, new_static: PeerId) {
366        let covered = self.covered_peers();
367        if covered.contains(&new_static) {
368            return;
369        }
370        let max = self.config.max_reservations.get() as usize;
371        if covered.len() < max {
372            return;
373        }
374
375        if let Some(listener_id) = self
376            .reservations
377            .iter()
378            .find(|(_, (peer_id, _))| !self.static_relays.contains_key(peer_id))
379            .map(|(listener_id, _)| *listener_id)
380        {
381            self.events
382                .push_back(ToSwarm::RemoveListener { id: listener_id });
383        }
384    }
385
386    fn select_connection_for_reservation(&mut self, peer_id: PeerId, connection_id: ConnectionId) {
387        let info = self
388            .connections
389            .get_mut(&(peer_id, connection_id))
390            .expect("connection is present");
391
392        if info.relay_status
393            != (RelayStatus::Supported {
394                status: ReservationStatus::Idle,
395            })
396        {
397            return;
398        }
399
400        let addr_with_peer_id = match info.address.clone().with_p2p(peer_id) {
401            Ok(addr) => addr,
402            Err(addr) => {
403                tracing::warn!(%addr, "address unexpectedly contains a different peer id than the connection; marking relay connection ineligible");
404                info.relay_status = RelayStatus::NotSupported;
405                return;
406            }
407        };
408
409        let opts = ListenOpts::new(addr_with_peer_id.with(Protocol::P2pCircuit));
410        let id = opts.listener_id();
411
412        info.relay_status = RelayStatus::Supported {
413            status: ReservationStatus::Pending { id },
414        };
415        self.reservations.insert(id, (peer_id, connection_id));
416        self.events.push_back(ToSwarm::ListenOn { opts });
417    }
418
419    /// Removes all existing reservations.
420    pub fn remove_all_reservations(&mut self) {
421        let relay_listeners = self
422            .reservations
423            .iter()
424            .map(|(id, (peer_id, conn_id))| (*id, *peer_id, *conn_id))
425            .collect::<Vec<_>>();
426
427        for (listener_id, peer_id, connection_id) in relay_listeners {
428            let Some(connection) = self.connections.get_mut(&(peer_id, connection_id)) else {
429                continue;
430            };
431
432            if !matches!(
433                connection.relay_status,
434                RelayStatus::Supported {
435                    status: ReservationStatus::Active { id } | ReservationStatus::Pending { id }
436                } if id == listener_id
437            ) {
438                continue;
439            }
440
441            connection.relay_status = RelayStatus::Supported {
442                status: ReservationStatus::Idle,
443            };
444
445            self.events
446                .push_back(ToSwarm::RemoveListener { id: listener_id });
447        }
448    }
449
450    fn disable_reservation(&mut self, id: ListenerId, failed: bool) {
451        self.expire_reservation_addrs(id);
452
453        if self.external_reservations.remove(&id).is_some() {
454            self.meet_reservation_target();
455            return;
456        }
457
458        let Some((peer_id, connection_id)) = self.reservations.remove(&id) else {
459            return;
460        };
461
462        let Some(address) = self
463            .connections
464            .get(&(peer_id, connection_id))
465            .filter(|info| {
466                matches!(
467                    info.relay_status,
468                    RelayStatus::Supported {
469                        status: ReservationStatus::Active { .. }
470                            | ReservationStatus::Pending { .. }
471                    }
472                )
473            })
474            .map(|info| info.address.clone())
475        else {
476            self.meet_reservation_target();
477            return;
478        };
479
480        let blacklist_duration = failed.then(|| self.record_failure(peer_id));
481
482        let connection = self
483            .connections
484            .get_mut(&(peer_id, connection_id))
485            .expect("connection is tracked");
486        match blacklist_duration {
487            Some(duration) => {
488                connection.relay_status = RelayStatus::Supported {
489                    status: ReservationStatus::Blacklisted,
490                };
491                self.events.push_back(ToSwarm::NotifyHandler {
492                    peer_id,
493                    handler: NotifyHandler::One(connection_id),
494                    event: Either::Left(handler::In::Blacklist { duration }),
495                });
496            }
497            None => {
498                connection.relay_status = RelayStatus::Supported {
499                    status: ReservationStatus::Idle,
500                };
501            }
502        }
503
504        self.record_previous_relay(peer_id, address);
505        self.meet_reservation_target();
506    }
507
508    fn reconcile_reservation_addrs(&mut self) {
509        let confirmed: HashSet<Multiaddr> = self
510            .external_addresses
511            .iter()
512            .filter(|addr| addr.is_relayed())
513            .cloned()
514            .collect();
515
516        for addrs in self.reservation_addrs.values_mut() {
517            addrs.retain(|addr| confirmed.contains(addr));
518        }
519        self.reservation_addrs.retain(|_, addrs| !addrs.is_empty());
520
521        for addr in confirmed {
522            let Some(relay_peer) = addr.relay_peer_id() else {
523                continue;
524            };
525
526            let listeners = self
527                .reservations
528                .iter()
529                .filter(|(_, (peer_id, _))| *peer_id == relay_peer)
530                .map(|(id, _)| *id)
531                .chain(
532                    self.external_reservations
533                        .iter()
534                        .filter(|(_, peer_id)| **peer_id == relay_peer)
535                        .map(|(id, _)| *id),
536                )
537                .collect::<Vec<_>>();
538
539            for id in listeners {
540                self.reservation_addrs
541                    .entry(id)
542                    .or_default()
543                    .insert(addr.clone());
544            }
545        }
546    }
547
548    fn expire_reservation_addrs(&mut self, id: ListenerId) {
549        let Some(addrs) = self.reservation_addrs.remove(&id) else {
550            return;
551        };
552
553        for addr in addrs {
554            let still_backed = self
555                .reservation_addrs
556                .values()
557                .any(|other| other.contains(&addr));
558            if !still_backed {
559                self.events.push_back(ToSwarm::ExternalAddrExpired(addr));
560            }
561        }
562    }
563
564    fn covered_peers(&self) -> HashSet<PeerId> {
565        self.reservations
566            .values()
567            .map(|(peer_id, _)| *peer_id)
568            .chain(self.external_reservations.values().copied())
569            .collect()
570    }
571
572    /// Meet the reservation target by selecting connections to establish a reservation.
573    fn meet_reservation_target(&mut self) {
574        if self.status == Status::Disable {
575            return;
576        }
577
578        let max = self.config.max_reservations.get() as usize;
579        let covered = self.covered_peers();
580        let budget = max.saturating_sub(covered.len());
581        if budget == 0 {
582            return;
583        }
584
585        let mut static_candidates = BTreeMap::new();
586        let mut candidates: BTreeMap<_, ConnectionId> = BTreeMap::new();
587        for ((peer_id, connection_id), info) in self.connections.iter() {
588            if covered.contains(peer_id) {
589                continue;
590            }
591            if info.relay_status
592                != (RelayStatus::Supported {
593                    status: ReservationStatus::Idle,
594                })
595            {
596                continue;
597            }
598            let bucket = if self.static_relays.contains_key(peer_id) {
599                &mut static_candidates
600            } else {
601                &mut candidates
602            };
603            bucket
604                .entry(*peer_id)
605                .and_modify(|existing| *existing = (*existing).min(*connection_id))
606                .or_insert(*connection_id);
607        }
608
609        let selected_candidates: Vec<(PeerId, ConnectionId)> = static_candidates
610            .into_iter()
611            .chain(candidates)
612            .take(budget)
613            .collect();
614
615        for (peer_id, connection_id) in selected_candidates {
616            self.select_connection_for_reservation(peer_id, connection_id);
617        }
618
619        debug_assert!(self.covered_peers().len() <= max);
620    }
621
622    fn update_relay_availability(&mut self) {
623        let has_hop_peer = self
624            .connections
625            .values()
626            .any(|info| matches!(info.relay_status, RelayStatus::Supported { .. }));
627
628        match (has_hop_peer, self.relays_available) {
629            (true, false) => {
630                self.relays_available = true;
631                self.events
632                    .push_back(ToSwarm::GenerateEvent(Event::RelaysAvailable));
633            }
634            (false, true) => {
635                self.relays_available = false;
636                self.events
637                    .push_back(ToSwarm::GenerateEvent(Event::NoRelaysAvailable));
638            }
639            _ => {}
640        }
641    }
642}
643
644impl NetworkBehaviour for Behaviour {
645    type ConnectionHandler = Either<handler::Handler, dummy::ConnectionHandler>;
646    type ToSwarm = Event;
647
648    fn handle_established_inbound_connection(
649        &mut self,
650        _connection_id: ConnectionId,
651        _peer: PeerId,
652        local_addr: &Multiaddr,
653        _remote_addr: &Multiaddr,
654    ) -> Result<THandler<Self>, ConnectionDenied> {
655        if local_addr.is_relayed() {
656            Ok(Either::Right(dummy::ConnectionHandler))
657        } else {
658            Ok(Either::Left(handler::Handler::default()))
659        }
660    }
661
662    fn handle_established_outbound_connection(
663        &mut self,
664        _connection_id: ConnectionId,
665        _peer: PeerId,
666        addr: &Multiaddr,
667        _role_override: Endpoint,
668        _port_use: PortUse,
669    ) -> Result<THandler<Self>, ConnectionDenied> {
670        if addr.is_relayed() {
671            Ok(Either::Right(dummy::ConnectionHandler))
672        } else {
673            Ok(Either::Left(handler::Handler::default()))
674        }
675    }
676
677    fn on_swarm_event(&mut self, event: FromSwarm) {
678        let change = self.external_addresses.on_swarm_event(&event);
679
680        if self.auto_status_change && change {
681            self.determine_status_from_external_addresses();
682        }
683
684        if change {
685            self.reconcile_reservation_addrs();
686        }
687
688        match event {
689            FromSwarm::ConnectionEstablished(ConnectionEstablished {
690                peer_id,
691                endpoint,
692                connection_id,
693                ..
694            }) => {
695                let remote_addr = endpoint.get_remote_address().clone();
696
697                let mut connection = Connection {
698                    address: remote_addr,
699                    relay_status: RelayStatus::Pending,
700                };
701
702                connection.disqualify_connection_if_relayed();
703
704                self.connections
705                    .insert((peer_id, connection_id), connection);
706
707                if self.static_relays.contains_key(&peer_id) {
708                    self.static_dial_cooldowns.remove(&peer_id);
709                }
710            }
711            FromSwarm::ConnectionClosed(ConnectionClosed {
712                peer_id,
713                connection_id,
714                ..
715            }) => {
716                let Some(connection) = self.connections.remove(&(peer_id, connection_id)) else {
717                    return;
718                };
719
720                if !self.connections.keys().any(|(pid, _)| *pid == peer_id) {
721                    self.clear_failure(&peer_id);
722                }
723
724                let had_reservation = matches!(
725                    connection.relay_status,
726                    RelayStatus::Supported {
727                        status: ReservationStatus::Active { .. }
728                            | ReservationStatus::Pending { .. }
729                            | ReservationStatus::Blacklisted
730                    }
731                );
732
733                if let RelayStatus::Supported {
734                    status: ReservationStatus::Active { id } | ReservationStatus::Pending { id },
735                } = connection.relay_status
736                {
737                    self.reservations.remove(&id);
738                    self.meet_reservation_target();
739                }
740
741                if had_reservation {
742                    self.record_previous_relay(peer_id, connection.address);
743                }
744
745                if let Some(addresses) = self.static_relays.get(&peer_id).cloned() {
746                    self.queue_static_dial(peer_id, addresses);
747                }
748
749                self.update_relay_availability();
750            }
751            FromSwarm::AddressChange(AddressChange {
752                peer_id,
753                connection_id,
754                old: _,
755                new,
756            }) => {
757                let Some(connection) = self.connections.get_mut(&(peer_id, connection_id)) else {
758                    return;
759                };
760
761                let new_addr = new.get_remote_address();
762
763                connection.address = new_addr.clone();
764            }
765            FromSwarm::NewListenAddr(NewListenAddr { listener_id, addr }) => {
766                if !addr.is_relayed() {
767                    return;
768                }
769
770                if let Some((peer_id, connection_id)) = self.reservations.get(&listener_id).copied()
771                {
772                    let Some(connection) = self.connections.get_mut(&(peer_id, connection_id))
773                    else {
774                        return;
775                    };
776
777                    if matches!(
778                        connection.relay_status,
779                        RelayStatus::Supported {
780                            status: ReservationStatus::Pending { id }
781                        } if id == listener_id
782                    ) {
783                        connection.relay_status = RelayStatus::Supported {
784                            status: ReservationStatus::Active { id: listener_id },
785                        };
786                        self.forget_previous_relay(&peer_id);
787                        self.clear_failure(&peer_id);
788                    }
789                    return;
790                }
791
792                if let Some(relay_peer_id) = addr.relay_peer_id() {
793                    self.external_reservations
794                        .insert(listener_id, relay_peer_id);
795                    self.reconcile_reservation_addrs();
796                }
797            }
798            FromSwarm::ExpiredListenAddr(ExpiredListenAddr { listener_id, .. }) => {
799                self.disable_reservation(listener_id, false);
800            }
801            FromSwarm::ListenerError(ListenerError { listener_id, .. }) => {
802                self.disable_reservation(listener_id, true);
803            }
804            FromSwarm::ListenerClosed(ListenerClosed {
805                listener_id,
806                reason,
807                ..
808            }) => {
809                self.disable_reservation(listener_id, reason.is_err());
810            }
811            FromSwarm::DialFailure(DialFailure {
812                peer_id: Some(peer_id),
813                error,
814                ..
815            }) if self.static_relays.contains_key(&peer_id) => {
816                tracing::warn!(%peer_id, %error, "dial to static relay failed");
817                self.static_dial_cooldowns
818                    .insert(peer_id, Instant::now() + self.config.failure_cooldown);
819            }
820            _ => {}
821        }
822    }
823
824    fn on_connection_handler_event(
825        &mut self,
826        peer_id: PeerId,
827        connection_id: ConnectionId,
828        event: THandlerOutEvent<Self>,
829    ) {
830        let Either::Left(event) = event;
831
832        let Some(connection) = self.connections.get_mut(&(peer_id, connection_id)) else {
833            return;
834        };
835
836        match event {
837            Out::Supported => {
838                if matches!(
839                    connection.relay_status,
840                    RelayStatus::Pending | RelayStatus::NotSupported
841                ) {
842                    connection.relay_status = RelayStatus::Supported {
843                        status: ReservationStatus::Idle,
844                    };
845                    if self.static_relays.contains_key(&peer_id) {
846                        self.evict_for_static_peer(peer_id);
847                    }
848                    self.meet_reservation_target();
849                    self.update_relay_availability();
850                }
851            }
852            Out::Unsupported => {
853                let drop_listener = match connection.relay_status {
854                    RelayStatus::Supported {
855                        status: ReservationStatus::Pending { id } | ReservationStatus::Active { id },
856                    } => Some(id),
857                    _ => None,
858                };
859                let lost_address = drop_listener.map(|_| connection.address.clone());
860                connection.relay_status = RelayStatus::NotSupported;
861                if let Some(id) = drop_listener {
862                    self.expire_reservation_addrs(id);
863                    self.reservations.remove(&id);
864                    self.events.push_back(ToSwarm::RemoveListener { id });
865                    self.meet_reservation_target();
866                }
867                if let Some(address) = lost_address {
868                    self.record_previous_relay(peer_id, address);
869                }
870                self.update_relay_availability();
871            }
872            Out::BlacklistExpired => {
873                if matches!(
874                    connection.relay_status,
875                    RelayStatus::Supported {
876                        status: ReservationStatus::Blacklisted
877                    }
878                ) {
879                    connection.relay_status = RelayStatus::Supported {
880                        status: ReservationStatus::Idle,
881                    };
882                    self.meet_reservation_target();
883                }
884            }
885        }
886    }
887
888    fn poll(
889        &mut self,
890        cx: &mut Context<'_>,
891    ) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
892        if let Some(event) = self.events.pop_front() {
893            return Poll::Ready(event);
894        }
895
896        self.waker = Some(cx.waker().clone());
897
898        Poll::Pending
899    }
900}
901
902#[cfg(test)]
903mod tests {
904    use crate::behaviour::autorelay;
905    use futures::StreamExt;
906    use futures::{AsyncRead, AsyncWrite};
907    use libp2p::core::muxing::StreamMuxerBox;
908    use libp2p::core::transport::{Boxed, MemoryTransport, OrTransport};
909    use libp2p::core::upgrade;
910    use libp2p::multiaddr::Protocol;
911    use libp2p::swarm::{Config, ConnectionId, NetworkBehaviour, SwarmEvent};
912    use libp2p::{Multiaddr, PeerId, Swarm, Transport, identify, identity, noise, relay, yamux};
913    use std::collections::{HashMap, HashSet};
914    use std::num::NonZeroU8;
915    use std::time::Duration;
916
917    #[tokio::test]
918    async fn autorelay_respects_max_reservations() {
919        init_tracing();
920
921        let (relay_a_peer_id, relay_a_addr) = spawn_relay();
922        let (relay_b_peer_id, relay_b_addr) = spawn_relay();
923
924        let mut client = build_client(
925            autorelay::Config::default().set_max_reservations(NonZeroU8::new(1).unwrap()),
926        );
927        client.dial(relay_a_addr).unwrap();
928        client.dial(relay_b_addr).unwrap();
929
930        let first = wait_until_some(&mut client, Duration::from_secs(20), |event| {
931            if let SwarmEvent::Behaviour(ClientEvent::RelayClient(
932                relay::client::Event::ReservationReqAccepted {
933                    relay_peer_id,
934                    renewal: false,
935                    ..
936                },
937            )) = event
938            {
939                Some(*relay_peer_id)
940            } else {
941                None
942            }
943        })
944        .await;
945        assert!(first == relay_a_peer_id || first == relay_b_peer_id);
946
947        let mut extra = 0usize;
948        let mut settle = futures_timer::Delay::new(Duration::from_secs(5));
949        loop {
950            tokio::select! {
951                _ = &mut settle => break,
952                ev = client.select_next_some() => {
953                    if matches!(
954                        ev,
955                        SwarmEvent::Behaviour(ClientEvent::RelayClient(
956                            relay::client::Event::ReservationReqAccepted { renewal: false, .. }
957                        ))
958                    ) {
959                        extra += 1;
960                    }
961                }
962            }
963        }
964
965        assert_eq!(
966            extra, 0,
967            "autorelay opened {extra} reservation(s) beyond max_reservations=1"
968        );
969    }
970
971    #[tokio::test]
972    async fn autorelay_with_two_reservations_among_five_relays() {
973        init_tracing();
974
975        let relay_addrs: Vec<(PeerId, Multiaddr)> = (0..5).map(|_| spawn_relay()).collect();
976        let relay_peers: HashSet<PeerId> = relay_addrs.iter().map(|(p, _)| *p).collect();
977
978        let mut client = build_client(
979            autorelay::Config::default().set_max_reservations(NonZeroU8::new(2).unwrap()),
980        );
981        for (_, addr) in &relay_addrs {
982            client.dial(addr.clone()).unwrap();
983        }
984
985        let mut direct_conns: HashMap<PeerId, ConnectionId> = HashMap::new();
986        let mut reservations: HashSet<PeerId> = HashSet::new();
987
988        let mut sleep = futures_timer::Delay::new(Duration::from_secs(30));
989        loop {
990            tokio::select! {
991                _ = &mut sleep => panic!(
992                    "timeout: got {} reservations, expected 2",
993                    reservations.len()
994                ),
995                ev = client.select_next_some() => match ev {
996                    SwarmEvent::ConnectionEstablished {
997                        peer_id, connection_id, endpoint, ..
998                    } if !endpoint.is_relayed() && relay_peers.contains(&peer_id) => {
999                        direct_conns.insert(peer_id, connection_id);
1000                    }
1001                    SwarmEvent::Behaviour(ClientEvent::RelayClient(
1002                        relay::client::Event::ReservationReqAccepted {
1003                            relay_peer_id,
1004                            renewal: false,
1005                            ..
1006                        },
1007                    )) => {
1008                        reservations.insert(relay_peer_id);
1009                    }
1010                    _ => {}
1011                }
1012            }
1013            if reservations.len() == 2 {
1014                break;
1015            }
1016        }
1017
1018        let drop_peer = *reservations.iter().next().expect("two reservations held");
1019        let keep_peer = reservations
1020            .iter()
1021            .find(|p| **p != drop_peer)
1022            .copied()
1023            .expect("two reservations held");
1024        let drop_conn = *direct_conns
1025            .get(&drop_peer)
1026            .expect("direct connection observed");
1027
1028        assert!(
1029            client.close_connection(drop_conn),
1030            "should close the relay connection holding a reservation"
1031        );
1032
1033        let mut sleep = futures_timer::Delay::new(Duration::from_secs(30));
1034
1035        loop {
1036            tokio::select! {
1037                _ = &mut sleep => panic!("timeout waiting for replacement reservation"),
1038                ev = client.select_next_some() => {
1039                    if let SwarmEvent::Behaviour(ClientEvent::RelayClient(
1040                        relay::client::Event::ReservationReqAccepted {
1041                            relay_peer_id,
1042                            renewal: false,
1043                            ..
1044                        },
1045                    )) = ev
1046                        && relay_peer_id != keep_peer
1047                        && relay_peer_id != drop_peer
1048                    {
1049                        return;
1050                    }
1051                }
1052            }
1053        }
1054    }
1055
1056    #[tokio::test]
1057    async fn autorelay_drops_reservations_when_public_address_appears() {
1058        init_tracing();
1059
1060        let (_, relay_a_addr) = spawn_relay();
1061        let (_, relay_b_addr) = spawn_relay();
1062
1063        let mut client = build_client(
1064            autorelay::Config::default().set_max_reservations(NonZeroU8::new(2).unwrap()),
1065        );
1066        client.dial(relay_a_addr).unwrap();
1067        client.dial(relay_b_addr).unwrap();
1068
1069        let mut confirmed: HashSet<Multiaddr> = HashSet::new();
1070        let mut sleep = futures_timer::Delay::new(Duration::from_secs(30));
1071
1072        loop {
1073            tokio::select! {
1074                _ = &mut sleep => panic!(
1075                    "timeout: got {} confirmed external addresses, expected 2",
1076                    confirmed.len()
1077                ),
1078                ev = client.select_next_some() => {
1079                    if let SwarmEvent::ExternalAddrConfirmed { address } = ev
1080                        && address.iter().any(|p| p == Protocol::P2pCircuit)
1081                    {
1082                        confirmed.insert(address);
1083                    }
1084                }
1085            }
1086            if confirmed.len() == 2 {
1087                break;
1088            }
1089        }
1090
1091        let public_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::<u64>()));
1092        client.add_external_address(public_addr);
1093
1094        let mut expired: HashSet<Multiaddr> = HashSet::new();
1095        let mut sleep = futures_timer::Delay::new(Duration::from_secs(15));
1096
1097        loop {
1098            tokio::select! {
1099                _ = &mut sleep => panic!(
1100                    "timeout: only {}/{} relayed addresses expired",
1101                    expired.len(),
1102                    confirmed.len()
1103                ),
1104                ev = client.select_next_some() => {
1105                    if let SwarmEvent::ExternalAddrExpired { address } = ev
1106                        && confirmed.contains(&address)
1107                    {
1108                        expired.insert(address);
1109                    }
1110                }
1111            }
1112            if expired == confirmed {
1113                break;
1114            }
1115        }
1116    }
1117
1118    #[tokio::test]
1119    async fn autorelay_expires_circuit_address_once_on_connection_close() {
1120        init_tracing();
1121
1122        let (relay_peer, relay_addr) = spawn_relay();
1123
1124        let mut client = build_client(autorelay::Config::default());
1125        client.dial(relay_addr).unwrap();
1126
1127        let (conn, circuit) = wait_until_some(&mut client, Duration::from_secs(15), {
1128            let mut direct: Option<ConnectionId> = None;
1129            move |event| match event {
1130                SwarmEvent::ConnectionEstablished {
1131                    peer_id,
1132                    connection_id,
1133                    endpoint,
1134                    ..
1135                } if *peer_id == relay_peer && !endpoint.is_relayed() => {
1136                    direct = Some(*connection_id);
1137                    None
1138                }
1139                SwarmEvent::ExternalAddrConfirmed { address }
1140                    if address.iter().any(|p| p == Protocol::P2pCircuit) =>
1141                {
1142                    direct.map(|c| (c, address.clone()))
1143                }
1144                _ => None,
1145            }
1146        })
1147        .await;
1148
1149        assert!(client.close_connection(conn));
1150
1151        let mut expired = 0usize;
1152        let mut settle = futures_timer::Delay::new(Duration::from_secs(5));
1153        loop {
1154            tokio::select! {
1155                _ = &mut settle => break,
1156                ev = client.select_next_some() => {
1157                    if let SwarmEvent::ExternalAddrExpired { address } = &ev
1158                        && *address == circuit
1159                    {
1160                        expired += 1;
1161                    }
1162                }
1163            }
1164        }
1165
1166        assert_eq!(
1167            expired, 1,
1168            "expected exactly one expiry of the circuit address, got {expired}"
1169        );
1170    }
1171
1172    #[tokio::test]
1173    async fn autorelay_expires_user_circuit_listen_on_removal() {
1174        init_tracing();
1175
1176        let (relay_peer, relay_addr) = spawn_relay();
1177
1178        let mut client = build_client(autorelay::Config::default());
1179        client
1180            .behaviour_mut()
1181            .autorelay
1182            .set_status(Some(autorelay::Status::Disable));
1183
1184        let circuit_listen = relay_addr
1185            .with(Protocol::P2p(relay_peer))
1186            .with(Protocol::P2pCircuit);
1187        let listener_id = client.listen_on(circuit_listen).unwrap();
1188
1189        let circuit = wait_until_some(&mut client, Duration::from_secs(15), |event| {
1190            if let SwarmEvent::ExternalAddrConfirmed { address } = event
1191                && address.iter().any(|p| p == Protocol::P2pCircuit)
1192            {
1193                Some(address.clone())
1194            } else {
1195                None
1196            }
1197        })
1198        .await;
1199
1200        wait_until(&mut client, Duration::from_secs(10), |event| {
1201            matches!(
1202                event,
1203                SwarmEvent::NewListenAddr { address, .. } if *address == circuit
1204            )
1205        })
1206        .await;
1207
1208        assert!(client.remove_listener(listener_id));
1209
1210        wait_until(&mut client, Duration::from_secs(10), |event| {
1211            matches!(
1212                event,
1213                SwarmEvent::ExternalAddrExpired { address } if *address == circuit
1214            )
1215        })
1216        .await;
1217    }
1218
1219    #[tokio::test]
1220    async fn autorelay_blacklists_failing_relay_and_retries_after_cooldown() {
1221        init_tracing();
1222
1223        let (_, relay_addr) = spawn_rejecting_relay();
1224
1225        let cooldown = Duration::from_secs(1);
1226        let mut client = build_client(
1227            autorelay::Config::default()
1228                .set_max_reservations(NonZeroU8::new(1).unwrap())
1229                .set_failure_cooldown(cooldown),
1230        );
1231        client.dial(relay_addr).unwrap();
1232
1233        let first_failure_at =
1234            wait_for_listener_failure(&mut client, Duration::from_secs(10)).await;
1235
1236        let early_retry = with_timeout(
1237            wait_for_listener_failure(&mut client, cooldown * 5),
1238            cooldown / 2,
1239        )
1240        .await;
1241        assert!(
1242            early_retry.is_none(),
1243            "autorelay retried during the cooldown window"
1244        );
1245
1246        let second_failure_at = wait_for_listener_failure(&mut client, cooldown * 5).await;
1247        let elapsed = second_failure_at.duration_since(first_failure_at);
1248        assert!(
1249            elapsed >= cooldown,
1250            "retry should respect cooldown (elapsed {elapsed:?}, cooldown {cooldown:?})"
1251        );
1252    }
1253
1254    async fn wait_for_listener_failure(
1255        client: &mut Swarm<Client>,
1256        timeout: Duration,
1257    ) -> std::time::Instant {
1258        let mut sleep = futures_timer::Delay::new(timeout);
1259
1260        loop {
1261            tokio::select! {
1262                _ = &mut sleep => panic!("timeout waiting for listener failure"),
1263                ev = client.select_next_some() => {
1264                    if let SwarmEvent::ListenerClosed { reason: Err(_), .. } = ev {
1265                        return std::time::Instant::now();
1266                    }
1267                }
1268            }
1269        }
1270    }
1271
1272    #[tokio::test]
1273    async fn autorelay_disabled_does_not_reserve() {
1274        init_tracing();
1275
1276        let (_, relay_addr) = spawn_relay();
1277
1278        let mut client = build_client(autorelay::Config::default());
1279        client
1280            .behaviour_mut()
1281            .autorelay
1282            .set_status(Some(autorelay::Status::Disable));
1283        client.dial(relay_addr).unwrap();
1284
1285        let observed = with_timeout(
1286            wait_until(&mut client, Duration::from_secs(5), |event| {
1287                matches!(
1288                    event,
1289                    SwarmEvent::Behaviour(ClientEvent::RelayClient(
1290                        relay::client::Event::ReservationReqAccepted { .. }
1291                    ))
1292                )
1293            }),
1294            Duration::from_secs(3),
1295        )
1296        .await;
1297
1298        assert!(
1299            observed.is_none(),
1300            "autorelay opened a reservation while disabled"
1301        );
1302    }
1303
1304    #[tokio::test]
1305    async fn autorelay_re_enable_triggers_reservation() {
1306        init_tracing();
1307
1308        let (_, relay_addr) = spawn_relay();
1309
1310        let mut client = build_client(autorelay::Config::default());
1311        client
1312            .behaviour_mut()
1313            .autorelay
1314            .set_status(Some(autorelay::Status::Disable));
1315        client.dial(relay_addr).unwrap();
1316
1317        let mut sleep = futures_timer::Delay::new(Duration::from_secs(3));
1318
1319        loop {
1320            tokio::select! {
1321                _ = &mut sleep => break,
1322                ev = client.select_next_some() => {
1323                    if matches!(
1324                        ev,
1325                        SwarmEvent::Behaviour(ClientEvent::RelayClient(
1326                            relay::client::Event::ReservationReqAccepted { .. }
1327                        ))
1328                    ) {
1329                        panic!("autorelay reserved while disabled");
1330                    }
1331                }
1332            }
1333        }
1334
1335        client
1336            .behaviour_mut()
1337            .autorelay
1338            .set_status(Some(autorelay::Status::Enable));
1339
1340        wait_until(&mut client, Duration::from_secs(10), |event| {
1341            matches!(
1342                event,
1343                SwarmEvent::Behaviour(ClientEvent::RelayClient(
1344                    relay::client::Event::ReservationReqAccepted { .. }
1345                ))
1346            )
1347        })
1348        .await;
1349    }
1350
1351    #[tokio::test]
1352    async fn autorelay_disable_preserves_active_reservation() {
1353        init_tracing();
1354
1355        let (_, relay_addr) = spawn_relay();
1356
1357        let mut client = build_client(autorelay::Config::default());
1358        client.dial(relay_addr).unwrap();
1359
1360        wait_until(&mut client, Duration::from_secs(20), |event| {
1361            matches!(
1362                event,
1363                SwarmEvent::Behaviour(ClientEvent::RelayClient(
1364                    relay::client::Event::ReservationReqAccepted { .. }
1365                ))
1366            )
1367        })
1368        .await;
1369
1370        client
1371            .behaviour_mut()
1372            .autorelay
1373            .set_status(Some(autorelay::Status::Disable));
1374
1375        let mut sleep = futures_timer::Delay::new(Duration::from_secs(3));
1376
1377        loop {
1378            tokio::select! {
1379                _ = &mut sleep => break,
1380                ev = client.select_next_some() => {
1381                    if let SwarmEvent::ListenerClosed { reason: Err(_), .. } = ev {
1382                        panic!("disabling autorelay dropped an active reservation");
1383                    }
1384                    if let SwarmEvent::ExternalAddrExpired { .. } = ev {
1385                        panic!("disabling autorelay expired an external address");
1386                    }
1387                }
1388            }
1389        }
1390    }
1391
1392    #[tokio::test]
1393    async fn autorelay_prefers_static_relay() {
1394        init_tracing();
1395
1396        let (opportunistic_peer, opportunistic_addr) = spawn_relay();
1397        let (static_peer, static_addr) = spawn_relay();
1398
1399        let mut client = build_client(
1400            autorelay::Config::default().set_max_reservations(NonZeroU8::new(1).unwrap()),
1401        );
1402        client
1403            .behaviour_mut()
1404            .autorelay
1405            .set_status(Some(autorelay::Status::Disable));
1406
1407        client.dial(opportunistic_addr).unwrap();
1408        client
1409            .behaviour_mut()
1410            .autorelay
1411            .add_static_relay(static_peer, static_addr);
1412
1413        // Let both connections establish and identify exchanges complete.
1414        let mut warmup = futures_timer::Delay::new(Duration::from_secs(3));
1415        loop {
1416            tokio::select! {
1417                _ = &mut warmup => break,
1418                _ = client.select_next_some() => {}
1419            }
1420        }
1421
1422        client
1423            .behaviour_mut()
1424            .autorelay
1425            .set_status(Some(autorelay::Status::Enable));
1426
1427        let accepted_peer = wait_until_some(&mut client, Duration::from_secs(15), |event| {
1428            if let SwarmEvent::Behaviour(ClientEvent::RelayClient(
1429                relay::client::Event::ReservationReqAccepted { relay_peer_id, .. },
1430            )) = event
1431            {
1432                Some(*relay_peer_id)
1433            } else {
1434                None
1435            }
1436        })
1437        .await;
1438
1439        assert_eq!(
1440            accepted_peer, static_peer,
1441            "autorelay should pick the static relay over the opportunistic one"
1442        );
1443        assert_ne!(accepted_peer, opportunistic_peer);
1444    }
1445
1446    #[tokio::test]
1447    async fn remove_static_relay_preserves_active_reservation() {
1448        init_tracing();
1449
1450        let (relay_peer, relay_addr) = spawn_relay();
1451
1452        let mut client = build_client(autorelay::Config::default());
1453        client
1454            .behaviour_mut()
1455            .autorelay
1456            .add_static_relay(relay_peer, relay_addr);
1457
1458        wait_until(&mut client, Duration::from_secs(15), |event| {
1459            matches!(
1460                event,
1461                SwarmEvent::Behaviour(ClientEvent::RelayClient(
1462                    relay::client::Event::ReservationReqAccepted { .. }
1463                ))
1464            )
1465        })
1466        .await;
1467
1468        assert!(
1469            client
1470                .behaviour_mut()
1471                .autorelay
1472                .remove_static_relay(&relay_peer)
1473        );
1474
1475        let mut sleep = futures_timer::Delay::new(Duration::from_secs(3));
1476
1477        loop {
1478            tokio::select! {
1479                _ = &mut sleep => break,
1480                ev = client.select_next_some() => {
1481                    if let SwarmEvent::ListenerClosed { reason: Err(_), .. } = ev {
1482                        panic!("removing static relay dropped an active reservation");
1483                    }
1484                    if let SwarmEvent::ExternalAddrExpired { .. } = ev {
1485                        panic!("removing static relay expired an external address");
1486                    }
1487                }
1488            }
1489        }
1490    }
1491
1492    #[tokio::test]
1493    async fn static_relay_redials_after_connection_drop() {
1494        init_tracing();
1495
1496        let (relay_peer, relay_addr) = spawn_relay();
1497
1498        let mut client = build_client(autorelay::Config::default());
1499        client
1500            .behaviour_mut()
1501            .autorelay
1502            .add_static_relay(relay_peer, relay_addr);
1503
1504        let conn_id =
1505            wait_for_reservation_with_conn(&mut client, relay_peer, Duration::from_secs(15)).await;
1506
1507        assert!(client.close_connection(conn_id));
1508
1509        wait_until(&mut client, Duration::from_secs(20), {
1510            let mut redialed = false;
1511            let mut reserved_again = false;
1512            move |event| {
1513                match event {
1514                    SwarmEvent::ConnectionEstablished {
1515                        peer_id, endpoint, ..
1516                    } if *peer_id == relay_peer && !endpoint.is_relayed() => {
1517                        redialed = true;
1518                    }
1519                    SwarmEvent::Behaviour(ClientEvent::RelayClient(
1520                        relay::client::Event::ReservationReqAccepted { relay_peer_id, .. },
1521                    )) if *relay_peer_id == relay_peer => {
1522                        reserved_again = true;
1523                    }
1524                    _ => {}
1525                }
1526                redialed && reserved_again
1527            }
1528        })
1529        .await;
1530    }
1531
1532    async fn wait_until_some<F, T>(
1533        client: &mut Swarm<Client>,
1534        timeout: Duration,
1535        mut extract: F,
1536    ) -> T
1537    where
1538        F: FnMut(&SwarmEvent<ClientEvent>) -> Option<T>,
1539    {
1540        let mut sleep = futures_timer::Delay::new(timeout);
1541
1542        loop {
1543            tokio::select! {
1544                _ = &mut sleep => panic!("timeout waiting on predicate"),
1545                ev = client.select_next_some() => {
1546                    if let Some(value) = extract(&ev) {
1547                        return value;
1548                    }
1549                }
1550            }
1551        }
1552    }
1553
1554    #[tokio::test]
1555    async fn autorelay_emits_relay_available_after_recovery() {
1556        init_tracing();
1557
1558        let (relay_peer, relay_addr) = spawn_relay();
1559
1560        let mut client = build_client(autorelay::Config::default());
1561        client.dial(relay_addr.clone()).unwrap();
1562
1563        let conn_id =
1564            wait_for_reservation_with_conn(&mut client, relay_peer, Duration::from_secs(15)).await;
1565
1566        assert!(client.close_connection(conn_id));
1567
1568        wait_until(&mut client, Duration::from_secs(10), |event| {
1569            matches!(
1570                event,
1571                SwarmEvent::Behaviour(ClientEvent::Autorelay(autorelay::Event::NoRelaysAvailable))
1572            )
1573        })
1574        .await;
1575
1576        client.dial(relay_addr).unwrap();
1577
1578        wait_until(&mut client, Duration::from_secs(15), |event| {
1579            matches!(
1580                event,
1581                SwarmEvent::Behaviour(ClientEvent::Autorelay(autorelay::Event::RelaysAvailable))
1582            )
1583        })
1584        .await;
1585    }
1586
1587    #[tokio::test]
1588    async fn autorelay_no_relays_available_is_edge_triggered() {
1589        init_tracing();
1590
1591        let (relay_a_peer, relay_a_addr) = spawn_relay();
1592        let (relay_b_peer, relay_b_addr) = spawn_relay();
1593
1594        let mut client = build_client(autorelay::Config::default());
1595        client.dial(relay_a_addr).unwrap();
1596        client.dial(relay_b_addr).unwrap();
1597
1598        let mut conns: HashMap<PeerId, ConnectionId> = HashMap::new();
1599        let mut reserved: HashSet<PeerId> = HashSet::new();
1600        let mut sleep = futures_timer::Delay::new(Duration::from_secs(20));
1601
1602        loop {
1603            tokio::select! {
1604                _ = &mut sleep => panic!("did not get both reservations in time"),
1605                ev = client.select_next_some() => match ev {
1606                    SwarmEvent::ConnectionEstablished {
1607                        peer_id, connection_id, endpoint, ..
1608                    } if !endpoint.is_relayed()
1609                        && (peer_id == relay_a_peer || peer_id == relay_b_peer) =>
1610                    {
1611                        conns.insert(peer_id, connection_id);
1612                    }
1613                    SwarmEvent::Behaviour(ClientEvent::RelayClient(
1614                        relay::client::Event::ReservationReqAccepted { relay_peer_id, .. }
1615                    )) if relay_peer_id == relay_a_peer || relay_peer_id == relay_b_peer => {
1616                        reserved.insert(relay_peer_id);
1617                    }
1618                    _ => {}
1619                }
1620            }
1621            if reserved.len() == 2 {
1622                break;
1623            }
1624        }
1625
1626        let conn_a = *conns.get(&relay_a_peer).unwrap();
1627        let conn_b = *conns.get(&relay_b_peer).unwrap();
1628
1629        assert!(client.close_connection(conn_a));
1630        assert!(client.close_connection(conn_b));
1631
1632        let mut starved_count = 0usize;
1633        let mut sleep = futures_timer::Delay::new(Duration::from_secs(5));
1634
1635        loop {
1636            tokio::select! {
1637                _ = &mut sleep => break,
1638                ev = client.select_next_some() => {
1639                    if matches!(
1640                        ev,
1641                        SwarmEvent::Behaviour(ClientEvent::Autorelay(
1642                            autorelay::Event::NoRelaysAvailable
1643                        ))
1644                    ) {
1645                        starved_count += 1;
1646                    }
1647                }
1648            }
1649        }
1650
1651        assert_eq!(
1652            starved_count, 1,
1653            "NoRelaysAvailable should fire exactly once across multiple meet_reservation_target invocations"
1654        );
1655    }
1656
1657    #[tokio::test]
1658    async fn autorelay_resumes_after_public_address_removed() {
1659        init_tracing();
1660
1661        let (relay_peer, relay_addr) = spawn_relay();
1662
1663        let mut client = build_client(autorelay::Config::default());
1664        client.dial(relay_addr).unwrap();
1665
1666        wait_for_reservation_from(&mut client, relay_peer, Duration::from_secs(15)).await;
1667
1668        let public_addr = memory_addr();
1669        client.add_external_address(public_addr.clone());
1670
1671        wait_until(&mut client, Duration::from_secs(10), |event| {
1672            matches!(event, SwarmEvent::ExternalAddrExpired { .. })
1673        })
1674        .await;
1675
1676        client.remove_external_address(&public_addr);
1677
1678        wait_for_reservation_from(&mut client, relay_peer, Duration::from_secs(15)).await;
1679    }
1680
1681    #[tokio::test]
1682    async fn autorelay_manual_enable_ignores_public_address() {
1683        init_tracing();
1684
1685        let (relay_peer, relay_addr) = spawn_relay();
1686
1687        let mut client = build_client(autorelay::Config::default());
1688        client
1689            .behaviour_mut()
1690            .autorelay
1691            .set_status(Some(autorelay::Status::Enable));
1692        client.dial(relay_addr).unwrap();
1693
1694        wait_for_reservation_from(&mut client, relay_peer, Duration::from_secs(15)).await;
1695
1696        client.add_external_address(memory_addr());
1697
1698        let mut sleep = futures_timer::Delay::new(Duration::from_secs(3));
1699
1700        loop {
1701            tokio::select! {
1702                _ = &mut sleep => break,
1703                ev = client.select_next_some() => {
1704                    if let SwarmEvent::ListenerClosed { reason: Err(_), .. } = ev {
1705                        panic!("manual-Enable autorelay dropped reservation after public addr appeared");
1706                    }
1707                    if let SwarmEvent::ExternalAddrExpired { address } = &ev
1708                        && address.iter().any(|p| p == Protocol::P2pCircuit)
1709                    {
1710                        panic!("manual-Enable autorelay expired the relayed external address");
1711                    }
1712                    if let SwarmEvent::Behaviour(ClientEvent::Autorelay(
1713                        autorelay::Event::StatusChanged { status: autorelay::Status::Disable },
1714                    )) = ev
1715                    {
1716                        panic!("manual-Enable autorelay flipped to Disable on public addr");
1717                    }
1718                }
1719            }
1720        }
1721    }
1722
1723    #[tokio::test]
1724    async fn autorelay_forgets_previous_relay_on_reacquire() {
1725        init_tracing();
1726
1727        let (relay_peer, relay_addr) = spawn_relay();
1728
1729        let mut client = build_client(autorelay::Config::default());
1730        client.dial(relay_addr.clone()).unwrap();
1731
1732        let conn_id =
1733            wait_for_reservation_with_conn(&mut client, relay_peer, Duration::from_secs(15)).await;
1734
1735        assert!(client.close_connection(conn_id));
1736
1737        wait_until(&mut client, Duration::from_secs(10), |event| {
1738            matches!(
1739                event,
1740                SwarmEvent::Behaviour(ClientEvent::Autorelay(autorelay::Event::NoRelaysAvailable))
1741            )
1742        })
1743        .await;
1744
1745        assert!(
1746            client
1747                .behaviour()
1748                .autorelay
1749                .previous_relays()
1750                .any(|(p, _, _)| *p == relay_peer),
1751            "expected {relay_peer} in previous_relays after loss"
1752        );
1753
1754        client.dial(relay_addr).unwrap();
1755
1756        wait_until(&mut client, Duration::from_secs(15), |event| {
1757            matches!(
1758            event,
1759            SwarmEvent::NewListenAddr { address, .. } if address.iter().any(|p| p == Protocol::P2pCircuit)
1760        )
1761        })
1762            .await;
1763
1764        let previous: Vec<PeerId> = client
1765            .behaviour()
1766            .autorelay
1767            .previous_relays()
1768            .map(|(p, _, _)| *p)
1769            .collect();
1770        assert!(
1771            !previous.contains(&relay_peer),
1772            "expected {relay_peer} to be removed from previous_relays after re-acquire, got {previous:?}"
1773        );
1774    }
1775
1776    #[tokio::test]
1777    async fn autorelay_previous_relays_is_bounded() {
1778        init_tracing();
1779
1780        let peers_and_addrs: Vec<(PeerId, Multiaddr)> = (0..3).map(|_| spawn_relay()).collect();
1781
1782        let mut client = build_client(
1783            autorelay::Config::default()
1784                .set_max_reservations(NonZeroU8::new(1).unwrap())
1785                .set_max_previous_relays(2),
1786        );
1787
1788        for (peer, addr) in &peers_and_addrs {
1789            client.dial(addr.clone()).unwrap();
1790
1791            let conn_id =
1792                wait_for_reservation_with_conn(&mut client, *peer, Duration::from_secs(15)).await;
1793
1794            assert!(client.close_connection(conn_id));
1795
1796            wait_until(&mut client, Duration::from_secs(10), |event| {
1797                matches!(
1798                    event,
1799                    SwarmEvent::Behaviour(ClientEvent::Autorelay(
1800                        autorelay::Event::NoRelaysAvailable
1801                    ))
1802                )
1803            })
1804            .await;
1805        }
1806
1807        let previous: Vec<PeerId> = client
1808            .behaviour()
1809            .autorelay
1810            .previous_relays()
1811            .map(|(p, _, _)| *p)
1812            .collect();
1813
1814        assert_eq!(
1815            previous.len(),
1816            2,
1817            "expected previous_relays to be bounded to 2, got {previous:?}"
1818        );
1819        assert!(
1820            !previous.contains(&peers_and_addrs[0].0),
1821            "oldest relay should have been evicted: {previous:?}"
1822        );
1823        assert!(previous.contains(&peers_and_addrs[1].0));
1824        assert!(previous.contains(&peers_and_addrs[2].0));
1825    }
1826
1827    #[tokio::test]
1828    async fn autorelay_static_relay_dial_cooldown_after_failure() {
1829        init_tracing();
1830
1831        let cooldown = Duration::from_secs(2);
1832        let mut client = build_client(autorelay::Config::default().set_failure_cooldown(cooldown));
1833
1834        let unreachable_peer = PeerId::random();
1835        let unreachable_addr = memory_addr();
1836
1837        client
1838            .behaviour_mut()
1839            .autorelay
1840            .add_static_relay(unreachable_peer, unreachable_addr.clone());
1841
1842        wait_until(&mut client, Duration::from_secs(5), |event| {
1843            matches!(
1844            event,
1845            SwarmEvent::OutgoingConnectionError { peer_id: Some(p), .. } if *p == unreachable_peer
1846        )
1847        })
1848            .await;
1849
1850        let first_failure_at = std::time::Instant::now();
1851
1852        client
1853            .behaviour_mut()
1854            .autorelay
1855            .add_static_relay(unreachable_peer, unreachable_addr.clone());
1856
1857        let mut redialed = false;
1858        let mut watch = futures_timer::Delay::new(cooldown / 2);
1859        loop {
1860            tokio::select! {
1861                _ = &mut watch => break,
1862                ev = client.select_next_some() => {
1863                    if matches!(
1864                        ev,
1865                        SwarmEvent::OutgoingConnectionError { peer_id: Some(p), .. } if p == unreachable_peer
1866                    ) {
1867                        redialed = true;
1868                        break;
1869                    }
1870                }
1871            }
1872        }
1873        assert!(!redialed, "autorelay redialed within cooldown");
1874
1875        let remaining = cooldown
1876            .checked_sub(first_failure_at.elapsed())
1877            .unwrap_or_default();
1878        if !remaining.is_zero() {
1879            futures_timer::Delay::new(remaining + Duration::from_millis(200)).await;
1880        }
1881
1882        client
1883            .behaviour_mut()
1884            .autorelay
1885            .add_static_relay(unreachable_peer, unreachable_addr);
1886
1887        wait_until(&mut client, Duration::from_secs(5), |event| {
1888            matches!(
1889            event,
1890            SwarmEvent::OutgoingConnectionError { peer_id: Some(p), .. } if *p == unreachable_peer
1891        )
1892        })
1893            .await;
1894    }
1895
1896    #[tokio::test]
1897    async fn autorelay_evicts_discovered_peers_for_static() {
1898        init_tracing();
1899
1900        let (opp_a_peer, opp_a_addr) = spawn_relay();
1901        let (opp_b_peer, opp_b_addr) = spawn_relay();
1902        let (static_peer, static_addr) = spawn_relay();
1903
1904        let mut client = build_client(
1905            autorelay::Config::default().set_max_reservations(NonZeroU8::new(1).unwrap()),
1906        );
1907
1908        client.dial(opp_a_addr).unwrap();
1909        client.dial(opp_b_addr).unwrap();
1910
1911        wait_until_some(&mut client, Duration::from_secs(20), |event| {
1912            if let SwarmEvent::Behaviour(ClientEvent::RelayClient(
1913                relay::client::Event::ReservationReqAccepted { relay_peer_id, .. },
1914            )) = event
1915                && (*relay_peer_id == opp_a_peer || *relay_peer_id == opp_b_peer)
1916            {
1917                Some(*relay_peer_id)
1918            } else {
1919                None
1920            }
1921        })
1922        .await;
1923
1924        client
1925            .behaviour_mut()
1926            .autorelay
1927            .add_static_relay(static_peer, static_addr);
1928
1929        wait_for_reservation_from(&mut client, static_peer, Duration::from_secs(20)).await;
1930    }
1931
1932    async fn wait_until<F>(client: &mut Swarm<Client>, timeout: Duration, mut predicate: F)
1933    where
1934        F: FnMut(&SwarmEvent<ClientEvent>) -> bool,
1935    {
1936        let mut sleep = futures_timer::Delay::new(timeout);
1937        loop {
1938            tokio::select! {
1939                _ = &mut sleep => panic!("timeout waiting on predicate"),
1940                ev = client.select_next_some() => {
1941                    if predicate(&ev) {
1942                        return;
1943                    }
1944                }
1945            }
1946        }
1947    }
1948
1949    async fn with_timeout<F: Future>(future: F, timeout: Duration) -> Option<F::Output> {
1950        use futures::future::Either;
1951        let timer = futures_timer::Delay::new(timeout);
1952        futures::pin_mut!(future);
1953        match futures::future::select(future, timer).await {
1954            Either::Left((output, _)) => Some(output),
1955            Either::Right(_) => None,
1956        }
1957    }
1958
1959    async fn wait_for_reservation_from(
1960        client: &mut Swarm<Client>,
1961        peer: PeerId,
1962        timeout: Duration,
1963    ) {
1964        wait_until(client, timeout, |event| {
1965            matches!(
1966                event,
1967                SwarmEvent::Behaviour(ClientEvent::RelayClient(
1968                    relay::client::Event::ReservationReqAccepted { relay_peer_id, .. }
1969                )) if *relay_peer_id == peer
1970            )
1971        })
1972        .await;
1973    }
1974
1975    async fn wait_for_reservation_with_conn(
1976        client: &mut Swarm<Client>,
1977        peer: PeerId,
1978        timeout: Duration,
1979    ) -> ConnectionId {
1980        wait_until_some(client, timeout, {
1981            let mut established: Option<ConnectionId> = None;
1982            let mut reserved = false;
1983            move |event| {
1984                match event {
1985                    SwarmEvent::ConnectionEstablished {
1986                        peer_id,
1987                        connection_id,
1988                        endpoint,
1989                        ..
1990                    } if *peer_id == peer && !endpoint.is_relayed() => {
1991                        established = Some(*connection_id);
1992                    }
1993                    SwarmEvent::Behaviour(ClientEvent::RelayClient(
1994                        relay::client::Event::ReservationReqAccepted { relay_peer_id, .. },
1995                    )) if *relay_peer_id == peer => {
1996                        reserved = true;
1997                    }
1998                    _ => {}
1999                }
2000                if reserved { established } else { None }
2001            }
2002        })
2003        .await
2004    }
2005
2006    fn init_tracing() {
2007        // let _ = tracing_subscriber::fmt()
2008        //     .with_env_filter(EnvFilter::from_default_env())
2009        //     .try_init();
2010    }
2011
2012    fn memory_addr() -> Multiaddr {
2013        Multiaddr::empty().with(Protocol::Memory(rand::random::<u64>()))
2014    }
2015
2016    fn spawn_relay() -> (PeerId, Multiaddr) {
2017        spawn_relay_swarm(build_relay())
2018    }
2019
2020    fn spawn_rejecting_relay() -> (PeerId, Multiaddr) {
2021        spawn_relay_swarm(build_rejecting_relay())
2022    }
2023
2024    fn spawn_relay_swarm(mut relay: Swarm<Relay>) -> (PeerId, Multiaddr) {
2025        let addr = memory_addr();
2026        let peer = *relay.local_peer_id();
2027        relay.listen_on(addr.clone()).unwrap();
2028        relay.add_external_address(addr.clone());
2029        tokio::spawn(relay.collect::<Vec<_>>());
2030        (peer, addr)
2031    }
2032
2033    fn build_relay() -> Swarm<Relay> {
2034        build_relay_with_config(relay::Config {
2035            reservation_duration: Duration::from_secs(60),
2036            ..Default::default()
2037        })
2038    }
2039
2040    fn build_rejecting_relay() -> Swarm<Relay> {
2041        build_relay_with_config(relay::Config {
2042            max_reservations: 0,
2043            ..Default::default()
2044        })
2045    }
2046
2047    fn build_relay_with_config(config: relay::Config) -> Swarm<Relay> {
2048        let local_key = identity::Keypair::generate_ed25519();
2049        let local_peer_id = local_key.public().to_peer_id();
2050        let transport = upgrade_transport(MemoryTransport::default().boxed(), &local_key);
2051
2052        Swarm::new(
2053            transport,
2054            Relay {
2055                relay: relay::Behaviour::new(local_peer_id, config),
2056                identify: identify::Behaviour::new(identify::Config::new(
2057                    "/autorelay-test/1.0.0".to_owned(),
2058                    local_key.public(),
2059                )),
2060            },
2061            local_peer_id,
2062            Config::with_tokio_executor(),
2063        )
2064    }
2065
2066    fn build_client(autorelay_config: autorelay::Config) -> Swarm<Client> {
2067        let local_key = identity::Keypair::generate_ed25519();
2068        let local_peer_id = local_key.public().to_peer_id();
2069        let (relay_transport, relay_client) = relay::client::new(local_peer_id);
2070
2071        let transport = upgrade_transport(
2072            OrTransport::new(relay_transport, MemoryTransport::default()).boxed(),
2073            &local_key,
2074        );
2075
2076        Swarm::new(
2077            transport,
2078            Client {
2079                relay_client,
2080                autorelay: autorelay::Behaviour::new_with_config(autorelay_config),
2081                identify: identify::Behaviour::new(identify::Config::new(
2082                    "/autorelay-test/1.0.0".to_owned(),
2083                    local_key.public(),
2084                )),
2085            },
2086            local_peer_id,
2087            Config::with_tokio_executor(),
2088        )
2089    }
2090
2091    fn upgrade_transport<StreamSink>(
2092        transport: Boxed<StreamSink>,
2093        identity: &identity::Keypair,
2094    ) -> Boxed<(PeerId, StreamMuxerBox)>
2095    where
2096        StreamSink: AsyncRead + AsyncWrite + Send + Unpin + 'static,
2097    {
2098        transport
2099            .upgrade(upgrade::Version::V1)
2100            .authenticate(noise::Config::new(identity).unwrap())
2101            .multiplex(yamux::Config::default())
2102            .boxed()
2103    }
2104
2105    #[derive(NetworkBehaviour)]
2106    struct Relay {
2107        relay: relay::Behaviour,
2108        identify: identify::Behaviour,
2109    }
2110
2111    #[derive(NetworkBehaviour)]
2112    struct Client {
2113        relay_client: relay::client::Behaviour,
2114        autorelay: autorelay::Behaviour,
2115        identify: identify::Behaviour,
2116    }
2117}