use super::message::{ ConnectionId, Message, OutboundMessage, SubstreamId, SubstreamMessage, TransportMessage, }; use futures::{ io::{Error as IoError, ErrorKind}, AsyncRead, AsyncWrite, }; use log::debug; use nym_sphinx::addressing::clients::Recipient; use parking_lot::Mutex; use std::{ pin::Pin, sync::{ atomic::{AtomicU64, Ordering}, Arc, }, task::{Context, Poll}, }; use tokio::sync::{ mpsc::{UnboundedReceiver, UnboundedSender}, oneshot::Receiver, }; #[derive(Debug)] pub struct Substream { remote_recipient: Recipient, connection_id: ConnectionId, pub(crate) substream_id: SubstreamId, /// inbound messages; inbound_tx is in the corresponding Connection pub(crate) inbound_rx: UnboundedReceiver>, /// outbound messages; go directly to the mixnet outbound_tx: UnboundedSender, /// used to signal when the substream is closed close_rx: Receiver<()>, closed: Mutex, // buffer of data that's been written to the stream, // but not yet read by the application. unread_data: Mutex>, message_nonce: Arc, } impl Substream { pub(crate) fn new( remote_recipient: Recipient, connection_id: ConnectionId, substream_id: SubstreamId, inbound_rx: UnboundedReceiver>, outbound_tx: UnboundedSender, close_rx: Receiver<()>, message_nonce: Arc, ) -> Self { Substream { remote_recipient, connection_id, substream_id, inbound_rx, outbound_tx, close_rx, closed: Mutex::new(false), unread_data: Mutex::new(vec![]), message_nonce, } } fn check_closed(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Result<(), IoError> { let closed_err = IoError::other("stream closed"); // close_rx will return an error if the channel is closed (ie. sender was dropped), // or if it's empty let received_closed = self.close_rx.try_recv(); let mut closed = self.closed.lock(); if *closed { return Err(closed_err); } if received_closed.is_ok() { *closed = true; return Err(closed_err); } Ok(()) } } impl AsyncRead for Substream { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let closed_result = self.as_mut().check_closed(cx); if let Err(e) = closed_result { return Poll::Ready(Err(e)); } let inbound_rx_data = self.inbound_rx.poll_recv(cx); // first, write any previously unread data to the buf let mut unread_data = self.unread_data.lock(); let filled_len = if unread_data.len() > 0 { let unread_len = unread_data.len(); let buf_len = buf.len(); let copy_len = std::cmp::min(unread_len, buf_len); buf[..copy_len].copy_from_slice(&unread_data[..copy_len]); *unread_data = unread_data[copy_len..].to_vec(); copy_len } else { 0 }; if let Poll::Ready(Some(data)) = inbound_rx_data { if filled_len == buf.len() { // we've filled the buffer, so we'll have to save the rest for later let mut new = vec![]; new.extend(unread_data.drain(..)); new.extend(data.iter()); *unread_data = new; return Poll::Ready(Ok(filled_len)); } // otherwise, there's still room in the buffer, so we'll copy the rest of the data let remaining_len = buf.len() - filled_len; let data_len = data.len(); // we have more data than buffer room remaining, save the extra for later if remaining_len < data_len { unread_data.extend_from_slice(&data[remaining_len..]); } let copied = std::cmp::min(remaining_len, data_len); buf[filled_len..filled_len + copied].copy_from_slice(&data[..copied]); debug!("poll_read copied {} bytes: data {:?}", copied, buf); return Poll::Ready(Ok(copied)); } if filled_len > 0 { debug!("poll_read copied {} bytes: data {:?}", filled_len, buf); return Poll::Ready(Ok(filled_len)); } Poll::Pending } } impl AsyncWrite for Substream { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { if let Err(e) = self.as_mut().check_closed(cx) { return Poll::Ready(Err(e)); } let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); self.outbound_tx .send(OutboundMessage { recipient: self.remote_recipient, message: Message::TransportMessage(TransportMessage { nonce, id: self.connection_id.clone(), message: SubstreamMessage::new_with_data( self.substream_id.clone(), buf.to_vec(), ), }), }) .map_err(|e| IoError::other(format!("poll_write outbound_tx error: {}", e)))?; Poll::Ready(Ok(buf.len())) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if let Err(e) = self.check_closed(cx) { return Poll::Ready(Err(e)); } Poll::Ready(Ok(())) } fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); let mut closed = self.closed.lock(); if *closed { return Poll::Ready(Err(IoError::other("stream closed"))); } *closed = true; // send a close message to the mixnet self.outbound_tx .send(OutboundMessage { recipient: self.remote_recipient, message: Message::TransportMessage(TransportMessage { nonce, id: self.connection_id.clone(), message: SubstreamMessage::new_close(self.substream_id.clone()), }), }) .map_err(|e| IoError::other(format!("poll_close outbound_rx error: {}", e)))?; Poll::Ready(Ok(())) } } #[cfg(test)] mod test { use super::super::message::{ ConnectionId, Message, SubstreamId, SubstreamMessage, TransportMessage, }; use super::super::mixnet::initialize_mixnet; use super::Substream; use futures::{AsyncReadExt, AsyncWriteExt}; use nym_sdk::mixnet::MixnetClient; use nym_sphinx::addressing::clients::Recipient; use std::sync::atomic::AtomicU64; use std::sync::Arc; #[tokio::test] async fn test_substream_poll_read_unread_data() { let (outbound_tx, _) = tokio::sync::mpsc::unbounded_channel(); let connection_id = ConnectionId::generate(); let substream_id = SubstreamId::generate(); let (inbound_tx, inbound_rx) = tokio::sync::mpsc::unbounded_channel(); let (_, close_rx) = tokio::sync::oneshot::channel(); let mut substream = Substream::new( Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), connection_id, substream_id, inbound_rx, outbound_tx, close_rx, Arc::new(AtomicU64::new(1)), ); // test writing and reading w/ same length data let data = b"hello".to_vec(); inbound_tx.send(data.clone()).unwrap(); let mut buf = [0u8; 5]; let read_len = substream.read(&mut buf).await.unwrap(); assert_eq!(read_len, data.len()); assert_eq!(buf.to_vec(), data); // test writing data longer than read buffer let data = b"nootwashere".to_vec(); inbound_tx.send(data.clone()).unwrap(); let mut buf = [0u8; 4]; let read_len = substream.read(&mut buf).await.unwrap(); assert_eq!(read_len, buf.len()); assert_eq!(buf.to_vec(), b"noot".to_vec()); let mut buf = [0u8; 7]; let read_len = substream.read(&mut buf).await.unwrap(); assert_eq!(read_len, buf.len()); assert_eq!(buf.to_vec(), b"washere".to_vec()); // test read buffer larger than written data let data = b"nootwashere".to_vec(); inbound_tx.send(data.clone()).unwrap(); let mut buf = [0u8; 16]; let read_len = substream.read(&mut buf).await.unwrap(); assert_eq!(read_len, data.len()); assert_eq!(buf[..data.len()], data); assert_eq!(buf[data.len()..].to_vec(), vec![0u8; 16 - data.len()]); // test writing data longer than read buffer multiple times let data = b"nootwashere".to_vec(); inbound_tx.send(data.clone()).unwrap(); let mut buf = [0u8; 4]; let read_len = substream.read(&mut buf).await.unwrap(); assert_eq!(read_len, buf.len()); assert_eq!(buf.to_vec(), b"noot".to_vec()); let data = b"asdf".to_vec(); inbound_tx.send(data.clone()).unwrap(); let mut buf = [0u8; 4]; let read_len = substream.read(&mut buf).await.unwrap(); assert_eq!(read_len, buf.len()); assert_eq!(buf.to_vec(), b"wash".to_vec()); let mut buf = [0u8; 8]; let read_len = substream.read(&mut buf).await.unwrap(); assert_eq!(read_len, 7); assert_eq!(buf[..7], b"ereasdf".to_vec()); } #[tokio::test] async fn test_substream_read_write() { let client = MixnetClient::connect_new().await.unwrap(); let (self_address, mut mixnet_inbound_rx, outbound_tx) = initialize_mixnet(client, None).await.unwrap(); const MSG_INNER: &[u8] = "hello".as_bytes(); let connection_id = ConnectionId::generate(); let substream_id = SubstreamId::generate(); let (inbound_tx, inbound_rx) = tokio::sync::mpsc::unbounded_channel(); let (_, close_rx) = tokio::sync::oneshot::channel(); let mut substream = Substream::new( self_address, connection_id, substream_id, inbound_rx, outbound_tx, close_rx, Arc::new(AtomicU64::new(1)), ); // send message to ourselves over the mixnet substream.write_all(MSG_INNER).await.unwrap(); // receive full message over the mixnet let recv_msg = mixnet_inbound_rx.recv().await.unwrap(); match recv_msg.0 { Message::TransportMessage(TransportMessage { nonce, id: _, message: SubstreamMessage { substream_id: _, message_type: msg, }, }) => { assert_eq!(nonce, 1); match msg { super::super::message::SubstreamMessageType::Data(data) => { assert_eq!(data, MSG_INNER); // send message to substream inbound channel inbound_tx.send(data).unwrap(); } _ => panic!("unexpected message type"), } } _ => panic!("unexpected message"), } // read message from substream let mut buf = [0u8; MSG_INNER.len()]; substream.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, MSG_INNER); // close substream substream.close().await.unwrap(); // try to read/write to closed substream; should error substream.write_all(MSG_INNER).await.unwrap_err(); substream.read_exact(&mut buf).await.unwrap_err(); // assert a close message was sent over the mixnet let recv_msg = mixnet_inbound_rx.recv().await.unwrap(); match recv_msg.0 { Message::TransportMessage(TransportMessage { nonce: _, id: _, message: SubstreamMessage { substream_id: _, message_type: msg, }, }) => match msg { super::super::message::SubstreamMessageType::Close => {} _ => panic!("unexpected message type"), }, _ => panic!("unexpected message: {:?}", recv_msg.0), } } #[tokio::test] async fn test_substream_recv_close() { let client = MixnetClient::connect_new().await.unwrap(); let (self_address, _, outbound_tx) = initialize_mixnet(client, None).await.unwrap(); const MSG_INNER: &[u8] = "hello".as_bytes(); let connection_id = ConnectionId::generate(); let substream_id = SubstreamId::generate(); let (_, inbound_rx) = tokio::sync::mpsc::unbounded_channel(); let (close_tx, close_rx) = tokio::sync::oneshot::channel(); let mut substream = Substream::new( self_address, connection_id, substream_id, inbound_rx, outbound_tx, close_rx, Arc::new(AtomicU64::new(1)), ); // close substream close_tx.send(()).unwrap(); // try to read/write to closed substream; should error substream.write_all(MSG_INNER).await.unwrap_err(); let mut buf = [0u8; MSG_INNER.len()]; substream.read_exact(&mut buf).await.unwrap_err(); } }