1use std::{
2 pin::Pin,
3 sync::{Arc, OnceLock, atomic::Ordering},
4 time::Duration,
5};
6
7use anyhow::anyhow;
8use futures::{Sink, SinkExt, StreamExt, TryStreamExt, future::AbortHandle};
9use futures_time::future::FutureExt as TimeExt;
10use hopr_api::types::{
11 crypto_random::Randomizable,
12 internal::{
13 prelude::HoprPseudonym,
14 routing::{DestinationRouting, RoutingOptions},
15 },
16 primitive::prelude::Address,
17};
18use hopr_crypto_packet::prelude::HoprPacket;
19use hopr_protocol_app::prelude::*;
20use hopr_protocol_start::{
21 KeepAliveFlag, KeepAliveMessage, StartChallenge, StartErrorReason, StartErrorType, StartEstablished,
22 StartInitiation,
23};
24use hopr_utils::runtime::AbortableList;
25use tracing::{debug, error, info, trace, warn};
26
27#[cfg(feature = "telemetry")]
28use crate::telemetry::{
29 SessionLifecycleState, initialize_session_metrics, remove_session_metrics_state, set_session_balancer_data,
30 set_session_state,
31};
32use crate::{
33 Capability, HoprSession, IncomingSession, SESSION_MTU, SessionClientConfig, SessionId, SessionTarget,
34 SurbBalancerConfig,
35 balancer::{
36 AtomicSurbFlowEstimator, BalancerStateValues, RateController, RateLimitSinkExt, SurbBalancer,
37 SurbControllerWithCorrection,
38 pid::{PidBalancerController, PidControllerGains},
39 simple::SimpleBalancerController,
40 },
41 errors::{SessionManagerError, TransportSessionError},
42 types::{ByteCapabilities, ClosureReason, HoprSessionConfig, HoprStartProtocol, SESSION_APPLICATION_TAG},
43 utils,
44 utils::{SurbNotificationMode, insert_into_next_slot},
45};
46
47#[cfg(all(feature = "telemetry", not(test)))]
48lazy_static::lazy_static! {
49 static ref METRIC_ACTIVE_SESSIONS: hopr_api::types::telemetry::SimpleGauge = hopr_api::types::telemetry::SimpleGauge::new(
50 "hopr_session_num_active_sessions",
51 "Number of currently active HOPR sessions"
52 ).unwrap();
53 static ref METRIC_NUM_ESTABLISHED_SESSIONS: hopr_api::types::telemetry::SimpleCounter = hopr_api::types::telemetry::SimpleCounter::new(
54 "hopr_session_established_sessions_count",
55 "Number of sessions that were successfully established as an Exit node"
56 ).unwrap();
57 static ref METRIC_NUM_INITIATED_SESSIONS: hopr_api::types::telemetry::SimpleCounter = hopr_api::types::telemetry::SimpleCounter::new(
58 "hopr_session_initiated_sessions_count",
59 "Number of sessions that were successfully initiated as an Entry node"
60 ).unwrap();
61 static ref METRIC_RECEIVED_SESSION_ERRS: hopr_api::types::telemetry::MultiCounter = hopr_api::types::telemetry::MultiCounter::new(
62 "hopr_session_received_error_count",
63 "Number of HOPR session errors received from an Exit node",
64 &["kind"]
65 ).unwrap();
66 static ref METRIC_DISPATCHED_MSGS: hopr_api::types::telemetry::MultiCounter = hopr_api::types::telemetry::MultiCounter::new(
67 "hopr_session_dispatched_messages",
68 "Number dispatched HOPR session messages and their classification",
69 &["kind"]
70 ).unwrap();
71 static ref METRIC_SENT_SESSION_ERRS: hopr_api::types::telemetry::MultiCounter = hopr_api::types::telemetry::MultiCounter::new(
72 "hopr_session_sent_error_count",
73 "Number of HOPR session errors sent to an Entry node",
74 &["kind"]
75 ).unwrap();
76}
77
78#[tracing::instrument(level = "debug", skip(session_data))]
79fn close_session(session_id: SessionId, session_data: SessionSlot, reason: ClosureReason) {
80 debug!("closing session");
81
82 #[cfg(feature = "telemetry")]
83 {
84 set_session_state(&session_id, SessionLifecycleState::Closed);
85 remove_session_metrics_state(&session_id);
86 }
87
88 if reason != ClosureReason::EmptyRead {
89 debug!("data tx channel closed on session");
91 }
92
93 session_data.abort_handles.lock().abort_all();
95
96 #[cfg(all(feature = "telemetry", not(test)))]
97 METRIC_ACTIVE_SESSIONS.decrement(1.0);
98}
99
100fn initiation_timeout_max_one_way(base: Duration, hops: usize) -> Duration {
101 base * (hops as u32)
102}
103
104pub const MIN_SURB_BUFFER_DURATION: Duration = Duration::from_secs(1);
106pub const MIN_SURB_BUFFER_NOTIFICATION_PERIOD: Duration = Duration::from_secs(1);
108
109pub(crate) const MIN_CHALLENGE: StartChallenge = 1;
111
112const SESSION_READINESS_TIMEOUT: Duration = Duration::from_secs(10);
114
115const MIN_FRAME_TIMEOUT: Duration = Duration::from_millis(10);
117
118const EXTERNAL_SEND_TIMEOUT: Duration = Duration::from_millis(200);
120
121#[allow(dead_code)]
123pub const SESSION_FORWARD_CAPACITY: usize = 10000;
124
125type SessionInitiationCache = moka::sync::Cache<
129 StartChallenge,
130 crossfire::MTx<crossfire::mpsc::One<Result<StartEstablished<SessionId>, StartErrorType>>>,
131>;
132
133#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, strum::Display)]
135enum SessionHandles {
136 Ingress,
138 KeepAlive,
140 Balancer,
142}
143
144#[derive(Clone)]
145pub(crate) struct SessionSlot {
146 session_tx: crossfire::MTx<crossfire::mpsc::Array<ApplicationDataIn>>,
149 routing_opts: DestinationRouting,
150 abort_handles: Arc<parking_lot::Mutex<AbortableList<SessionHandles>>>,
152 surb_mgmt: Arc<BalancerStateValues>,
155 surb_estimator: AtomicSurbFlowEstimator,
158}
159
160struct SessionSlotGuard<'a> {
174 sessions: &'a moka::sync::Cache<SessionId, SessionSlot>,
175 active_sessions: Arc<std::sync::atomic::AtomicUsize>,
176 session_id: SessionId,
177 committed: bool,
178}
179
180impl<'a> SessionSlotGuard<'a> {
181 fn new(
182 sessions: &'a moka::sync::Cache<SessionId, SessionSlot>,
183 session_id: SessionId,
184 active_sessions: Arc<std::sync::atomic::AtomicUsize>,
185 ) -> Self {
186 Self {
187 sessions,
188 active_sessions,
189 session_id,
190 committed: false,
191 }
192 }
193
194 fn commit(&mut self) {
197 self.committed = true;
198
199 #[cfg(all(feature = "telemetry", not(test)))]
200 METRIC_ACTIVE_SESSIONS.increment(1.0);
201 }
202}
203
204impl Drop for SessionSlotGuard<'_> {
205 fn drop(&mut self) {
206 if !self.committed {
207 let session_id = self.session_id;
210 warn!(%session_id, "rolling back partially established session slot after setup failure");
211 if let Some(slot) = self.sessions.remove(&session_id) {
212 self.active_sessions.fetch_sub(1, Ordering::Relaxed);
213
214 close_session(session_id, slot, ClosureReason::Eviction);
215 }
216 }
217 }
218}
219
220#[derive(Clone, Copy, Debug, PartialEq, Eq)]
228pub enum DropReason {
229 SinkClosed,
232 SinkFull,
236 Unregistered,
239}
240
241#[derive(Clone, Debug, PartialEq, Eq)]
243pub enum DispatchResult {
244 Processed,
246 Unrelated(ApplicationDataIn),
248 Dropped(DropReason),
251}
252
253#[derive(Clone, Debug, PartialEq, smart_default::SmartDefault)]
255pub struct SessionManagerConfig {
256 #[default(1500)]
260 pub frame_mtu: usize,
261
262 #[default(Duration::from_secs(3))]
273 pub max_frame_timeout: Duration,
274
275 #[default(0)]
280 pub max_buffered_segments: usize,
281
282 #[default(Some(256))]
308 pub max_frames_behind_gap: Option<usize>,
309
310 #[default(Duration::from_millis(500))]
317 pub initiation_timeout_base: Duration,
318
319 #[default(Duration::from_secs(180))]
323 pub idle_timeout: Duration,
324
325 #[default(Duration::from_millis(100))]
334 pub min_session_touch_period: Duration,
335
336 #[default(Duration::from_millis(100))]
341 pub balancer_sampling_interval: Duration,
342
343 #[default(10)]
349 pub initial_return_session_egress_rate: usize,
350
351 #[default(Duration::from_secs(5))]
362 pub minimum_surb_buffer_duration: Duration,
363
364 #[default(10_000)]
372 pub maximum_surb_buffer_size: usize,
373
374 #[default(Some(Duration::from_secs(60)))]
387 pub surb_balance_notify_period: Option<Duration>,
388
389 #[default(true)]
396 pub surb_target_notify: bool,
397
398 #[default(10_000)]
402 pub maximum_sessions: usize,
403
404 #[default(10000)]
410 pub session_forward_capacity: usize,
411}
412
413type IncomingSessionSink = Pin<Box<dyn Sink<IncomingSession, Error = SessionManagerError> + Send>>;
416
417type SessionNotifiers = (
418 Arc<hopr_utils::runtime::prelude::Mutex<IncomingSessionSink>>,
419 crossfire::MTx<crossfire::mpsc::Array<(SessionId, ClosureReason)>>,
420);
421
422type StartProtocolMsgSink = Arc<OnceLock<crossfire::MTx<crossfire::mpsc::Array<(HoprPseudonym, HoprStartProtocol)>>>>;
426
427pub struct SessionManager<S> {
578 session_initiations: SessionInitiationCache,
579 session_notifiers: Arc<OnceLock<SessionNotifiers>>,
580 start_protocol_tx: StartProtocolMsgSink,
581 active_sessions: Arc<std::sync::atomic::AtomicUsize>,
585 sessions: moka::sync::Cache<SessionId, SessionSlot>,
586 msg_sender: Arc<OnceLock<S>>,
587 cfg: SessionManagerConfig,
588}
589
590impl<S> Clone for SessionManager<S> {
591 fn clone(&self) -> Self {
592 Self {
593 session_initiations: self.session_initiations.clone(),
594 session_notifiers: self.session_notifiers.clone(),
595 start_protocol_tx: self.start_protocol_tx.clone(),
596 active_sessions: self.active_sessions.clone(),
597 sessions: self.sessions.clone(),
598 cfg: self.cfg.clone(),
599 msg_sender: self.msg_sender.clone(),
600 }
601 }
602}
603
604fn session_config(cfg: &SessionManagerConfig, capabilities: crate::Capabilities) -> HoprSessionConfig {
605 session_config_with(cfg, capabilities, None)
606}
607
608fn session_config_with(
617 cfg: &SessionManagerConfig,
618 capabilities: crate::Capabilities,
619 max_frames_behind_gap: Option<usize>,
620) -> HoprSessionConfig {
621 let can_retransmit =
626 capabilities.contains(Capability::RetransmissionAck) || capabilities.contains(Capability::RetransmissionNack);
627
628 let bound = match max_frames_behind_gap.or(cfg.max_frames_behind_gap) {
632 Some(0) | None => None,
633 Some(n) => Some(n),
634 };
635
636 HoprSessionConfig {
637 capabilities,
638 frame_mtu: cfg.frame_mtu,
639 frame_timeout: cfg.max_frame_timeout,
640 max_buffered_segments: cfg.max_buffered_segments,
641 max_frames_behind_gap: (!can_retransmit).then_some(bound).flatten(),
642 }
643}
644
645#[cfg(feature = "telemetry")]
646fn initialize_session_telemetry(
647 session_id: SessionId,
648 cfg: &SessionManagerConfig,
649 capabilities: crate::Capabilities,
650 surb_estimator: Option<&AtomicSurbFlowEstimator>,
651 surb_mgmt: Option<&Arc<BalancerStateValues>>,
652) {
653 initialize_session_metrics(session_id, session_config(cfg, capabilities));
654 set_session_state(&session_id, SessionLifecycleState::Active);
655 if let (Some(estimator), Some(mgmt)) = (surb_estimator, surb_mgmt) {
656 set_session_balancer_data(&session_id, estimator.clone(), mgmt.clone());
657 }
658}
659
660async fn send_via_msg_sender<S, D>(
661 msg_sender: &mut S,
662 routing: DestinationRouting,
663 data: D,
664 error_context: &'static str,
665) -> crate::errors::Result<()>
666where
667 S: futures::Sink<(DestinationRouting, ApplicationDataOut)> + Unpin,
668 S::Error: std::error::Error + Send + Sync + Clone + 'static,
669 D: TryInto<ApplicationData>,
670 D::Error: std::error::Error + Send + Sync + 'static,
671{
672 let app_data: ApplicationData = data.try_into().map_err(SessionManagerError::other)?;
673 msg_sender
674 .send((routing, ApplicationDataOut::with_no_packet_info(app_data)))
675 .timeout(futures_time::time::Duration::from(EXTERNAL_SEND_TIMEOUT))
676 .await
677 .map_err(|_| {
678 error!("timeout sending {error_context}");
679 TransportSessionError::Timeout
680 })?
681 .map_err(|error| {
682 error!(%error, "failed to send {error_context}");
683 SessionManagerError::other(error)
684 })?;
685 Ok(())
686}
687
688impl<S> SessionManager<S>
689where
690 S: futures::Sink<(DestinationRouting, ApplicationDataOut)> + Clone + Send + Sync + Unpin + 'static,
691 S::Error: std::error::Error + Send + Sync + Clone + 'static,
692{
693 pub fn new(mut cfg: SessionManagerConfig) -> Self {
695 let maximum_sessions = cfg.maximum_sessions;
696 cfg.surb_balance_notify_period = cfg
697 .surb_balance_notify_period
698 .map(|p| p.max(MIN_SURB_BUFFER_NOTIFICATION_PERIOD));
699 cfg.minimum_surb_buffer_duration = cfg.minimum_surb_buffer_duration.max(MIN_SURB_BUFFER_DURATION);
700
701 cfg.frame_mtu = cfg.frame_mtu.max(SESSION_MTU);
703 cfg.max_frame_timeout = cfg.max_frame_timeout.max(MIN_FRAME_TIMEOUT);
704
705 #[cfg(all(feature = "telemetry", not(test)))]
706 METRIC_ACTIVE_SESSIONS.set(0.0);
707
708 let active_sessions: Arc<std::sync::atomic::AtomicUsize> = Arc::new(std::sync::atomic::AtomicUsize::new(0));
709 let active_sessions_for_listener = active_sessions.clone();
710
711 let msg_sender = Arc::new(OnceLock::new());
712 Self {
713 msg_sender: msg_sender.clone(),
714 session_initiations: moka::sync::Cache::builder()
715 .max_capacity(maximum_sessions as u64)
716 .time_to_live(
717 2 * initiation_timeout_max_one_way(
718 cfg.initiation_timeout_base,
719 RoutingOptions::MAX_INTERMEDIATE_HOPS,
720 ),
721 )
722 .build(),
723 sessions: moka::sync::Cache::builder()
724 .max_capacity(maximum_sessions as u64)
725 .time_to_idle(cfg.idle_timeout)
726 .eviction_listener(move |session_id: Arc<SessionId>, entry, reason| match &reason {
727 moka::notification::RemovalCause::Expired | moka::notification::RemovalCause::Size => {
728 trace!(?session_id, ?reason, "session evicted from the cache");
729 active_sessions_for_listener.fetch_sub(1, Ordering::Relaxed);
730 close_session(*session_id.as_ref(), entry, ClosureReason::Eviction);
731 }
732 _ => {}
733 })
734 .build(),
735 session_notifiers: Arc::new(OnceLock::new()),
736 start_protocol_tx: Arc::new(OnceLock::new()),
737 active_sessions,
738 cfg,
739 }
740 }
741
742 pub fn start<T>(&self, msg_sender: S, new_session_notifier: T) -> crate::errors::Result<Vec<AbortHandle>>
748 where
749 T: futures::Sink<IncomingSession> + Send + 'static,
750 T::Error: std::error::Error + Send + Sync + 'static,
751 {
752 self.msg_sender
753 .set(msg_sender)
754 .map_err(|_| SessionManagerError::AlreadyStarted)?;
755
756 let new_session_notifier: IncomingSessionSink =
761 Box::pin(new_session_notifier.sink_map_err(SessionManagerError::other));
762 let new_session_notifier = Arc::new(hopr_utils::runtime::prelude::Mutex::new(new_session_notifier));
763
764 let (session_close_tx, session_close_rx) =
765 crossfire::mpsc::bounded_blocking_async(self.cfg.maximum_sessions + 10);
766 self.session_notifiers
767 .set((new_session_notifier, session_close_tx))
768 .map_err(|_| SessionManagerError::AlreadyStarted)?;
769
770 let (start_protocol_tx, start_protocol_rx) =
771 crossfire::mpsc::bounded_blocking_async(self.cfg.maximum_sessions + 10);
772 let _ = self.start_protocol_tx.set(start_protocol_tx);
773
774 let myself = self.clone();
775 let closure_diag = hopr_utils::runtime::diagnostics::ConcurrentDiagnostics::new(
776 "session_close_for_each_concurrent",
777 module_path!(),
778 file!(),
779 line!(),
780 );
781 let ah_closure_notifications = hopr_utils::spawn_as_abortable_named!(
782 "session_close_notifications",
783 session_close_rx.into_stream().for_each_concurrent(
784 self.cfg.maximum_sessions + 10,
785 move |(session_id, closure_reason)| {
786 let myself = myself.clone();
787 let closure_diag = closure_diag.clone();
788 closure_diag.wrap(|| {
789 if let Some(session_data) = myself.sessions.remove(&session_id) {
793 myself.active_sessions.fetch_sub(1, Ordering::Relaxed);
794 close_session(session_id, session_data, closure_reason);
795 } else {
796 debug!(
798 ?session_id,
799 ?closure_reason,
800 "could not find session id to close, maybe the session is already closed"
801 );
802 }
803 futures::future::ready(())
804 })
805 }
806 )
807 );
808
809 let myself = self.clone();
814 let ah_session_expiration = hopr_utils::spawn_as_abortable!(async move {
815 let jitter = hopr_api::types::crypto_random::random_float_in_range(1.0..1.5);
816 let timeout = 2 * initiation_timeout_max_one_way(
817 myself.cfg.initiation_timeout_base,
818 RoutingOptions::MAX_INTERMEDIATE_HOPS,
819 )
820 .min(myself.cfg.idle_timeout)
821 .mul_f64(jitter)
822 / 2;
823 futures_time::stream::interval(timeout.into())
824 .for_each(|_| async {
825 trace!("executing session cache evictions");
826 myself.sessions.run_pending_tasks();
827 myself.session_initiations.run_pending_tasks();
828 })
829 .await;
830 });
831
832 let myself = self.clone();
834 let ah_start_protocol = hopr_utils::spawn_as_abortable_named!(
835 "session_start_protocol_processor",
836 start_protocol_rx.into_stream().for_each_concurrent(
837 Some(self.cfg.maximum_sessions + 10),
838 move |(pseudonym, protocol_msg)| {
839 let myself = myself.clone();
840 async move {
841 let result = match protocol_msg {
842 HoprStartProtocol::StartSession(session_req) => {
843 myself.handle_incoming_session_initiation(pseudonym, session_req).await
844 }
845 HoprStartProtocol::SessionEstablished(est) => myself.handle_session_established(est).await,
846 HoprStartProtocol::SessionError(error_type) => {
847 myself.handle_session_error(error_type).await
848 }
849 HoprStartProtocol::KeepAlive(msg) => myself.handle_keep_alive(msg).await,
850 };
851
852 if let Err(error) = result {
853 error!(%error, "failed to process Start protocol message");
854 }
855 }
856 }
857 )
858 );
859
860 Ok(vec![ah_closure_notifications, ah_session_expiration, ah_start_protocol])
861 }
862
863 pub fn is_started(&self) -> bool {
865 self.session_notifiers.get().is_some()
866 }
867
868 fn allocate_session_slot(&self, session_id: SessionId, slot: SessionSlot) -> Option<SessionSlotGuard<'_>> {
892 let counter = &self.active_sessions;
896 #[allow(clippy::incompatible_msrv)]
897 let did_reserve = counter
898 .try_update(Ordering::Relaxed, Ordering::Relaxed, |n| {
899 (n < self.cfg.maximum_sessions).then_some(n + 1)
900 })
901 .is_ok();
902
903 if !did_reserve {
904 return None;
905 }
906
907 let result =
908 self.sessions
909 .entry(session_id)
910 .and_compute_with(|entry: Option<moka::Entry<SessionId, SessionSlot>>| {
911 if entry.is_none() {
912 moka::ops::compute::Op::Put(slot)
913 } else {
914 counter.fetch_sub(1, Ordering::Relaxed);
916 moka::ops::compute::Op::Nop
917 }
918 });
919
920 match result {
921 moka::ops::compute::CompResult::Inserted(_) => {
922 Some(SessionSlotGuard::new(&self.sessions, session_id, counter.clone()))
924 }
925 _ => None,
926 }
927 }
928
929 pub async fn new_session(
937 &self,
938 destination: Address,
939 target: SessionTarget,
940 cfg: SessionClientConfig,
941 ) -> crate::errors::Result<HoprSession> {
942 self.sessions.run_pending_tasks();
943 if self.cfg.maximum_sessions <= self.active_sessions.load(Ordering::Relaxed) {
944 return Err(SessionManagerError::TooManySessions.into());
945 }
946
947 let mut msg_sender = self.msg_sender.get().cloned().ok_or(SessionManagerError::NotStarted)?;
948
949 let (tx_initiation_done, rx_initiation_done): (
950 crossfire::MTx<crossfire::mpsc::One<_>>,
951 crossfire::AsyncRx<crossfire::mpsc::One<_>>,
952 ) = crossfire::mpsc::build(crossfire::mpsc::One::new());
953
954 let (challenge, _) = insert_into_next_slot(
955 &self.session_initiations,
956 |ch| {
957 if let Some(challenge) = ch {
958 ((challenge + 1) % hopr_api::types::crypto_random::MAX_RANDOM_INTEGER).max(MIN_CHALLENGE)
959 } else {
960 hopr_api::types::crypto_random::random_integer(MIN_CHALLENGE, None)
961 }
962 },
963 |_| tx_initiation_done,
964 Some(self.cfg.maximum_sessions as u64),
965 )
966 .ok_or(SessionManagerError::NoChallengeSlots)?; trace!(challenge, ?cfg, "initiating session with config");
970 let start_session_msg = HoprStartProtocol::StartSession(StartInitiation {
971 challenge,
972 target,
973 capabilities: ByteCapabilities(cfg.capabilities),
974 additional_data: if !cfg.capabilities.contains(Capability::NoRateControl) {
975 cfg.surb_management
976 .map(|c| c.target_surb_buffer_size)
977 .unwrap_or(
978 self.cfg.initial_return_session_egress_rate as u64
979 * self
980 .cfg
981 .minimum_surb_buffer_duration
982 .max(MIN_SURB_BUFFER_DURATION)
983 .as_secs(),
984 )
985 .min(u32::MAX as u64) as u32
986 } else {
987 0
988 },
989 });
990
991 let pseudonym = cfg.pseudonym.unwrap_or(HoprPseudonym::random());
992 let forward_routing = DestinationRouting::Forward {
993 destination: Box::new(destination.into()),
994 pseudonym: Some(pseudonym), forward_options: cfg.forward_path_options.clone(),
996 return_options: cfg.return_path_options.clone().into(),
997 };
998
999 info!(challenge, %pseudonym, %destination, "new session request");
1001 send_via_msg_sender(
1002 &mut msg_sender,
1003 forward_routing.clone(),
1004 start_session_msg,
1005 "session request message",
1006 )
1007 .await
1008 .map_err(|error| {
1009 self.session_initiations.remove(&challenge);
1010 TransportSessionError::packet_sending(error)
1011 })?;
1012
1013 let initiation_timeout: futures_time::time::Duration = initiation_timeout_max_one_way(
1015 self.cfg.initiation_timeout_base,
1016 cfg.forward_path_options.count_hops() + cfg.return_path_options.count_hops() + 2,
1017 )
1018 .into();
1019
1020 trace!(challenge, "awaiting session establishment");
1023 match rx_initiation_done
1024 .into_stream()
1025 .try_next()
1026 .timeout(initiation_timeout)
1027 .await
1028 {
1029 Ok(Ok(Some(est))) => {
1030 let session_id = est.session_id;
1032 debug!(challenge = est.orig_challenge, ?session_id, "started a new session");
1033
1034 let (session_tx, session_rx) =
1035 crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(self.cfg.session_forward_capacity);
1036 let (session_rx, session_rx_ah) = hopr_utils::runtime::DropAbortable::new(session_rx.into_stream());
1037
1038 let mut abort_handles = AbortableList::default();
1039 abort_handles.insert(SessionHandles::Ingress, session_rx_ah);
1040
1041 let notifier = self
1042 .session_notifiers
1043 .get()
1044 .map(|(_, notifier)| {
1045 let notifier = notifier.clone();
1046 Box::new(move |session_id: SessionId, reason: ClosureReason| {
1047 let _ = notifier
1048 .try_send((session_id, reason))
1049 .inspect_err(|error| error!(%session_id, %error, "failed to notify session closure"));
1050 })
1051 })
1052 .ok_or(SessionManagerError::NotStarted)?;
1053
1054 if let Some(balancer_config) = cfg.surb_management {
1058 let surb_estimator = AtomicSurbFlowEstimator::default();
1059
1060 let surb_estimator_clone = surb_estimator.clone();
1062 let full_surb_scoring_sender =
1063 msg_sender.with(move |(routing, data): (DestinationRouting, ApplicationDataOut)| {
1064 let produced = data.estimate_surbs_with_msg() as u64;
1065 surb_estimator_clone
1067 .produced
1068 .fetch_add(produced, std::sync::atomic::Ordering::Relaxed);
1069 #[cfg(feature = "telemetry")]
1070 crate::telemetry::record_session_surb_produced(&session_id, produced);
1071 futures::future::ok::<_, S::Error>((routing, data))
1072 });
1073
1074 let max_out_organic_surbs = cfg.always_max_out_surbs;
1077 let reduced_surb_scoring_sender = full_surb_scoring_sender.clone().with(
1078 move |(routing, mut data): (DestinationRouting, ApplicationDataOut)| {
1082 if !max_out_organic_surbs {
1083 data.packet_info
1085 .get_or_insert_with(|| OutgoingPacketInfo {
1086 max_surbs_in_packet: 1,
1087 ..Default::default()
1088 })
1089 .max_surbs_in_packet = 1;
1090 }
1091 futures::future::ok::<_, S::Error>((routing, data))
1092 },
1093 );
1094
1095 let surb_mgmt = Arc::new(BalancerStateValues::from(balancer_config));
1096 surb_mgmt.set_counterparty_buffer_capacity(self.cfg.maximum_surb_buffer_size as u64);
1100
1101 let (ka_controller, ka_abort_handle) = utils::spawn_keep_alive_stream(
1103 session_id,
1104 full_surb_scoring_sender,
1105 forward_routing.clone(),
1106 if self.cfg.surb_target_notify {
1107 SurbNotificationMode::Target
1108 } else {
1109 SurbNotificationMode::DoNotNotify
1110 },
1111 surb_mgmt.clone(),
1112 );
1113 abort_handles.insert(SessionHandles::KeepAlive, ka_abort_handle);
1114
1115 debug!(%session_id, ?balancer_config ,"spawning entry SURB balancer");
1117 let balancer = SurbBalancer::new(
1118 session_id,
1119 PidBalancerController::from_gains(PidControllerGains::from_env_or_default()),
1121 surb_estimator.clone(),
1122 SurbControllerWithCorrection(ka_controller, HoprPacket::MAX_SURBS_IN_PACKET as u32),
1125 surb_mgmt.clone(),
1126 );
1127
1128 let (level_stream, balancer_abort_handle) =
1129 balancer.start_control_loop(self.cfg.balancer_sampling_interval);
1130 abort_handles.insert(SessionHandles::Balancer, balancer_abort_handle);
1131
1132 let mut slot_guard = self
1137 .allocate_session_slot(
1138 session_id,
1139 SessionSlot {
1140 session_tx,
1141 routing_opts: forward_routing.clone(),
1142 abort_handles: Arc::new(parking_lot::Mutex::new(abort_handles)),
1143 surb_mgmt: surb_mgmt.clone(),
1144 surb_estimator: surb_estimator.clone(),
1145 },
1146 )
1147 .ok_or_else(|| {
1148 error!(%session_id, "session already exists - loopback attempt");
1150 SessionManagerError::Loopback
1151 })?;
1152
1153 let sessions_keepalive = self.sessions.clone();
1157 let touch_period = (self.cfg.idle_timeout / 2).max(self.cfg.min_session_touch_period);
1158 let slot_keepalive = hopr_utils::runtime::prelude::spawn(async move {
1159 loop {
1160 hopr_utils::runtime::prelude::sleep(touch_period).await;
1161 let _ = sessions_keepalive.get(&session_id);
1162 }
1163 });
1164
1165 let wait_result = level_stream
1167 .skip_while(|current_level| {
1168 futures::future::ready(*current_level < balancer_config.target_surb_buffer_size / 2)
1169 })
1170 .next()
1171 .timeout(futures_time::time::Duration::from(SESSION_READINESS_TIMEOUT))
1172 .await;
1173 slot_keepalive.abort();
1174 match wait_result {
1175 Ok(Some(surb_level)) => {
1176 info!(%session_id, surb_level, "session is ready");
1177 }
1178 Ok(None) => {
1179 return Err(
1180 SessionManagerError::other(anyhow!("surb balancer was cancelled prematurely")).into(),
1181 );
1182 }
1183 Err(_) => {
1184 warn!(%session_id, "session didn't reach target SURB buffer size in time");
1185 }
1186 }
1187
1188 #[cfg(all(feature = "telemetry", not(test)))]
1189 METRIC_NUM_INITIATED_SESSIONS.increment();
1190
1191 let surb_estimator_for_rx = surb_estimator.clone();
1192 let session = HoprSession::new_with_surb_state(
1193 session_id,
1194 forward_routing,
1195 session_config_with(&self.cfg, cfg.capabilities, cfg.max_frames_behind_gap),
1196 (
1197 reduced_surb_scoring_sender,
1198 session_rx.inspect(move |_| {
1199 surb_estimator_for_rx
1202 .consumed
1203 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1204 #[cfg(feature = "telemetry")]
1205 crate::telemetry::record_session_surb_consumed(&session_id, 1);
1206 }),
1207 ),
1208 Some(notifier),
1209 Some(surb_mgmt.clone()),
1212 cfg.flow_control,
1213 )?;
1214
1215 #[cfg(feature = "telemetry")]
1216 initialize_session_telemetry(
1217 session_id,
1218 &self.cfg,
1219 cfg.capabilities,
1220 Some(&surb_estimator),
1221 Some(&surb_mgmt),
1222 );
1223
1224 slot_guard.commit();
1225 Ok(session)
1226 } else {
1227 warn!(%session_id, "session ready without SURB balancing");
1228
1229 let mut slot_guard = self
1232 .allocate_session_slot(
1233 session_id,
1234 SessionSlot {
1235 session_tx,
1236 routing_opts: forward_routing.clone(),
1237 abort_handles: Arc::new(parking_lot::Mutex::new(abort_handles)),
1238 surb_mgmt: Default::default(), surb_estimator: Default::default(), },
1241 )
1242 .ok_or_else(|| {
1243 error!(%session_id, "session already exists - loopback attempt");
1245 SessionManagerError::Loopback
1246 })?;
1247
1248 #[cfg(all(feature = "telemetry", not(test)))]
1249 METRIC_NUM_INITIATED_SESSIONS.increment();
1250
1251 let max_out_organic_surbs = cfg.always_max_out_surbs;
1254 let reduced_surb_sender =
1255 msg_sender.with(move |(routing, mut data): (DestinationRouting, ApplicationDataOut)| {
1256 if !max_out_organic_surbs {
1257 data.packet_info
1258 .get_or_insert_with(|| OutgoingPacketInfo {
1259 max_surbs_in_packet: 1,
1260 ..Default::default()
1261 })
1262 .max_surbs_in_packet = 1;
1263 }
1264 futures::future::ok::<_, S::Error>((routing, data))
1265 });
1266
1267 let session = HoprSession::new(
1268 session_id,
1269 forward_routing,
1270 session_config_with(&self.cfg, cfg.capabilities, cfg.max_frames_behind_gap),
1271 (reduced_surb_sender, session_rx),
1272 Some(notifier),
1273 )?;
1274
1275 #[cfg(feature = "telemetry")]
1276 initialize_session_telemetry(session_id, &self.cfg, cfg.capabilities, None, None);
1277
1278 slot_guard.commit();
1279 Ok(session)
1280 }
1281 }
1282 Ok(Ok(None)) => {
1283 self.session_initiations.remove(&challenge);
1284 Err(SessionManagerError::other(anyhow!(
1285 "internal error: sender has been closed without completing the session establishment"
1286 ))
1287 .into())
1288 }
1289 Ok(Err(error)) => {
1290 error!(
1292 challenge = error.challenge,
1293 ?error,
1294 "the other party rejected the session initiation with error"
1295 );
1296 Err(TransportSessionError::Rejected(error.reason))
1297 }
1298 Err(_) => {
1299 error!(challenge, "session initiation attempt timed out");
1301
1302 #[cfg(all(feature = "telemetry", not(test)))]
1303 METRIC_RECEIVED_SESSION_ERRS.increment(&["timeout"]);
1304
1305 self.session_initiations.remove(&challenge);
1306 Err(TransportSessionError::Timeout)
1307 }
1308 }
1309 }
1310
1311 pub async fn ping_session(&self, id: &SessionId) -> crate::errors::Result<()> {
1315 if let Some(session_data) = self.sessions.get(id) {
1316 trace!(session_id = ?id, "pinging manually session");
1317 let mut msg_sender = self.msg_sender.get().cloned().ok_or(SessionManagerError::NotStarted)?;
1318 send_via_msg_sender(
1319 &mut msg_sender,
1320 session_data.routing_opts.clone(),
1321 HoprStartProtocol::KeepAlive((*id).into()),
1322 "session ping message",
1323 )
1324 .await
1325 .map_err(TransportSessionError::packet_sending)
1326 } else {
1327 Err(SessionManagerError::NonExistingSession.into())
1328 }
1329 }
1330
1331 pub fn active_sessions(&self) -> Vec<SessionId> {
1333 self.sessions.run_pending_tasks();
1334 self.sessions.iter().map(|(k, _)| *k).collect()
1335 }
1336
1337 pub fn close_session(&self, id: &SessionId) -> bool {
1347 if let Some(slot) = self.sessions.remove(id) {
1348 self.active_sessions.fetch_sub(1, Ordering::Relaxed);
1349 close_session(*id, slot, ClosureReason::Eviction);
1350 true
1351 } else {
1352 false
1353 }
1354 }
1355
1356 pub fn update_surb_balancer_config(&self, id: &SessionId, config: SurbBalancerConfig) -> crate::errors::Result<()> {
1361 let cfg = self
1362 .sessions
1363 .get(id)
1364 .ok_or(SessionManagerError::NonExistingSession)?
1365 .surb_mgmt;
1366
1367 if !cfg.is_disabled() {
1369 cfg.update(&config);
1370 Ok(())
1371 } else {
1372 Err(SessionManagerError::other(anyhow!("session does not use SURB balancing")).into())
1373 }
1374 }
1375
1376 pub fn get_surb_balancer_config(&self, id: &SessionId) -> crate::errors::Result<Option<SurbBalancerConfig>> {
1380 match self.sessions.get(id) {
1381 Some(session) => Ok(Some(session.surb_mgmt.as_ref())
1382 .filter(|c| !c.is_disabled())
1383 .map(|d| d.as_config())),
1384 None => Err(SessionManagerError::NonExistingSession.into()),
1385 }
1386 }
1387
1388 pub fn get_surb_level_estimates(&self, id: &SessionId) -> crate::errors::Result<(u64, u64)> {
1395 match self.sessions.get(id) {
1396 Some(session) => Ok((
1397 session
1398 .surb_estimator
1399 .produced
1400 .load(std::sync::atomic::Ordering::Relaxed),
1401 session
1402 .surb_estimator
1403 .consumed
1404 .load(std::sync::atomic::Ordering::Relaxed),
1405 )),
1406 None => Err(SessionManagerError::NonExistingSession.into()),
1407 }
1408 }
1409
1410 pub fn mark_return_path_degraded(
1419 &self,
1420 destination: &hopr_api::types::internal::NodeId,
1421 grace: std::time::Duration,
1422 ) -> usize {
1423 self.sessions
1424 .iter()
1425 .filter(|(_, slot)| {
1426 matches!(&slot.routing_opts, DestinationRouting::Forward { destination: d, .. } if d.as_ref() == destination)
1427 })
1428 .map(|(_, slot)| slot.surb_mgmt.mark_return_path_degraded(grace))
1429 .count()
1430 }
1431
1432 pub fn dispatch_message(
1439 &self,
1440 pseudonym: HoprPseudonym,
1441 in_data: ApplicationDataIn,
1442 ) -> crate::errors::Result<DispatchResult> {
1443 if in_data.data.application_tag == HoprStartProtocol::START_PROTOCOL_MESSAGE_TAG {
1444 trace!("dispatching Start protocol message");
1446 if let Some(start_protocol_tx) = self.start_protocol_tx.get() {
1447 start_protocol_tx
1448 .try_send((pseudonym, HoprStartProtocol::try_from(in_data.data)?))
1449 .map_err(|error| {
1450 error!(%error, "failed to send Start protocol message to processing task");
1451 SessionManagerError::other(error)
1452 })?;
1453 } else {
1454 return Err(SessionManagerError::NotStarted.into());
1455 }
1456
1457 #[cfg(all(feature = "telemetry", not(test)))]
1458 METRIC_DISPATCHED_MSGS.increment_by(&["processed"], 1);
1459
1460 return Ok(DispatchResult::Processed);
1461 } else if in_data.data.application_tag == SESSION_APPLICATION_TAG {
1462 let session_id = pseudonym;
1463
1464 const SESSION_INBOX_FULL_WARN_INTERVAL: usize = 256;
1468
1469 return if let Some(session_slot) = self.sessions.get(&session_id) {
1470 trace!(%session_id, "received data for a registered session");
1471
1472 match session_slot.session_tx.try_send(in_data) {
1473 Ok(_) => {
1474 #[cfg(all(feature = "telemetry", not(test)))]
1475 METRIC_DISPATCHED_MSGS.increment_by(&["processed"], 1);
1476
1477 Ok(DispatchResult::Processed)
1478 }
1479 Err(crossfire::TrySendError::Disconnected(_)) => {
1482 trace!(%session_id, "dropping data for a session whose sink has closed");
1483 crate::counters::SESSION_INBOX_CLOSED_DROPS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1484 #[cfg(all(feature = "telemetry", not(test)))]
1485 METRIC_DISPATCHED_MSGS.increment_by(&["dropped_sink_closed"], 1);
1486 Ok(DispatchResult::Dropped(DropReason::SinkClosed))
1487 }
1488 Err(crossfire::TrySendError::Full(_)) => {
1491 let prev =
1492 crate::counters::SESSION_INBOX_DROPS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1493 if prev.is_multiple_of(SESSION_INBOX_FULL_WARN_INTERVAL) {
1494 warn!(%session_id, total_inbox_full_drops = prev + 1, "session inbox full, dropping data (backpressure)");
1495 }
1496 #[cfg(all(feature = "telemetry", not(test)))]
1497 METRIC_DISPATCHED_MSGS.increment_by(&["dropped_sink_full"], 1);
1498 Ok(DispatchResult::Dropped(DropReason::SinkFull))
1499 }
1500 }
1501 } else {
1502 trace!(%session_id, "dropping data for an unregistered session");
1505 crate::counters::SESSION_UNKNOWN_DATA_DROPS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1506 #[cfg(all(feature = "telemetry", not(test)))]
1507 METRIC_DISPATCHED_MSGS.increment_by(&["dropped_unregistered"], 1);
1508 Ok(DispatchResult::Dropped(DropReason::Unregistered))
1509 };
1510 }
1511
1512 trace!(tag = %in_data.data.application_tag, "received data not associated with session protocol or any existing session");
1513
1514 crate::counters::SESSION_UNRELATED_DATA_DISPATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1515
1516 #[cfg(all(feature = "telemetry", not(test)))]
1517 METRIC_DISPATCHED_MSGS.increment_by(&["unrelated"], 1);
1518
1519 Ok(DispatchResult::Unrelated(in_data))
1520 }
1521
1522 #[cfg(any(feature = "benchmark", test))]
1529 pub fn pre_populate_session(&self, session_id: SessionId, routing_opts: DestinationRouting) {
1530 let (session_tx, _) =
1531 crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(self.cfg.session_forward_capacity);
1532 let slot = SessionSlot {
1533 session_tx,
1534 routing_opts,
1535 abort_handles: Default::default(),
1536 surb_mgmt: Arc::new(BalancerStateValues::default()),
1537 surb_estimator: Default::default(),
1538 };
1539 self.sessions.insert(session_id, slot);
1540 }
1541
1542 #[cfg(any(feature = "benchmark", test))]
1547 pub fn pre_populate_session_with_receiver(
1548 &self,
1549 session_id: SessionId,
1550 routing_opts: DestinationRouting,
1551 ) -> crossfire::AsyncRx<crossfire::mpsc::Array<ApplicationDataIn>> {
1552 let (session_tx, session_rx) =
1553 crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(self.cfg.session_forward_capacity);
1554 let slot = SessionSlot {
1555 session_tx,
1556 routing_opts,
1557 abort_handles: Default::default(),
1558 surb_mgmt: Arc::new(BalancerStateValues::default()),
1559 surb_estimator: Default::default(),
1560 };
1561 self.sessions.insert(session_id, slot);
1562 session_rx
1563 }
1564
1565 async fn handle_incoming_session_initiation(
1566 &self,
1567 pseudonym: HoprPseudonym,
1568 session_req: StartInitiation<SessionTarget, ByteCapabilities>,
1569 ) -> crate::errors::Result<()> {
1570 trace!(challenge = session_req.challenge, "received session initiation request");
1571
1572 debug!(%pseudonym, "got new session request, searching for a free session slot");
1573
1574 let mut msg_sender = self.msg_sender.get().cloned().ok_or(SessionManagerError::NotStarted)?;
1575
1576 let (new_session_notifier, close_session_notifier) = self
1577 .session_notifiers
1578 .get()
1579 .cloned()
1580 .ok_or(SessionManagerError::NotStarted)?;
1581
1582 let reply_routing = DestinationRouting::Return(pseudonym.into());
1584
1585 self.sessions.run_pending_tasks();
1587
1588 if let Some(stale_slot) = self.sessions.remove(&pseudonym) {
1596 self.active_sessions.fetch_sub(1, Ordering::Relaxed);
1597 info!(%pseudonym, "closing stale session superseded by a new initiation with the same pseudonym");
1598 close_session(pseudonym, stale_slot, ClosureReason::Eviction);
1599 }
1600
1601 let session_id = pseudonym;
1602
1603 let (session_tx, session_rx) =
1604 crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(self.cfg.session_forward_capacity);
1605 let (session_rx, session_rx_ah) = hopr_utils::runtime::DropAbortable::new(session_rx.into_stream());
1606
1607 let slot = SessionSlot {
1608 session_tx,
1609 routing_opts: reply_routing.clone(),
1610 abort_handles: Default::default(),
1611 surb_mgmt: Default::default(),
1612 surb_estimator: Default::default(),
1613 };
1614 slot.abort_handles.lock().insert(SessionHandles::Ingress, session_rx_ah);
1615
1616 let Some(mut slot_guard) = self.allocate_session_slot(session_id, slot.clone()) else {
1621 error!(%pseudonym, "no session slot available");
1624 let reason = StartErrorReason::NoSlotsAvailable;
1625 let data = HoprStartProtocol::SessionError(StartErrorType {
1626 challenge: session_req.challenge,
1627 reason,
1628 });
1629 send_via_msg_sender(&mut msg_sender, reply_routing.clone(), data, "session error message").await?;
1630 return Ok(());
1631 };
1632
1633 debug!(?pseudonym, ?session_req, "assigned a new session");
1634
1635 let closure_notifier = Box::new(move |session_id: SessionId, reason: ClosureReason| {
1636 if let Err(error) = close_session_notifier.try_send((session_id, reason)) {
1637 error!(%session_id, %error, %reason, "failed to notify session closure");
1638 }
1639 });
1640
1641 let session = if !session_req.capabilities.0.contains(Capability::NoRateControl) {
1642 let egress_rate_control =
1644 RateController::new(self.cfg.initial_return_session_egress_rate, Duration::from_secs(1));
1645
1646 let target_surb_buffer_size = if session_req.additional_data > 0 {
1649 (session_req.additional_data as u64).min(self.cfg.maximum_surb_buffer_size as u64)
1650 } else {
1651 self.cfg.initial_return_session_egress_rate as u64
1652 * self
1653 .cfg
1654 .minimum_surb_buffer_duration
1655 .max(MIN_SURB_BUFFER_DURATION)
1656 .as_secs()
1657 };
1658
1659 let surb_estimator_clone = slot.surb_estimator.clone();
1660 let session = HoprSession::new(
1661 session_id,
1662 reply_routing.clone(),
1663 session_config(&self.cfg, session_req.capabilities.into()),
1664 (
1665 msg_sender
1667 .clone()
1668 .with(move |(routing, data): (DestinationRouting, ApplicationDataOut)| {
1669 surb_estimator_clone
1671 .consumed
1672 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1673 #[cfg(feature = "telemetry")]
1674 crate::telemetry::record_session_surb_consumed(&session_id, 1);
1675 futures::future::ok::<_, S::Error>((routing, data))
1676 })
1677 .rate_limit_with_controller(&egress_rate_control)
1678 .buffer((2 * target_surb_buffer_size) as usize),
1679 session_rx.inspect(move |data| {
1681 let produced = data.num_surbs_with_msg() as u64;
1682 surb_estimator_clone
1684 .produced
1685 .fetch_add(produced, std::sync::atomic::Ordering::Relaxed);
1686 #[cfg(feature = "telemetry")]
1687 crate::telemetry::record_session_surb_produced(&session_id, produced);
1688 }),
1689 ),
1690 Some(closure_notifier),
1691 )?;
1692
1693 let balancer_config = SurbBalancerConfig {
1697 target_surb_buffer_size,
1698 max_surbs_per_sec: target_surb_buffer_size / self.cfg.minimum_surb_buffer_duration.as_secs(),
1700 surb_decay: None,
1703 sustain_on_return_path_loss: false,
1704 };
1705
1706 slot.surb_mgmt.update(&balancer_config);
1707 slot.surb_mgmt
1708 .set_counterparty_buffer_capacity(self.cfg.maximum_surb_buffer_size as u64);
1709
1710 debug!(%session_id, ?balancer_config ,"spawning exit SURB balancer");
1713 let balancer = SurbBalancer::new(
1714 session_id,
1715 SimpleBalancerController::default(),
1716 slot.surb_estimator.clone(),
1717 SurbControllerWithCorrection(egress_rate_control, 1), slot.surb_mgmt.clone(),
1719 );
1720
1721 let (_, balancer_abort_handle) = balancer.start_control_loop(self.cfg.balancer_sampling_interval);
1723 slot.abort_handles
1724 .lock()
1725 .insert(SessionHandles::Balancer, balancer_abort_handle);
1726
1727 if let Some(period) = self.cfg.surb_balance_notify_period {
1729 let surb_estimator_clone = slot.surb_estimator.clone();
1730 let (ka_controller, ka_abort_handle) = utils::spawn_keep_alive_stream(
1731 session_id,
1732 msg_sender
1734 .clone()
1735 .with(move |(routing, data): (DestinationRouting, ApplicationDataOut)| {
1736 surb_estimator_clone
1738 .consumed
1739 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1740 #[cfg(feature = "telemetry")]
1741 crate::telemetry::record_session_surb_consumed(&session_id, 1);
1742 futures::future::ok::<_, S::Error>((routing, data))
1743 }),
1744 slot.routing_opts.clone(),
1745 SurbNotificationMode::Level(slot.surb_estimator.clone()),
1746 slot.surb_mgmt.clone(),
1747 );
1748
1749 hopr_utils::runtime::prelude::spawn(async move {
1751 hopr_utils::runtime::prelude::sleep(period).await;
1753 ka_controller.set_rate_per_unit(1, period);
1754 });
1755
1756 slot.abort_handles
1757 .lock()
1758 .insert(SessionHandles::KeepAlive, ka_abort_handle);
1759
1760 debug!(%session_id, ?period, "started SURB level-notifying keep-alive stream");
1761 }
1762
1763 session
1764 } else {
1765 HoprSession::new(
1766 session_id,
1767 reply_routing.clone(),
1768 session_config(&self.cfg, session_req.capabilities.into()),
1769 (msg_sender.clone(), session_rx),
1770 Some(closure_notifier),
1771 )?
1772 };
1773
1774 let incoming_session = IncomingSession {
1776 id: session_id,
1777 session,
1778 target: session_req.target,
1779 };
1780
1781 match async {
1784 let mut guard = new_session_notifier.lock().await;
1785 guard.send(incoming_session).await
1786 }
1787 .timeout(futures_time::time::Duration::from(EXTERNAL_SEND_TIMEOUT))
1788 .await
1789 {
1790 Err(_) => {
1791 error!(%session_id, "timeout to notify about new incoming session");
1792 return Err(TransportSessionError::Timeout);
1793 }
1794 Ok(Err(error)) => {
1795 error!(%session_id, %error, "failed to notify about new incoming session");
1796 return Err(SessionManagerError::other(error).into());
1797 }
1798 _ => {}
1799 };
1800
1801 trace!(?session_id, "session notification sent");
1802
1803 let data = HoprStartProtocol::SessionEstablished(StartEstablished {
1806 orig_challenge: session_req.challenge,
1807 session_id,
1808 });
1809
1810 send_via_msg_sender(
1811 &mut msg_sender,
1812 reply_routing.clone(),
1813 data,
1814 "session establishment message",
1815 )
1816 .await?;
1817
1818 #[cfg(feature = "telemetry")]
1819 initialize_session_telemetry(
1820 session_id,
1821 &self.cfg,
1822 session_req.capabilities.0,
1823 Some(&slot.surb_estimator),
1824 Some(&slot.surb_mgmt),
1825 );
1826
1827 info!(%session_id, "new session established");
1828
1829 #[cfg(all(feature = "telemetry", not(test)))]
1830 METRIC_NUM_ESTABLISHED_SESSIONS.increment();
1831
1832 slot_guard.commit();
1833 Ok(())
1834 }
1835
1836 async fn handle_session_established(&self, est: StartEstablished<SessionId>) -> crate::errors::Result<()> {
1837 trace!(
1838 session_id = ?est.session_id,
1839 "received session establishment confirmation"
1840 );
1841 let challenge = est.orig_challenge;
1842 let session_id = est.session_id;
1843 if let Some(tx_est) = self.session_initiations.remove(&est.orig_challenge) {
1844 if let Err(error) = tx_est.try_send(Ok(est)) {
1845 error!(%challenge, %session_id, %error, "failed to send session establishment confirmation");
1846 return Err(SessionManagerError::other(error).into());
1847 }
1848 debug!(?session_id, challenge, "session establishment complete");
1849 } else {
1850 error!(%session_id, challenge, "unknown session establishment attempt or expired");
1851 }
1852 Ok(())
1853 }
1854
1855 async fn handle_session_error(&self, error_type: StartErrorType) -> crate::errors::Result<()> {
1856 trace!(
1857 challenge = error_type.challenge,
1858 error = ?error_type.reason,
1859 "failed to initialize a session",
1860 );
1861 if let Some(tx_est) = self.session_initiations.remove(&error_type.challenge) {
1864 if let Err(error) = tx_est.try_send(Err(error_type)) {
1865 error!(%error, ?error_type, "could not send session error message");
1866 return Err(SessionManagerError::other(error).into());
1867 }
1868 error!(
1869 challenge = error_type.challenge,
1870 ?error_type,
1871 "session establishment error received"
1872 );
1873 } else {
1874 error!(
1875 challenge = error_type.challenge,
1876 ?error_type,
1877 "session establishment attempt expired before error could be delivered"
1878 );
1879 }
1880
1881 #[cfg(all(feature = "telemetry", not(test)))]
1882 METRIC_RECEIVED_SESSION_ERRS.increment(&[&error_type.reason.to_string()]);
1883
1884 Ok(())
1885 }
1886
1887 async fn handle_keep_alive(&self, msg: KeepAliveMessage<SessionId>) -> crate::errors::Result<()> {
1888 let session_id = msg.session_id;
1889 if let Some(session_slot) = self.sessions.get(&session_id) {
1890 trace!(?session_id, "received keep-alive message");
1891 match &session_slot.routing_opts {
1892 DestinationRouting::Forward { .. } => {
1894 if msg.flags.contains(KeepAliveFlag::BalancerState)
1895 && !session_slot.surb_mgmt.is_disabled()
1896 && session_slot.surb_mgmt.buffer_level() != msg.additional_data
1897 {
1898 session_slot
1900 .surb_mgmt
1901 .buffer_level
1902 .store(msg.additional_data, std::sync::atomic::Ordering::Relaxed);
1903 debug!(%session_id, surb_level = msg.additional_data, "keep-alive updated SURB buffer size from the Exit");
1904 }
1905
1906 session_slot
1908 .surb_estimator
1909 .consumed
1910 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1911 #[cfg(feature = "telemetry")]
1912 crate::telemetry::record_session_surb_consumed(&session_id, 1);
1913 }
1914 DestinationRouting::Return(_) => {
1916 if msg.flags.contains(KeepAliveFlag::BalancerTarget)
1918 && msg.additional_data > 0
1919 && !session_slot.surb_mgmt.is_disabled()
1920 && session_slot.surb_mgmt.controller_bounds().target() != msg.additional_data
1921 {
1922 session_slot
1924 .surb_mgmt
1925 .target_surb_buffer_size
1926 .store(msg.additional_data, std::sync::atomic::Ordering::Relaxed);
1927 session_slot.surb_mgmt.max_surbs_per_sec.store(
1929 msg.additional_data / self.cfg.minimum_surb_buffer_duration.as_secs(),
1930 std::sync::atomic::Ordering::Relaxed,
1931 );
1932 debug!(%session_id, target_surb_buffer_size = msg.additional_data, "keep-alive updated SURB balancer target buffer size from the Entry");
1933 }
1934
1935 let produced = KeepAliveMessage::<SessionId>::MIN_SURBS_PER_MESSAGE as u64;
1938 session_slot
1939 .surb_estimator
1940 .produced
1941 .fetch_add(produced, std::sync::atomic::Ordering::Relaxed);
1942 #[cfg(feature = "telemetry")]
1943 crate::telemetry::record_session_surb_produced(&session_id, produced);
1944 }
1945 }
1946 } else {
1947 debug!(%session_id, "received keep-alive request for an unknown session");
1948 }
1949 Ok(())
1950 }
1951}
1952
1953#[cfg(test)]
1954mod tests {
1955 use anyhow::{Context, anyhow};
1956 use futures::{AsyncWriteExt, channel::mpsc::UnboundedSender, future::BoxFuture, pin_mut};
1957 use hopr_api::types::{
1958 crypto::{keypairs::ChainKeypair, prelude::Keypair},
1959 crypto_random::Randomizable,
1960 internal::routing::SurbMatcher,
1961 primitive::prelude::Address,
1962 };
1963 use hopr_protocol_start::{StartProtocol, StartProtocolDiscriminants};
1964 use hopr_utils::network_types::prelude::SealedHost;
1965 use moka::future::FutureExt;
1966 use tokio::time::timeout;
1967
1968 use super::*;
1969 use crate::{Capabilities, balancer::SurbBalancerConfig, types::SessionTarget};
1970
1971 #[test]
1972 fn session_config_forwards_max_buffered_segments() {
1973 assert_eq!(
1974 SessionManagerConfig::default().max_buffered_segments,
1975 0,
1976 "default must leave the transport unbuffered"
1977 );
1978
1979 for segments in [0, 64] {
1980 let cfg = SessionManagerConfig {
1981 max_buffered_segments: segments,
1982 ..Default::default()
1983 };
1984 assert_eq!(
1985 session_config(&cfg, Capabilities::empty()).max_buffered_segments,
1986 segments
1987 );
1988 }
1989 }
1990
1991 #[test]
2000 fn session_config_should_bound_the_gap_only_without_retransmission() {
2001 assert_eq!(
2002 SessionManagerConfig::default().max_frames_behind_gap,
2003 Some(256),
2004 "the default must bound the gap, or the stall stays in place unless opted out of"
2005 );
2006
2007 let cfg = SessionManagerConfig {
2008 max_frames_behind_gap: Some(8),
2009 ..Default::default()
2010 };
2011
2012 for reliable in [Capability::RetransmissionAck, Capability::RetransmissionNack] {
2013 assert_eq!(
2014 session_config(&cfg, reliable.into()).max_frames_behind_gap,
2015 None,
2016 "{reliable:?} can recover the gap, so the wait is productive and must be left alone"
2017 );
2018 }
2019
2020 for unreliable in [Capabilities::empty(), Capability::Segmentation.into()] {
2021 assert_eq!(
2022 session_config(&cfg, unreliable).max_frames_behind_gap,
2023 Some(8),
2024 "without retransmission the gap must be bounded"
2025 );
2026 }
2027 }
2028
2029 #[test]
2033 fn a_session_should_be_able_to_override_the_nodes_gap_bound() {
2034 let node = SessionManagerConfig {
2035 max_frames_behind_gap: Some(256),
2036 ..Default::default()
2037 };
2038
2039 assert_eq!(
2040 session_config_with(&node, Capabilities::empty(), Some(16)).max_frames_behind_gap,
2041 Some(16),
2042 "the session's own value must win over the node default"
2043 );
2044 assert_eq!(
2045 session_config_with(&node, Capabilities::empty(), None).max_frames_behind_gap,
2046 Some(256),
2047 "saying nothing must inherit the node default"
2048 );
2049 assert_eq!(
2050 session_config_with(&node, Capabilities::empty(), Some(0)).max_frames_behind_gap,
2051 None,
2052 "zero disables the bound for this session, matching the env knob's semantics"
2053 );
2054 }
2055
2056 #[test]
2058 fn a_session_override_should_not_reach_a_session_that_can_retransmit() {
2059 let node = SessionManagerConfig::default();
2060 assert_eq!(
2061 session_config_with(&node, Capability::RetransmissionAck.into(), Some(4)).max_frames_behind_gap,
2062 None,
2063 "retransmission can recover the gap, so no caller should be able to cut the wait short"
2064 );
2065 }
2066
2067 #[test]
2070 fn session_config_should_allow_the_gap_bound_to_be_disabled() {
2071 let cfg = SessionManagerConfig {
2072 max_frames_behind_gap: None,
2073 ..Default::default()
2074 };
2075 assert_eq!(session_config(&cfg, Capabilities::empty()).max_frames_behind_gap, None);
2076 }
2077
2078 #[test]
2079 fn a_zero_gap_bound_should_disable_it_at_the_node_level_too() {
2080 let cfg = SessionManagerConfig {
2084 max_frames_behind_gap: Some(0),
2085 ..Default::default()
2086 };
2087 assert_eq!(session_config(&cfg, Capabilities::empty()).max_frames_behind_gap, None);
2088 }
2089
2090 #[async_trait::async_trait]
2091 trait SendMsg {
2092 async fn send_message(
2093 &self,
2094 routing: DestinationRouting,
2095 data: ApplicationDataOut,
2096 ) -> crate::errors::Result<()>;
2097 }
2098
2099 mockall::mock! {
2100 MsgSender {}
2101 impl SendMsg for MsgSender {
2102 fn send_message<'a, 'b>(&'a self, routing: DestinationRouting, data: ApplicationDataOut)
2103 -> BoxFuture<'b, crate::errors::Result<()>> where 'a: 'b, Self: Sync + 'b;
2104 }
2105 }
2106
2107 fn mock_packet_planning(
2108 sender: MockMsgSender,
2109 ) -> (
2110 UnboundedSender<(DestinationRouting, ApplicationDataOut)>,
2111 tokio::task::JoinHandle<()>,
2112 ) {
2113 let (tx, rx) = futures::channel::mpsc::unbounded();
2114 let handle = tokio::task::spawn(async move {
2115 pin_mut!(rx);
2116 while let Some((routing, data)) = rx.next().await {
2117 sender
2118 .send_message(routing, data)
2119 .await
2120 .expect("send message must not fail in mock");
2121 }
2122 });
2123 (tx, handle)
2124 }
2125
2126 fn msg_type(data: &ApplicationDataOut, expected: StartProtocolDiscriminants) -> bool {
2127 HoprStartProtocol::decode(data.data.application_tag, &data.data.plain_text)
2128 .map(|d| StartProtocolDiscriminants::from(d) == expected)
2129 .unwrap_or(false)
2130 }
2131
2132 fn start_msg_match(data: &ApplicationDataOut, msg: impl Fn(HoprStartProtocol) -> bool) -> bool {
2133 HoprStartProtocol::decode(data.data.application_tag, &data.data.plain_text)
2134 .map(msg)
2135 .unwrap_or(false)
2136 }
2137
2138 async fn wait_for_no_active_sessions(
2143 mgr: &SessionManager<UnboundedSender<(DestinationRouting, ApplicationDataOut)>>,
2144 ) -> bool {
2145 for _ in 0..50 {
2146 if mgr.active_sessions().is_empty() {
2147 return true;
2148 }
2149 tokio::time::sleep(Duration::from_millis(20)).await;
2150 }
2151 mgr.active_sessions().is_empty()
2152 }
2153
2154 #[test_log::test(tokio::test)]
2155 async fn session_manager_should_follow_start_protocol_to_establish_new_session_and_close_it() -> anyhow::Result<()>
2156 {
2157 let alice_pseudonym = HoprPseudonym::random();
2158 let bob_peer: Address = (&ChainKeypair::random()).into();
2159
2160 let alice_mgr = SessionManager::new(Default::default());
2161 let bob_mgr = SessionManager::new(Default::default());
2162
2163 let mut sequence = mockall::Sequence::new();
2164 let mut alice_transport = MockMsgSender::new();
2165 let mut bob_transport = MockMsgSender::new();
2166
2167 let bob_mgr_clone = bob_mgr.clone();
2169 alice_transport
2170 .expect_send_message()
2171 .once()
2172 .in_sequence(&mut sequence)
2173 .withf(move |peer, data| {
2174 info!("alice sends {}", data.data.application_tag);
2175 msg_type(data, StartProtocolDiscriminants::StartSession)
2176 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2177 })
2178 .returning(move |_, data| {
2179 let bob_mgr_clone = bob_mgr_clone.clone();
2180 Box::pin(async move {
2181 bob_mgr_clone
2182 .dispatch_message(
2183 alice_pseudonym,
2184 ApplicationDataIn {
2185 data: data.data,
2186 packet_info: Default::default(),
2187 },
2188 )
2189 ?;
2190 Ok(())
2191 })
2192 });
2193
2194 let alice_mgr_clone = alice_mgr.clone();
2196 bob_transport
2197 .expect_send_message()
2198 .once()
2199 .in_sequence(&mut sequence)
2200 .withf(move |peer, data| {
2201 info!("bob sends {}", data.data.application_tag);
2202 msg_type(data, StartProtocolDiscriminants::SessionEstablished)
2203 && matches!(peer, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &alice_pseudonym)
2204 })
2205 .returning(move |_, data| {
2206 let alice_mgr_clone = alice_mgr_clone.clone();
2207
2208 Box::pin(async move {
2209 alice_mgr_clone.dispatch_message(
2210 alice_pseudonym,
2211 ApplicationDataIn {
2212 data: data.data,
2213 packet_info: Default::default(),
2214 },
2215 )?;
2216 Ok(())
2217 })
2218 });
2219
2220 let bob_mgr_clone = bob_mgr.clone();
2222 alice_transport
2223 .expect_send_message()
2224 .once()
2225 .in_sequence(&mut sequence)
2226 .withf(move |peer, data| {
2227 hopr_protocol_session::types::SessionMessage::<{ ApplicationData::PAYLOAD_SIZE }>::try_from(
2228 data.data.plain_text.as_ref(),
2229 )
2230 .expect("must be a session message")
2231 .try_as_segment()
2232 .expect("must be a segment")
2233 .is_terminating()
2234 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2235 })
2236 .returning(move |_, data| {
2237 let bob_mgr_clone = bob_mgr_clone.clone();
2238 Box::pin(async move {
2239 bob_mgr_clone
2240 .dispatch_message(
2241 alice_pseudonym,
2242 ApplicationDataIn {
2243 data: data.data,
2244 packet_info: Default::default(),
2245 },
2246 )
2247 ?;
2248 Ok(())
2249 })
2250 });
2251
2252 let mut ahs = Vec::new();
2253
2254 let (new_session_tx_alice, _) = futures::channel::mpsc::channel(1024);
2256 let (alice_sender, alice_handle) = mock_packet_planning(alice_transport);
2257 ahs.extend(alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?);
2258 assert!(alice_mgr.is_started());
2259
2260 let (new_session_tx_bob, new_session_rx_bob) = futures::channel::mpsc::channel(1024);
2262 let (bob_sender, bob_handle) = mock_packet_planning(bob_transport);
2263 ahs.extend(bob_mgr.start(bob_sender.clone(), new_session_tx_bob)?);
2264 assert!(bob_mgr.is_started());
2265
2266 let target = SealedHost::Plain("127.0.0.1:80".parse()?);
2267
2268 pin_mut!(new_session_rx_bob);
2269 let (alice_session, bob_session) = timeout(
2270 Duration::from_secs(2),
2271 futures::future::join(
2272 alice_mgr.new_session(
2273 bob_peer,
2274 SessionTarget::TcpStream(target.clone()),
2275 SessionClientConfig {
2276 pseudonym: alice_pseudonym.into(),
2277 capabilities: Capability::NoRateControl | Capability::Segmentation,
2278 surb_management: None,
2279 ..Default::default()
2280 },
2281 ),
2282 new_session_rx_bob.next(),
2283 ),
2284 )
2285 .await?;
2286
2287 let mut alice_session = alice_session?;
2288 let bob_session = bob_session.ok_or(anyhow!("bob must get an incoming session"))?;
2289
2290 assert_eq!(
2291 alice_session.config().capabilities,
2292 Capability::Segmentation | Capability::NoRateControl
2293 );
2294 assert_eq!(
2295 alice_session.config().capabilities,
2296 bob_session.session.config().capabilities
2297 );
2298 assert!(matches!(bob_session.target, SessionTarget::TcpStream(host) if host == target));
2299
2300 assert_eq!(vec![*alice_session.id()], alice_mgr.active_sessions());
2301 assert_eq!(None, alice_mgr.get_surb_balancer_config(alice_session.id())?);
2302 assert!(
2303 alice_mgr
2304 .update_surb_balancer_config(alice_session.id(), SurbBalancerConfig::default())
2305 .is_err()
2306 );
2307
2308 assert_eq!(vec![*bob_session.session.id()], bob_mgr.active_sessions());
2309 assert_eq!(None, bob_mgr.get_surb_balancer_config(bob_session.session.id())?);
2310 assert!(
2311 bob_mgr
2312 .update_surb_balancer_config(bob_session.session.id(), SurbBalancerConfig::default())
2313 .is_err()
2314 );
2315
2316 tokio::time::sleep(Duration::from_millis(100)).await;
2317 alice_session.close().await?;
2318
2319 tokio::time::sleep(Duration::from_millis(100)).await;
2320
2321 assert!(matches!(
2322 alice_mgr.ping_session(alice_session.id()).await,
2323 Err(TransportSessionError::Manager(SessionManagerError::NonExistingSession))
2324 ));
2325
2326 futures::stream::iter(ahs)
2327 .for_each(|ah| async move { ah.abort() })
2328 .await;
2329
2330 alice_sender.close_channel();
2332 bob_sender.close_channel();
2333 let _ = alice_handle.await;
2334 let _ = bob_handle.await;
2335
2336 Ok(())
2337 }
2338
2339 #[test_log::test(tokio::test)]
2340 async fn session_manager_should_close_idle_session_automatically() -> anyhow::Result<()> {
2341 let alice_pseudonym = HoprPseudonym::random();
2342 let bob_peer: Address = (&ChainKeypair::random()).into();
2343
2344 let cfg = SessionManagerConfig {
2345 idle_timeout: Duration::from_millis(200),
2346 ..Default::default()
2347 };
2348
2349 let alice_mgr = SessionManager::new(cfg);
2350 let bob_mgr = SessionManager::new(Default::default());
2351
2352 let mut sequence = mockall::Sequence::new();
2353 let mut alice_transport = MockMsgSender::new();
2354 let mut bob_transport = MockMsgSender::new();
2355
2356 let bob_mgr_clone = bob_mgr.clone();
2358 alice_transport
2359 .expect_send_message()
2360 .once()
2361 .in_sequence(&mut sequence)
2362 .withf(move |peer, data| {
2363 msg_type(data, StartProtocolDiscriminants::StartSession)
2364 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2365 })
2366 .returning(move |_, data| {
2367 let bob_mgr_clone = bob_mgr_clone.clone();
2368 Box::pin(async move {
2369 bob_mgr_clone
2370 .dispatch_message(
2371 alice_pseudonym,
2372 ApplicationDataIn {
2373 data: data.data,
2374 packet_info: Default::default(),
2375 },
2376 )
2377 ?;
2378 Ok(())
2379 })
2380 });
2381
2382 let alice_mgr_clone = alice_mgr.clone();
2384 bob_transport
2385 .expect_send_message()
2386 .once()
2387 .in_sequence(&mut sequence)
2388 .withf(move |peer, data| {
2389 msg_type(data, StartProtocolDiscriminants::SessionEstablished)
2390 && matches!(peer, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &alice_pseudonym)
2391 })
2392 .returning(move |_, data| {
2393 let alice_mgr_clone = alice_mgr_clone.clone();
2394
2395 Box::pin(async move {
2396 alice_mgr_clone.dispatch_message(
2397 alice_pseudonym,
2398 ApplicationDataIn {
2399 data: data.data,
2400 packet_info: Default::default(),
2401 },
2402 )?;
2403 Ok(())
2404 })
2405 });
2406
2407 let mut ahs = Vec::new();
2408
2409 let (new_session_tx_alice, _) = futures::channel::mpsc::channel(1024);
2411 let (alice_sender, alice_handle) = mock_packet_planning(alice_transport);
2412 ahs.extend(alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?);
2413
2414 let (new_session_tx_bob, new_session_rx_bob) = futures::channel::mpsc::channel(1024);
2416 let (bob_sender, bob_handle) = mock_packet_planning(bob_transport);
2417 ahs.extend(bob_mgr.start(bob_sender.clone(), new_session_tx_bob)?);
2418 assert!(bob_mgr.is_started());
2419
2420 let target = SealedHost::Plain("127.0.0.1:80".parse()?);
2421
2422 pin_mut!(new_session_rx_bob);
2423 let (alice_session, bob_session) = timeout(
2424 Duration::from_secs(2),
2425 futures::future::join(
2426 alice_mgr.new_session(
2427 bob_peer,
2428 SessionTarget::TcpStream(target.clone()),
2429 SessionClientConfig {
2430 pseudonym: alice_pseudonym.into(),
2431 capabilities: Capability::NoRateControl | Capability::Segmentation,
2432 surb_management: None,
2433 ..Default::default()
2434 },
2435 ),
2436 new_session_rx_bob.next(),
2437 ),
2438 )
2439 .await?;
2440
2441 let alice_session = alice_session?;
2442 let bob_session = bob_session.ok_or(anyhow!("bob must get an incoming session"))?;
2443
2444 assert_eq!(
2445 alice_session.config().capabilities,
2446 Capability::Segmentation | Capability::NoRateControl,
2447 );
2448 assert_eq!(
2449 alice_session.config().capabilities,
2450 bob_session.session.config().capabilities
2451 );
2452 assert!(matches!(bob_session.target, SessionTarget::TcpStream(host) if host == target));
2453
2454 tokio::time::sleep(Duration::from_millis(300)).await;
2456
2457 assert!(matches!(
2458 alice_mgr.ping_session(alice_session.id()).await,
2459 Err(TransportSessionError::Manager(SessionManagerError::NonExistingSession))
2460 ));
2461
2462 futures::stream::iter(ahs)
2463 .for_each(|ah| async move { ah.abort() })
2464 .await;
2465
2466 alice_sender.close_channel();
2468 bob_sender.close_channel();
2469 let _ = alice_handle.await;
2470 let _ = bob_handle.await;
2471
2472 Ok(())
2473 }
2474
2475 #[test_log::test(tokio::test)]
2476 async fn session_manager_should_update_surb_balancer_config() -> anyhow::Result<()> {
2477 let alice_pseudonym = HoprPseudonym::random();
2478 let session_id = alice_pseudonym;
2479 let balancer_cfg = SurbBalancerConfig {
2480 target_surb_buffer_size: 1000,
2481 max_surbs_per_sec: 100,
2482 ..Default::default()
2483 };
2484
2485 let alice_mgr =
2486 SessionManager::<UnboundedSender<(DestinationRouting, ApplicationDataOut)>>::new(Default::default());
2487
2488 let (dummy_tx, _) = crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(SESSION_FORWARD_CAPACITY);
2489 alice_mgr.sessions.insert(
2490 session_id,
2491 SessionSlot {
2492 session_tx: dummy_tx,
2493 routing_opts: DestinationRouting::Return(SurbMatcher::Pseudonym(alice_pseudonym)),
2494 abort_handles: Default::default(),
2495 surb_mgmt: Arc::new(BalancerStateValues::from(balancer_cfg)),
2496 surb_estimator: Default::default(),
2497 },
2498 );
2499
2500 let actual_cfg = alice_mgr
2501 .get_surb_balancer_config(&session_id)?
2502 .ok_or(anyhow!("session must have a surb balancer config"))?;
2503 assert_eq!(actual_cfg, balancer_cfg);
2504
2505 let new_cfg = SurbBalancerConfig {
2506 target_surb_buffer_size: 2000,
2507 max_surbs_per_sec: 200,
2508 ..Default::default()
2509 };
2510 alice_mgr.update_surb_balancer_config(&session_id, new_cfg)?;
2511
2512 let actual_cfg = alice_mgr
2513 .get_surb_balancer_config(&session_id)?
2514 .ok_or(anyhow!("session must have a surb balancer config"))?;
2515 assert_eq!(actual_cfg, new_cfg);
2516
2517 Ok(())
2518 }
2519
2520 #[test_log::test(tokio::test)]
2521 async fn session_manager_should_not_allow_loopback_sessions() -> anyhow::Result<()> {
2522 let alice_pseudonym = HoprPseudonym::random();
2523 let bob_peer: Address = (&ChainKeypair::random()).into();
2524
2525 let alice_mgr = SessionManager::new(Default::default());
2526
2527 let mut sequence = mockall::Sequence::new();
2528 let mut alice_transport = MockMsgSender::new();
2529
2530 let alice_mgr_clone = alice_mgr.clone();
2532 alice_transport
2533 .expect_send_message()
2534 .once()
2535 .in_sequence(&mut sequence)
2536 .withf(move |peer, data| {
2537 msg_type(data, StartProtocolDiscriminants::StartSession)
2538 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2539 })
2540 .returning(move |_, data| {
2541 let alice_mgr_clone = alice_mgr_clone.clone();
2543 Box::pin(async move {
2544 alice_mgr_clone
2545 .dispatch_message(
2546 alice_pseudonym,
2547 ApplicationDataIn {
2548 data: data.data,
2549 packet_info: Default::default(),
2550 },
2551 )
2552 ?;
2553 Ok(())
2554 })
2555 });
2556
2557 let alice_mgr_clone = alice_mgr.clone();
2559 alice_transport
2560 .expect_send_message()
2561 .once()
2562 .in_sequence(&mut sequence)
2563 .withf(move |peer, data| {
2564 msg_type(data, StartProtocolDiscriminants::SessionEstablished)
2565 && matches!(peer, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &alice_pseudonym)
2566 })
2567 .returning(move |_, data| {
2568 let alice_mgr_clone = alice_mgr_clone.clone();
2569
2570 Box::pin(async move {
2571 alice_mgr_clone.dispatch_message(
2572 alice_pseudonym,
2573 ApplicationDataIn {
2574 data: data.data,
2575 packet_info: Default::default(),
2576 },
2577 )?;
2578 Ok(())
2579 })
2580 });
2581
2582 let (new_session_tx_alice, new_session_rx_alice) = futures::channel::mpsc::channel(1024);
2584 let (alice_sender, alice_handle) = mock_packet_planning(alice_transport);
2585 alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?;
2586 assert!(alice_mgr.is_started());
2587
2588 let alice_session = alice_mgr
2589 .new_session(
2590 bob_peer,
2591 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2592 SessionClientConfig {
2593 capabilities: None.into(),
2594 pseudonym: alice_pseudonym.into(),
2595 surb_management: None,
2596 ..Default::default()
2597 },
2598 )
2599 .await;
2600
2601 println!("{alice_session:?}");
2602 assert!(matches!(
2603 alice_session,
2604 Err(TransportSessionError::Manager(SessionManagerError::Loopback))
2605 ));
2606
2607 drop(new_session_rx_alice);
2608
2609 alice_sender.close_channel();
2611 let _ = alice_handle.await;
2612
2613 Ok(())
2614 }
2615
2616 #[test_log::test(tokio::test)]
2617 async fn session_manager_should_timeout_new_session_attempt_when_no_response() -> anyhow::Result<()> {
2618 let bob_peer: Address = (&ChainKeypair::random()).into();
2619
2620 let cfg = SessionManagerConfig {
2621 initiation_timeout_base: Duration::from_millis(100),
2622 ..Default::default()
2623 };
2624
2625 let alice_mgr = SessionManager::new(cfg);
2626 let bob_mgr = SessionManager::new(Default::default());
2627
2628 let mut sequence = mockall::Sequence::new();
2629 let mut alice_transport = MockMsgSender::new();
2630 let bob_transport = MockMsgSender::new();
2631
2632 alice_transport
2634 .expect_send_message()
2635 .once()
2636 .in_sequence(&mut sequence)
2637 .withf(move |peer, data| {
2638 msg_type(data, StartProtocolDiscriminants::StartSession)
2639 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2640 })
2641 .returning(|_, _| Box::pin(async { Ok(()) }));
2642
2643 let (new_session_tx_alice, _) = futures::channel::mpsc::channel(1024);
2645 let (alice_sender, _alice_handle) = mock_packet_planning(alice_transport);
2646 alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?;
2647 assert!(alice_mgr.is_started());
2648
2649 let (new_session_tx_bob, _) = futures::channel::mpsc::channel(1024);
2651 let (bob_sender, _bob_handle) = mock_packet_planning(bob_transport);
2652 bob_mgr.start(bob_sender.clone(), new_session_tx_bob)?;
2653 assert!(bob_mgr.is_started());
2654
2655 let result = alice_mgr
2656 .new_session(
2657 bob_peer,
2658 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2659 SessionClientConfig {
2660 capabilities: None.into(),
2661 pseudonym: None,
2662 surb_management: None,
2663 ..Default::default()
2664 },
2665 )
2666 .await;
2667
2668 assert!(matches!(result, Err(TransportSessionError::Timeout)));
2669
2670 Ok(())
2671 }
2672
2673 #[cfg(feature = "telemetry")]
2674 #[test_log::test(tokio::test)]
2675 async fn failed_incoming_session_establishment_does_not_register_telemetry() -> anyhow::Result<()> {
2676 let mgr = SessionManager::new(Default::default());
2677
2678 let transport = MockMsgSender::new();
2679 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
2680 drop(new_session_rx);
2681 let (sender, _handle) = mock_packet_planning(transport);
2682 mgr.start(sender.clone(), new_session_tx)?;
2683 assert!(mgr.is_started());
2684
2685 let pseudonym = HoprPseudonym::random();
2686 let result = mgr
2687 .handle_incoming_session_initiation(
2688 pseudonym,
2689 StartInitiation {
2690 challenge: MIN_CHALLENGE,
2691 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2692 capabilities: ByteCapabilities(Capabilities::empty()),
2693 additional_data: 0,
2694 },
2695 )
2696 .await;
2697
2698 assert!(result.is_err());
2699
2700 assert!(
2703 wait_for_no_active_sessions(&mgr).await,
2704 "the partially established session slot was not rolled back"
2705 );
2706
2707 sender.close_channel();
2709 let _ = _handle.await;
2710
2711 Ok(())
2712 }
2713
2714 #[test_log::test(tokio::test)]
2715 async fn session_manager_should_roll_back_slot_when_incoming_session_setup_fails() -> anyhow::Result<()> {
2716 let mgr = SessionManager::new(Default::default());
2717
2718 let transport = MockMsgSender::new();
2721 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
2722 drop(new_session_rx);
2723 let (sender, handle) = mock_packet_planning(transport);
2724 mgr.start(sender.clone(), new_session_tx)?;
2725 assert!(mgr.is_started());
2726
2727 let pseudonym = HoprPseudonym::random();
2728
2729 let result = mgr
2733 .handle_incoming_session_initiation(
2734 pseudonym,
2735 StartInitiation {
2736 challenge: MIN_CHALLENGE,
2737 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2738 capabilities: ByteCapabilities(Capabilities::empty()),
2739 additional_data: 0,
2740 },
2741 )
2742 .await;
2743 assert!(result.is_err());
2744
2745 assert!(
2748 wait_for_no_active_sessions(&mgr).await,
2749 "the partially established session slot was not rolled back"
2750 );
2751
2752 sender.close_channel();
2754 let _ = handle.await;
2755
2756 Ok(())
2757 }
2758
2759 async fn originated_during(
2761 rx: &mut futures::channel::mpsc::UnboundedReceiver<(DestinationRouting, ApplicationDataOut)>,
2762 window: Duration,
2763 ) -> Vec<(DestinationRouting, ApplicationDataOut)> {
2764 let mut collected = Vec::new();
2765 let deadline = tokio::time::Instant::now() + window;
2766 while let Ok(Some(item)) = timeout(
2767 deadline.saturating_duration_since(tokio::time::Instant::now()),
2768 rx.next(),
2769 )
2770 .await
2771 {
2772 collected.push(item);
2773 }
2774 collected
2775 }
2776
2777 const KEEP_ALIVE_PERIOD: Duration = MIN_SURB_BUFFER_NOTIFICATION_PERIOD;
2780
2781 type RecordingManager = SessionManager<UnboundedSender<(DestinationRouting, ApplicationDataOut)>>;
2782 type Originated = futures::channel::mpsc::UnboundedReceiver<(DestinationRouting, ApplicationDataOut)>;
2783
2784 async fn exit_session_originating_keep_alives(
2790 cfg: SessionManagerConfig,
2791 ) -> anyhow::Result<(RecordingManager, Originated, HoprPseudonym)> {
2792 let mgr = RecordingManager::new(SessionManagerConfig {
2793 surb_balance_notify_period: Some(KEEP_ALIVE_PERIOD),
2794 ..cfg
2795 });
2796
2797 let (msg_tx, mut msg_rx) = futures::channel::mpsc::unbounded();
2798 let (new_session_tx, _new_session_rx) = futures::channel::mpsc::channel(4);
2800 mgr.start(msg_tx, new_session_tx)?;
2801
2802 let pseudonym = HoprPseudonym::random();
2803 mgr.handle_incoming_session_initiation(
2804 pseudonym,
2805 StartInitiation {
2806 challenge: MIN_CHALLENGE,
2807 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2808 capabilities: ByteCapabilities(Capabilities::empty()),
2811 additional_data: 0,
2812 },
2813 )
2814 .await?;
2815
2816 let observed = originated_during(&mut msg_rx, KEEP_ALIVE_PERIOD * 2 + Duration::from_millis(500)).await;
2817 let keep_alives = observed
2818 .iter()
2819 .filter(|(routing, data)| {
2820 msg_type(data, StartProtocolDiscriminants::KeepAlive)
2821 && matches!(routing, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &pseudonym)
2822 })
2823 .count();
2824 anyhow::ensure!(
2825 keep_alives > 0,
2826 "no return-routed keep-alive was originated, so this test cannot tell a stopped stream from one that \
2827 never ran; {} message(s) were observed in total",
2828 observed.len()
2829 );
2830
2831 Ok((mgr, msg_rx, pseudonym))
2832 }
2833
2834 async fn assert_no_further_origination(rx: &mut Originated, closure: &str) {
2839 let _in_flight = originated_during(rx, Duration::from_millis(200)).await;
2840
2841 let window = KEEP_ALIVE_PERIOD * 3;
2842 let after = originated_during(rx, window).await;
2843 assert!(
2844 after.is_empty(),
2845 "an Exit session closed by {closure} originated {} further packet(s) over {window:?} — each one is \
2846 return-routed to a pseudonym whose SURBs are gone, and one such packet is enough to stall all \
2847 origination on the node",
2848 after.len()
2849 );
2850 }
2851
2852 #[test_log::test(tokio::test)]
2865 async fn an_exit_session_should_originate_nothing_after_an_explicit_close() -> anyhow::Result<()> {
2866 let (mgr, mut msg_rx, pseudonym) = exit_session_originating_keep_alives(Default::default()).await?;
2868
2869 assert!(mgr.close_session(&pseudonym), "the session must exist to be closed");
2870
2871 assert_no_further_origination(&mut msg_rx, "an explicit close").await;
2872 Ok(())
2873 }
2874
2875 #[test_log::test(tokio::test)]
2882 async fn an_exit_session_should_originate_nothing_after_idle_eviction() -> anyhow::Result<()> {
2883 let idle_timeout = KEEP_ALIVE_PERIOD * 3;
2884 let (mgr, mut msg_rx, _) = exit_session_originating_keep_alives(SessionManagerConfig {
2885 idle_timeout,
2886 ..Default::default()
2887 })
2888 .await?;
2889
2890 for _ in 0..50 {
2893 mgr.sessions.run_pending_tasks();
2894 if mgr.active_sessions().is_empty() {
2895 break;
2896 }
2897 tokio::time::sleep(idle_timeout / 10).await;
2898 }
2899 assert!(
2900 mgr.active_sessions().is_empty(),
2901 "the idle session was never evicted, so this test cannot say anything about eviction"
2902 );
2903
2904 assert_no_further_origination(&mut msg_rx, "idle eviction").await;
2905 Ok(())
2906 }
2907
2908 #[test_log::test(tokio::test)]
2909 async fn session_manager_should_send_keep_alives_via_surb_balancer() -> anyhow::Result<()> {
2910 let alice_pseudonym = HoprPseudonym::random();
2911 let bob_peer: Address = (&ChainKeypair::random()).into();
2912
2913 let bob_cfg = SessionManagerConfig {
2914 surb_balance_notify_period: Some(Duration::from_millis(500)),
2915 ..Default::default()
2916 };
2917 let alice_mgr = SessionManager::new(Default::default());
2918 let bob_mgr = SessionManager::new(bob_cfg.clone());
2919
2920 let mut alice_transport = MockMsgSender::new();
2921 let mut bob_transport = MockMsgSender::new();
2922
2923 let mut open_sequence = mockall::Sequence::new();
2925 let bob_mgr_clone = bob_mgr.clone();
2926 alice_transport
2927 .expect_send_message()
2928 .once()
2929 .in_sequence(&mut open_sequence)
2930 .withf(move |peer, data| {
2931 msg_type(data, StartProtocolDiscriminants::StartSession)
2932 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2933 })
2934 .returning(move |_, data| {
2935 let bob_mgr_clone = bob_mgr_clone.clone();
2936 Box::pin(async move {
2937 bob_mgr_clone
2938 .dispatch_message(
2939 alice_pseudonym,
2940 ApplicationDataIn {
2941 data: data.data,
2942 packet_info: Default::default(),
2943 },
2944 )
2945 ?;
2946 Ok(())
2947 })
2948 });
2949
2950 let alice_mgr_clone = alice_mgr.clone();
2952 bob_transport
2953 .expect_send_message()
2954 .once()
2955 .in_sequence(&mut open_sequence)
2956 .withf(move |peer, data| {
2957 msg_type(data, StartProtocolDiscriminants::SessionEstablished)
2958 && matches!(peer, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &alice_pseudonym)
2959 })
2960 .returning(move |_, data| {
2961 let alice_mgr_clone = alice_mgr_clone.clone();
2962 Box::pin(async move {
2963 alice_mgr_clone.dispatch_message(
2964 alice_pseudonym,
2965 ApplicationDataIn {
2966 data: data.data,
2967 packet_info: Default::default(),
2968 },
2969 )?;
2970 Ok(())
2971 })
2972 });
2973
2974 const INITIAL_BALANCER_TARGET: u64 = 10;
2975
2976 let bob_mgr_clone = bob_mgr.clone();
2978 alice_transport
2979 .expect_send_message()
2980 .times(5..)
2981 .withf(move |peer, data| {
2983 start_msg_match(data, |msg| matches!(msg, StartProtocol::KeepAlive(ka) if ka.flags.contains(KeepAliveFlag::BalancerTarget) && ka.additional_data == INITIAL_BALANCER_TARGET))
2984 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2986 })
2987 .returning(move |_, data| {
2988 let bob_mgr_clone = bob_mgr_clone.clone();
2989 Box::pin(async move {
2990 bob_mgr_clone
2991 .dispatch_message(
2992 alice_pseudonym,
2993 ApplicationDataIn {
2994 data: data.data,
2995 packet_info: Default::default(),
2996 },
2997 )
2998 ?;
2999 Ok(())
3000 })
3001 });
3002
3003 const NEXT_BALANCER_TARGET: u64 = 50;
3004
3005 let bob_mgr_clone = bob_mgr.clone();
3007 alice_transport
3008 .expect_send_message()
3009 .times(5..)
3010 .withf(move |peer, data| {
3012 start_msg_match(data, |msg| matches!(msg, StartProtocol::KeepAlive(ka) if ka.flags.contains(KeepAliveFlag::BalancerTarget) && ka.additional_data == NEXT_BALANCER_TARGET))
3013 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
3014 })
3015 .returning(move |_, data| {
3016 let bob_mgr_clone = bob_mgr_clone.clone();
3017 Box::pin(async move {
3018 bob_mgr_clone
3019 .dispatch_message(
3020 alice_pseudonym,
3021 ApplicationDataIn {
3022 data: data.data,
3023 packet_info: Default::default(),
3024 },
3025 )
3026 ?;
3027 Ok(())
3028 })
3029 });
3030
3031 let alice_mgr_clone = alice_mgr.clone();
3033 bob_transport
3034 .expect_send_message()
3035 .times(1..)
3036 .withf(move |peer, data| {
3038 start_msg_match(data, |msg| matches!(msg, StartProtocol::KeepAlive(ka) if ka.flags.contains(KeepAliveFlag::BalancerState) && ka.additional_data > 0))
3039 && matches!(peer, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &alice_pseudonym)
3040 })
3041 .returning(move |_, data| {
3042 let alice_mgr_clone = alice_mgr_clone.clone();
3043 Box::pin(async move {
3044 alice_mgr_clone
3045 .dispatch_message(
3046 alice_pseudonym,
3047 ApplicationDataIn {
3048 data: data.data,
3049 packet_info: Default::default(),
3050 },
3051 )
3052 ?;
3053 Ok(())
3054 })
3055 });
3056
3057 let bob_mgr_clone = bob_mgr.clone();
3059 alice_transport
3060 .expect_send_message()
3061 .once()
3062 .withf(move |peer, data| {
3064 hopr_protocol_session::types::SessionMessage::<{ ApplicationData::PAYLOAD_SIZE }>::try_from(
3065 data.data.plain_text.as_ref(),
3066 )
3067 .ok()
3068 .and_then(|m| m.try_as_segment())
3069 .map(|s| s.is_terminating())
3070 .unwrap_or(false)
3071 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
3072 })
3073 .returning(move |_, data| {
3074 let bob_mgr_clone = bob_mgr_clone.clone();
3075 Box::pin(async move {
3076 bob_mgr_clone
3077 .dispatch_message(
3078 alice_pseudonym,
3079 ApplicationDataIn {
3080 data: data.data,
3081 packet_info: Default::default(),
3082 },
3083 )
3084 ?;
3085 Ok(())
3086 })
3087 });
3088
3089 let mut ahs = Vec::new();
3090
3091 let (new_session_tx_alice, _) = futures::channel::mpsc::channel(1024);
3093 let (alice_sender, alice_handle) = mock_packet_planning(alice_transport);
3094 ahs.extend(alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?);
3095 assert!(alice_mgr.is_started());
3096
3097 let (new_session_tx_bob, new_session_rx_bob) = futures::channel::mpsc::channel(1024);
3099 let (bob_sender, bob_handle) = mock_packet_planning(bob_transport);
3100 ahs.extend(bob_mgr.start(bob_sender.clone(), new_session_tx_bob)?);
3101 assert!(bob_mgr.is_started());
3102
3103 let target = SealedHost::Plain("127.0.0.1:80".parse()?);
3104
3105 let balancer_cfg = SurbBalancerConfig {
3106 target_surb_buffer_size: INITIAL_BALANCER_TARGET,
3107 max_surbs_per_sec: 100,
3108 ..Default::default()
3109 };
3110
3111 pin_mut!(new_session_rx_bob);
3112 let (alice_session, bob_session) = timeout(
3113 Duration::from_secs(2),
3114 futures::future::join(
3115 alice_mgr.new_session(
3116 bob_peer,
3117 SessionTarget::TcpStream(target.clone()),
3118 SessionClientConfig {
3119 pseudonym: alice_pseudonym.into(),
3120 capabilities: Capability::Segmentation.into(),
3121 surb_management: Some(balancer_cfg),
3122 ..Default::default()
3123 },
3124 ),
3125 new_session_rx_bob.next(),
3126 ),
3127 )
3128 .await?;
3129
3130 let mut alice_session = alice_session?;
3131 let bob_session = bob_session.ok_or(anyhow!("bob must get an incoming session"))?;
3132
3133 assert!(matches!(bob_session.target, SessionTarget::TcpStream(host) if host == target));
3134
3135 assert_eq!(
3136 Some(balancer_cfg),
3137 alice_mgr.get_surb_balancer_config(alice_session.id())?
3138 );
3139
3140 let remote_cfg = bob_mgr
3141 .get_surb_balancer_config(bob_session.session.id())?
3142 .ok_or(anyhow!("no remote config at bob"))?;
3143 assert_eq!(remote_cfg.target_surb_buffer_size, balancer_cfg.target_surb_buffer_size);
3144 assert_eq!(
3145 remote_cfg.max_surbs_per_sec,
3146 remote_cfg.target_surb_buffer_size
3147 / bob_cfg
3148 .minimum_surb_buffer_duration
3149 .max(MIN_SURB_BUFFER_DURATION)
3150 .as_secs()
3151 );
3152
3153 tokio::time::sleep(Duration::from_millis(1500)).await;
3155
3156 let new_balancer_cfg = SurbBalancerConfig {
3157 target_surb_buffer_size: NEXT_BALANCER_TARGET,
3158 max_surbs_per_sec: 100,
3159 ..Default::default()
3160 };
3161
3162 alice_mgr.update_surb_balancer_config(alice_session.id(), new_balancer_cfg)?;
3164
3165 tokio::time::sleep(Duration::from_millis(1500)).await;
3167
3168 let remote_cfg = bob_mgr
3170 .get_surb_balancer_config(bob_session.session.id())?
3171 .ok_or(anyhow!("no remote config at bob"))?;
3172 assert_eq!(
3173 remote_cfg.target_surb_buffer_size,
3174 new_balancer_cfg.target_surb_buffer_size
3175 );
3176 assert_eq!(
3177 remote_cfg.max_surbs_per_sec,
3178 new_balancer_cfg.target_surb_buffer_size / bob_cfg.minimum_surb_buffer_duration.as_secs()
3179 );
3180
3181 let (alice_surb_sent, alice_surb_used) = alice_mgr.get_surb_level_estimates(alice_session.id())?;
3182 let (bob_surb_recv, bob_surb_used) = bob_mgr.get_surb_level_estimates(bob_session.session.id())?;
3183
3184 alice_session.close().await?;
3185
3186 assert!(alice_surb_sent > 0, "alice must've sent surbs");
3187 assert!(bob_surb_recv > 0, "bob must've received surbs");
3188 assert!(
3189 bob_surb_recv <= alice_surb_sent,
3190 "bob cannot receive more surbs than alice sent"
3191 );
3192
3193 assert!(alice_surb_used > 0, "alice must see bob used surbs");
3194 assert!(bob_surb_used > 0, "bob must've used surbs");
3195 assert!(
3196 alice_surb_used <= bob_surb_used,
3197 "alice cannot see bob used more surbs than bob actually used"
3198 );
3199
3200 tokio::time::sleep(Duration::from_millis(300)).await;
3201 assert!(matches!(
3202 alice_mgr.ping_session(alice_session.id()).await,
3203 Err(TransportSessionError::Manager(SessionManagerError::NonExistingSession))
3204 ));
3205
3206 futures::stream::iter(ahs)
3207 .for_each(|ah| async move { ah.abort() })
3208 .await;
3209
3210 alice_sender.close_channel();
3212 bob_sender.close_channel();
3213 let _ = alice_handle.await;
3214 let _ = bob_handle.await;
3215
3216 Ok(())
3217 }
3218
3219 #[test_log::test(tokio::test)]
3220 async fn session_manager_should_supersede_stale_session_on_reinitiation_with_same_pseudonym() -> anyhow::Result<()>
3221 {
3222 use hopr_utils::network_types::prelude::SealedHost;
3223
3224 let bob_mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3225 SessionManager::new(Default::default());
3226
3227 let mut transport = MockMsgSender::new();
3229 transport
3230 .expect_send_message()
3231 .times(2)
3232 .returning(|_, _| futures::future::ok(()).boxed());
3233
3234 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3235 let _notifications = tokio::spawn(async move {
3237 pin_mut!(new_session_rx);
3238 while let Some(_session) = new_session_rx.next().await {
3239 }
3241 });
3242 let (sender, _handle) = mock_packet_planning(transport);
3243 bob_mgr.start(sender.clone(), new_session_tx)?;
3244 assert!(bob_mgr.is_started());
3245
3246 let pseudonym = HoprPseudonym::random();
3247
3248 let result = bob_mgr
3250 .handle_incoming_session_initiation(
3251 pseudonym,
3252 StartInitiation {
3253 challenge: MIN_CHALLENGE,
3254 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3255 capabilities: ByteCapabilities(Capabilities::empty()),
3256 additional_data: 0,
3257 },
3258 )
3259 .await;
3260
3261 assert!(result.is_ok(), "first session initiation should succeed");
3262
3263 let active = bob_mgr.active_sessions();
3265 assert_eq!(active.len(), 1, "should have exactly one active session");
3266
3267 let result = bob_mgr
3271 .handle_incoming_session_initiation(
3272 pseudonym,
3273 StartInitiation {
3274 challenge: MIN_CHALLENGE + 1,
3275 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3276 capabilities: ByteCapabilities(Capabilities::empty()),
3277 additional_data: 0,
3278 },
3279 )
3280 .await;
3281
3282 assert!(result.is_ok(), "re-initiation should supersede the stale session");
3283
3284 let active = bob_mgr.active_sessions();
3286 assert_eq!(active.len(), 1, "should still have exactly one active session");
3287
3288 sender.close_channel();
3290 let _ = _handle.await;
3291
3292 Ok(())
3293 }
3294
3295 #[test_log::test(tokio::test)]
3296 async fn session_manager_should_return_error_when_pinging_non_existent_session() -> anyhow::Result<()> {
3297 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3298 SessionManager::new(Default::default());
3299
3300 let transport = MockMsgSender::new();
3301 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3302 let _notifications = tokio::spawn(async move {
3303 pin_mut!(new_session_rx);
3304 while let Some(_session) = new_session_rx.next().await {}
3305 });
3306 let (sender, _handle) = mock_packet_planning(transport);
3307 mgr.start(sender.clone(), new_session_tx)?;
3308 assert!(mgr.is_started());
3309
3310 let fake_session_id = HoprPseudonym::random();
3311 let result = mgr.ping_session(&fake_session_id).await;
3312
3313 assert!(result.is_err());
3314 assert!(matches!(
3315 result.unwrap_err(),
3316 TransportSessionError::Manager(SessionManagerError::NonExistingSession)
3317 ));
3318
3319 sender.close_channel();
3321 let _ = _handle.await;
3322
3323 Ok(())
3324 }
3325
3326 #[test_log::test(tokio::test)]
3327 async fn session_manager_should_return_false_when_closing_non_existent_session() -> anyhow::Result<()> {
3328 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3329 SessionManager::new(Default::default());
3330
3331 let transport = MockMsgSender::new();
3332 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3333 let _notifications = tokio::spawn(async move {
3334 pin_mut!(new_session_rx);
3335 while let Some(_session) = new_session_rx.next().await {}
3336 });
3337 let (sender, _handle) = mock_packet_planning(transport);
3338 mgr.start(sender.clone(), new_session_tx)?;
3339 assert!(mgr.is_started());
3340
3341 let fake_session_id = HoprPseudonym::random();
3342 let result = mgr.close_session(&fake_session_id);
3343
3344 assert!(!result, "closing non-existent session should return false");
3345
3346 Ok(())
3347 }
3348
3349 #[test_log::test(tokio::test)]
3350 async fn session_manager_should_return_error_when_updating_surb_config_for_non_existent_session()
3351 -> anyhow::Result<()> {
3352 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3353 SessionManager::new(Default::default());
3354
3355 let transport = MockMsgSender::new();
3356 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3357 let _notifications = tokio::spawn(async move {
3358 pin_mut!(new_session_rx);
3359 while let Some(_session) = new_session_rx.next().await {}
3360 });
3361 let (sender, _handle) = mock_packet_planning(transport);
3362 mgr.start(sender.clone(), new_session_tx)?;
3363 assert!(mgr.is_started());
3364
3365 let fake_session_id = HoprPseudonym::random();
3366 let result = mgr.update_surb_balancer_config(&fake_session_id, SurbBalancerConfig::default());
3367
3368 assert!(result.is_err());
3369
3370 sender.close_channel();
3372 let _ = _handle.await;
3373
3374 Ok(())
3375 }
3376
3377 #[test_log::test(tokio::test)]
3378 async fn session_manager_should_return_error_when_getting_surb_config_for_non_existent_session()
3379 -> anyhow::Result<()> {
3380 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3381 SessionManager::new(Default::default());
3382
3383 let transport = MockMsgSender::new();
3384 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3385 let _notifications = tokio::spawn(async move {
3386 pin_mut!(new_session_rx);
3387 while let Some(_session) = new_session_rx.next().await {}
3388 });
3389 let (sender, _handle) = mock_packet_planning(transport);
3390 mgr.start(sender.clone(), new_session_tx)?;
3391 assert!(mgr.is_started());
3392
3393 let fake_session_id = HoprPseudonym::random();
3394 let result = mgr.get_surb_balancer_config(&fake_session_id);
3395
3396 assert!(result.is_err());
3397 assert!(matches!(
3398 result.unwrap_err(),
3399 TransportSessionError::Manager(SessionManagerError::NonExistingSession)
3400 ));
3401
3402 sender.close_channel();
3404 let _ = _handle.await;
3405
3406 Ok(())
3407 }
3408
3409 #[test_log::test(tokio::test)]
3410 async fn session_manager_should_return_error_when_getting_surb_estimates_for_non_existent_session()
3411 -> anyhow::Result<()> {
3412 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3413 SessionManager::new(Default::default());
3414
3415 let transport = MockMsgSender::new();
3416 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3417 let _notifications = tokio::spawn(async move {
3418 pin_mut!(new_session_rx);
3419 while let Some(_session) = new_session_rx.next().await {}
3420 });
3421 let (sender, _handle) = mock_packet_planning(transport);
3422 mgr.start(sender.clone(), new_session_tx)?;
3423 assert!(mgr.is_started());
3424
3425 let fake_session_id = HoprPseudonym::random();
3426 let result = mgr.get_surb_level_estimates(&fake_session_id);
3427
3428 assert!(result.is_err());
3429 assert!(matches!(
3430 result.unwrap_err(),
3431 TransportSessionError::Manager(SessionManagerError::NonExistingSession)
3432 ));
3433
3434 sender.close_channel();
3436 let _ = _handle.await;
3437
3438 Ok(())
3439 }
3440
3441 #[test_log::test(tokio::test)]
3448 async fn handle_session_error_propagates_peer_rejection_to_pending_new_session() -> anyhow::Result<()> {
3449 let mgr: SessionManager<UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3450 SessionManager::new(Default::default());
3451
3452 let mut transport = MockMsgSender::new();
3453 transport
3456 .expect_send_message()
3457 .returning(|_, _| futures::future::ok(()).boxed());
3458
3459 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3460 let _notifications = tokio::spawn(async move {
3461 pin_mut!(new_session_rx);
3462 while let Some(_session) = new_session_rx.next().await {}
3463 });
3464 let (sender, _handle) = mock_packet_planning(transport);
3465 mgr.start(sender.clone(), new_session_tx)?;
3466 assert!(mgr.is_started());
3467
3468 let mgr_clone = mgr.clone();
3470 let peer_address: Address = (&ChainKeypair::random()).into();
3471 let handle = tokio::spawn(async move {
3472 mgr_clone
3473 .new_session(
3474 peer_address,
3475 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3476 SessionClientConfig {
3477 surb_management: None,
3478 ..Default::default()
3479 },
3480 )
3481 .await
3482 });
3483
3484 let challenge = tokio::time::timeout(Duration::from_secs(1), async {
3486 loop {
3487 if let Some((ch, _)) = mgr.session_initiations.iter().next() {
3488 break *ch;
3489 }
3490 tokio::time::sleep(Duration::from_millis(10)).await;
3491 }
3492 })
3493 .await
3494 .context("new_session did not insert a challenge into session_initiations")?;
3495
3496 let error_type = StartErrorType {
3498 challenge,
3499 reason: StartErrorReason::NoSlotsAvailable,
3500 };
3501 mgr.handle_session_error(error_type).await?;
3502
3503 let result = handle.await?;
3505 match result {
3506 Ok(_session) => panic!("expected rejection error, got session"),
3507 Err(e) => {
3508 assert!(matches!(
3509 e,
3510 TransportSessionError::Rejected(StartErrorReason::NoSlotsAvailable)
3511 ));
3512 }
3513 }
3514
3515 sender.close_channel();
3516 let _ = _handle.await;
3517 Ok(())
3518 }
3519
3520 #[test_log::test(tokio::test)]
3521 async fn session_manager_should_reject_new_session_when_max_sessions_reached() -> anyhow::Result<()> {
3522 use hopr_utils::network_types::prelude::SealedHost;
3523
3524 let cfg = SessionManagerConfig {
3526 maximum_sessions: 1,
3527 ..Default::default()
3528 };
3529 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3530 SessionManager::new(cfg);
3531
3532 let mut transport = MockMsgSender::new();
3533 transport
3534 .expect_send_message()
3535 .times(2)
3536 .returning(|_, _| futures::future::ok(()).boxed());
3537
3538 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3539 let _notifications = tokio::spawn(async move {
3540 pin_mut!(new_session_rx);
3541 while let Some(_session) = new_session_rx.next().await {}
3542 });
3543 let (sender, _handle) = mock_packet_planning(transport);
3544 mgr.start(sender.clone(), new_session_tx)?;
3545 assert!(mgr.is_started());
3546
3547 let pseudonym1 = HoprPseudonym::random();
3549 mgr.handle_incoming_session_initiation(
3550 pseudonym1,
3551 StartInitiation {
3552 challenge: MIN_CHALLENGE,
3553 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3554 capabilities: ByteCapabilities(Capabilities::empty()),
3555 additional_data: 0,
3556 },
3557 )
3558 .await?;
3559
3560 assert_eq!(mgr.active_sessions().len(), 1);
3562
3563 let pseudonym2 = HoprPseudonym::random();
3565 let _result = mgr
3566 .handle_incoming_session_initiation(
3567 pseudonym2,
3568 StartInitiation {
3569 challenge: MIN_CHALLENGE,
3570 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3571 capabilities: ByteCapabilities(Capabilities::empty()),
3572 additional_data: 0,
3573 },
3574 )
3575 .await;
3576
3577 assert_eq!(mgr.active_sessions().len(), 1);
3580
3581 sender.close_channel();
3583 let _ = _handle.await;
3584
3585 Ok(())
3586 }
3587
3588 #[test_log::test(tokio::test)]
3594 async fn new_session_returns_too_many_sessions_when_cache_is_full() -> anyhow::Result<()> {
3595 use hopr_utils::network_types::prelude::SealedHost;
3596
3597 let cfg = SessionManagerConfig {
3598 maximum_sessions: 2,
3599 idle_timeout: Duration::from_secs(3600),
3600 ..Default::default()
3601 };
3602 let mgr: SessionManager<UnboundedSender<(DestinationRouting, ApplicationDataOut)>> = SessionManager::new(cfg);
3603
3604 let mut transport = MockMsgSender::new();
3605 transport
3607 .expect_send_message()
3608 .times(2)
3609 .returning(|_, _| futures::future::ok(()).boxed());
3610
3611 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3612 let _notifications = tokio::spawn(async move {
3613 pin_mut!(new_session_rx);
3614 while let Some(_session) = new_session_rx.next().await {}
3615 });
3616 let (sender, _handle) = mock_packet_planning(transport);
3617 mgr.start(sender.clone(), new_session_tx)?;
3618 assert!(mgr.is_started());
3619
3620 for i in 0..2 {
3622 let pseudonym = HoprPseudonym::random();
3623 mgr.handle_incoming_session_initiation(
3624 pseudonym,
3625 StartInitiation {
3626 challenge: MIN_CHALLENGE + i as u64,
3627 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3628 capabilities: ByteCapabilities(Capabilities::empty()),
3629 additional_data: 0,
3630 },
3631 )
3632 .await?;
3633 }
3634 assert_eq!(mgr.active_sessions().len(), 2);
3635
3636 let result = mgr
3638 .new_session(
3639 Address::from(&ChainKeypair::random()),
3640 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3641 SessionClientConfig {
3642 surb_management: None,
3643 ..Default::default()
3644 },
3645 )
3646 .await;
3647
3648 assert!(result.is_err());
3649 assert!(matches!(
3650 result.unwrap_err(),
3651 TransportSessionError::Manager(SessionManagerError::TooManySessions)
3652 ));
3653
3654 sender.close_channel();
3655 let _ = _handle.await;
3656 Ok(())
3657 }
3658
3659 #[test_log::test(tokio::test)]
3662 async fn new_session_removes_challenge_on_send_failure() -> anyhow::Result<()> {
3663 let mgr: SessionManager<UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3664 SessionManager::new(Default::default());
3665
3666 let (tx, rx) = futures::channel::mpsc::unbounded();
3671 drop(rx);
3672
3673 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3674 let _notifications = tokio::spawn(async move {
3675 pin_mut!(new_session_rx);
3676 while let Some(_session) = new_session_rx.next().await {}
3677 });
3678 mgr.start(tx, new_session_tx)?;
3679 assert!(mgr.is_started());
3680
3681 let result = mgr
3683 .new_session(
3684 Address::from(&ChainKeypair::random()),
3685 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3686 SessionClientConfig {
3687 surb_management: None,
3688 ..Default::default()
3689 },
3690 )
3691 .await;
3692
3693 assert!(result.is_err());
3694 assert_eq!(
3697 mgr.session_initiations.entry_count(),
3698 0,
3699 "session_initiations was not cleaned up after send failure"
3700 );
3701
3702 Ok(())
3703 }
3704
3705 #[test_log::test(tokio::test)]
3709 async fn new_session_removes_challenge_on_timeout() -> anyhow::Result<()> {
3710 let cfg = SessionManagerConfig {
3711 initiation_timeout_base: Duration::from_millis(100),
3712 ..Default::default()
3713 };
3714
3715 let alice_mgr = SessionManager::new(cfg);
3716 let bob_mgr = SessionManager::new(Default::default());
3717
3718 let bob_peer: Address = (&ChainKeypair::random()).into();
3719
3720 let mut alice_transport = MockMsgSender::new();
3721 let bob_transport = MockMsgSender::new();
3722
3723 alice_transport
3725 .expect_send_message()
3726 .once()
3727 .returning(|_, _| futures::future::ok(()).boxed());
3728
3729 let (alice_sender, _alice_handle) = mock_packet_planning(alice_transport);
3730 let (new_session_tx_alice, _) = futures::channel::mpsc::channel(1024);
3731 alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?;
3732 assert!(alice_mgr.is_started());
3733
3734 let (bob_sender, _bob_handle) = mock_packet_planning(bob_transport);
3735 let (new_session_tx_bob, _) = futures::channel::mpsc::channel(1024);
3736 bob_mgr.start(bob_sender.clone(), new_session_tx_bob)?;
3737 assert!(bob_mgr.is_started());
3738
3739 assert_eq!(alice_mgr.session_initiations.entry_count(), 0);
3741
3742 let result = alice_mgr
3743 .new_session(
3744 bob_peer,
3745 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3746 SessionClientConfig {
3747 capabilities: None.into(),
3748 pseudonym: None,
3749 surb_management: None,
3750 ..Default::default()
3751 },
3752 )
3753 .await;
3754
3755 assert!(matches!(result, Err(TransportSessionError::Timeout)));
3756 assert_eq!(
3759 alice_mgr.session_initiations.entry_count(),
3760 0,
3761 "session_initiations was not cleaned up after timeout"
3762 );
3763
3764 Ok(())
3765 }
3766
3767 #[cfg(test)]
3769 fn session_data_packet(payload: &[u8]) -> anyhow::Result<ApplicationDataIn> {
3770 Ok(ApplicationDataIn {
3771 data: ApplicationData::new(SESSION_APPLICATION_TAG, payload)?,
3772 packet_info: Default::default(),
3773 })
3774 }
3775
3776 #[cfg(test)]
3779 type TestManager =
3780 SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>>;
3781
3782 #[test_log::test(tokio::test)]
3787 async fn dispatching_to_an_unregistered_session_should_be_a_quiet_counted_drop() -> anyhow::Result<()> {
3788 let mgr: TestManager = SessionManager::new(Default::default());
3789
3790 let pseudonym = HoprPseudonym::random();
3791 let before = crate::counters::session_unknown_data_drop_count();
3792
3793 const N: usize = 5;
3795 for _ in 0..N {
3796 let result = mgr.dispatch_message(pseudonym, session_data_packet(b"test data")?);
3797 assert!(
3798 matches!(result, Ok(DispatchResult::Dropped(DropReason::Unregistered))),
3799 "unregistered-session packet must be a benign Dropped(Unregistered), got {result:?}"
3800 );
3801 }
3802
3803 assert!(crate::counters::session_unknown_data_drop_count() >= before + N);
3806
3807 Ok(())
3808 }
3809
3810 #[test_log::test(tokio::test)]
3816 async fn dispatching_to_a_session_whose_sink_closed_should_be_a_quiet_counted_drop() -> anyhow::Result<()> {
3817 let mgr: TestManager = SessionManager::new(Default::default());
3818
3819 let pseudonym = HoprPseudonym::random();
3822 mgr.pre_populate_session(pseudonym, DestinationRouting::Return(pseudonym.into()));
3823
3824 let before = crate::counters::session_inbox_closed_drop_count();
3825 let result = mgr.dispatch_message(pseudonym, session_data_packet(b"after teardown")?);
3826
3827 assert!(
3828 matches!(result, Ok(DispatchResult::Dropped(DropReason::SinkClosed))),
3829 "closed-sink packet must be a benign Dropped(SinkClosed), not an error or SinkFull, got {result:?}"
3830 );
3831 assert!(crate::counters::session_inbox_closed_drop_count() > before);
3832
3833 Ok(())
3834 }
3835
3836 #[test_log::test(tokio::test)]
3839 async fn dispatching_to_a_full_session_inbox_should_be_a_backpressure_drop() -> anyhow::Result<()> {
3840 let cfg = SessionManagerConfig {
3842 session_forward_capacity: 1,
3843 ..Default::default()
3844 };
3845 let mgr: TestManager = SessionManager::new(cfg);
3846
3847 let pseudonym = HoprPseudonym::random();
3849 let _rx = mgr.pre_populate_session_with_receiver(pseudonym, DestinationRouting::Return(pseudonym.into()));
3850
3851 let accepted = mgr.dispatch_message(pseudonym, session_data_packet(b"fills the slot")?);
3853 assert!(
3854 matches!(accepted, Ok(DispatchResult::Processed)),
3855 "first packet should be accepted, got {accepted:?}"
3856 );
3857
3858 let before = crate::counters::session_inbox_drop_count();
3860 let overflow = mgr.dispatch_message(pseudonym, session_data_packet(b"overflows")?);
3861 assert!(
3862 matches!(overflow, Ok(DispatchResult::Dropped(DropReason::SinkFull))),
3863 "overflow packet must be a Dropped(SinkFull) backpressure drop, got {overflow:?}"
3864 );
3865 assert!(crate::counters::session_inbox_drop_count() > before);
3866
3867 Ok(())
3868 }
3869
3870 #[test_log::test(tokio::test)]
3871 async fn session_manager_should_return_true_when_closing_existing_session() -> anyhow::Result<()> {
3872 use hopr_utils::network_types::prelude::SealedHost;
3873
3874 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3875 SessionManager::new(Default::default());
3876
3877 let mut transport = MockMsgSender::new();
3878 transport
3879 .expect_send_message()
3880 .once()
3881 .returning(|_, _| futures::future::ok(()).boxed());
3882
3883 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3884 let _notifications = tokio::spawn(async move {
3885 pin_mut!(new_session_rx);
3886 while let Some(_session) = new_session_rx.next().await {}
3887 });
3888 let (sender, _handle) = mock_packet_planning(transport);
3889 mgr.start(sender.clone(), new_session_tx)?;
3890 assert!(mgr.is_started());
3891
3892 let pseudonym = HoprPseudonym::random();
3894 mgr.handle_incoming_session_initiation(
3895 pseudonym,
3896 StartInitiation {
3897 challenge: MIN_CHALLENGE,
3898 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3899 capabilities: ByteCapabilities(Capabilities::empty()),
3900 additional_data: 0,
3901 },
3902 )
3903 .await?;
3904
3905 assert_eq!(mgr.active_sessions().len(), 1);
3907
3908 let result = mgr.close_session(&pseudonym);
3910 assert!(result, "closing existing session should return true");
3911
3912 assert_eq!(mgr.active_sessions().len(), 0);
3914
3915 sender.close_channel();
3917 let _ = _handle.await;
3918
3919 Ok(())
3920 }
3921
3922 #[test_log::test(tokio::test)]
3923 async fn session_manager_should_update_buffer_level_on_keep_alive_with_balancer_state_flag() -> anyhow::Result<()> {
3924 use std::sync::atomic::Ordering;
3925
3926 let alice_pseudonym = HoprPseudonym::random();
3927 let session_id = alice_pseudonym;
3928 let initial_buffer_level = 100u64;
3929 let new_buffer_level = 200u64;
3930
3931 let balancer_cfg = SurbBalancerConfig {
3932 target_surb_buffer_size: 1000,
3933 max_surbs_per_sec: 100,
3934 ..Default::default()
3935 };
3936
3937 let alice_mgr =
3938 SessionManager::<UnboundedSender<(DestinationRouting, ApplicationDataOut)>>::new(Default::default());
3939
3940 let (new_session_tx, _) = futures::channel::mpsc::channel(1024);
3941 let (mock_sender, _) = futures::channel::mpsc::unbounded();
3942 let _ahs = alice_mgr.start(mock_sender, new_session_tx)?;
3943 assert!(alice_mgr.is_started());
3944
3945 let (dummy_tx, _) = crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(SESSION_FORWARD_CAPACITY);
3946 let peer_address: Address = (&ChainKeypair::random()).into();
3947 alice_mgr.sessions.insert(
3948 session_id,
3949 SessionSlot {
3950 session_tx: dummy_tx,
3951 routing_opts: DestinationRouting::Forward {
3952 destination: Box::new(peer_address.into()),
3953 pseudonym: Some(alice_pseudonym),
3954 forward_options: RoutingOptions::Hops(hopr_api::types::primitive::bounded::BoundedSize::MIN),
3955 return_options: RoutingOptions::Hops(hopr_api::types::primitive::bounded::BoundedSize::MIN).into(),
3956 },
3957 abort_handles: Default::default(),
3958 surb_mgmt: Arc::new(BalancerStateValues::from(balancer_cfg)),
3959 surb_estimator: Default::default(),
3960 },
3961 );
3962
3963 let session_slot = alice_mgr.sessions.get(&session_id).unwrap();
3965 session_slot
3966 .surb_mgmt
3967 .buffer_level
3968 .store(initial_buffer_level, Ordering::Relaxed);
3969 drop(session_slot);
3970
3971 let session_slot = alice_mgr.sessions.get(&session_id).unwrap();
3973 assert_eq!(session_slot.surb_mgmt.buffer_level(), initial_buffer_level);
3974 drop(session_slot);
3975
3976 let ka = KeepAliveMessage::<SessionId> {
3978 session_id,
3979 flags: KeepAliveFlag::BalancerState.into(),
3980 additional_data: new_buffer_level,
3981 };
3982 let app_data: ApplicationData = HoprStartProtocol::KeepAlive(ka).try_into()?;
3983 let app_data_in = ApplicationDataIn {
3984 data: app_data,
3985 packet_info: Default::default(),
3986 };
3987
3988 alice_mgr.dispatch_message(alice_pseudonym, app_data_in)?;
3990
3991 tokio::time::timeout(Duration::from_secs(1), async {
3993 loop {
3994 if let Some(slot) = alice_mgr.sessions.get(&session_id)
3995 && slot.surb_mgmt.buffer_level() == new_buffer_level
3996 {
3997 break;
3998 }
3999 tokio::time::sleep(Duration::from_millis(10)).await;
4000 }
4001 })
4002 .await
4003 .context("keep-alive BalancerState update timed out")?;
4004
4005 let session_slot = alice_mgr.sessions.get(&session_id).unwrap();
4007 assert_eq!(
4008 session_slot.surb_mgmt.buffer_level(),
4009 new_buffer_level,
4010 "buffer level should be updated via keep-alive with BalancerState flag"
4011 );
4012
4013 Ok(())
4014 }
4015
4016 #[test_log::test(tokio::test)]
4017 async fn session_manager_should_update_target_on_keep_alive_with_balancer_target_flag() -> anyhow::Result<()> {
4018 use std::sync::atomic::Ordering;
4019
4020 let alice_pseudonym = HoprPseudonym::random();
4021 let session_id = alice_pseudonym;
4022 let initial_target = 1000u64;
4023 let new_target = 2000u64;
4024
4025 let balancer_cfg = SurbBalancerConfig {
4026 target_surb_buffer_size: initial_target,
4027 max_surbs_per_sec: 100,
4028 ..Default::default()
4029 };
4030
4031 let alice_mgr =
4032 SessionManager::<UnboundedSender<(DestinationRouting, ApplicationDataOut)>>::new(Default::default());
4033
4034 let (new_session_tx, _) = futures::channel::mpsc::channel(1024);
4035 let (mock_sender, _) = futures::channel::mpsc::unbounded();
4036 let _ahs = alice_mgr.start(mock_sender, new_session_tx)?;
4037 assert!(alice_mgr.is_started());
4038
4039 let (dummy_tx, _) = crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(SESSION_FORWARD_CAPACITY);
4040 alice_mgr.sessions.insert(
4041 session_id,
4042 SessionSlot {
4043 session_tx: dummy_tx,
4044 routing_opts: DestinationRouting::Return(SurbMatcher::Pseudonym(alice_pseudonym)),
4045 abort_handles: Default::default(),
4046 surb_mgmt: Arc::new(BalancerStateValues::from(balancer_cfg)),
4047 surb_estimator: Default::default(),
4048 },
4049 );
4050
4051 let session_slot = alice_mgr.sessions.get(&session_id).unwrap();
4053 assert_eq!(
4054 session_slot.surb_mgmt.controller_bounds().target(),
4055 initial_target,
4056 "initial target should be set"
4057 );
4058 drop(session_slot);
4059
4060 let ka = KeepAliveMessage::<SessionId> {
4062 session_id,
4063 flags: KeepAliveFlag::BalancerTarget.into(),
4064 additional_data: new_target,
4065 };
4066 let app_data: ApplicationData = HoprStartProtocol::KeepAlive(ka).try_into()?;
4067 let app_data_in = ApplicationDataIn {
4068 data: app_data,
4069 packet_info: Default::default(),
4070 };
4071
4072 alice_mgr.dispatch_message(alice_pseudonym, app_data_in)?;
4074
4075 tokio::time::timeout(Duration::from_secs(1), async {
4077 loop {
4078 if let Some(slot) = alice_mgr.sessions.get(&session_id)
4079 && slot.surb_mgmt.target_surb_buffer_size.load(Ordering::Relaxed) == new_target
4080 {
4081 break;
4082 }
4083 tokio::time::sleep(Duration::from_millis(10)).await;
4084 }
4085 })
4086 .await
4087 .context("keep-alive BalancerTarget update timed out")?;
4088
4089 let session_slot = alice_mgr.sessions.get(&session_id).unwrap();
4091 assert_eq!(
4092 session_slot.surb_mgmt.target_surb_buffer_size.load(Ordering::Relaxed),
4093 new_target,
4094 "target buffer size should be updated via keep-alive with BalancerTarget flag"
4095 );
4096
4097 Ok(())
4098 }
4099
4100 #[test_log::test(tokio::test)]
4101 async fn session_manager_should_evict_idle_session_and_call_close_callback() -> anyhow::Result<()> {
4102 use hopr_utils::network_types::prelude::SealedHost;
4103
4104 let cfg = SessionManagerConfig {
4105 maximum_sessions: 1,
4106 idle_timeout: Duration::from_millis(100),
4107 ..Default::default()
4108 };
4109 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
4110 SessionManager::new(cfg);
4111
4112 let mut transport = MockMsgSender::new();
4113 transport
4114 .expect_send_message()
4115 .times(1)
4116 .returning(|_, _| futures::future::ok(()).boxed());
4117
4118 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
4119 let _notifications = tokio::spawn(async move {
4120 pin_mut!(new_session_rx);
4121 while let Some(_session) = new_session_rx.next().await {}
4122 });
4123 let (sender, _handle) = mock_packet_planning(transport);
4124 mgr.start(sender.clone(), new_session_tx)?;
4125 assert!(mgr.is_started());
4126
4127 let pseudonym1 = HoprPseudonym::random();
4129 mgr.handle_incoming_session_initiation(
4130 pseudonym1,
4131 StartInitiation {
4132 challenge: MIN_CHALLENGE,
4133 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
4134 capabilities: ByteCapabilities(Capabilities::empty()),
4135 additional_data: 0,
4136 },
4137 )
4138 .await?;
4139
4140 assert_eq!(mgr.active_sessions().len(), 1);
4142
4143 tokio::time::sleep(Duration::from_millis(200)).await;
4145 mgr.sessions.run_pending_tasks();
4146
4147 assert_eq!(
4149 mgr.active_sessions().len(),
4150 0,
4151 "idle session should be evicted after timeout"
4152 );
4153
4154 Ok(())
4155 }
4156
4157 #[test_log::test(tokio::test)]
4158 async fn session_manager_should_reject_new_session_when_max_sessions_reached_no_eviction() -> anyhow::Result<()> {
4159 use hopr_utils::network_types::prelude::SealedHost;
4160
4161 let cfg = SessionManagerConfig {
4163 maximum_sessions: 1,
4164 idle_timeout: Duration::from_secs(3600), ..Default::default()
4166 };
4167 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
4168 SessionManager::new(cfg);
4169
4170 let mut transport = MockMsgSender::new();
4171 transport
4172 .expect_send_message()
4173 .times(2)
4174 .returning(|_, _| futures::future::ok(()).boxed());
4175
4176 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
4177 let _notifications = tokio::spawn(async move {
4178 pin_mut!(new_session_rx);
4179 while let Some(_session) = new_session_rx.next().await {}
4180 });
4181 let (sender, _handle) = mock_packet_planning(transport);
4182 mgr.start(sender.clone(), new_session_tx)?;
4183 assert!(mgr.is_started());
4184
4185 let pseudonym1 = HoprPseudonym::random();
4187 mgr.handle_incoming_session_initiation(
4188 pseudonym1,
4189 StartInitiation {
4190 challenge: MIN_CHALLENGE,
4191 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
4192 capabilities: ByteCapabilities(Capabilities::empty()),
4193 additional_data: 0,
4194 },
4195 )
4196 .await?;
4197
4198 assert_eq!(mgr.active_sessions().len(), 1);
4200
4201 let pseudonym2 = HoprPseudonym::random();
4203 let _result = mgr
4204 .handle_incoming_session_initiation(
4205 pseudonym2,
4206 StartInitiation {
4207 challenge: MIN_CHALLENGE,
4208 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
4209 capabilities: ByteCapabilities(Capabilities::empty()),
4210 additional_data: 0,
4211 },
4212 )
4213 .await;
4214
4215 assert_eq!(
4217 mgr.active_sessions().len(),
4218 1,
4219 "should still have exactly one session - second session should be rejected"
4220 );
4221
4222 assert!(
4224 mgr.active_sessions().contains(&pseudonym1),
4225 "the first session should still be active"
4226 );
4227
4228 sender.close_channel();
4230 let _ = _handle.await;
4231
4232 Ok(())
4233 }
4234}