4a1ce8154a
* Clippy in wallet & sdk
* Clippy in wallet
* Pin rust to 1.86 in builder
* apply changes from b7da75a18c
* missing nym-node features
* Box all the things
* additional boxes in the wallet
* post rebasing clippy
---------
Co-authored-by: dynco-nym <173912580+dynco-nym@users.noreply.github.com>
413 lines
14 KiB
Rust
413 lines
14 KiB
Rust
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<Vec<u8>>,
|
|
|
|
/// outbound messages; go directly to the mixnet
|
|
outbound_tx: UnboundedSender<OutboundMessage>,
|
|
|
|
/// used to signal when the substream is closed
|
|
close_rx: Receiver<()>,
|
|
closed: Mutex<bool>,
|
|
|
|
// buffer of data that's been written to the stream,
|
|
// but not yet read by the application.
|
|
unread_data: Mutex<Vec<u8>>,
|
|
|
|
message_nonce: Arc<AtomicU64>,
|
|
}
|
|
|
|
impl Substream {
|
|
pub(crate) fn new(
|
|
remote_recipient: Recipient,
|
|
connection_id: ConnectionId,
|
|
substream_id: SubstreamId,
|
|
inbound_rx: UnboundedReceiver<Vec<u8>>,
|
|
outbound_tx: UnboundedSender<OutboundMessage>,
|
|
close_rx: Receiver<()>,
|
|
message_nonce: Arc<AtomicU64>,
|
|
) -> 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<Result<usize, IoError>> {
|
|
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<Result<usize, IoError>> {
|
|
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<Result<(), IoError>> {
|
|
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<Result<(), IoError>> {
|
|
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();
|
|
}
|
|
}
|