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, Debug, PartialEq, Eq)]
222pub enum DispatchResult {
223 Processed,
225 Unrelated(ApplicationDataIn),
227}
228
229#[derive(Clone, Debug, PartialEq, smart_default::SmartDefault)]
231pub struct SessionManagerConfig {
232 #[default(1500)]
236 pub frame_mtu: usize,
237
238 #[default(Duration::from_millis(800))]
242 pub max_frame_timeout: Duration,
243
244 #[default(0)]
249 pub max_buffered_segments: usize,
250
251 #[default(Duration::from_millis(500))]
258 pub initiation_timeout_base: Duration,
259
260 #[default(Duration::from_secs(180))]
264 pub idle_timeout: Duration,
265
266 #[default(Duration::from_millis(100))]
271 pub balancer_sampling_interval: Duration,
272
273 #[default(10)]
279 pub initial_return_session_egress_rate: usize,
280
281 #[default(Duration::from_secs(5))]
292 pub minimum_surb_buffer_duration: Duration,
293
294 #[default(10_000)]
302 pub maximum_surb_buffer_size: usize,
303
304 #[default(Some(Duration::from_secs(60)))]
317 pub surb_balance_notify_period: Option<Duration>,
318
319 #[default(true)]
326 pub surb_target_notify: bool,
327
328 #[default(10_000)]
332 pub maximum_sessions: usize,
333
334 #[default(10000)]
340 pub session_forward_capacity: usize,
341}
342
343type IncomingSessionSink = Pin<Box<dyn Sink<IncomingSession, Error = SessionManagerError> + Send>>;
346
347type SessionNotifiers = (
348 Arc<hopr_utils::runtime::prelude::Mutex<IncomingSessionSink>>,
349 crossfire::MTx<crossfire::mpsc::Array<(SessionId, ClosureReason)>>,
350);
351
352type StartProtocolMsgSink = Arc<OnceLock<crossfire::MTx<crossfire::mpsc::Array<(HoprPseudonym, HoprStartProtocol)>>>>;
356
357pub struct SessionManager<S> {
508 session_initiations: SessionInitiationCache,
509 session_notifiers: Arc<OnceLock<SessionNotifiers>>,
510 start_protocol_tx: StartProtocolMsgSink,
511 active_sessions: Arc<std::sync::atomic::AtomicUsize>,
515 sessions: moka::sync::Cache<SessionId, SessionSlot>,
516 msg_sender: Arc<OnceLock<S>>,
517 cfg: SessionManagerConfig,
518}
519
520impl<S> Clone for SessionManager<S> {
521 fn clone(&self) -> Self {
522 Self {
523 session_initiations: self.session_initiations.clone(),
524 session_notifiers: self.session_notifiers.clone(),
525 start_protocol_tx: self.start_protocol_tx.clone(),
526 active_sessions: self.active_sessions.clone(),
527 sessions: self.sessions.clone(),
528 cfg: self.cfg.clone(),
529 msg_sender: self.msg_sender.clone(),
530 }
531 }
532}
533
534fn session_config(cfg: &SessionManagerConfig, capabilities: crate::Capabilities) -> HoprSessionConfig {
535 HoprSessionConfig {
536 capabilities,
537 frame_mtu: cfg.frame_mtu,
538 frame_timeout: cfg.max_frame_timeout,
539 max_buffered_segments: cfg.max_buffered_segments,
540 }
541}
542
543#[cfg(feature = "telemetry")]
544fn initialize_session_telemetry(
545 session_id: SessionId,
546 cfg: &SessionManagerConfig,
547 capabilities: crate::Capabilities,
548 surb_estimator: Option<&AtomicSurbFlowEstimator>,
549 surb_mgmt: Option<&Arc<BalancerStateValues>>,
550) {
551 initialize_session_metrics(session_id, session_config(cfg, capabilities));
552 set_session_state(&session_id, SessionLifecycleState::Active);
553 if let (Some(estimator), Some(mgmt)) = (surb_estimator, surb_mgmt) {
554 set_session_balancer_data(&session_id, estimator.clone(), mgmt.clone());
555 }
556}
557
558async fn send_via_msg_sender<S, D>(
559 msg_sender: &mut S,
560 routing: DestinationRouting,
561 data: D,
562 error_context: &'static str,
563) -> crate::errors::Result<()>
564where
565 S: futures::Sink<(DestinationRouting, ApplicationDataOut)> + Unpin,
566 S::Error: std::error::Error + Send + Sync + Clone + 'static,
567 D: TryInto<ApplicationData>,
568 D::Error: std::error::Error + Send + Sync + 'static,
569{
570 let app_data: ApplicationData = data.try_into().map_err(SessionManagerError::other)?;
571 msg_sender
572 .send((routing, ApplicationDataOut::with_no_packet_info(app_data)))
573 .timeout(futures_time::time::Duration::from(EXTERNAL_SEND_TIMEOUT))
574 .await
575 .map_err(|_| {
576 error!("timeout sending {error_context}");
577 TransportSessionError::Timeout
578 })?
579 .map_err(|error| {
580 error!(%error, "failed to send {error_context}");
581 SessionManagerError::other(error)
582 })?;
583 Ok(())
584}
585
586impl<S> SessionManager<S>
587where
588 S: futures::Sink<(DestinationRouting, ApplicationDataOut)> + Clone + Send + Sync + Unpin + 'static,
589 S::Error: std::error::Error + Send + Sync + Clone + 'static,
590{
591 pub fn new(mut cfg: SessionManagerConfig) -> Self {
593 let maximum_sessions = cfg.maximum_sessions;
594 cfg.surb_balance_notify_period = cfg
595 .surb_balance_notify_period
596 .map(|p| p.max(MIN_SURB_BUFFER_NOTIFICATION_PERIOD));
597 cfg.minimum_surb_buffer_duration = cfg.minimum_surb_buffer_duration.max(MIN_SURB_BUFFER_DURATION);
598
599 cfg.frame_mtu = cfg.frame_mtu.max(SESSION_MTU);
601 cfg.max_frame_timeout = cfg.max_frame_timeout.max(MIN_FRAME_TIMEOUT);
602
603 #[cfg(all(feature = "telemetry", not(test)))]
604 METRIC_ACTIVE_SESSIONS.set(0.0);
605
606 let active_sessions: Arc<std::sync::atomic::AtomicUsize> = Arc::new(std::sync::atomic::AtomicUsize::new(0));
607 let active_sessions_for_listener = active_sessions.clone();
608
609 let msg_sender = Arc::new(OnceLock::new());
610 Self {
611 msg_sender: msg_sender.clone(),
612 session_initiations: moka::sync::Cache::builder()
613 .max_capacity(maximum_sessions as u64)
614 .time_to_live(
615 2 * initiation_timeout_max_one_way(
616 cfg.initiation_timeout_base,
617 RoutingOptions::MAX_INTERMEDIATE_HOPS,
618 ),
619 )
620 .build(),
621 sessions: moka::sync::Cache::builder()
622 .max_capacity(maximum_sessions as u64)
623 .time_to_idle(cfg.idle_timeout)
624 .eviction_listener(move |session_id: Arc<SessionId>, entry, reason| match &reason {
625 moka::notification::RemovalCause::Expired | moka::notification::RemovalCause::Size => {
626 trace!(?session_id, ?reason, "session evicted from the cache");
627 active_sessions_for_listener.fetch_sub(1, Ordering::Relaxed);
628 close_session(*session_id.as_ref(), entry, ClosureReason::Eviction);
629 }
630 _ => {}
631 })
632 .build(),
633 session_notifiers: Arc::new(OnceLock::new()),
634 start_protocol_tx: Arc::new(OnceLock::new()),
635 active_sessions,
636 cfg,
637 }
638 }
639
640 pub fn start<T>(&self, msg_sender: S, new_session_notifier: T) -> crate::errors::Result<Vec<AbortHandle>>
646 where
647 T: futures::Sink<IncomingSession> + Send + 'static,
648 T::Error: std::error::Error + Send + Sync + 'static,
649 {
650 self.msg_sender
651 .set(msg_sender)
652 .map_err(|_| SessionManagerError::AlreadyStarted)?;
653
654 let new_session_notifier: IncomingSessionSink =
659 Box::pin(new_session_notifier.sink_map_err(SessionManagerError::other));
660 let new_session_notifier = Arc::new(hopr_utils::runtime::prelude::Mutex::new(new_session_notifier));
661
662 let (session_close_tx, session_close_rx) =
663 crossfire::mpsc::bounded_blocking_async(self.cfg.maximum_sessions + 10);
664 self.session_notifiers
665 .set((new_session_notifier, session_close_tx))
666 .map_err(|_| SessionManagerError::AlreadyStarted)?;
667
668 let (start_protocol_tx, start_protocol_rx) =
669 crossfire::mpsc::bounded_blocking_async(self.cfg.maximum_sessions + 10);
670 let _ = self.start_protocol_tx.set(start_protocol_tx);
671
672 let myself = self.clone();
673 let closure_diag = hopr_utils::runtime::diagnostics::ConcurrentDiagnostics::new(
674 "session_close_for_each_concurrent",
675 module_path!(),
676 file!(),
677 line!(),
678 );
679 let ah_closure_notifications = hopr_utils::spawn_as_abortable_named!(
680 "session_close_notifications",
681 session_close_rx.into_stream().for_each_concurrent(
682 self.cfg.maximum_sessions + 10,
683 move |(session_id, closure_reason)| {
684 let myself = myself.clone();
685 let closure_diag = closure_diag.clone();
686 closure_diag.wrap(|| {
687 if let Some(session_data) = myself.sessions.remove(&session_id) {
691 myself.active_sessions.fetch_sub(1, Ordering::Relaxed);
692 close_session(session_id, session_data, closure_reason);
693 } else {
694 debug!(
696 ?session_id,
697 ?closure_reason,
698 "could not find session id to close, maybe the session is already closed"
699 );
700 }
701 futures::future::ready(())
702 })
703 }
704 )
705 );
706
707 let myself = self.clone();
712 let ah_session_expiration = hopr_utils::spawn_as_abortable!(async move {
713 let jitter = hopr_api::types::crypto_random::random_float_in_range(1.0..1.5);
714 let timeout = 2 * initiation_timeout_max_one_way(
715 myself.cfg.initiation_timeout_base,
716 RoutingOptions::MAX_INTERMEDIATE_HOPS,
717 )
718 .min(myself.cfg.idle_timeout)
719 .mul_f64(jitter)
720 / 2;
721 futures_time::stream::interval(timeout.into())
722 .for_each(|_| async {
723 trace!("executing session cache evictions");
724 myself.sessions.run_pending_tasks();
725 myself.session_initiations.run_pending_tasks();
726 })
727 .await;
728 });
729
730 let myself = self.clone();
732 let ah_start_protocol = hopr_utils::spawn_as_abortable_named!(
733 "session_start_protocol_processor",
734 start_protocol_rx.into_stream().for_each_concurrent(
735 Some(self.cfg.maximum_sessions + 10),
736 move |(pseudonym, protocol_msg)| {
737 let myself = myself.clone();
738 async move {
739 let result = match protocol_msg {
740 HoprStartProtocol::StartSession(session_req) => {
741 myself.handle_incoming_session_initiation(pseudonym, session_req).await
742 }
743 HoprStartProtocol::SessionEstablished(est) => myself.handle_session_established(est).await,
744 HoprStartProtocol::SessionError(error_type) => {
745 myself.handle_session_error(error_type).await
746 }
747 HoprStartProtocol::KeepAlive(msg) => myself.handle_keep_alive(msg).await,
748 };
749
750 if let Err(error) = result {
751 error!(%error, "failed to process Start protocol message");
752 }
753 }
754 }
755 )
756 );
757
758 Ok(vec![ah_closure_notifications, ah_session_expiration, ah_start_protocol])
759 }
760
761 pub fn is_started(&self) -> bool {
763 self.session_notifiers.get().is_some()
764 }
765
766 fn allocate_session_slot(&self, session_id: SessionId, slot: SessionSlot) -> Option<SessionSlotGuard<'_>> {
790 let counter = &self.active_sessions;
794 #[allow(clippy::incompatible_msrv)]
795 let did_reserve = counter
796 .try_update(Ordering::Relaxed, Ordering::Relaxed, |n| {
797 (n < self.cfg.maximum_sessions).then_some(n + 1)
798 })
799 .is_ok();
800
801 if !did_reserve {
802 return None;
803 }
804
805 let result =
806 self.sessions
807 .entry(session_id)
808 .and_compute_with(|entry: Option<moka::Entry<SessionId, SessionSlot>>| {
809 if entry.is_none() {
810 moka::ops::compute::Op::Put(slot)
811 } else {
812 counter.fetch_sub(1, Ordering::Relaxed);
814 moka::ops::compute::Op::Nop
815 }
816 });
817
818 match result {
819 moka::ops::compute::CompResult::Inserted(_) => {
820 Some(SessionSlotGuard::new(&self.sessions, session_id, counter.clone()))
822 }
823 _ => None,
824 }
825 }
826
827 pub async fn new_session(
835 &self,
836 destination: Address,
837 target: SessionTarget,
838 cfg: SessionClientConfig,
839 ) -> crate::errors::Result<HoprSession> {
840 self.sessions.run_pending_tasks();
841 if self.cfg.maximum_sessions <= self.active_sessions.load(Ordering::Relaxed) {
842 return Err(SessionManagerError::TooManySessions.into());
843 }
844
845 let mut msg_sender = self.msg_sender.get().cloned().ok_or(SessionManagerError::NotStarted)?;
846
847 let (tx_initiation_done, rx_initiation_done): (
848 crossfire::MTx<crossfire::mpsc::One<_>>,
849 crossfire::AsyncRx<crossfire::mpsc::One<_>>,
850 ) = crossfire::mpsc::build(crossfire::mpsc::One::new());
851
852 let (challenge, _) = insert_into_next_slot(
853 &self.session_initiations,
854 |ch| {
855 if let Some(challenge) = ch {
856 ((challenge + 1) % hopr_api::types::crypto_random::MAX_RANDOM_INTEGER).max(MIN_CHALLENGE)
857 } else {
858 hopr_api::types::crypto_random::random_integer(MIN_CHALLENGE, None)
859 }
860 },
861 |_| tx_initiation_done,
862 Some(self.cfg.maximum_sessions as u64),
863 )
864 .ok_or(SessionManagerError::NoChallengeSlots)?; trace!(challenge, ?cfg, "initiating session with config");
868 let start_session_msg = HoprStartProtocol::StartSession(StartInitiation {
869 challenge,
870 target,
871 capabilities: ByteCapabilities(cfg.capabilities),
872 additional_data: if !cfg.capabilities.contains(Capability::NoRateControl) {
873 cfg.surb_management
874 .map(|c| c.target_surb_buffer_size)
875 .unwrap_or(
876 self.cfg.initial_return_session_egress_rate as u64
877 * self
878 .cfg
879 .minimum_surb_buffer_duration
880 .max(MIN_SURB_BUFFER_DURATION)
881 .as_secs(),
882 )
883 .min(u32::MAX as u64) as u32
884 } else {
885 0
886 },
887 });
888
889 let pseudonym = cfg.pseudonym.unwrap_or(HoprPseudonym::random());
890 let forward_routing = DestinationRouting::Forward {
891 destination: Box::new(destination.into()),
892 pseudonym: Some(pseudonym), forward_options: cfg.forward_path_options.clone(),
894 return_options: cfg.return_path_options.clone().into(),
895 };
896
897 info!(challenge, %pseudonym, %destination, "new session request");
899 send_via_msg_sender(
900 &mut msg_sender,
901 forward_routing.clone(),
902 start_session_msg,
903 "session request message",
904 )
905 .await
906 .map_err(|error| {
907 self.session_initiations.remove(&challenge);
908 TransportSessionError::packet_sending(error)
909 })?;
910
911 let initiation_timeout: futures_time::time::Duration = initiation_timeout_max_one_way(
913 self.cfg.initiation_timeout_base,
914 cfg.forward_path_options.count_hops() + cfg.return_path_options.count_hops() + 2,
915 )
916 .into();
917
918 trace!(challenge, "awaiting session establishment");
921 match rx_initiation_done
922 .into_stream()
923 .try_next()
924 .timeout(initiation_timeout)
925 .await
926 {
927 Ok(Ok(Some(est))) => {
928 let session_id = est.session_id;
930 debug!(challenge = est.orig_challenge, ?session_id, "started a new session");
931
932 let (session_tx, session_rx) =
933 crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(self.cfg.session_forward_capacity);
934 let (session_rx, session_rx_ah) = hopr_utils::runtime::DropAbortable::new(session_rx.into_stream());
935
936 let mut abort_handles = AbortableList::default();
937 abort_handles.insert(SessionHandles::Ingress, session_rx_ah);
938
939 let notifier = self
940 .session_notifiers
941 .get()
942 .map(|(_, notifier)| {
943 let notifier = notifier.clone();
944 Box::new(move |session_id: SessionId, reason: ClosureReason| {
945 let _ = notifier
946 .try_send((session_id, reason))
947 .inspect_err(|error| error!(%session_id, %error, "failed to notify session closure"));
948 })
949 })
950 .ok_or(SessionManagerError::NotStarted)?;
951
952 if let Some(balancer_config) = cfg.surb_management {
956 let surb_estimator = AtomicSurbFlowEstimator::default();
957
958 let surb_estimator_clone = surb_estimator.clone();
960 let full_surb_scoring_sender =
961 msg_sender.with(move |(routing, data): (DestinationRouting, ApplicationDataOut)| {
962 let produced = data.estimate_surbs_with_msg() as u64;
963 surb_estimator_clone
965 .produced
966 .fetch_add(produced, std::sync::atomic::Ordering::Relaxed);
967 #[cfg(feature = "telemetry")]
968 crate::telemetry::record_session_surb_produced(&session_id, produced);
969 futures::future::ok::<_, S::Error>((routing, data))
970 });
971
972 let max_out_organic_surbs = cfg.always_max_out_surbs;
975 let reduced_surb_scoring_sender = full_surb_scoring_sender.clone().with(
976 move |(routing, mut data): (DestinationRouting, ApplicationDataOut)| {
980 if !max_out_organic_surbs {
981 data.packet_info
983 .get_or_insert_with(|| OutgoingPacketInfo {
984 max_surbs_in_packet: 1,
985 ..Default::default()
986 })
987 .max_surbs_in_packet = 1;
988 }
989 futures::future::ok::<_, S::Error>((routing, data))
990 },
991 );
992
993 let surb_mgmt = Arc::new(BalancerStateValues::from(balancer_config));
994
995 let (ka_controller, ka_abort_handle) = utils::spawn_keep_alive_stream(
997 session_id,
998 full_surb_scoring_sender,
999 forward_routing.clone(),
1000 if self.cfg.surb_target_notify {
1001 SurbNotificationMode::Target
1002 } else {
1003 SurbNotificationMode::DoNotNotify
1004 },
1005 surb_mgmt.clone(),
1006 );
1007 abort_handles.insert(SessionHandles::KeepAlive, ka_abort_handle);
1008
1009 debug!(%session_id, ?balancer_config ,"spawning entry SURB balancer");
1011 let balancer = SurbBalancer::new(
1012 session_id,
1013 PidBalancerController::from_gains(PidControllerGains::from_env_or_default()),
1015 surb_estimator.clone(),
1016 SurbControllerWithCorrection(ka_controller, HoprPacket::MAX_SURBS_IN_PACKET as u32),
1019 surb_mgmt.clone(),
1020 );
1021
1022 let (level_stream, balancer_abort_handle) =
1023 balancer.start_control_loop(self.cfg.balancer_sampling_interval);
1024 abort_handles.insert(SessionHandles::Balancer, balancer_abort_handle);
1025
1026 let mut slot_guard = self
1029 .allocate_session_slot(
1030 session_id,
1031 SessionSlot {
1032 session_tx,
1033 routing_opts: forward_routing.clone(),
1034 abort_handles: Arc::new(parking_lot::Mutex::new(abort_handles)),
1035 surb_mgmt: surb_mgmt.clone(),
1036 surb_estimator: surb_estimator.clone(),
1037 },
1038 )
1039 .ok_or_else(|| {
1040 error!(%session_id, "session already exists - loopback attempt");
1042 SessionManagerError::Loopback
1043 })?;
1044
1045 #[cfg(all(feature = "telemetry", not(test)))]
1046 METRIC_NUM_INITIATED_SESSIONS.increment();
1047
1048 match level_stream
1051 .skip_while(|current_level| {
1052 futures::future::ready(*current_level < balancer_config.target_surb_buffer_size / 2)
1053 })
1054 .next()
1055 .timeout(futures_time::time::Duration::from(SESSION_READINESS_TIMEOUT))
1056 .await
1057 {
1058 Ok(Some(surb_level)) => {
1059 info!(%session_id, surb_level, "session is ready");
1060 }
1061 Ok(None) => {
1062 return Err(
1063 SessionManagerError::other(anyhow!("surb balancer was cancelled prematurely")).into(),
1064 );
1065 }
1066 Err(_) => {
1067 warn!(%session_id, "session didn't reach target SURB buffer size in time");
1068 }
1069 }
1070
1071 let surb_estimator_for_rx = surb_estimator.clone();
1072 let session = HoprSession::new(
1073 session_id,
1074 forward_routing,
1075 session_config(&self.cfg, cfg.capabilities),
1076 (
1077 reduced_surb_scoring_sender,
1078 session_rx.inspect(move |_| {
1079 surb_estimator_for_rx
1082 .consumed
1083 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1084 #[cfg(feature = "telemetry")]
1085 crate::telemetry::record_session_surb_consumed(&session_id, 1);
1086 }),
1087 ),
1088 Some(notifier),
1089 )?;
1090
1091 #[cfg(feature = "telemetry")]
1092 initialize_session_telemetry(
1093 session_id,
1094 &self.cfg,
1095 cfg.capabilities,
1096 Some(&surb_estimator),
1097 Some(&surb_mgmt),
1098 );
1099
1100 slot_guard.commit();
1101 Ok(session)
1102 } else {
1103 warn!(%session_id, "session ready without SURB balancing");
1104
1105 let mut slot_guard = self
1108 .allocate_session_slot(
1109 session_id,
1110 SessionSlot {
1111 session_tx,
1112 routing_opts: forward_routing.clone(),
1113 abort_handles: Arc::new(parking_lot::Mutex::new(abort_handles)),
1114 surb_mgmt: Default::default(), surb_estimator: Default::default(), },
1117 )
1118 .ok_or_else(|| {
1119 error!(%session_id, "session already exists - loopback attempt");
1121 SessionManagerError::Loopback
1122 })?;
1123
1124 #[cfg(all(feature = "telemetry", not(test)))]
1125 METRIC_NUM_INITIATED_SESSIONS.increment();
1126
1127 let max_out_organic_surbs = cfg.always_max_out_surbs;
1130 let reduced_surb_sender =
1131 msg_sender.with(move |(routing, mut data): (DestinationRouting, ApplicationDataOut)| {
1132 if !max_out_organic_surbs {
1133 data.packet_info
1134 .get_or_insert_with(|| OutgoingPacketInfo {
1135 max_surbs_in_packet: 1,
1136 ..Default::default()
1137 })
1138 .max_surbs_in_packet = 1;
1139 }
1140 futures::future::ok::<_, S::Error>((routing, data))
1141 });
1142
1143 let session = HoprSession::new(
1144 session_id,
1145 forward_routing,
1146 session_config(&self.cfg, cfg.capabilities),
1147 (reduced_surb_sender, session_rx),
1148 Some(notifier),
1149 )?;
1150
1151 #[cfg(feature = "telemetry")]
1152 initialize_session_telemetry(session_id, &self.cfg, cfg.capabilities, None, None);
1153
1154 slot_guard.commit();
1155 Ok(session)
1156 }
1157 }
1158 Ok(Ok(None)) => {
1159 self.session_initiations.remove(&challenge);
1160 Err(SessionManagerError::other(anyhow!(
1161 "internal error: sender has been closed without completing the session establishment"
1162 ))
1163 .into())
1164 }
1165 Ok(Err(error)) => {
1166 error!(
1168 challenge = error.challenge,
1169 ?error,
1170 "the other party rejected the session initiation with error"
1171 );
1172 Err(TransportSessionError::Rejected(error.reason))
1173 }
1174 Err(_) => {
1175 error!(challenge, "session initiation attempt timed out");
1177
1178 #[cfg(all(feature = "telemetry", not(test)))]
1179 METRIC_RECEIVED_SESSION_ERRS.increment(&["timeout"]);
1180
1181 self.session_initiations.remove(&challenge);
1182 Err(TransportSessionError::Timeout)
1183 }
1184 }
1185 }
1186
1187 pub async fn ping_session(&self, id: &SessionId) -> crate::errors::Result<()> {
1191 if let Some(session_data) = self.sessions.get(id) {
1192 trace!(session_id = ?id, "pinging manually session");
1193 let mut msg_sender = self.msg_sender.get().cloned().ok_or(SessionManagerError::NotStarted)?;
1194 send_via_msg_sender(
1195 &mut msg_sender,
1196 session_data.routing_opts.clone(),
1197 HoprStartProtocol::KeepAlive((*id).into()),
1198 "session ping message",
1199 )
1200 .await
1201 .map_err(TransportSessionError::packet_sending)
1202 } else {
1203 Err(SessionManagerError::NonExistingSession.into())
1204 }
1205 }
1206
1207 pub fn active_sessions(&self) -> Vec<SessionId> {
1209 self.sessions.run_pending_tasks();
1210 self.sessions.iter().map(|(k, _)| *k).collect()
1211 }
1212
1213 pub fn close_session(&self, id: &SessionId) -> bool {
1223 if let Some(slot) = self.sessions.remove(id) {
1224 self.active_sessions.fetch_sub(1, Ordering::Relaxed);
1225 close_session(*id, slot, ClosureReason::Eviction);
1226 true
1227 } else {
1228 false
1229 }
1230 }
1231
1232 pub fn update_surb_balancer_config(&self, id: &SessionId, config: SurbBalancerConfig) -> crate::errors::Result<()> {
1237 let cfg = self
1238 .sessions
1239 .get(id)
1240 .ok_or(SessionManagerError::NonExistingSession)?
1241 .surb_mgmt;
1242
1243 if !cfg.is_disabled() {
1245 cfg.update(&config);
1246 Ok(())
1247 } else {
1248 Err(SessionManagerError::other(anyhow!("session does not use SURB balancing")).into())
1249 }
1250 }
1251
1252 pub fn get_surb_balancer_config(&self, id: &SessionId) -> crate::errors::Result<Option<SurbBalancerConfig>> {
1256 match self.sessions.get(id) {
1257 Some(session) => Ok(Some(session.surb_mgmt.as_ref())
1258 .filter(|c| !c.is_disabled())
1259 .map(|d| d.as_config())),
1260 None => Err(SessionManagerError::NonExistingSession.into()),
1261 }
1262 }
1263
1264 pub fn get_surb_level_estimates(&self, id: &SessionId) -> crate::errors::Result<(u64, u64)> {
1271 match self.sessions.get(id) {
1272 Some(session) => Ok((
1273 session
1274 .surb_estimator
1275 .produced
1276 .load(std::sync::atomic::Ordering::Relaxed),
1277 session
1278 .surb_estimator
1279 .consumed
1280 .load(std::sync::atomic::Ordering::Relaxed),
1281 )),
1282 None => Err(SessionManagerError::NonExistingSession.into()),
1283 }
1284 }
1285
1286 pub fn dispatch_message(
1293 &self,
1294 pseudonym: HoprPseudonym,
1295 in_data: ApplicationDataIn,
1296 ) -> crate::errors::Result<DispatchResult> {
1297 if in_data.data.application_tag == HoprStartProtocol::START_PROTOCOL_MESSAGE_TAG {
1298 trace!("dispatching Start protocol message");
1300 if let Some(start_protocol_tx) = self.start_protocol_tx.get() {
1301 start_protocol_tx
1302 .try_send((pseudonym, HoprStartProtocol::try_from(in_data.data)?))
1303 .map_err(|error| {
1304 error!(%error, "failed to send Start protocol message to processing task");
1305 SessionManagerError::other(error)
1306 })?;
1307 } else {
1308 return Err(SessionManagerError::NotStarted.into());
1309 }
1310
1311 #[cfg(all(feature = "telemetry", not(test)))]
1312 METRIC_DISPATCHED_MSGS.increment_by(&["processed"], 1);
1313
1314 return Ok(DispatchResult::Processed);
1315 } else if in_data.data.application_tag == SESSION_APPLICATION_TAG {
1316 let session_id = pseudonym;
1317
1318 return if let Some(session_slot) = self.sessions.get(&session_id) {
1319 trace!(%session_id, "received data for a registered session");
1320
1321 Ok(session_slot
1322 .session_tx
1323 .try_send(in_data)
1324 .map(|_| {
1325 #[cfg(all(feature = "telemetry", not(test)))]
1326 METRIC_DISPATCHED_MSGS.increment_by(&["processed"], 1);
1327
1328 DispatchResult::Processed
1329 })
1330 .map_err(|error| {
1331 error!(%session_id, %error, "failed to dispatch session data");
1332 SessionManagerError::other(error)
1333 })?)
1334 } else {
1335 error!(%session_id, "received data from an unestablished session");
1336 Err(TransportSessionError::UnknownData)
1337 };
1338 }
1339
1340 trace!(tag = %in_data.data.application_tag, "received data not associated with session protocol or any existing session");
1341
1342 #[cfg(all(feature = "telemetry", not(test)))]
1343 METRIC_DISPATCHED_MSGS.increment_by(&["unrelated"], 1);
1344
1345 Ok(DispatchResult::Unrelated(in_data))
1346 }
1347
1348 #[cfg(feature = "benchmark")]
1355 pub fn pre_populate_session(&self, session_id: SessionId, routing_opts: DestinationRouting) {
1356 let (session_tx, _) =
1357 crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(self.cfg.session_forward_capacity);
1358 let slot = SessionSlot {
1359 session_tx,
1360 routing_opts,
1361 abort_handles: Default::default(),
1362 surb_mgmt: Arc::new(BalancerStateValues::default()),
1363 surb_estimator: Default::default(),
1364 };
1365 self.sessions.insert(session_id, slot);
1366 }
1367
1368 #[cfg(feature = "benchmark")]
1373 pub fn pre_populate_session_with_receiver(
1374 &self,
1375 session_id: SessionId,
1376 routing_opts: DestinationRouting,
1377 ) -> crossfire::AsyncRx<crossfire::mpsc::Array<ApplicationDataIn>> {
1378 let (session_tx, session_rx) =
1379 crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(self.cfg.session_forward_capacity);
1380 let slot = SessionSlot {
1381 session_tx,
1382 routing_opts,
1383 abort_handles: Default::default(),
1384 surb_mgmt: Arc::new(BalancerStateValues::default()),
1385 surb_estimator: Default::default(),
1386 };
1387 self.sessions.insert(session_id, slot);
1388 session_rx
1389 }
1390
1391 async fn handle_incoming_session_initiation(
1392 &self,
1393 pseudonym: HoprPseudonym,
1394 session_req: StartInitiation<SessionTarget, ByteCapabilities>,
1395 ) -> crate::errors::Result<()> {
1396 trace!(challenge = session_req.challenge, "received session initiation request");
1397
1398 debug!(%pseudonym, "got new session request, searching for a free session slot");
1399
1400 let mut msg_sender = self.msg_sender.get().cloned().ok_or(SessionManagerError::NotStarted)?;
1401
1402 let (new_session_notifier, close_session_notifier) = self
1403 .session_notifiers
1404 .get()
1405 .cloned()
1406 .ok_or(SessionManagerError::NotStarted)?;
1407
1408 let reply_routing = DestinationRouting::Return(pseudonym.into());
1410
1411 self.sessions.run_pending_tasks();
1413
1414 if let Some(stale_slot) = self.sessions.remove(&pseudonym) {
1422 self.active_sessions.fetch_sub(1, Ordering::Relaxed);
1423 info!(%pseudonym, "closing stale session superseded by a new initiation with the same pseudonym");
1424 close_session(pseudonym, stale_slot, ClosureReason::Eviction);
1425 }
1426
1427 let session_id = pseudonym;
1428
1429 let (session_tx, session_rx) =
1430 crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(self.cfg.session_forward_capacity);
1431 let (session_rx, session_rx_ah) = hopr_utils::runtime::DropAbortable::new(session_rx.into_stream());
1432
1433 let slot = SessionSlot {
1434 session_tx,
1435 routing_opts: reply_routing.clone(),
1436 abort_handles: Default::default(),
1437 surb_mgmt: Default::default(),
1438 surb_estimator: Default::default(),
1439 };
1440 slot.abort_handles.lock().insert(SessionHandles::Ingress, session_rx_ah);
1441
1442 let Some(mut slot_guard) = self.allocate_session_slot(session_id, slot.clone()) else {
1447 error!(%pseudonym, "no session slot available");
1450 let reason = StartErrorReason::NoSlotsAvailable;
1451 let data = HoprStartProtocol::SessionError(StartErrorType {
1452 challenge: session_req.challenge,
1453 reason,
1454 });
1455 send_via_msg_sender(&mut msg_sender, reply_routing.clone(), data, "session error message").await?;
1456 return Ok(());
1457 };
1458
1459 debug!(?pseudonym, ?session_req, "assigned a new session");
1460
1461 let closure_notifier = Box::new(move |session_id: SessionId, reason: ClosureReason| {
1462 if let Err(error) = close_session_notifier.try_send((session_id, reason)) {
1463 error!(%session_id, %error, %reason, "failed to notify session closure");
1464 }
1465 });
1466
1467 let session = if !session_req.capabilities.0.contains(Capability::NoRateControl) {
1468 let egress_rate_control =
1470 RateController::new(self.cfg.initial_return_session_egress_rate, Duration::from_secs(1));
1471
1472 let target_surb_buffer_size = if session_req.additional_data > 0 {
1475 (session_req.additional_data as u64).min(self.cfg.maximum_surb_buffer_size as u64)
1476 } else {
1477 self.cfg.initial_return_session_egress_rate as u64
1478 * self
1479 .cfg
1480 .minimum_surb_buffer_duration
1481 .max(MIN_SURB_BUFFER_DURATION)
1482 .as_secs()
1483 };
1484
1485 let surb_estimator_clone = slot.surb_estimator.clone();
1486 let session = HoprSession::new(
1487 session_id,
1488 reply_routing.clone(),
1489 session_config(&self.cfg, session_req.capabilities.into()),
1490 (
1491 msg_sender
1493 .clone()
1494 .with(move |(routing, data): (DestinationRouting, ApplicationDataOut)| {
1495 surb_estimator_clone
1497 .consumed
1498 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1499 #[cfg(feature = "telemetry")]
1500 crate::telemetry::record_session_surb_consumed(&session_id, 1);
1501 futures::future::ok::<_, S::Error>((routing, data))
1502 })
1503 .rate_limit_with_controller(&egress_rate_control)
1504 .buffer((2 * target_surb_buffer_size) as usize),
1505 session_rx.inspect(move |data| {
1507 let produced = data.num_surbs_with_msg() as u64;
1508 surb_estimator_clone
1510 .produced
1511 .fetch_add(produced, std::sync::atomic::Ordering::Relaxed);
1512 #[cfg(feature = "telemetry")]
1513 crate::telemetry::record_session_surb_produced(&session_id, produced);
1514 }),
1515 ),
1516 Some(closure_notifier),
1517 )?;
1518
1519 let balancer_config = SurbBalancerConfig {
1523 target_surb_buffer_size,
1524 max_surbs_per_sec: target_surb_buffer_size / self.cfg.minimum_surb_buffer_duration.as_secs(),
1526 surb_decay: None,
1529 };
1530
1531 slot.surb_mgmt.update(&balancer_config);
1532
1533 debug!(%session_id, ?balancer_config ,"spawning exit SURB balancer");
1536 let balancer = SurbBalancer::new(
1537 session_id,
1538 SimpleBalancerController::default(),
1539 slot.surb_estimator.clone(),
1540 SurbControllerWithCorrection(egress_rate_control, 1), slot.surb_mgmt.clone(),
1542 );
1543
1544 let (_, balancer_abort_handle) = balancer.start_control_loop(self.cfg.balancer_sampling_interval);
1546 slot.abort_handles
1547 .lock()
1548 .insert(SessionHandles::Balancer, balancer_abort_handle);
1549
1550 if let Some(period) = self.cfg.surb_balance_notify_period {
1552 let surb_estimator_clone = slot.surb_estimator.clone();
1553 let (ka_controller, ka_abort_handle) = utils::spawn_keep_alive_stream(
1554 session_id,
1555 msg_sender
1557 .clone()
1558 .with(move |(routing, data): (DestinationRouting, ApplicationDataOut)| {
1559 surb_estimator_clone
1561 .consumed
1562 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1563 #[cfg(feature = "telemetry")]
1564 crate::telemetry::record_session_surb_consumed(&session_id, 1);
1565 futures::future::ok::<_, S::Error>((routing, data))
1566 }),
1567 slot.routing_opts.clone(),
1568 SurbNotificationMode::Level(slot.surb_estimator.clone()),
1569 slot.surb_mgmt.clone(),
1570 );
1571
1572 hopr_utils::runtime::prelude::spawn(async move {
1574 hopr_utils::runtime::prelude::sleep(period).await;
1576 ka_controller.set_rate_per_unit(1, period);
1577 });
1578
1579 slot.abort_handles
1580 .lock()
1581 .insert(SessionHandles::KeepAlive, ka_abort_handle);
1582
1583 debug!(%session_id, ?period, "started SURB level-notifying keep-alive stream");
1584 }
1585
1586 session
1587 } else {
1588 HoprSession::new(
1589 session_id,
1590 reply_routing.clone(),
1591 session_config(&self.cfg, session_req.capabilities.into()),
1592 (msg_sender.clone(), session_rx),
1593 Some(closure_notifier),
1594 )?
1595 };
1596
1597 let incoming_session = IncomingSession {
1599 session,
1600 target: session_req.target,
1601 };
1602
1603 match async {
1606 let mut guard = new_session_notifier.lock().await;
1607 guard.send(incoming_session).await
1608 }
1609 .timeout(futures_time::time::Duration::from(EXTERNAL_SEND_TIMEOUT))
1610 .await
1611 {
1612 Err(_) => {
1613 error!(%session_id, "timeout to notify about new incoming session");
1614 return Err(TransportSessionError::Timeout);
1615 }
1616 Ok(Err(error)) => {
1617 error!(%session_id, %error, "failed to notify about new incoming session");
1618 return Err(SessionManagerError::other(error).into());
1619 }
1620 _ => {}
1621 };
1622
1623 trace!(?session_id, "session notification sent");
1624
1625 let data = HoprStartProtocol::SessionEstablished(StartEstablished {
1628 orig_challenge: session_req.challenge,
1629 session_id,
1630 });
1631
1632 send_via_msg_sender(
1633 &mut msg_sender,
1634 reply_routing.clone(),
1635 data,
1636 "session establishment message",
1637 )
1638 .await?;
1639
1640 #[cfg(feature = "telemetry")]
1641 initialize_session_telemetry(
1642 session_id,
1643 &self.cfg,
1644 session_req.capabilities.0,
1645 Some(&slot.surb_estimator),
1646 Some(&slot.surb_mgmt),
1647 );
1648
1649 info!(%session_id, "new session established");
1650
1651 #[cfg(all(feature = "telemetry", not(test)))]
1652 METRIC_NUM_ESTABLISHED_SESSIONS.increment();
1653
1654 slot_guard.commit();
1655 Ok(())
1656 }
1657
1658 async fn handle_session_established(&self, est: StartEstablished<SessionId>) -> crate::errors::Result<()> {
1659 trace!(
1660 session_id = ?est.session_id,
1661 "received session establishment confirmation"
1662 );
1663 let challenge = est.orig_challenge;
1664 let session_id = est.session_id;
1665 if let Some(tx_est) = self.session_initiations.remove(&est.orig_challenge) {
1666 if let Err(error) = tx_est.try_send(Ok(est)) {
1667 error!(%challenge, %session_id, %error, "failed to send session establishment confirmation");
1668 return Err(SessionManagerError::other(error).into());
1669 }
1670 debug!(?session_id, challenge, "session establishment complete");
1671 } else {
1672 error!(%session_id, challenge, "unknown session establishment attempt or expired");
1673 }
1674 Ok(())
1675 }
1676
1677 async fn handle_session_error(&self, error_type: StartErrorType) -> crate::errors::Result<()> {
1678 trace!(
1679 challenge = error_type.challenge,
1680 error = ?error_type.reason,
1681 "failed to initialize a session",
1682 );
1683 if let Some(tx_est) = self.session_initiations.remove(&error_type.challenge) {
1686 if let Err(error) = tx_est.try_send(Err(error_type)) {
1687 error!(%error, ?error_type, "could not send session error message");
1688 return Err(SessionManagerError::other(error).into());
1689 }
1690 error!(
1691 challenge = error_type.challenge,
1692 ?error_type,
1693 "session establishment error received"
1694 );
1695 } else {
1696 error!(
1697 challenge = error_type.challenge,
1698 ?error_type,
1699 "session establishment attempt expired before error could be delivered"
1700 );
1701 }
1702
1703 #[cfg(all(feature = "telemetry", not(test)))]
1704 METRIC_RECEIVED_SESSION_ERRS.increment(&[&error_type.reason.to_string()]);
1705
1706 Ok(())
1707 }
1708
1709 async fn handle_keep_alive(&self, msg: KeepAliveMessage<SessionId>) -> crate::errors::Result<()> {
1710 let session_id = msg.session_id;
1711 if let Some(session_slot) = self.sessions.get(&session_id) {
1712 trace!(?session_id, "received keep-alive message");
1713 match &session_slot.routing_opts {
1714 DestinationRouting::Forward { .. } => {
1716 if msg.flags.contains(KeepAliveFlag::BalancerState)
1717 && !session_slot.surb_mgmt.is_disabled()
1718 && session_slot.surb_mgmt.buffer_level() != msg.additional_data
1719 {
1720 session_slot
1722 .surb_mgmt
1723 .buffer_level
1724 .store(msg.additional_data, std::sync::atomic::Ordering::Relaxed);
1725 debug!(%session_id, surb_level = msg.additional_data, "keep-alive updated SURB buffer size from the Exit");
1726 }
1727
1728 session_slot
1730 .surb_estimator
1731 .consumed
1732 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1733 #[cfg(feature = "telemetry")]
1734 crate::telemetry::record_session_surb_consumed(&session_id, 1);
1735 }
1736 DestinationRouting::Return(_) => {
1738 if msg.flags.contains(KeepAliveFlag::BalancerTarget)
1740 && msg.additional_data > 0
1741 && !session_slot.surb_mgmt.is_disabled()
1742 && session_slot.surb_mgmt.controller_bounds().target() != msg.additional_data
1743 {
1744 session_slot
1746 .surb_mgmt
1747 .target_surb_buffer_size
1748 .store(msg.additional_data, std::sync::atomic::Ordering::Relaxed);
1749 session_slot.surb_mgmt.max_surbs_per_sec.store(
1751 msg.additional_data / self.cfg.minimum_surb_buffer_duration.as_secs(),
1752 std::sync::atomic::Ordering::Relaxed,
1753 );
1754 debug!(%session_id, target_surb_buffer_size = msg.additional_data, "keep-alive updated SURB balancer target buffer size from the Entry");
1755 }
1756
1757 let produced = KeepAliveMessage::<SessionId>::MIN_SURBS_PER_MESSAGE as u64;
1760 session_slot
1761 .surb_estimator
1762 .produced
1763 .fetch_add(produced, std::sync::atomic::Ordering::Relaxed);
1764 #[cfg(feature = "telemetry")]
1765 crate::telemetry::record_session_surb_produced(&session_id, produced);
1766 }
1767 }
1768 } else {
1769 debug!(%session_id, "received keep-alive request for an unknown session");
1770 }
1771 Ok(())
1772 }
1773}
1774
1775#[cfg(test)]
1776mod tests {
1777 use anyhow::{Context, anyhow};
1778 use futures::{AsyncWriteExt, channel::mpsc::UnboundedSender, future::BoxFuture, pin_mut};
1779 use hopr_api::types::{
1780 crypto::{keypairs::ChainKeypair, prelude::Keypair},
1781 crypto_random::Randomizable,
1782 internal::routing::SurbMatcher,
1783 primitive::prelude::Address,
1784 };
1785 use hopr_protocol_start::{StartProtocol, StartProtocolDiscriminants};
1786 use hopr_utils::network_types::prelude::SealedHost;
1787 use moka::future::FutureExt;
1788 use tokio::time::timeout;
1789
1790 use super::*;
1791 use crate::{Capabilities, balancer::SurbBalancerConfig, types::SessionTarget};
1792
1793 #[test]
1794 fn session_config_forwards_max_buffered_segments() {
1795 assert_eq!(
1796 SessionManagerConfig::default().max_buffered_segments,
1797 0,
1798 "default must leave the transport unbuffered"
1799 );
1800
1801 for segments in [0, 64] {
1802 let cfg = SessionManagerConfig {
1803 max_buffered_segments: segments,
1804 ..Default::default()
1805 };
1806 assert_eq!(
1807 session_config(&cfg, Capabilities::empty()).max_buffered_segments,
1808 segments
1809 );
1810 }
1811 }
1812
1813 #[async_trait::async_trait]
1814 trait SendMsg {
1815 async fn send_message(
1816 &self,
1817 routing: DestinationRouting,
1818 data: ApplicationDataOut,
1819 ) -> crate::errors::Result<()>;
1820 }
1821
1822 mockall::mock! {
1823 MsgSender {}
1824 impl SendMsg for MsgSender {
1825 fn send_message<'a, 'b>(&'a self, routing: DestinationRouting, data: ApplicationDataOut)
1826 -> BoxFuture<'b, crate::errors::Result<()>> where 'a: 'b, Self: Sync + 'b;
1827 }
1828 }
1829
1830 fn mock_packet_planning(
1831 sender: MockMsgSender,
1832 ) -> (
1833 UnboundedSender<(DestinationRouting, ApplicationDataOut)>,
1834 tokio::task::JoinHandle<()>,
1835 ) {
1836 let (tx, rx) = futures::channel::mpsc::unbounded();
1837 let handle = tokio::task::spawn(async move {
1838 pin_mut!(rx);
1839 while let Some((routing, data)) = rx.next().await {
1840 sender
1841 .send_message(routing, data)
1842 .await
1843 .expect("send message must not fail in mock");
1844 }
1845 });
1846 (tx, handle)
1847 }
1848
1849 fn msg_type(data: &ApplicationDataOut, expected: StartProtocolDiscriminants) -> bool {
1850 HoprStartProtocol::decode(data.data.application_tag, &data.data.plain_text)
1851 .map(|d| StartProtocolDiscriminants::from(d) == expected)
1852 .unwrap_or(false)
1853 }
1854
1855 fn start_msg_match(data: &ApplicationDataOut, msg: impl Fn(HoprStartProtocol) -> bool) -> bool {
1856 HoprStartProtocol::decode(data.data.application_tag, &data.data.plain_text)
1857 .map(msg)
1858 .unwrap_or(false)
1859 }
1860
1861 async fn wait_for_no_active_sessions(
1866 mgr: &SessionManager<UnboundedSender<(DestinationRouting, ApplicationDataOut)>>,
1867 ) -> bool {
1868 for _ in 0..50 {
1869 if mgr.active_sessions().is_empty() {
1870 return true;
1871 }
1872 tokio::time::sleep(Duration::from_millis(20)).await;
1873 }
1874 mgr.active_sessions().is_empty()
1875 }
1876
1877 #[test_log::test(tokio::test)]
1878 async fn session_manager_should_follow_start_protocol_to_establish_new_session_and_close_it() -> anyhow::Result<()>
1879 {
1880 let alice_pseudonym = HoprPseudonym::random();
1881 let bob_peer: Address = (&ChainKeypair::random()).into();
1882
1883 let alice_mgr = SessionManager::new(Default::default());
1884 let bob_mgr = SessionManager::new(Default::default());
1885
1886 let mut sequence = mockall::Sequence::new();
1887 let mut alice_transport = MockMsgSender::new();
1888 let mut bob_transport = MockMsgSender::new();
1889
1890 let bob_mgr_clone = bob_mgr.clone();
1892 alice_transport
1893 .expect_send_message()
1894 .once()
1895 .in_sequence(&mut sequence)
1896 .withf(move |peer, data| {
1897 info!("alice sends {}", data.data.application_tag);
1898 msg_type(data, StartProtocolDiscriminants::StartSession)
1899 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
1900 })
1901 .returning(move |_, data| {
1902 let bob_mgr_clone = bob_mgr_clone.clone();
1903 Box::pin(async move {
1904 bob_mgr_clone
1905 .dispatch_message(
1906 alice_pseudonym,
1907 ApplicationDataIn {
1908 data: data.data,
1909 packet_info: Default::default(),
1910 },
1911 )
1912 ?;
1913 Ok(())
1914 })
1915 });
1916
1917 let alice_mgr_clone = alice_mgr.clone();
1919 bob_transport
1920 .expect_send_message()
1921 .once()
1922 .in_sequence(&mut sequence)
1923 .withf(move |peer, data| {
1924 info!("bob sends {}", data.data.application_tag);
1925 msg_type(data, StartProtocolDiscriminants::SessionEstablished)
1926 && matches!(peer, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &alice_pseudonym)
1927 })
1928 .returning(move |_, data| {
1929 let alice_mgr_clone = alice_mgr_clone.clone();
1930
1931 Box::pin(async move {
1932 alice_mgr_clone.dispatch_message(
1933 alice_pseudonym,
1934 ApplicationDataIn {
1935 data: data.data,
1936 packet_info: Default::default(),
1937 },
1938 )?;
1939 Ok(())
1940 })
1941 });
1942
1943 let bob_mgr_clone = bob_mgr.clone();
1945 alice_transport
1946 .expect_send_message()
1947 .once()
1948 .in_sequence(&mut sequence)
1949 .withf(move |peer, data| {
1950 hopr_protocol_session::types::SessionMessage::<{ ApplicationData::PAYLOAD_SIZE }>::try_from(
1951 data.data.plain_text.as_ref(),
1952 )
1953 .expect("must be a session message")
1954 .try_as_segment()
1955 .expect("must be a segment")
1956 .is_terminating()
1957 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
1958 })
1959 .returning(move |_, data| {
1960 let bob_mgr_clone = bob_mgr_clone.clone();
1961 Box::pin(async move {
1962 bob_mgr_clone
1963 .dispatch_message(
1964 alice_pseudonym,
1965 ApplicationDataIn {
1966 data: data.data,
1967 packet_info: Default::default(),
1968 },
1969 )
1970 ?;
1971 Ok(())
1972 })
1973 });
1974
1975 let mut ahs = Vec::new();
1976
1977 let (new_session_tx_alice, _) = futures::channel::mpsc::channel(1024);
1979 let (alice_sender, alice_handle) = mock_packet_planning(alice_transport);
1980 ahs.extend(alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?);
1981 assert!(alice_mgr.is_started());
1982
1983 let (new_session_tx_bob, new_session_rx_bob) = futures::channel::mpsc::channel(1024);
1985 let (bob_sender, bob_handle) = mock_packet_planning(bob_transport);
1986 ahs.extend(bob_mgr.start(bob_sender.clone(), new_session_tx_bob)?);
1987 assert!(bob_mgr.is_started());
1988
1989 let target = SealedHost::Plain("127.0.0.1:80".parse()?);
1990
1991 pin_mut!(new_session_rx_bob);
1992 let (alice_session, bob_session) = timeout(
1993 Duration::from_secs(2),
1994 futures::future::join(
1995 alice_mgr.new_session(
1996 bob_peer,
1997 SessionTarget::TcpStream(target.clone()),
1998 SessionClientConfig {
1999 pseudonym: alice_pseudonym.into(),
2000 capabilities: Capability::NoRateControl | Capability::Segmentation,
2001 surb_management: None,
2002 ..Default::default()
2003 },
2004 ),
2005 new_session_rx_bob.next(),
2006 ),
2007 )
2008 .await?;
2009
2010 let mut alice_session = alice_session?;
2011 let bob_session = bob_session.ok_or(anyhow!("bob must get an incoming session"))?;
2012
2013 assert_eq!(
2014 alice_session.config().capabilities,
2015 Capability::Segmentation | Capability::NoRateControl
2016 );
2017 assert_eq!(
2018 alice_session.config().capabilities,
2019 bob_session.session.config().capabilities
2020 );
2021 assert!(matches!(bob_session.target, SessionTarget::TcpStream(host) if host == target));
2022
2023 assert_eq!(vec![*alice_session.id()], alice_mgr.active_sessions());
2024 assert_eq!(None, alice_mgr.get_surb_balancer_config(alice_session.id())?);
2025 assert!(
2026 alice_mgr
2027 .update_surb_balancer_config(alice_session.id(), SurbBalancerConfig::default())
2028 .is_err()
2029 );
2030
2031 assert_eq!(vec![*bob_session.session.id()], bob_mgr.active_sessions());
2032 assert_eq!(None, bob_mgr.get_surb_balancer_config(bob_session.session.id())?);
2033 assert!(
2034 bob_mgr
2035 .update_surb_balancer_config(bob_session.session.id(), SurbBalancerConfig::default())
2036 .is_err()
2037 );
2038
2039 tokio::time::sleep(Duration::from_millis(100)).await;
2040 alice_session.close().await?;
2041
2042 tokio::time::sleep(Duration::from_millis(100)).await;
2043
2044 assert!(matches!(
2045 alice_mgr.ping_session(alice_session.id()).await,
2046 Err(TransportSessionError::Manager(SessionManagerError::NonExistingSession))
2047 ));
2048
2049 futures::stream::iter(ahs)
2050 .for_each(|ah| async move { ah.abort() })
2051 .await;
2052
2053 alice_sender.close_channel();
2055 bob_sender.close_channel();
2056 let _ = alice_handle.await;
2057 let _ = bob_handle.await;
2058
2059 Ok(())
2060 }
2061
2062 #[test_log::test(tokio::test)]
2063 async fn session_manager_should_close_idle_session_automatically() -> anyhow::Result<()> {
2064 let alice_pseudonym = HoprPseudonym::random();
2065 let bob_peer: Address = (&ChainKeypair::random()).into();
2066
2067 let cfg = SessionManagerConfig {
2068 idle_timeout: Duration::from_millis(200),
2069 ..Default::default()
2070 };
2071
2072 let alice_mgr = SessionManager::new(cfg);
2073 let bob_mgr = SessionManager::new(Default::default());
2074
2075 let mut sequence = mockall::Sequence::new();
2076 let mut alice_transport = MockMsgSender::new();
2077 let mut bob_transport = MockMsgSender::new();
2078
2079 let bob_mgr_clone = bob_mgr.clone();
2081 alice_transport
2082 .expect_send_message()
2083 .once()
2084 .in_sequence(&mut sequence)
2085 .withf(move |peer, data| {
2086 msg_type(data, StartProtocolDiscriminants::StartSession)
2087 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2088 })
2089 .returning(move |_, data| {
2090 let bob_mgr_clone = bob_mgr_clone.clone();
2091 Box::pin(async move {
2092 bob_mgr_clone
2093 .dispatch_message(
2094 alice_pseudonym,
2095 ApplicationDataIn {
2096 data: data.data,
2097 packet_info: Default::default(),
2098 },
2099 )
2100 ?;
2101 Ok(())
2102 })
2103 });
2104
2105 let alice_mgr_clone = alice_mgr.clone();
2107 bob_transport
2108 .expect_send_message()
2109 .once()
2110 .in_sequence(&mut sequence)
2111 .withf(move |peer, data| {
2112 msg_type(data, StartProtocolDiscriminants::SessionEstablished)
2113 && matches!(peer, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &alice_pseudonym)
2114 })
2115 .returning(move |_, data| {
2116 let alice_mgr_clone = alice_mgr_clone.clone();
2117
2118 Box::pin(async move {
2119 alice_mgr_clone.dispatch_message(
2120 alice_pseudonym,
2121 ApplicationDataIn {
2122 data: data.data,
2123 packet_info: Default::default(),
2124 },
2125 )?;
2126 Ok(())
2127 })
2128 });
2129
2130 let mut ahs = Vec::new();
2131
2132 let (new_session_tx_alice, _) = futures::channel::mpsc::channel(1024);
2134 let (alice_sender, alice_handle) = mock_packet_planning(alice_transport);
2135 ahs.extend(alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?);
2136
2137 let (new_session_tx_bob, new_session_rx_bob) = futures::channel::mpsc::channel(1024);
2139 let (bob_sender, bob_handle) = mock_packet_planning(bob_transport);
2140 ahs.extend(bob_mgr.start(bob_sender.clone(), new_session_tx_bob)?);
2141 assert!(bob_mgr.is_started());
2142
2143 let target = SealedHost::Plain("127.0.0.1:80".parse()?);
2144
2145 pin_mut!(new_session_rx_bob);
2146 let (alice_session, bob_session) = timeout(
2147 Duration::from_secs(2),
2148 futures::future::join(
2149 alice_mgr.new_session(
2150 bob_peer,
2151 SessionTarget::TcpStream(target.clone()),
2152 SessionClientConfig {
2153 pseudonym: alice_pseudonym.into(),
2154 capabilities: Capability::NoRateControl | Capability::Segmentation,
2155 surb_management: None,
2156 ..Default::default()
2157 },
2158 ),
2159 new_session_rx_bob.next(),
2160 ),
2161 )
2162 .await?;
2163
2164 let alice_session = alice_session?;
2165 let bob_session = bob_session.ok_or(anyhow!("bob must get an incoming session"))?;
2166
2167 assert_eq!(
2168 alice_session.config().capabilities,
2169 Capability::Segmentation | Capability::NoRateControl,
2170 );
2171 assert_eq!(
2172 alice_session.config().capabilities,
2173 bob_session.session.config().capabilities
2174 );
2175 assert!(matches!(bob_session.target, SessionTarget::TcpStream(host) if host == target));
2176
2177 tokio::time::sleep(Duration::from_millis(300)).await;
2179
2180 assert!(matches!(
2181 alice_mgr.ping_session(alice_session.id()).await,
2182 Err(TransportSessionError::Manager(SessionManagerError::NonExistingSession))
2183 ));
2184
2185 futures::stream::iter(ahs)
2186 .for_each(|ah| async move { ah.abort() })
2187 .await;
2188
2189 alice_sender.close_channel();
2191 bob_sender.close_channel();
2192 let _ = alice_handle.await;
2193 let _ = bob_handle.await;
2194
2195 Ok(())
2196 }
2197
2198 #[test_log::test(tokio::test)]
2199 async fn session_manager_should_update_surb_balancer_config() -> anyhow::Result<()> {
2200 let alice_pseudonym = HoprPseudonym::random();
2201 let session_id = alice_pseudonym;
2202 let balancer_cfg = SurbBalancerConfig {
2203 target_surb_buffer_size: 1000,
2204 max_surbs_per_sec: 100,
2205 ..Default::default()
2206 };
2207
2208 let alice_mgr =
2209 SessionManager::<UnboundedSender<(DestinationRouting, ApplicationDataOut)>>::new(Default::default());
2210
2211 let (dummy_tx, _) = crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(SESSION_FORWARD_CAPACITY);
2212 alice_mgr.sessions.insert(
2213 session_id,
2214 SessionSlot {
2215 session_tx: dummy_tx,
2216 routing_opts: DestinationRouting::Return(SurbMatcher::Pseudonym(alice_pseudonym)),
2217 abort_handles: Default::default(),
2218 surb_mgmt: Arc::new(BalancerStateValues::from(balancer_cfg)),
2219 surb_estimator: Default::default(),
2220 },
2221 );
2222
2223 let actual_cfg = alice_mgr
2224 .get_surb_balancer_config(&session_id)?
2225 .ok_or(anyhow!("session must have a surb balancer config"))?;
2226 assert_eq!(actual_cfg, balancer_cfg);
2227
2228 let new_cfg = SurbBalancerConfig {
2229 target_surb_buffer_size: 2000,
2230 max_surbs_per_sec: 200,
2231 ..Default::default()
2232 };
2233 alice_mgr.update_surb_balancer_config(&session_id, new_cfg)?;
2234
2235 let actual_cfg = alice_mgr
2236 .get_surb_balancer_config(&session_id)?
2237 .ok_or(anyhow!("session must have a surb balancer config"))?;
2238 assert_eq!(actual_cfg, new_cfg);
2239
2240 Ok(())
2241 }
2242
2243 #[test_log::test(tokio::test)]
2244 async fn session_manager_should_not_allow_loopback_sessions() -> anyhow::Result<()> {
2245 let alice_pseudonym = HoprPseudonym::random();
2246 let bob_peer: Address = (&ChainKeypair::random()).into();
2247
2248 let alice_mgr = SessionManager::new(Default::default());
2249
2250 let mut sequence = mockall::Sequence::new();
2251 let mut alice_transport = MockMsgSender::new();
2252
2253 let alice_mgr_clone = alice_mgr.clone();
2255 alice_transport
2256 .expect_send_message()
2257 .once()
2258 .in_sequence(&mut sequence)
2259 .withf(move |peer, data| {
2260 msg_type(data, StartProtocolDiscriminants::StartSession)
2261 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2262 })
2263 .returning(move |_, data| {
2264 let alice_mgr_clone = alice_mgr_clone.clone();
2266 Box::pin(async move {
2267 alice_mgr_clone
2268 .dispatch_message(
2269 alice_pseudonym,
2270 ApplicationDataIn {
2271 data: data.data,
2272 packet_info: Default::default(),
2273 },
2274 )
2275 ?;
2276 Ok(())
2277 })
2278 });
2279
2280 let alice_mgr_clone = alice_mgr.clone();
2282 alice_transport
2283 .expect_send_message()
2284 .once()
2285 .in_sequence(&mut sequence)
2286 .withf(move |peer, data| {
2287 msg_type(data, StartProtocolDiscriminants::SessionEstablished)
2288 && matches!(peer, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &alice_pseudonym)
2289 })
2290 .returning(move |_, data| {
2291 let alice_mgr_clone = alice_mgr_clone.clone();
2292
2293 Box::pin(async move {
2294 alice_mgr_clone.dispatch_message(
2295 alice_pseudonym,
2296 ApplicationDataIn {
2297 data: data.data,
2298 packet_info: Default::default(),
2299 },
2300 )?;
2301 Ok(())
2302 })
2303 });
2304
2305 let (new_session_tx_alice, new_session_rx_alice) = futures::channel::mpsc::channel(1024);
2307 let (alice_sender, alice_handle) = mock_packet_planning(alice_transport);
2308 alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?;
2309 assert!(alice_mgr.is_started());
2310
2311 let alice_session = alice_mgr
2312 .new_session(
2313 bob_peer,
2314 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2315 SessionClientConfig {
2316 capabilities: None.into(),
2317 pseudonym: alice_pseudonym.into(),
2318 surb_management: None,
2319 ..Default::default()
2320 },
2321 )
2322 .await;
2323
2324 println!("{alice_session:?}");
2325 assert!(matches!(
2326 alice_session,
2327 Err(TransportSessionError::Manager(SessionManagerError::Loopback))
2328 ));
2329
2330 drop(new_session_rx_alice);
2331
2332 alice_sender.close_channel();
2334 let _ = alice_handle.await;
2335
2336 Ok(())
2337 }
2338
2339 #[test_log::test(tokio::test)]
2340 async fn session_manager_should_timeout_new_session_attempt_when_no_response() -> anyhow::Result<()> {
2341 let bob_peer: Address = (&ChainKeypair::random()).into();
2342
2343 let cfg = SessionManagerConfig {
2344 initiation_timeout_base: Duration::from_millis(100),
2345 ..Default::default()
2346 };
2347
2348 let alice_mgr = SessionManager::new(cfg);
2349 let bob_mgr = SessionManager::new(Default::default());
2350
2351 let mut sequence = mockall::Sequence::new();
2352 let mut alice_transport = MockMsgSender::new();
2353 let bob_transport = MockMsgSender::new();
2354
2355 alice_transport
2357 .expect_send_message()
2358 .once()
2359 .in_sequence(&mut sequence)
2360 .withf(move |peer, data| {
2361 msg_type(data, StartProtocolDiscriminants::StartSession)
2362 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2363 })
2364 .returning(|_, _| Box::pin(async { Ok(()) }));
2365
2366 let (new_session_tx_alice, _) = futures::channel::mpsc::channel(1024);
2368 let (alice_sender, _alice_handle) = mock_packet_planning(alice_transport);
2369 alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?;
2370 assert!(alice_mgr.is_started());
2371
2372 let (new_session_tx_bob, _) = futures::channel::mpsc::channel(1024);
2374 let (bob_sender, _bob_handle) = mock_packet_planning(bob_transport);
2375 bob_mgr.start(bob_sender.clone(), new_session_tx_bob)?;
2376 assert!(bob_mgr.is_started());
2377
2378 let result = alice_mgr
2379 .new_session(
2380 bob_peer,
2381 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2382 SessionClientConfig {
2383 capabilities: None.into(),
2384 pseudonym: None,
2385 surb_management: None,
2386 ..Default::default()
2387 },
2388 )
2389 .await;
2390
2391 assert!(matches!(result, Err(TransportSessionError::Timeout)));
2392
2393 Ok(())
2394 }
2395
2396 #[cfg(feature = "telemetry")]
2397 #[test_log::test(tokio::test)]
2398 async fn failed_incoming_session_establishment_does_not_register_telemetry() -> anyhow::Result<()> {
2399 let mgr = SessionManager::new(Default::default());
2400
2401 let transport = MockMsgSender::new();
2402 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
2403 drop(new_session_rx);
2404 let (sender, _handle) = mock_packet_planning(transport);
2405 mgr.start(sender.clone(), new_session_tx)?;
2406 assert!(mgr.is_started());
2407
2408 let pseudonym = HoprPseudonym::random();
2409 let result = mgr
2410 .handle_incoming_session_initiation(
2411 pseudonym,
2412 StartInitiation {
2413 challenge: MIN_CHALLENGE,
2414 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2415 capabilities: ByteCapabilities(Capabilities::empty()),
2416 additional_data: 0,
2417 },
2418 )
2419 .await;
2420
2421 assert!(result.is_err());
2422
2423 assert!(
2426 wait_for_no_active_sessions(&mgr).await,
2427 "the partially established session slot was not rolled back"
2428 );
2429
2430 sender.close_channel();
2432 let _ = _handle.await;
2433
2434 Ok(())
2435 }
2436
2437 #[test_log::test(tokio::test)]
2438 async fn session_manager_should_roll_back_slot_when_incoming_session_setup_fails() -> anyhow::Result<()> {
2439 let mgr = SessionManager::new(Default::default());
2440
2441 let transport = MockMsgSender::new();
2444 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
2445 drop(new_session_rx);
2446 let (sender, handle) = mock_packet_planning(transport);
2447 mgr.start(sender.clone(), new_session_tx)?;
2448 assert!(mgr.is_started());
2449
2450 let pseudonym = HoprPseudonym::random();
2451
2452 let result = mgr
2456 .handle_incoming_session_initiation(
2457 pseudonym,
2458 StartInitiation {
2459 challenge: MIN_CHALLENGE,
2460 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2461 capabilities: ByteCapabilities(Capabilities::empty()),
2462 additional_data: 0,
2463 },
2464 )
2465 .await;
2466 assert!(result.is_err());
2467
2468 assert!(
2471 wait_for_no_active_sessions(&mgr).await,
2472 "the partially established session slot was not rolled back"
2473 );
2474
2475 sender.close_channel();
2477 let _ = handle.await;
2478
2479 Ok(())
2480 }
2481
2482 #[test_log::test(tokio::test)]
2483 async fn session_manager_should_send_keep_alives_via_surb_balancer() -> anyhow::Result<()> {
2484 let alice_pseudonym = HoprPseudonym::random();
2485 let bob_peer: Address = (&ChainKeypair::random()).into();
2486
2487 let bob_cfg = SessionManagerConfig {
2488 surb_balance_notify_period: Some(Duration::from_millis(500)),
2489 ..Default::default()
2490 };
2491 let alice_mgr = SessionManager::new(Default::default());
2492 let bob_mgr = SessionManager::new(bob_cfg.clone());
2493
2494 let mut alice_transport = MockMsgSender::new();
2495 let mut bob_transport = MockMsgSender::new();
2496
2497 let mut open_sequence = mockall::Sequence::new();
2499 let bob_mgr_clone = bob_mgr.clone();
2500 alice_transport
2501 .expect_send_message()
2502 .once()
2503 .in_sequence(&mut open_sequence)
2504 .withf(move |peer, data| {
2505 msg_type(data, StartProtocolDiscriminants::StartSession)
2506 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2507 })
2508 .returning(move |_, data| {
2509 let bob_mgr_clone = bob_mgr_clone.clone();
2510 Box::pin(async move {
2511 bob_mgr_clone
2512 .dispatch_message(
2513 alice_pseudonym,
2514 ApplicationDataIn {
2515 data: data.data,
2516 packet_info: Default::default(),
2517 },
2518 )
2519 ?;
2520 Ok(())
2521 })
2522 });
2523
2524 let alice_mgr_clone = alice_mgr.clone();
2526 bob_transport
2527 .expect_send_message()
2528 .once()
2529 .in_sequence(&mut open_sequence)
2530 .withf(move |peer, data| {
2531 msg_type(data, StartProtocolDiscriminants::SessionEstablished)
2532 && matches!(peer, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &alice_pseudonym)
2533 })
2534 .returning(move |_, data| {
2535 let alice_mgr_clone = alice_mgr_clone.clone();
2536 Box::pin(async move {
2537 alice_mgr_clone.dispatch_message(
2538 alice_pseudonym,
2539 ApplicationDataIn {
2540 data: data.data,
2541 packet_info: Default::default(),
2542 },
2543 )?;
2544 Ok(())
2545 })
2546 });
2547
2548 const INITIAL_BALANCER_TARGET: u64 = 10;
2549
2550 let bob_mgr_clone = bob_mgr.clone();
2552 alice_transport
2553 .expect_send_message()
2554 .times(5..)
2555 .withf(move |peer, data| {
2557 start_msg_match(data, |msg| matches!(msg, StartProtocol::KeepAlive(ka) if ka.flags.contains(KeepAliveFlag::BalancerTarget) && ka.additional_data == INITIAL_BALANCER_TARGET))
2558 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2560 })
2561 .returning(move |_, data| {
2562 let bob_mgr_clone = bob_mgr_clone.clone();
2563 Box::pin(async move {
2564 bob_mgr_clone
2565 .dispatch_message(
2566 alice_pseudonym,
2567 ApplicationDataIn {
2568 data: data.data,
2569 packet_info: Default::default(),
2570 },
2571 )
2572 ?;
2573 Ok(())
2574 })
2575 });
2576
2577 const NEXT_BALANCER_TARGET: u64 = 50;
2578
2579 let bob_mgr_clone = bob_mgr.clone();
2581 alice_transport
2582 .expect_send_message()
2583 .times(5..)
2584 .withf(move |peer, data| {
2586 start_msg_match(data, |msg| matches!(msg, StartProtocol::KeepAlive(ka) if ka.flags.contains(KeepAliveFlag::BalancerTarget) && ka.additional_data == NEXT_BALANCER_TARGET))
2587 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2588 })
2589 .returning(move |_, data| {
2590 let bob_mgr_clone = bob_mgr_clone.clone();
2591 Box::pin(async move {
2592 bob_mgr_clone
2593 .dispatch_message(
2594 alice_pseudonym,
2595 ApplicationDataIn {
2596 data: data.data,
2597 packet_info: Default::default(),
2598 },
2599 )
2600 ?;
2601 Ok(())
2602 })
2603 });
2604
2605 let alice_mgr_clone = alice_mgr.clone();
2607 bob_transport
2608 .expect_send_message()
2609 .times(1..)
2610 .withf(move |peer, data| {
2612 start_msg_match(data, |msg| matches!(msg, StartProtocol::KeepAlive(ka) if ka.flags.contains(KeepAliveFlag::BalancerState) && ka.additional_data > 0))
2613 && matches!(peer, DestinationRouting::Return(SurbMatcher::Pseudonym(p)) if p == &alice_pseudonym)
2614 })
2615 .returning(move |_, data| {
2616 let alice_mgr_clone = alice_mgr_clone.clone();
2617 Box::pin(async move {
2618 alice_mgr_clone
2619 .dispatch_message(
2620 alice_pseudonym,
2621 ApplicationDataIn {
2622 data: data.data,
2623 packet_info: Default::default(),
2624 },
2625 )
2626 ?;
2627 Ok(())
2628 })
2629 });
2630
2631 let bob_mgr_clone = bob_mgr.clone();
2633 alice_transport
2634 .expect_send_message()
2635 .once()
2636 .withf(move |peer, data| {
2638 hopr_protocol_session::types::SessionMessage::<{ ApplicationData::PAYLOAD_SIZE }>::try_from(
2639 data.data.plain_text.as_ref(),
2640 )
2641 .ok()
2642 .and_then(|m| m.try_as_segment())
2643 .map(|s| s.is_terminating())
2644 .unwrap_or(false)
2645 && matches!(peer, DestinationRouting::Forward { destination, .. } if destination.as_ref() == &bob_peer.into())
2646 })
2647 .returning(move |_, data| {
2648 let bob_mgr_clone = bob_mgr_clone.clone();
2649 Box::pin(async move {
2650 bob_mgr_clone
2651 .dispatch_message(
2652 alice_pseudonym,
2653 ApplicationDataIn {
2654 data: data.data,
2655 packet_info: Default::default(),
2656 },
2657 )
2658 ?;
2659 Ok(())
2660 })
2661 });
2662
2663 let mut ahs = Vec::new();
2664
2665 let (new_session_tx_alice, _) = futures::channel::mpsc::channel(1024);
2667 let (alice_sender, alice_handle) = mock_packet_planning(alice_transport);
2668 ahs.extend(alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?);
2669 assert!(alice_mgr.is_started());
2670
2671 let (new_session_tx_bob, new_session_rx_bob) = futures::channel::mpsc::channel(1024);
2673 let (bob_sender, bob_handle) = mock_packet_planning(bob_transport);
2674 ahs.extend(bob_mgr.start(bob_sender.clone(), new_session_tx_bob)?);
2675 assert!(bob_mgr.is_started());
2676
2677 let target = SealedHost::Plain("127.0.0.1:80".parse()?);
2678
2679 let balancer_cfg = SurbBalancerConfig {
2680 target_surb_buffer_size: INITIAL_BALANCER_TARGET,
2681 max_surbs_per_sec: 100,
2682 ..Default::default()
2683 };
2684
2685 pin_mut!(new_session_rx_bob);
2686 let (alice_session, bob_session) = timeout(
2687 Duration::from_secs(2),
2688 futures::future::join(
2689 alice_mgr.new_session(
2690 bob_peer,
2691 SessionTarget::TcpStream(target.clone()),
2692 SessionClientConfig {
2693 pseudonym: alice_pseudonym.into(),
2694 capabilities: Capability::Segmentation.into(),
2695 surb_management: Some(balancer_cfg),
2696 ..Default::default()
2697 },
2698 ),
2699 new_session_rx_bob.next(),
2700 ),
2701 )
2702 .await?;
2703
2704 let mut alice_session = alice_session?;
2705 let bob_session = bob_session.ok_or(anyhow!("bob must get an incoming session"))?;
2706
2707 assert!(matches!(bob_session.target, SessionTarget::TcpStream(host) if host == target));
2708
2709 assert_eq!(
2710 Some(balancer_cfg),
2711 alice_mgr.get_surb_balancer_config(alice_session.id())?
2712 );
2713
2714 let remote_cfg = bob_mgr
2715 .get_surb_balancer_config(bob_session.session.id())?
2716 .ok_or(anyhow!("no remote config at bob"))?;
2717 assert_eq!(remote_cfg.target_surb_buffer_size, balancer_cfg.target_surb_buffer_size);
2718 assert_eq!(
2719 remote_cfg.max_surbs_per_sec,
2720 remote_cfg.target_surb_buffer_size
2721 / bob_cfg
2722 .minimum_surb_buffer_duration
2723 .max(MIN_SURB_BUFFER_DURATION)
2724 .as_secs()
2725 );
2726
2727 tokio::time::sleep(Duration::from_millis(1500)).await;
2729
2730 let new_balancer_cfg = SurbBalancerConfig {
2731 target_surb_buffer_size: NEXT_BALANCER_TARGET,
2732 max_surbs_per_sec: 100,
2733 ..Default::default()
2734 };
2735
2736 alice_mgr.update_surb_balancer_config(alice_session.id(), new_balancer_cfg)?;
2738
2739 tokio::time::sleep(Duration::from_millis(1500)).await;
2741
2742 let remote_cfg = bob_mgr
2744 .get_surb_balancer_config(bob_session.session.id())?
2745 .ok_or(anyhow!("no remote config at bob"))?;
2746 assert_eq!(
2747 remote_cfg.target_surb_buffer_size,
2748 new_balancer_cfg.target_surb_buffer_size
2749 );
2750 assert_eq!(
2751 remote_cfg.max_surbs_per_sec,
2752 new_balancer_cfg.target_surb_buffer_size / bob_cfg.minimum_surb_buffer_duration.as_secs()
2753 );
2754
2755 let (alice_surb_sent, alice_surb_used) = alice_mgr.get_surb_level_estimates(alice_session.id())?;
2756 let (bob_surb_recv, bob_surb_used) = bob_mgr.get_surb_level_estimates(bob_session.session.id())?;
2757
2758 alice_session.close().await?;
2759
2760 assert!(alice_surb_sent > 0, "alice must've sent surbs");
2761 assert!(bob_surb_recv > 0, "bob must've received surbs");
2762 assert!(
2763 bob_surb_recv <= alice_surb_sent,
2764 "bob cannot receive more surbs than alice sent"
2765 );
2766
2767 assert!(alice_surb_used > 0, "alice must see bob used surbs");
2768 assert!(bob_surb_used > 0, "bob must've used surbs");
2769 assert!(
2770 alice_surb_used <= bob_surb_used,
2771 "alice cannot see bob used more surbs than bob actually used"
2772 );
2773
2774 tokio::time::sleep(Duration::from_millis(300)).await;
2775 assert!(matches!(
2776 alice_mgr.ping_session(alice_session.id()).await,
2777 Err(TransportSessionError::Manager(SessionManagerError::NonExistingSession))
2778 ));
2779
2780 futures::stream::iter(ahs)
2781 .for_each(|ah| async move { ah.abort() })
2782 .await;
2783
2784 alice_sender.close_channel();
2786 bob_sender.close_channel();
2787 let _ = alice_handle.await;
2788 let _ = bob_handle.await;
2789
2790 Ok(())
2791 }
2792
2793 #[test_log::test(tokio::test)]
2794 async fn session_manager_should_supersede_stale_session_on_reinitiation_with_same_pseudonym() -> anyhow::Result<()>
2795 {
2796 use hopr_utils::network_types::prelude::SealedHost;
2797
2798 let bob_mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
2799 SessionManager::new(Default::default());
2800
2801 let mut transport = MockMsgSender::new();
2803 transport
2804 .expect_send_message()
2805 .times(2)
2806 .returning(|_, _| futures::future::ok(()).boxed());
2807
2808 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
2809 let _notifications = tokio::spawn(async move {
2811 pin_mut!(new_session_rx);
2812 while let Some(_session) = new_session_rx.next().await {
2813 }
2815 });
2816 let (sender, _handle) = mock_packet_planning(transport);
2817 bob_mgr.start(sender.clone(), new_session_tx)?;
2818 assert!(bob_mgr.is_started());
2819
2820 let pseudonym = HoprPseudonym::random();
2821
2822 let result = bob_mgr
2824 .handle_incoming_session_initiation(
2825 pseudonym,
2826 StartInitiation {
2827 challenge: MIN_CHALLENGE,
2828 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2829 capabilities: ByteCapabilities(Capabilities::empty()),
2830 additional_data: 0,
2831 },
2832 )
2833 .await;
2834
2835 assert!(result.is_ok(), "first session initiation should succeed");
2836
2837 let active = bob_mgr.active_sessions();
2839 assert_eq!(active.len(), 1, "should have exactly one active session");
2840
2841 let result = bob_mgr
2845 .handle_incoming_session_initiation(
2846 pseudonym,
2847 StartInitiation {
2848 challenge: MIN_CHALLENGE + 1,
2849 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
2850 capabilities: ByteCapabilities(Capabilities::empty()),
2851 additional_data: 0,
2852 },
2853 )
2854 .await;
2855
2856 assert!(result.is_ok(), "re-initiation should supersede the stale session");
2857
2858 let active = bob_mgr.active_sessions();
2860 assert_eq!(active.len(), 1, "should still have exactly one active session");
2861
2862 sender.close_channel();
2864 let _ = _handle.await;
2865
2866 Ok(())
2867 }
2868
2869 #[test_log::test(tokio::test)]
2870 async fn session_manager_should_return_error_when_pinging_non_existent_session() -> anyhow::Result<()> {
2871 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
2872 SessionManager::new(Default::default());
2873
2874 let transport = MockMsgSender::new();
2875 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
2876 let _notifications = tokio::spawn(async move {
2877 pin_mut!(new_session_rx);
2878 while let Some(_session) = new_session_rx.next().await {}
2879 });
2880 let (sender, _handle) = mock_packet_planning(transport);
2881 mgr.start(sender.clone(), new_session_tx)?;
2882 assert!(mgr.is_started());
2883
2884 let fake_session_id = HoprPseudonym::random();
2885 let result = mgr.ping_session(&fake_session_id).await;
2886
2887 assert!(result.is_err());
2888 assert!(matches!(
2889 result.unwrap_err(),
2890 TransportSessionError::Manager(SessionManagerError::NonExistingSession)
2891 ));
2892
2893 sender.close_channel();
2895 let _ = _handle.await;
2896
2897 Ok(())
2898 }
2899
2900 #[test_log::test(tokio::test)]
2901 async fn session_manager_should_return_false_when_closing_non_existent_session() -> anyhow::Result<()> {
2902 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
2903 SessionManager::new(Default::default());
2904
2905 let transport = MockMsgSender::new();
2906 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
2907 let _notifications = tokio::spawn(async move {
2908 pin_mut!(new_session_rx);
2909 while let Some(_session) = new_session_rx.next().await {}
2910 });
2911 let (sender, _handle) = mock_packet_planning(transport);
2912 mgr.start(sender.clone(), new_session_tx)?;
2913 assert!(mgr.is_started());
2914
2915 let fake_session_id = HoprPseudonym::random();
2916 let result = mgr.close_session(&fake_session_id);
2917
2918 assert!(!result, "closing non-existent session should return false");
2919
2920 Ok(())
2921 }
2922
2923 #[test_log::test(tokio::test)]
2924 async fn session_manager_should_return_error_when_updating_surb_config_for_non_existent_session()
2925 -> anyhow::Result<()> {
2926 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
2927 SessionManager::new(Default::default());
2928
2929 let transport = MockMsgSender::new();
2930 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
2931 let _notifications = tokio::spawn(async move {
2932 pin_mut!(new_session_rx);
2933 while let Some(_session) = new_session_rx.next().await {}
2934 });
2935 let (sender, _handle) = mock_packet_planning(transport);
2936 mgr.start(sender.clone(), new_session_tx)?;
2937 assert!(mgr.is_started());
2938
2939 let fake_session_id = HoprPseudonym::random();
2940 let result = mgr.update_surb_balancer_config(&fake_session_id, SurbBalancerConfig::default());
2941
2942 assert!(result.is_err());
2943
2944 sender.close_channel();
2946 let _ = _handle.await;
2947
2948 Ok(())
2949 }
2950
2951 #[test_log::test(tokio::test)]
2952 async fn session_manager_should_return_error_when_getting_surb_config_for_non_existent_session()
2953 -> anyhow::Result<()> {
2954 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
2955 SessionManager::new(Default::default());
2956
2957 let transport = MockMsgSender::new();
2958 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
2959 let _notifications = tokio::spawn(async move {
2960 pin_mut!(new_session_rx);
2961 while let Some(_session) = new_session_rx.next().await {}
2962 });
2963 let (sender, _handle) = mock_packet_planning(transport);
2964 mgr.start(sender.clone(), new_session_tx)?;
2965 assert!(mgr.is_started());
2966
2967 let fake_session_id = HoprPseudonym::random();
2968 let result = mgr.get_surb_balancer_config(&fake_session_id);
2969
2970 assert!(result.is_err());
2971 assert!(matches!(
2972 result.unwrap_err(),
2973 TransportSessionError::Manager(SessionManagerError::NonExistingSession)
2974 ));
2975
2976 sender.close_channel();
2978 let _ = _handle.await;
2979
2980 Ok(())
2981 }
2982
2983 #[test_log::test(tokio::test)]
2984 async fn session_manager_should_return_error_when_getting_surb_estimates_for_non_existent_session()
2985 -> anyhow::Result<()> {
2986 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
2987 SessionManager::new(Default::default());
2988
2989 let transport = MockMsgSender::new();
2990 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
2991 let _notifications = tokio::spawn(async move {
2992 pin_mut!(new_session_rx);
2993 while let Some(_session) = new_session_rx.next().await {}
2994 });
2995 let (sender, _handle) = mock_packet_planning(transport);
2996 mgr.start(sender.clone(), new_session_tx)?;
2997 assert!(mgr.is_started());
2998
2999 let fake_session_id = HoprPseudonym::random();
3000 let result = mgr.get_surb_level_estimates(&fake_session_id);
3001
3002 assert!(result.is_err());
3003 assert!(matches!(
3004 result.unwrap_err(),
3005 TransportSessionError::Manager(SessionManagerError::NonExistingSession)
3006 ));
3007
3008 sender.close_channel();
3010 let _ = _handle.await;
3011
3012 Ok(())
3013 }
3014
3015 #[test_log::test(tokio::test)]
3022 async fn handle_session_error_propagates_peer_rejection_to_pending_new_session() -> anyhow::Result<()> {
3023 let mgr: SessionManager<UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3024 SessionManager::new(Default::default());
3025
3026 let mut transport = MockMsgSender::new();
3027 transport
3030 .expect_send_message()
3031 .returning(|_, _| futures::future::ok(()).boxed());
3032
3033 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3034 let _notifications = tokio::spawn(async move {
3035 pin_mut!(new_session_rx);
3036 while let Some(_session) = new_session_rx.next().await {}
3037 });
3038 let (sender, _handle) = mock_packet_planning(transport);
3039 mgr.start(sender.clone(), new_session_tx)?;
3040 assert!(mgr.is_started());
3041
3042 let mgr_clone = mgr.clone();
3044 let peer_address: Address = (&ChainKeypair::random()).into();
3045 let handle = tokio::spawn(async move {
3046 mgr_clone
3047 .new_session(
3048 peer_address,
3049 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3050 SessionClientConfig {
3051 surb_management: None,
3052 ..Default::default()
3053 },
3054 )
3055 .await
3056 });
3057
3058 let challenge = tokio::time::timeout(Duration::from_secs(1), async {
3060 loop {
3061 if let Some((ch, _)) = mgr.session_initiations.iter().next() {
3062 break *ch;
3063 }
3064 tokio::time::sleep(Duration::from_millis(10)).await;
3065 }
3066 })
3067 .await
3068 .context("new_session did not insert a challenge into session_initiations")?;
3069
3070 let error_type = StartErrorType {
3072 challenge,
3073 reason: StartErrorReason::NoSlotsAvailable,
3074 };
3075 mgr.handle_session_error(error_type).await?;
3076
3077 let result = handle.await?;
3079 match result {
3080 Ok(_session) => panic!("expected rejection error, got session"),
3081 Err(e) => {
3082 assert!(matches!(
3083 e,
3084 TransportSessionError::Rejected(StartErrorReason::NoSlotsAvailable)
3085 ));
3086 }
3087 }
3088
3089 sender.close_channel();
3090 let _ = _handle.await;
3091 Ok(())
3092 }
3093
3094 #[test_log::test(tokio::test)]
3095 async fn session_manager_should_reject_new_session_when_max_sessions_reached() -> anyhow::Result<()> {
3096 use hopr_utils::network_types::prelude::SealedHost;
3097
3098 let cfg = SessionManagerConfig {
3100 maximum_sessions: 1,
3101 ..Default::default()
3102 };
3103 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3104 SessionManager::new(cfg);
3105
3106 let mut transport = MockMsgSender::new();
3107 transport
3108 .expect_send_message()
3109 .times(2)
3110 .returning(|_, _| futures::future::ok(()).boxed());
3111
3112 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3113 let _notifications = tokio::spawn(async move {
3114 pin_mut!(new_session_rx);
3115 while let Some(_session) = new_session_rx.next().await {}
3116 });
3117 let (sender, _handle) = mock_packet_planning(transport);
3118 mgr.start(sender.clone(), new_session_tx)?;
3119 assert!(mgr.is_started());
3120
3121 let pseudonym1 = HoprPseudonym::random();
3123 mgr.handle_incoming_session_initiation(
3124 pseudonym1,
3125 StartInitiation {
3126 challenge: MIN_CHALLENGE,
3127 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3128 capabilities: ByteCapabilities(Capabilities::empty()),
3129 additional_data: 0,
3130 },
3131 )
3132 .await?;
3133
3134 assert_eq!(mgr.active_sessions().len(), 1);
3136
3137 let pseudonym2 = HoprPseudonym::random();
3139 let _result = mgr
3140 .handle_incoming_session_initiation(
3141 pseudonym2,
3142 StartInitiation {
3143 challenge: MIN_CHALLENGE,
3144 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3145 capabilities: ByteCapabilities(Capabilities::empty()),
3146 additional_data: 0,
3147 },
3148 )
3149 .await;
3150
3151 assert_eq!(mgr.active_sessions().len(), 1);
3154
3155 sender.close_channel();
3157 let _ = _handle.await;
3158
3159 Ok(())
3160 }
3161
3162 #[test_log::test(tokio::test)]
3168 async fn new_session_returns_too_many_sessions_when_cache_is_full() -> anyhow::Result<()> {
3169 use hopr_utils::network_types::prelude::SealedHost;
3170
3171 let cfg = SessionManagerConfig {
3172 maximum_sessions: 2,
3173 idle_timeout: Duration::from_secs(3600),
3174 ..Default::default()
3175 };
3176 let mgr: SessionManager<UnboundedSender<(DestinationRouting, ApplicationDataOut)>> = SessionManager::new(cfg);
3177
3178 let mut transport = MockMsgSender::new();
3179 transport
3181 .expect_send_message()
3182 .times(2)
3183 .returning(|_, _| futures::future::ok(()).boxed());
3184
3185 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3186 let _notifications = tokio::spawn(async move {
3187 pin_mut!(new_session_rx);
3188 while let Some(_session) = new_session_rx.next().await {}
3189 });
3190 let (sender, _handle) = mock_packet_planning(transport);
3191 mgr.start(sender.clone(), new_session_tx)?;
3192 assert!(mgr.is_started());
3193
3194 for i in 0..2 {
3196 let pseudonym = HoprPseudonym::random();
3197 mgr.handle_incoming_session_initiation(
3198 pseudonym,
3199 StartInitiation {
3200 challenge: MIN_CHALLENGE + i as u64,
3201 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3202 capabilities: ByteCapabilities(Capabilities::empty()),
3203 additional_data: 0,
3204 },
3205 )
3206 .await?;
3207 }
3208 assert_eq!(mgr.active_sessions().len(), 2);
3209
3210 let result = mgr
3212 .new_session(
3213 Address::from(&ChainKeypair::random()),
3214 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3215 SessionClientConfig {
3216 surb_management: None,
3217 ..Default::default()
3218 },
3219 )
3220 .await;
3221
3222 assert!(result.is_err());
3223 assert!(matches!(
3224 result.unwrap_err(),
3225 TransportSessionError::Manager(SessionManagerError::TooManySessions)
3226 ));
3227
3228 sender.close_channel();
3229 let _ = _handle.await;
3230 Ok(())
3231 }
3232
3233 #[test_log::test(tokio::test)]
3236 async fn new_session_removes_challenge_on_send_failure() -> anyhow::Result<()> {
3237 let mgr: SessionManager<UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3238 SessionManager::new(Default::default());
3239
3240 let (tx, rx) = futures::channel::mpsc::unbounded();
3245 drop(rx);
3246
3247 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3248 let _notifications = tokio::spawn(async move {
3249 pin_mut!(new_session_rx);
3250 while let Some(_session) = new_session_rx.next().await {}
3251 });
3252 mgr.start(tx, new_session_tx)?;
3253 assert!(mgr.is_started());
3254
3255 let result = mgr
3257 .new_session(
3258 Address::from(&ChainKeypair::random()),
3259 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3260 SessionClientConfig {
3261 surb_management: None,
3262 ..Default::default()
3263 },
3264 )
3265 .await;
3266
3267 assert!(result.is_err());
3268 assert_eq!(
3271 mgr.session_initiations.entry_count(),
3272 0,
3273 "session_initiations was not cleaned up after send failure"
3274 );
3275
3276 Ok(())
3277 }
3278
3279 #[test_log::test(tokio::test)]
3283 async fn new_session_removes_challenge_on_timeout() -> anyhow::Result<()> {
3284 let cfg = SessionManagerConfig {
3285 initiation_timeout_base: Duration::from_millis(100),
3286 ..Default::default()
3287 };
3288
3289 let alice_mgr = SessionManager::new(cfg);
3290 let bob_mgr = SessionManager::new(Default::default());
3291
3292 let bob_peer: Address = (&ChainKeypair::random()).into();
3293
3294 let mut alice_transport = MockMsgSender::new();
3295 let bob_transport = MockMsgSender::new();
3296
3297 alice_transport
3299 .expect_send_message()
3300 .once()
3301 .returning(|_, _| futures::future::ok(()).boxed());
3302
3303 let (alice_sender, _alice_handle) = mock_packet_planning(alice_transport);
3304 let (new_session_tx_alice, _) = futures::channel::mpsc::channel(1024);
3305 alice_mgr.start(alice_sender.clone(), new_session_tx_alice)?;
3306 assert!(alice_mgr.is_started());
3307
3308 let (bob_sender, _bob_handle) = mock_packet_planning(bob_transport);
3309 let (new_session_tx_bob, _) = futures::channel::mpsc::channel(1024);
3310 bob_mgr.start(bob_sender.clone(), new_session_tx_bob)?;
3311 assert!(bob_mgr.is_started());
3312
3313 assert_eq!(alice_mgr.session_initiations.entry_count(), 0);
3315
3316 let result = alice_mgr
3317 .new_session(
3318 bob_peer,
3319 SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3320 SessionClientConfig {
3321 capabilities: None.into(),
3322 pseudonym: None,
3323 surb_management: None,
3324 ..Default::default()
3325 },
3326 )
3327 .await;
3328
3329 assert!(matches!(result, Err(TransportSessionError::Timeout)));
3330 assert_eq!(
3333 alice_mgr.session_initiations.entry_count(),
3334 0,
3335 "session_initiations was not cleaned up after timeout"
3336 );
3337
3338 Ok(())
3339 }
3340
3341 #[test_log::test(tokio::test)]
3342 async fn session_manager_should_return_unknown_data_error_when_dispatching_to_unknown_session() -> anyhow::Result<()>
3343 {
3344 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3345 SessionManager::new(Default::default());
3346
3347 let transport = MockMsgSender::new();
3348 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3349 let _notifications = tokio::spawn(async move {
3350 pin_mut!(new_session_rx);
3351 while let Some(_session) = new_session_rx.next().await {}
3352 });
3353 let (sender, _handle) = mock_packet_planning(transport);
3354 mgr.start(sender.clone(), new_session_tx)?;
3355 assert!(mgr.is_started());
3356
3357 let pseudonym = HoprPseudonym::random();
3359 let result = mgr.dispatch_message(
3360 pseudonym,
3361 ApplicationDataIn {
3362 data: ApplicationData::new(SESSION_APPLICATION_TAG, b"test data")?,
3363 packet_info: Default::default(),
3364 },
3365 );
3366
3367 assert!(result.is_err());
3368 assert!(matches!(result.unwrap_err(), TransportSessionError::UnknownData));
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_true_when_closing_existing_session() -> anyhow::Result<()> {
3379 use hopr_utils::network_types::prelude::SealedHost;
3380
3381 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3382 SessionManager::new(Default::default());
3383
3384 let mut transport = MockMsgSender::new();
3385 transport
3386 .expect_send_message()
3387 .once()
3388 .returning(|_, _| futures::future::ok(()).boxed());
3389
3390 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3391 let _notifications = tokio::spawn(async move {
3392 pin_mut!(new_session_rx);
3393 while let Some(_session) = new_session_rx.next().await {}
3394 });
3395 let (sender, _handle) = mock_packet_planning(transport);
3396 mgr.start(sender.clone(), new_session_tx)?;
3397 assert!(mgr.is_started());
3398
3399 let pseudonym = HoprPseudonym::random();
3401 mgr.handle_incoming_session_initiation(
3402 pseudonym,
3403 StartInitiation {
3404 challenge: MIN_CHALLENGE,
3405 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3406 capabilities: ByteCapabilities(Capabilities::empty()),
3407 additional_data: 0,
3408 },
3409 )
3410 .await?;
3411
3412 assert_eq!(mgr.active_sessions().len(), 1);
3414
3415 let result = mgr.close_session(&pseudonym);
3417 assert!(result, "closing existing session should return true");
3418
3419 assert_eq!(mgr.active_sessions().len(), 0);
3421
3422 sender.close_channel();
3424 let _ = _handle.await;
3425
3426 Ok(())
3427 }
3428
3429 #[test_log::test(tokio::test)]
3430 async fn session_manager_should_update_buffer_level_on_keep_alive_with_balancer_state_flag() -> anyhow::Result<()> {
3431 use std::sync::atomic::Ordering;
3432
3433 let alice_pseudonym = HoprPseudonym::random();
3434 let session_id = alice_pseudonym;
3435 let initial_buffer_level = 100u64;
3436 let new_buffer_level = 200u64;
3437
3438 let balancer_cfg = SurbBalancerConfig {
3439 target_surb_buffer_size: 1000,
3440 max_surbs_per_sec: 100,
3441 ..Default::default()
3442 };
3443
3444 let alice_mgr =
3445 SessionManager::<UnboundedSender<(DestinationRouting, ApplicationDataOut)>>::new(Default::default());
3446
3447 let (new_session_tx, _) = futures::channel::mpsc::channel(1024);
3448 let (mock_sender, _) = futures::channel::mpsc::unbounded();
3449 let _ahs = alice_mgr.start(mock_sender, new_session_tx)?;
3450 assert!(alice_mgr.is_started());
3451
3452 let (dummy_tx, _) = crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(SESSION_FORWARD_CAPACITY);
3453 let peer_address: Address = (&ChainKeypair::random()).into();
3454 alice_mgr.sessions.insert(
3455 session_id,
3456 SessionSlot {
3457 session_tx: dummy_tx,
3458 routing_opts: DestinationRouting::Forward {
3459 destination: Box::new(peer_address.into()),
3460 pseudonym: Some(alice_pseudonym),
3461 forward_options: RoutingOptions::Hops(hopr_api::types::primitive::bounded::BoundedSize::MIN),
3462 return_options: RoutingOptions::Hops(hopr_api::types::primitive::bounded::BoundedSize::MIN).into(),
3463 },
3464 abort_handles: Default::default(),
3465 surb_mgmt: Arc::new(BalancerStateValues::from(balancer_cfg)),
3466 surb_estimator: Default::default(),
3467 },
3468 );
3469
3470 let session_slot = alice_mgr.sessions.get(&session_id).unwrap();
3472 session_slot
3473 .surb_mgmt
3474 .buffer_level
3475 .store(initial_buffer_level, Ordering::Relaxed);
3476 drop(session_slot);
3477
3478 let session_slot = alice_mgr.sessions.get(&session_id).unwrap();
3480 assert_eq!(session_slot.surb_mgmt.buffer_level(), initial_buffer_level);
3481 drop(session_slot);
3482
3483 let ka = KeepAliveMessage::<SessionId> {
3485 session_id,
3486 flags: KeepAliveFlag::BalancerState.into(),
3487 additional_data: new_buffer_level,
3488 };
3489 let app_data: ApplicationData = HoprStartProtocol::KeepAlive(ka).try_into()?;
3490 let app_data_in = ApplicationDataIn {
3491 data: app_data,
3492 packet_info: Default::default(),
3493 };
3494
3495 alice_mgr.dispatch_message(alice_pseudonym, app_data_in)?;
3497
3498 tokio::time::timeout(Duration::from_secs(1), async {
3500 loop {
3501 if let Some(slot) = alice_mgr.sessions.get(&session_id)
3502 && slot.surb_mgmt.buffer_level() == new_buffer_level
3503 {
3504 break;
3505 }
3506 tokio::time::sleep(Duration::from_millis(10)).await;
3507 }
3508 })
3509 .await
3510 .context("keep-alive BalancerState update timed out")?;
3511
3512 let session_slot = alice_mgr.sessions.get(&session_id).unwrap();
3514 assert_eq!(
3515 session_slot.surb_mgmt.buffer_level(),
3516 new_buffer_level,
3517 "buffer level should be updated via keep-alive with BalancerState flag"
3518 );
3519
3520 Ok(())
3521 }
3522
3523 #[test_log::test(tokio::test)]
3524 async fn session_manager_should_update_target_on_keep_alive_with_balancer_target_flag() -> anyhow::Result<()> {
3525 use std::sync::atomic::Ordering;
3526
3527 let alice_pseudonym = HoprPseudonym::random();
3528 let session_id = alice_pseudonym;
3529 let initial_target = 1000u64;
3530 let new_target = 2000u64;
3531
3532 let balancer_cfg = SurbBalancerConfig {
3533 target_surb_buffer_size: initial_target,
3534 max_surbs_per_sec: 100,
3535 ..Default::default()
3536 };
3537
3538 let alice_mgr =
3539 SessionManager::<UnboundedSender<(DestinationRouting, ApplicationDataOut)>>::new(Default::default());
3540
3541 let (new_session_tx, _) = futures::channel::mpsc::channel(1024);
3542 let (mock_sender, _) = futures::channel::mpsc::unbounded();
3543 let _ahs = alice_mgr.start(mock_sender, new_session_tx)?;
3544 assert!(alice_mgr.is_started());
3545
3546 let (dummy_tx, _) = crossfire::mpsc::bounded_blocking_async::<ApplicationDataIn>(SESSION_FORWARD_CAPACITY);
3547 alice_mgr.sessions.insert(
3548 session_id,
3549 SessionSlot {
3550 session_tx: dummy_tx,
3551 routing_opts: DestinationRouting::Return(SurbMatcher::Pseudonym(alice_pseudonym)),
3552 abort_handles: Default::default(),
3553 surb_mgmt: Arc::new(BalancerStateValues::from(balancer_cfg)),
3554 surb_estimator: Default::default(),
3555 },
3556 );
3557
3558 let session_slot = alice_mgr.sessions.get(&session_id).unwrap();
3560 assert_eq!(
3561 session_slot.surb_mgmt.controller_bounds().target(),
3562 initial_target,
3563 "initial target should be set"
3564 );
3565 drop(session_slot);
3566
3567 let ka = KeepAliveMessage::<SessionId> {
3569 session_id,
3570 flags: KeepAliveFlag::BalancerTarget.into(),
3571 additional_data: new_target,
3572 };
3573 let app_data: ApplicationData = HoprStartProtocol::KeepAlive(ka).try_into()?;
3574 let app_data_in = ApplicationDataIn {
3575 data: app_data,
3576 packet_info: Default::default(),
3577 };
3578
3579 alice_mgr.dispatch_message(alice_pseudonym, app_data_in)?;
3581
3582 tokio::time::timeout(Duration::from_secs(1), async {
3584 loop {
3585 if let Some(slot) = alice_mgr.sessions.get(&session_id)
3586 && slot.surb_mgmt.target_surb_buffer_size.load(Ordering::Relaxed) == new_target
3587 {
3588 break;
3589 }
3590 tokio::time::sleep(Duration::from_millis(10)).await;
3591 }
3592 })
3593 .await
3594 .context("keep-alive BalancerTarget update timed out")?;
3595
3596 let session_slot = alice_mgr.sessions.get(&session_id).unwrap();
3598 assert_eq!(
3599 session_slot.surb_mgmt.target_surb_buffer_size.load(Ordering::Relaxed),
3600 new_target,
3601 "target buffer size should be updated via keep-alive with BalancerTarget flag"
3602 );
3603
3604 Ok(())
3605 }
3606
3607 #[test_log::test(tokio::test)]
3608 async fn session_manager_should_evict_idle_session_and_call_close_callback() -> anyhow::Result<()> {
3609 use hopr_utils::network_types::prelude::SealedHost;
3610
3611 let cfg = SessionManagerConfig {
3612 maximum_sessions: 1,
3613 idle_timeout: Duration::from_millis(100),
3614 ..Default::default()
3615 };
3616 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3617 SessionManager::new(cfg);
3618
3619 let mut transport = MockMsgSender::new();
3620 transport
3621 .expect_send_message()
3622 .times(1)
3623 .returning(|_, _| futures::future::ok(()).boxed());
3624
3625 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3626 let _notifications = tokio::spawn(async move {
3627 pin_mut!(new_session_rx);
3628 while let Some(_session) = new_session_rx.next().await {}
3629 });
3630 let (sender, _handle) = mock_packet_planning(transport);
3631 mgr.start(sender.clone(), new_session_tx)?;
3632 assert!(mgr.is_started());
3633
3634 let pseudonym1 = HoprPseudonym::random();
3636 mgr.handle_incoming_session_initiation(
3637 pseudonym1,
3638 StartInitiation {
3639 challenge: MIN_CHALLENGE,
3640 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3641 capabilities: ByteCapabilities(Capabilities::empty()),
3642 additional_data: 0,
3643 },
3644 )
3645 .await?;
3646
3647 assert_eq!(mgr.active_sessions().len(), 1);
3649
3650 tokio::time::sleep(Duration::from_millis(200)).await;
3652 mgr.sessions.run_pending_tasks();
3653
3654 assert_eq!(
3656 mgr.active_sessions().len(),
3657 0,
3658 "idle session should be evicted after timeout"
3659 );
3660
3661 Ok(())
3662 }
3663
3664 #[test_log::test(tokio::test)]
3665 async fn session_manager_should_reject_new_session_when_max_sessions_reached_no_eviction() -> anyhow::Result<()> {
3666 use hopr_utils::network_types::prelude::SealedHost;
3667
3668 let cfg = SessionManagerConfig {
3670 maximum_sessions: 1,
3671 idle_timeout: Duration::from_secs(3600), ..Default::default()
3673 };
3674 let mgr: SessionManager<futures::channel::mpsc::UnboundedSender<(DestinationRouting, ApplicationDataOut)>> =
3675 SessionManager::new(cfg);
3676
3677 let mut transport = MockMsgSender::new();
3678 transport
3679 .expect_send_message()
3680 .times(2)
3681 .returning(|_, _| futures::future::ok(()).boxed());
3682
3683 let (new_session_tx, new_session_rx) = futures::channel::mpsc::channel(1);
3684 let _notifications = tokio::spawn(async move {
3685 pin_mut!(new_session_rx);
3686 while let Some(_session) = new_session_rx.next().await {}
3687 });
3688 let (sender, _handle) = mock_packet_planning(transport);
3689 mgr.start(sender.clone(), new_session_tx)?;
3690 assert!(mgr.is_started());
3691
3692 let pseudonym1 = HoprPseudonym::random();
3694 mgr.handle_incoming_session_initiation(
3695 pseudonym1,
3696 StartInitiation {
3697 challenge: MIN_CHALLENGE,
3698 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3699 capabilities: ByteCapabilities(Capabilities::empty()),
3700 additional_data: 0,
3701 },
3702 )
3703 .await?;
3704
3705 assert_eq!(mgr.active_sessions().len(), 1);
3707
3708 let pseudonym2 = HoprPseudonym::random();
3710 let _result = mgr
3711 .handle_incoming_session_initiation(
3712 pseudonym2,
3713 StartInitiation {
3714 challenge: MIN_CHALLENGE,
3715 target: SessionTarget::TcpStream(SealedHost::Plain("127.0.0.1:80".parse()?)),
3716 capabilities: ByteCapabilities(Capabilities::empty()),
3717 additional_data: 0,
3718 },
3719 )
3720 .await;
3721
3722 assert_eq!(
3724 mgr.active_sessions().len(),
3725 1,
3726 "should still have exactly one session - second session should be rejected"
3727 );
3728
3729 assert!(
3731 mgr.active_sessions().contains(&pseudonym1),
3732 "the first session should still be active"
3733 );
3734
3735 sender.close_channel();
3737 let _ = _handle.await;
3738
3739 Ok(())
3740 }
3741}