hopr_transport_session/
lib.rs

1//! [`Session`] 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::{ByteCapabilities, IncomingSession, ServiceId, Session, SessionId, SessionTarget};
20#[cfg(feature = "runtime-tokio")]
21pub use utils::transfer_session;
22
23/// Number of bytes that can be sent in a single Session protocol payload.
24pub const SESSION_MTU: usize =
25    hopr_protocol_session::session_socket_mtu::<{ hopr_protocol_app::v1::ApplicationData::PAYLOAD_SIZE }>();
26
27/// Size of the HOPR SURB in bytes.
28///
29/// This is the re-export of [`hopr_crypto_packet::HoprSurb::SIZE`].
30pub const SURB_SIZE: usize = hopr_crypto_packet::HoprSurb::SIZE;
31
32flagset::flags! {
33    /// Individual capabilities of a Session.
34    #[repr(u8)]
35    #[derive(PartialOrd, Ord, strum::EnumString, strum::Display, serde_repr::Serialize_repr, serde_repr::Deserialize_repr)]
36    pub enum Capability : u8 {
37        /// Frame segmentation.
38        Segmentation = 0b0000_1000,
39        /// Frame retransmission (ACK-based)
40        ///
41        /// Implies [`Segmentation`].
42        RetransmissionAck = 0b0000_1100,
43        /// Frame retransmission (NACK-based)
44        ///
45        /// Implies [`Segmentation`].
46        RetransmissionNack = 0b000_1010,
47        /// Disable packet buffering.
48        ///
49        /// Implies [`Segmentation`].
50        NoDelay = 0b0000_1001,
51        /// Disable SURB-based egress rate control.
52        ///
53        /// This applies only to the recipient of the Session (Exit).
54        NoRateControl = 0b0001_0000,
55    }
56}
57
58/// Set of Session [capabilities](Capability).
59pub type Capabilities = flagset::FlagSet<Capability>;
60
61/// Configuration for the session.
62///
63/// Relevant primarily for the client, since the server is only
64/// a reactive component in regard to the session concept.
65#[derive(Debug, PartialEq, Clone, smart_default::SmartDefault)]
66pub struct SessionClientConfig {
67    /// The forward path options for the session.
68    #[default(RoutingOptions::Hops(hopr_primitive_types::bounded::BoundedSize::MIN))]
69    pub forward_path_options: RoutingOptions,
70    /// The return path options for the session.
71    #[default(RoutingOptions::Hops(hopr_primitive_types::bounded::BoundedSize::MIN))]
72    pub return_path_options: RoutingOptions,
73    /// Capabilities offered by the session.
74    #[default(_code = "Capability::Segmentation.into()")]
75    pub capabilities: Capabilities,
76    /// Optional pseudonym used for the session. Mostly useful for testing only.
77    #[default(None)]
78    pub pseudonym: Option<hopr_internal_types::protocol::HoprPseudonym>,
79    /// Enable automatic SURB management for the Session.
80    #[default(Some(SurbBalancerConfig::default()))]
81    pub surb_management: Option<SurbBalancerConfig>,
82}
83
84#[cfg(test)]
85mod tests {
86    use hopr_crypto_packet::prelude::HoprPacket;
87    use hopr_crypto_random::Randomizable;
88    use hopr_internal_types::prelude::HoprPseudonym;
89    use hopr_protocol_app::v1::ApplicationData;
90    use hopr_protocol_session::session_socket_mtu;
91    use hopr_protocol_start::{
92        KeepAliveMessage, StartChallenge, StartErrorReason, StartErrorType, StartEstablished, StartInitiation,
93    };
94
95    use super::*;
96    use crate::types::HoprStartProtocol;
97
98    #[test]
99    fn test_session_mtu() {
100        assert_eq!(SESSION_MTU, session_socket_mtu::<{ ApplicationData::PAYLOAD_SIZE }>());
101        assert_eq!(1002, SESSION_MTU);
102    }
103
104    #[test]
105    fn hopr_start_protocol_messages_must_fit_within_hopr_packet() -> anyhow::Result<()> {
106        let msg = HoprStartProtocol::StartSession(StartInitiation {
107            challenge: StartChallenge::MAX,
108            target: SessionTarget::TcpStream(SealedHost::Plain(
109                "example-of-a-very-very-long-second-level-name.on-a-very-very-long-domain-name.info:65530".parse()?,
110            )),
111            capabilities: Capabilities::full().into(),
112            additional_data: 0xffffffff,
113        });
114
115        assert!(
116            msg.encode()?.1.len() <= HoprPacket::PAYLOAD_SIZE,
117            "StartSession must fit within {}",
118            HoprPacket::PAYLOAD_SIZE
119        );
120
121        let msg = HoprStartProtocol::SessionEstablished(StartEstablished {
122            orig_challenge: StartChallenge::MAX,
123            session_id: SessionId::new(u64::MAX, HoprPseudonym::random()),
124        });
125
126        assert!(
127            msg.encode()?.1.len() <= HoprPacket::PAYLOAD_SIZE,
128            "SessionEstablished must fit within {}",
129            HoprPacket::PAYLOAD_SIZE
130        );
131
132        let msg = HoprStartProtocol::SessionError(StartErrorType {
133            challenge: StartChallenge::MAX,
134            reason: StartErrorReason::NoSlotsAvailable,
135        });
136
137        assert!(
138            msg.encode()?.1.len() <= HoprPacket::PAYLOAD_SIZE,
139            "SessionError must fit within {}",
140            HoprPacket::PAYLOAD_SIZE
141        );
142
143        let msg = HoprStartProtocol::KeepAlive(KeepAliveMessage {
144            session_id: SessionId::new(u64::MAX, HoprPseudonym::random()),
145            flags: 0xff,
146            additional_data: 0xffffffff,
147        });
148        assert!(
149            msg.encode()?.1.len() <= HoprPacket::PAYLOAD_SIZE,
150            "KeepAlive must fit within {}",
151            HoprPacket::PAYLOAD_SIZE
152        );
153
154        Ok(())
155    }
156
157    #[test]
158    fn hopr_start_protocol_message_session_initiation_message_should_allow_for_at_least_one_surb() -> anyhow::Result<()>
159    {
160        let msg = HoprStartProtocol::StartSession(StartInitiation {
161            challenge: StartChallenge::MAX,
162            target: SessionTarget::TcpStream(SealedHost::Plain(
163                "example-of-a-very-very-long-second-level-name.on-a-very-very-long-domain-name.info:65530".parse()?,
164            )),
165            capabilities: Capabilities::full().into(),
166            additional_data: 0xffffffff,
167        });
168        let len = msg.encode()?.1.len();
169        assert!(
170            HoprPacket::max_surbs_with_message(len) >= 1,
171            "Hopr StartSession message size ({}) must allow for at least 1 SURB in packet",
172            len
173        );
174
175        Ok(())
176    }
177
178    #[test]
179    fn hopr_start_protocol_message_keep_alive_message_should_allow_for_maximum_surbs() -> anyhow::Result<()> {
180        let msg = HoprStartProtocol::KeepAlive(KeepAliveMessage {
181            session_id: SessionId::new(u64::MAX, HoprPseudonym::random()),
182            flags: 0xff,
183            additional_data: 0xffffffff,
184        });
185        let len = msg.encode()?.1.len();
186        assert_eq!(
187            KeepAliveMessage::<SessionId>::MIN_SURBS_PER_MESSAGE,
188            HoprPacket::MAX_SURBS_IN_PACKET
189        );
190        assert!(
191            HoprPacket::max_surbs_with_message(len) >= HoprPacket::MAX_SURBS_IN_PACKET,
192            "Hopr KeepAlive message size ({}) must allow for at least {} SURBs in packet",
193            len,
194            HoprPacket::MAX_SURBS_IN_PACKET
195        );
196
197        Ok(())
198    }
199}