From 73016ed68757d913b14cce4bc2466df9dad6eeed Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 7 Nov 2025 11:52:01 +0000 Subject: [PATCH] Docs first pass --- .../mixnet_stream_wrapper_ipr.rs | 186 +++++++++++++++++- 1 file changed, 182 insertions(+), 4 deletions(-) diff --git a/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs b/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs index 7dccb52a90..5e6591e03b 100644 --- a/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs +++ b/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs @@ -49,10 +49,16 @@ pub enum ConnectionState { Connected, } -// TODO INLINE DOCS // TODO MAKE NETWORK CONFIGURABLE // TODO USERAGENT +/// Create a Nym API client with the provided URLs. +/// +/// # Arguments +/// * `nym_api_urls` - Vector of API URLs to use for the client +/// +/// # Returns +/// Configured `nym_http_api_client::Client` or an error if URLs are invalid or empty fn create_nym_api_client(nym_api_urls: Vec) -> Result { // TODO do something proper with this let user_agent = UserAgent { @@ -82,6 +88,16 @@ fn create_nym_api_client(nym_api_urls: Vec) -> Result Result, Error> { @@ -120,6 +136,15 @@ async fn retrieve_exit_nodes_with_performance( Ok(described) } +/// Select a random IPR (IP Packet Router) from available exit gateways. +/// +/// Currently selects the gateway with the highest performance score. +/// +/// # Arguments +/// * `client` - Nym API client to use for gateway discovery +/// +/// # Returns +/// `Recipient` address of the selected IPR async fn get_random_ipr(client: nym_http_api_client::Client) -> Result { let nodes = retrieve_exit_nodes_with_performance(client).await?; info!("Found {} Exit Gateways", nodes.len()); @@ -142,7 +167,18 @@ async fn get_random_ipr(client: nym_http_api_client::Client) -> Result Result { // JS: I think you need some bootstrapping here, something would need to be passed to `new()` // to indicate what endpoints to use // again, for now I'm default to mainnet as you did before let mainnet_network_defaults = crate::NymNetworkDetails::new_mainnet(); - let api_client = create_nym_api_client(mainnet_network_defaults.nym_api_urls.unwrap_or_default())?; let ipr_address = get_random_ipr(api_client).await?; + let stream = MixStream::new(None, Some(Recipient::from(ipr_address)), None).await; Ok(Self { @@ -172,15 +215,33 @@ impl IpMixStream { }) } + /// Get the Nym network address of this stream. + /// + /// # Returns + /// Reference to the stream's `Recipient` address pub fn nym_address(&self) -> &Recipient { self.stream.client.nym_address() } + /// Send an IP packet request to the connected IPR. + /// + /// # Arguments + /// * `request` - The `IpPacketRequest` to send + /// + /// # Returns + /// `Ok(())` on success, error otherwise async fn send_ipr_request(&mut self, request: IpPacketRequest) -> Result<(), Error> { let request_bytes = request.to_bytes()?; self.stream.send(&request_bytes).await } + /// Establish tunnel connection with the IPR and allocate IP addresses. + /// + /// Sends a connect request and waits for IP allocation response. + /// Updates connection state and stores allocated IPs on success. + /// + /// # Returns + /// `IpPair` containing allocated IPv4 and IPv6 addresses pub async fn connect_tunnel(&mut self) -> Result { if self.connection_state != ConnectionState::Disconnected { return Err(Error::IprStreamClientAlreadyConnectedOrConnecting); @@ -207,6 +268,10 @@ impl IpMixStream { } } + /// Internal connection logic for establishing the tunnel. + /// + /// # Returns + /// `IpPair` containing allocated IP addresses async fn connect_inner(&mut self) -> Result { let (request, request_id) = IpPacketRequest::new_connect_request(None); debug!("Sending connect request with ID: {}", request_id); @@ -215,6 +280,15 @@ impl IpMixStream { self.listen_for_connect_response(request_id).await } + /// Listen for and process the connect response from the IPR. + /// + /// Waits up to `IPR_CONNECT_TIMEOUT` for a response matching the request ID. + /// + /// # Arguments + /// * `request_id` - ID of the connect request to match against responses + /// + /// # Returns + /// `IpPair` containing allocated IP addresses async fn listen_for_connect_response(&mut self, request_id: u64) -> Result { let timeout = tokio::time::sleep(IPR_CONNECT_TIMEOUT); tokio::pin!(timeout); @@ -254,6 +328,15 @@ impl IpMixStream { } } + /// Handle the connect response from the IPR. + /// + /// Extracts IP allocation from successful response or returns error on failure. + /// + /// # Arguments + /// * `response` - The `IpPacketResponse` to process + /// + /// # Returns + /// `IpPair` on successful connection, error otherwise async fn handle_connect_response(&self, response: IpPacketResponse) -> Result { let control_response = match response.data { IpPacketResponseData::Control(c) => c, @@ -271,6 +354,15 @@ impl IpMixStream { } } + /// Send an IP packet through the tunnel. + /// + /// Requires an active tunnel connection. + /// + /// # Arguments + /// * `packet` - Raw IP packet bytes to send + /// + /// # Returns + /// `Ok(())` on success, error if not connected or send fails pub async fn send_ip_packet(&mut self, packet: &[u8]) -> Result<(), Error> { if self.connection_state != ConnectionState::Connected { return Err(Error::IprStreamClientNotConnected); @@ -279,6 +371,13 @@ impl IpMixStream { self.send_ipr_request(request).await } + /// Handle incoming messages from the mixnet. + /// + /// Processes reconstructed messages and extracts IP packets, disconnect signals, + /// or self-ping messages. Times out after 10 seconds if no message received. + /// + /// # Returns + /// Vector of received IP packet data as `Bytes`, empty vector if no packets or on timeout pub async fn handle_incoming(&mut self) -> Result, Error> { let mut framed = self.stream.framed_read(); @@ -314,10 +413,18 @@ impl IpMixStream { } } + /// Get the allocated IP addresses for this tunnel. + /// + /// # Returns + /// `Some(&IpPair)` if IPs are allocated, `None` otherwise pub fn allocated_ips(&self) -> Option<&IpPair> { self.allocated_ips.as_ref() } + /// Check if the tunnel is currently connected. + /// + /// # Returns + /// `true` if connected, `false` otherwise pub fn is_connected(&self) -> bool { self.connection_state == ConnectionState::Connected } @@ -329,7 +436,14 @@ impl IpMixStream { debug!("Disconnected"); } - /// Split for concurrent read/write (like TcpStream::Split) into IpMixnetStreamReader and IpMixnetStreamWriter. + /// Split the stream into separate reader and writer halves. + /// + /// Enables concurrent read and write operations similar to `TcpStream::split()`. + /// State updates (connection status, allocated IPs) are synchronized between halves + /// via oneshot channels. + /// + /// # Returns + /// Tuple of `(IpMixStreamReader, IpMixStreamWriter)` pub fn split(self) -> (IpMixStreamReader, IpMixStreamWriter) { debug!("Splitting IpMixStream"); let local_addr = *self.stream.client.nym_address(); @@ -394,6 +508,14 @@ impl AsyncWrite for IpMixStream { } } +/// Read half of a split `IpMixStream`. +/// +/// Handles incoming messages from the mixnet and processes IP packets, disconnect +/// signals, and control messages. Synchronizes connection state changes with the +/// writer half via oneshot channels. +/// +/// Created by calling `IpMixStream::split()`. Implements `AsyncRead` for standard +/// async read operations. pub struct IpMixStreamReader { stream_reader: MixStreamReader, listener: IprListener, @@ -404,10 +526,22 @@ pub struct IpMixStreamReader { } impl IpMixStreamReader { + /// Get the Nym network address of this reader. + /// + /// # Returns + /// The reader's `Recipient` address pub fn nym_address(self) -> Recipient { *self.stream_reader.local_addr() } + /// Handle incoming messages from the mixnet (reader half). + /// + /// Processes reconstructed messages and extracts IP packets, disconnect signals, + /// or self-ping messages. Updates connection state and notifies writer on disconnect. + /// Times out after 10 seconds if no message received. + /// + /// # Returns + /// Vector of received IP packet data as `Bytes`, empty vector if no packets or on timeout pub async fn handle_incoming(&mut self) -> Result, Error> { let mut framed = self.stream_reader.framed(); @@ -450,10 +584,18 @@ impl IpMixStreamReader { } } + /// Get the allocated IP addresses (reader half). + /// + /// # Returns + /// `Some(&IpPair)` if IPs are allocated, `None` otherwise pub fn allocated_ips(&self) -> Option<&IpPair> { self.allocated_ips.as_ref() } + /// Check if the tunnel is currently connected (reader half). + /// + /// # Returns + /// `true` if connected, `false` otherwise pub fn is_connected(&self) -> bool { self.connection_state == ConnectionState::Connected } @@ -469,6 +611,13 @@ impl AsyncRead for IpMixStreamReader { } } +/// Write half of a split `IpMixStream`. +/// +/// Handles outgoing IP packets to the mixnet. Receives connection state updates +/// from the reader half via oneshot channels to maintain synchronized state. +/// +/// Created by calling `IpMixStream::split()`. Implements `AsyncWrite` for standard +/// async write operations. pub struct IpMixStreamWriter { stream_writer: MixStreamWriter, local_addr: Recipient, @@ -479,15 +628,36 @@ pub struct IpMixStreamWriter { } impl IpMixStreamWriter { + /// Get the Nym network address of this writer. + /// + /// # Returns + /// Reference to the writer's `Recipient` address pub fn nym_address(&self) -> &Recipient { &self.local_addr } + /// Send an IP packet request to the IPR (writer half). + /// + /// # Arguments + /// * `request` - The `IpPacketRequest` to send + /// + /// # Returns + /// `Ok(())` on success, error otherwise async fn send_ipr_request(&mut self, request: IpPacketRequest) -> Result<(), Error> { let request_bytes = request.to_bytes()?; self.stream_writer.write_bytes(&request_bytes).await } + /// Send an IP packet through the tunnel (writer half). + /// + /// Checks for state updates from reader before sending. + /// Requires an active tunnel connection. + /// + /// # Arguments + /// * `packet` - Raw IP packet bytes to send + /// + /// # Returns + /// `Ok(())` on success, error if not connected or send fails pub async fn send_ip_packet(&mut self, packet: &[u8]) -> Result<(), Error> { // Check for state updates from reader if let Some(mut rx) = self.state_rx.take() { @@ -514,10 +684,18 @@ impl IpMixStreamWriter { self.send_ipr_request(request).await } + /// Get the allocated IP addresses (writer half). + /// + /// # Returns + /// `Some(&IpPair)` if IPs are allocated, `None` otherwise pub fn allocated_ips(&self) -> Option<&IpPair> { self.allocated_ips.as_ref() } + /// Check if the tunnel is connected (writer half). + /// + /// # Returns + /// `true` if connected, `false` otherwise pub fn is_connected(&self) -> bool { self.connection_state == ConnectionState::Connected }