hopr_transport_session/
lib.rs

1//! [`HoprSession`] object providing the session functionality over the HOPR transport
2//!
3//! The session proxies the user interactions with the transport to hide the
4//! advanced interactions and functionality.
5//!
6//! The [`SessionManager`] allows for automatic management of sessions via the Start protocol.
7//!
8//! This crate implements [RFC-0007](https://github.com/hoprnet/rfc/tree/main/rfcs/RFC-0007-session-protocol).
9
10pub(crate) mod balancer;
11pub mod errors;
12mod manager;
13mod types;
14mod utils;
15
16pub use balancer::{MIN_BALANCER_SAMPLING_INTERVAL, SurbBalancerConfig};
17pub use hopr_network_types::types::*;
18pub use manager::{DispatchResult, MIN_SURB_BUFFER_DURATION, SessionManager, SessionManagerConfig};
19pub use types::{
20    ByteCapabilities, HoprSession, HoprSessionConfig, IncomingSession, ServiceId, SessionId, SessionTarget,
21};
22#[cfg(feature = "runtime-tokio")]
23pub use utils::transfer_session;
24
25/// Number of bytes that can be sent in a single Session protocol payload.
26///
27/// In other words, this is the effective payload capacity of a single Session segment.
28pub const SESSION_MTU: usize =
29    hopr_protocol_session::session_socket_mtu::<{ hopr_protocol_app::v1::ApplicationData::PAYLOAD_SIZE }>();
30
31/// Size of the HOPR SURB in bytes.
32///
33/// This is the re-export of [`hopr_crypto_packet::HoprSurb::SIZE`].
34pub const SURB_SIZE: usize = hopr_crypto_packet::HoprSurb::SIZE;
35
36flagset::flags! {
37    /// Individual capabilities of a Session.
38    #[repr(u8)]
39    #[derive(PartialOrd, Ord, strum::EnumString, strum::Display, serde_repr::Serialize_repr, serde_repr::Deserialize_repr)]
40    pub enum Capability : u8 {
41        /// Frame segmentation.
42        Segmentation = 0b0000_1000,
43        /// Frame retransmission (ACK-based)
44        ///
45        /// Implies [`Segmentation`].
46        RetransmissionAck = 0b0000_1100,
47        /// Frame retransmission (NACK-based)
48        ///
49        /// Implies [`Segmentation`].
50        RetransmissionNack = 0b000_1010,
51        /// Disable packet buffering.
52        ///
53        /// Implies [`Segmentation`].
54        NoDelay = 0b0000_1001,
55        /// Disable SURB-based egress rate control.
56        ///
57        /// This applies only to the recipient of the Session (Exit).
58        NoRateControl = 0b0001_0000,
59    }
60}
61
62/// Set of Session [capabilities](Capability).
63pub type Capabilities = flagset::FlagSet<Capability>;
64
65/// Configuration for the session.
66///
67/// Relevant primarily for the client, since the server is only
68/// a reactive component in regard to the session concept.
69#[derive(Debug, PartialEq, Clone, smart_default::SmartDefault)]
70pub struct SessionClientConfig {
71    /// The forward path options for the session.
72    #[default(RoutingOptions::Hops(hopr_primitive_types::bounded::BoundedSize::MIN))]
73    pub forward_path_options: RoutingOptions,
74    /// The return path options for the session.
75    #[default(RoutingOptions::Hops(hopr_primitive_types::bounded::BoundedSize::MIN))]
76    pub return_path_options: RoutingOptions,
77    /// Capabilities offered by the session.
78    #[default(_code = "Capability::Segmentation.into()")]
79    pub capabilities: Capabilities,
80    /// Optional pseudonym used for the session. Mostly useful for testing only.
81    #[default(None)]
82    pub pseudonym: Option<hopr_internal_types::protocol::HoprPseudonym>,
83    /// Enable automatic SURB management for the Session.
84    #[default(Some(SurbBalancerConfig::default()))]
85    pub surb_management: Option<SurbBalancerConfig>,
86    /// If set, the maximum number of possible SURBs will always be sent with Session data packets (if they fit).
87    ///
88    /// This does not affect `KeepAlive` messages used with SURB balancing, as they will always
89    /// carry the maximum number of SURBs possible. Setting this to `true` will put additional CPU
90    /// pressure on the local node as it will generate the maximum number of SURBs for each data packet.
91    ///
92    /// Set this to `true` only when the underlying traffic is highly asymmetric.
93    ///
94    /// Default is `false`.
95    #[default(false)]
96    pub always_max_out_surbs: bool,
97}
98
99#[cfg(test)]
100mod tests {
101    use hopr_crypto_packet::prelude::HoprPacket;
102    use hopr_crypto_random::Randomizable;
103    use hopr_internal_types::prelude::HoprPseudonym;
104    use hopr_protocol_app::v1::ApplicationData;
105    use hopr_protocol_session::session_socket_mtu;
106    use hopr_protocol_start::{
107        KeepAliveMessage, StartChallenge, StartErrorReason, StartErrorType, StartEstablished, StartInitiation,
108    };
109
110    use super::*;
111    use crate::types::HoprStartProtocol;
112
113    #[test]
114    fn test_session_mtu() {
115        assert_eq!(SESSION_MTU, session_socket_mtu::<{ ApplicationData::PAYLOAD_SIZE }>());
116        assert_eq!(1002, SESSION_MTU);
117    }
118
119    #[test]
120    fn hopr_start_protocol_messages_must_fit_within_hopr_packet() -> anyhow::Result<()> {
121        let msg = HoprStartProtocol::StartSession(StartInitiation {
122            challenge: StartChallenge::MAX,
123            target: SessionTarget::TcpStream(SealedHost::Plain(
124                "example-of-a-very-very-long-second-level-name.on-a-very-very-long-domain-name.info:65530".parse()?,
125            )),
126            capabilities: Capabilities::full().into(),
127            additional_data: 0xffffffff,
128        });
129
130        assert!(
131            msg.encode()?.1.len() <= HoprPacket::PAYLOAD_SIZE,
132            "StartSession must fit within {}",
133            HoprPacket::PAYLOAD_SIZE
134        );
135
136        let msg = HoprStartProtocol::SessionEstablished(StartEstablished {
137            orig_challenge: StartChallenge::MAX,
138            session_id: SessionId::new(u64::MAX, HoprPseudonym::random()),
139        });
140
141        assert!(
142            msg.encode()?.1.len() <= HoprPacket::PAYLOAD_SIZE,
143            "SessionEstablished must fit within {}",
144            HoprPacket::PAYLOAD_SIZE
145        );
146
147        let msg = HoprStartProtocol::SessionError(StartErrorType {
148            challenge: StartChallenge::MAX,
149            reason: StartErrorReason::NoSlotsAvailable,
150        });
151
152        assert!(
153            msg.encode()?.1.len() <= HoprPacket::PAYLOAD_SIZE,
154            "SessionError must fit within {}",
155            HoprPacket::PAYLOAD_SIZE
156        );
157
158        let msg = HoprStartProtocol::KeepAlive(KeepAliveMessage {
159            session_id: SessionId::new(u64::MAX, HoprPseudonym::random()),
160            flags: 0xff,
161            additional_data: 0xffffffff,
162        });
163        assert!(
164            msg.encode()?.1.len() <= HoprPacket::PAYLOAD_SIZE,
165            "KeepAlive must fit within {}",
166            HoprPacket::PAYLOAD_SIZE
167        );
168
169        Ok(())
170    }
171
172    #[test]
173    fn hopr_start_protocol_message_session_initiation_message_should_allow_for_at_least_one_surb() -> anyhow::Result<()>
174    {
175        let msg = HoprStartProtocol::StartSession(StartInitiation {
176            challenge: StartChallenge::MAX,
177            target: SessionTarget::TcpStream(SealedHost::Plain(
178                "example-of-a-very-very-long-second-level-name.on-a-very-very-long-domain-name.info:65530".parse()?,
179            )),
180            capabilities: Capabilities::full().into(),
181            additional_data: 0xffffffff,
182        });
183        let len = msg.encode()?.1.len();
184        assert!(
185            HoprPacket::max_surbs_with_message(len) >= 1,
186            "Hopr StartSession message size ({}) must allow for at least 1 SURB in packet",
187            len
188        );
189
190        Ok(())
191    }
192
193    #[test]
194    fn hopr_start_protocol_message_keep_alive_message_should_allow_for_maximum_surbs() -> anyhow::Result<()> {
195        let msg = HoprStartProtocol::KeepAlive(KeepAliveMessage {
196            session_id: SessionId::new(u64::MAX, HoprPseudonym::random()),
197            flags: 0xff,
198            additional_data: 0xffffffff,
199        });
200        let len = msg.encode()?.1.len();
201        assert_eq!(
202            KeepAliveMessage::<SessionId>::MIN_SURBS_PER_MESSAGE,
203            HoprPacket::MAX_SURBS_IN_PACKET
204        );
205        assert!(
206            HoprPacket::max_surbs_with_message(len) >= HoprPacket::MAX_SURBS_IN_PACKET,
207            "Hopr KeepAlive message size ({}) must allow for at least {} SURBs in packet",
208            len,
209            HoprPacket::MAX_SURBS_IN_PACKET
210        );
211
212        Ok(())
213    }
214}