From 06da688ea4f5a43ed7db3c2ddc04a8476d507fb6 Mon Sep 17 00:00:00 2001 From: lukas Date: Sun, 29 Jan 2023 11:52:10 +0100 Subject: [PATCH 01/26] Add Android app to clients --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 252c37c4..84f4068e 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ If you don't fear touching code and want to contribute, `./src/lib.rs`, `./src/t ## Applications using Wormhole Rust as library - [Warp](https://gitlab.gnome.org/World/warp), a GUI client using Gtk +- [Wormhole File Transfer](https://gitlab.com/lukas-heiligenbrunner/wormhole), a Android client using Flutter (feel free to add yours) From 649f4cd26a74740ca7d8ebaa2a5df17e36c3246f Mon Sep 17 00:00:00 2001 From: piegames Date: Sat, 21 Jan 2023 11:25:17 +0100 Subject: [PATCH 02/26] Transit: decouple IO from TcpStream --- changelog.md | 2 + src/forwarding.rs | 12 +-- src/transfer.rs | 12 +-- src/transfer/v1.rs | 12 +-- src/transit.rs | 232 +++++++++++++++++++++++++----------------- src/transit/crypto.rs | 54 ++++++---- 6 files changed, 193 insertions(+), 131 deletions(-) diff --git a/changelog.md b/changelog.md index 14a983e6..45053253 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,8 @@ ## Unreleased +- \[lib\]\[breaking\] replaced `transit::TransitInfo` with a struct containing the address, the old enum has been renamed to `transit::ConnectionType`. + ## Version 0.6.0 - Add shell completion support for the CLI diff --git a/src/forwarding.rs b/src/forwarding.rs index 55dc45cf..03dc3a8e 100644 --- a/src/forwarding.rs +++ b/src/forwarding.rs @@ -135,7 +135,7 @@ impl ForwardingError { /// as the value. pub async fn serve( mut wormhole: Wormhole, - transit_handler: impl FnOnce(transit::TransitInfo, std::net::SocketAddr), + transit_handler: impl FnOnce(transit::TransitInfo), relay_hints: Vec, targets: Vec<(Option, u16)>, cancel: impl Future, @@ -190,7 +190,7 @@ pub async fn serve( }, }; - let (mut transit, info, addr) = match connector + let (mut transit, info) = match connector .leader_connect( wormhole.key().derive_transit_key(wormhole.appid()), peer_version.transit_abilities, @@ -207,7 +207,7 @@ pub async fn serve( return Err(error); }, }; - transit_handler(info, addr); + transit_handler(info); /* We got a transit, now close the Wormhole */ wormhole.close().await?; @@ -518,7 +518,7 @@ impl ForwardingServe { /// no more than 1024 ports may be forwarded at once. pub async fn connect( mut wormhole: Wormhole, - transit_handler: impl FnOnce(transit::TransitInfo, std::net::SocketAddr), + transit_handler: impl FnOnce(transit::TransitInfo), relay_hints: Vec, bind_address: Option, custom_ports: &[u16], @@ -561,7 +561,7 @@ pub async fn connect( }, }; - let (mut transit, info, addr) = match connector + let (mut transit, info) = match connector .follower_connect( wormhole.key().derive_transit_key(wormhole.appid()), peer_version.transit_abilities, @@ -578,7 +578,7 @@ pub async fn connect( return Err(error); }, }; - transit_handler(info, addr); + transit_handler(info); /* We got a transit, now close the Wormhole */ wormhole.close().await?; diff --git a/src/transfer.rs b/src/transfer.rs index 08e620ff..36f57437 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -214,7 +214,7 @@ pub async fn send_file_or_folder( where N: AsRef, M: AsRef, - G: FnOnce(transit::TransitInfo, std::net::SocketAddr), + G: FnOnce(transit::TransitInfo), H: FnMut(u64, u64) + 'static, { use async_std::fs::File; @@ -271,7 +271,7 @@ pub async fn send_file( where F: AsyncRead + Unpin, N: Into, - G: FnOnce(transit::TransitInfo, std::net::SocketAddr), + G: FnOnce(transit::TransitInfo), H: FnMut(u64, u64) + 'static, { let _peer_version: AppVersion = serde_json::from_value(wormhole.peer_version.clone())?; @@ -312,7 +312,7 @@ pub async fn send_folder( where N: Into, M: Into, - G: FnOnce(transit::TransitInfo, std::net::SocketAddr), + G: FnOnce(transit::TransitInfo), H: FnMut(u64, u64) + 'static, { v1::send_folder( @@ -453,7 +453,7 @@ impl ReceiveRequest { ) -> Result<(), TransferError> where F: FnMut(u64, u64) + 'static, - G: FnOnce(transit::TransitInfo, std::net::SocketAddr), + G: FnOnce(transit::TransitInfo), W: AsyncWrite + Unpin, { let run = Box::pin(async { @@ -463,7 +463,7 @@ impl ReceiveRequest { .send_json(&PeerMessage::file_ack("ok")) .await?; - let (mut transit, info, addr) = self + let (mut transit, info) = self .connector .follower_connect( self.wormhole @@ -473,7 +473,7 @@ impl ReceiveRequest { self.their_hints.clone(), ) .await?; - transit_handler(info, addr); + transit_handler(info); debug!("Beginning file transfer"); v1::tcp_file_receive( diff --git a/src/transfer/v1.rs b/src/transfer/v1.rs index 1049c404..be758490 100644 --- a/src/transfer/v1.rs +++ b/src/transfer/v1.rs @@ -19,7 +19,7 @@ pub async fn send_file( where F: AsyncRead + Unpin, N: Into, - G: FnOnce(transit::TransitInfo, std::net::SocketAddr), + G: FnOnce(transit::TransitInfo), H: FnMut(u64, u64) + 'static, { let run = Box::pin(async { @@ -76,14 +76,14 @@ where } } - let (mut transit, info, addr) = connector + let (mut transit, info) = connector .leader_connect( wormhole.key().derive_transit_key(wormhole.appid()), their_abilities, Arc::new(their_hints), ) .await?; - transit_handler(info, addr); + transit_handler(info); debug!("Beginning file transfer"); @@ -121,7 +121,7 @@ pub async fn send_folder( where N: Into, M: Into, - G: FnOnce(transit::TransitInfo, std::net::SocketAddr), + G: FnOnce(transit::TransitInfo), H: FnMut(u64, u64) + 'static, { let run = Box::pin(async { @@ -224,14 +224,14 @@ where }, } - let (mut transit, info, addr) = connector + let (mut transit, info) = connector .leader_connect( wormhole.key().derive_transit_key(wormhole.appid()), their_abilities, Arc::new(their_hints), ) .await?; - transit_handler(info, addr); + transit_handler(info); debug!("Beginning file transfer"); diff --git a/src/transit.rs b/src/transit.rs index 76ac8b8b..5d1838a8 100644 --- a/src/transit.rs +++ b/src/transit.rs @@ -20,6 +20,7 @@ use async_std::{ io::{prelude::WriteExt, ReadExt}, net::{TcpListener, TcpStream}, }; +use futures::io::{AsyncRead, AsyncWrite}; #[allow(unused_imports)] /* We need them for the docs */ use futures::{future::TryFutureExt, Sink, SinkExt, Stream, StreamExt, TryStreamExt}; use log::*; @@ -554,14 +555,30 @@ impl TryFrom<&DirectHint> for SocketAddr { } } +/// Direct or relay #[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] -pub enum TransitInfo { +pub enum ConnectionType { + /// We are directly connected to our peer Direct, + /// We are connected to a relay server, and may even know its name Relay { name: Option }, } -type TransitConnection = (TcpStream, TransitInfo); +/// Metadata for the established transit connection +#[derive(Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub struct TransitInfo { + /// Whether we are connected directly or via a relay server + pub conn_type: ConnectionType, + /// Target address of our connection. This may be our peer, or the relay server. + /// This says nothing about the actual transport protocol used. + pub peer_addr: SocketAddr, + // Prevent exhaustive destructuring for future proofing + _unused: (), +} + +type TransitConnection = (Box, TransitInfo); fn set_socket_opts(socket: &socket2::Socket) -> std::io::Result<()> { socket.set_nonblocking(true)?; @@ -741,31 +758,37 @@ async fn get_external_ip() -> Result<(SocketAddr, TcpStream), StunError> { /// ```no_run /// use magic_wormhole as mw; /// # #[async_std::main] async fn main() -> Result<(), mw::transit::TransitConnectError> { -/// # let derived_key = todo!(); -/// # let their_abilities = todo!(); -/// # let their_hints = todo!(); -/// let connector: mw::transit::TransitConnector = todo!("transit::init(…).await?"); -/// let (mut transit, info, addr) = connector +/// # let derived_key = unimplemented!(); +/// # let their_abilities = unimplemented!(); +/// # let their_hints = unimplemented!(); +/// let connector: mw::transit::TransitConnector = unimplemented!("transit::init(…).await?"); +/// let (mut transit, info) = connector /// .leader_connect(derived_key, their_abilities, their_hints) /// .await?; -/// mw::transit::log_transit_connection(info, addr); +/// mw::transit::log_transit_connection(info); /// # Ok(()) /// # } /// ``` -pub fn log_transit_connection(info: TransitInfo, peer_addr: SocketAddr) { - match info { - TransitInfo::Direct => { - log::info!("Established direct transit connection to '{}'", peer_addr,); +pub fn log_transit_connection(info: TransitInfo) { + match info.conn_type { + ConnectionType::Direct => { + log::info!( + "Established direct transit connection to '{}'", + info.peer_addr, + ); }, - TransitInfo::Relay { name: Some(name) } => { + ConnectionType::Relay { name: Some(name) } => { log::info!( "Established transit connection via relay '{}' ({})", name, - peer_addr, + info.peer_addr, ); }, - TransitInfo::Relay { name: None } => { - log::info!("Established transit connection via relay ({})", peer_addr,); + ConnectionType::Relay { name: None } => { + log::info!( + "Established transit connection via relay ({})", + info.peer_addr, + ); }, } } @@ -939,7 +962,7 @@ impl TransitConnector { transit_key: Key, their_abilities: Abilities, their_hints: Arc, - ) -> Result<(Transit, TransitInfo, SocketAddr), TransitConnectError> { + ) -> Result<(Transit, TransitInfo), TransitConnectError> { let Self { sockets, our_abilities, @@ -969,7 +992,7 @@ impl TransitConnector { }), ); - let (mut transit, mut host_type) = async_std::future::timeout( + let (mut transit, mut finalizer, mut conn_info) = async_std::future::timeout( std::time::Duration::from_secs(60), connection_stream.next(), ) @@ -980,7 +1003,7 @@ impl TransitConnector { })? .ok_or(TransitConnectError::Handshake)?; - if host_type != TransitInfo::Direct && our_abilities.can_direct() { + if conn_info.conn_type != ConnectionType::Direct && our_abilities.can_direct() { log::debug!( "Established transit connection over relay. Trying to find a direct connection …" ); @@ -995,11 +1018,14 @@ impl TransitConnector { elapsed.mul_f32(0.3) }; let _ = async_std::future::timeout(to_wait, async { - while let Some((new_transit, new_host_type)) = connection_stream.next().await { + while let Some((new_transit, new_finalizer, new_conn_info)) = + connection_stream.next().await + { /* We already got a connection, so we're only interested in direct ones */ - if new_host_type == TransitInfo::Direct { + if new_conn_info.conn_type == ConnectionType::Direct { transit = new_transit; - host_type = new_host_type; + finalizer = new_finalizer; + conn_info = new_conn_info; log::debug!("Found direct connection; using that instead."); break; } @@ -1016,17 +1042,23 @@ impl TransitConnector { */ std::mem::drop(connection_stream); - let (mut socket, finalizer) = transit; let (tx, rx) = finalizer - .handshake_finalize(&mut socket) + .handshake_finalize(&mut transit) .await .map_err(|e| { log::debug!("`handshake_finalize` failed: {e}"); TransitConnectError::Handshake })?; - let addr = socket.peer_addr().unwrap(); - Ok((Transit { socket, tx, rx }, host_type, addr)) + // let socket = Box::new(socket) as Box; + Ok(( + Transit { + socket: transit, + tx, + rx, + }, + conn_info, + )) } /** @@ -1037,7 +1069,7 @@ impl TransitConnector { transit_key: Key, their_abilities: Abilities, their_hints: Arc, - ) -> Result<(Transit, TransitInfo, SocketAddr), TransitConnectError> { + ) -> Result<(Transit, TransitInfo), TransitConnectError> { let Self { sockets, our_abilities, @@ -1072,8 +1104,7 @@ impl TransitConnector { ) .await { - Ok(Some(((mut socket, finalizer), host_type))) => { - let addr = socket.peer_addr().unwrap(); + Ok(Some((mut socket, finalizer, conn_info))) => { let (tx, rx) = finalizer .handshake_finalize(&mut socket) .await @@ -1081,8 +1112,9 @@ impl TransitConnector { log::debug!("`handshake_finalize` failed: {e}"); TransitConnectError::Handshake })?; + // let socket = Box::new(socket) as Box; - Ok((Transit { socket, tx, rx }, host_type, addr)) + Ok((Transit { socket, tx, rx }, conn_info)) }, Ok(None) | Err(_) => { log::debug!("`follower_connect` timed out"); @@ -1115,8 +1147,31 @@ impl TransitConnector { their_abilities: Abilities, their_hints: Arc, socket: Option<(MaybeConnectedSocket, TcpListener)>, - ) -> impl Stream> - + 'static { + ) -> impl Stream> + 'static { + /* Take a tcp connection and transform it into a `TransitConnection` (mainly set timeouts) */ + fn wrap_tcp_connection( + socket: TcpStream, + conn_type: ConnectionType, + ) -> Result { + /* Set proper read and write timeouts. This will temporarily set the socket into blocking mode :/ */ + // https://github.com/async-rs/async-std/issues/499 + let socket = std::net::TcpStream::try_from(socket) + .expect("Internal error: this should not fail because we never cloned the socket"); + socket.set_write_timeout(Some(std::time::Duration::from_secs(120)))?; + socket.set_read_timeout(Some(std::time::Duration::from_secs(120)))?; + let socket: TcpStream = socket.into(); + + let info = TransitInfo { + conn_type, + peer_addr: socket + .peer_addr() + .expect("Internal error: socket must be IP"), + _unused: (), + }; + + Ok((Box::new(socket), info)) + } + /* Have socket => can direct */ assert!(socket.is_none() || our_abilities.can_direct()); @@ -1165,7 +1220,8 @@ impl TransitConnector { log::debug!("Connecting directly to {}", dest_addr); let socket = connect_custom(&local_addr, &dest_addr.into()).await?; log::debug!("Connected to {}!", dest_addr); - Ok((socket, TransitInfo::Direct)) + + wrap_tcp_connection(socket, ConnectionType::Direct) } }) .map(|fut| Box::pin(fut) as ConnectorFuture), @@ -1187,7 +1243,8 @@ impl TransitConnector { log::debug!("Connecting directly to {}", dest_addr); let socket = async_std::net::TcpStream::connect(&dest_addr).await?; log::debug!("Connected to {}!", dest_addr); - Ok((socket, TransitInfo::Direct)) + + wrap_tcp_connection(socket, ConnectionType::Direct) }) .map(|fut| Box::pin(fut) as ConnectorFuture), ), @@ -1197,7 +1254,7 @@ impl TransitConnector { None }; - /* Relay hints. Make sure that both sides adverize it, since it is fine to support it without providing own hints. */ + /* Relay hints. Make sure that both sides advertize it, since it is fine to support it without providing own hints. */ if our_abilities.can_relay() && their_abilities.can_relay() { /* Collect intermediate into HashSet for deduplication */ let mut relay_hints = Vec::::new(); @@ -1206,20 +1263,6 @@ impl TransitConnector { hint.merge_into(&mut relay_hints); } - /* Take a relay hint and try to connect to it */ - async fn hint_connector( - host: DirectHint, - name: Option, - ) -> Result { - log::debug!("Connecting to relay {}", host); - let transit = TcpStream::connect((host.hostname.as_str(), host.port)) - .err_into::() - .await?; - log::debug!("Connected to {}!", host); - - Ok((transit, TransitInfo::Relay { name })) - } - connectors = Box::new( connectors.chain( relay_hints @@ -1255,7 +1298,13 @@ impl TransitConnector { index as u64 * 5, )) .await; - hint_connector(host, name).await + log::debug!("Connecting to relay {}", host); + let socket = TcpStream::connect((host.hostname.as_str(), host.port)) + .err_into::() + .await?; + log::debug!("Connected to {}!", host); + + wrap_tcp_connection(socket, ConnectionType::Relay { name }) }) .map(|fut| Box::pin(fut) as ConnectorFuture), ), @@ -1273,29 +1322,25 @@ impl TransitConnector { let tside = tside2.clone(); let cryptor = cryptor2.clone(); async move { - let (socket, host_type) = fut.await?; - let transit = handshake_exchange( + let (socket, conn_info) = fut.await?; + let (transit, finalizer) = handshake_exchange( is_leader, tside, socket, - &host_type, + &conn_info.conn_type, &*cryptor, transit_key, ) .await?; - Ok((transit, host_type)) + Ok((transit, finalizer, conn_info)) } }) .map(|fut| { Box::pin(fut) - as BoxFuture< - Result<(HandshakeResult, TransitInfo), crypto::TransitHandshakeError>, - > + as BoxFuture> }), ) - as BoxIterator< - BoxFuture>, - >; + as BoxIterator>>; /* Also listen on some port just in case. */ if let Some(socket2) = socket2 { @@ -1306,20 +1351,21 @@ impl TransitConnector { let tside = tside.clone(); let cryptor = cryptor.clone(); let connect = || async { - let (stream, peer) = socket2.accept().await?; + let (socket, peer) = socket2.accept().await?; + let (socket, info) = + wrap_tcp_connection(socket, ConnectionType::Direct)?; log::debug!("Got connection from {}!", peer); - let transit = handshake_exchange( + let (transit, finalizer) = handshake_exchange( is_leader, tside.clone(), - stream, - &TransitInfo::Direct, + socket, + &ConnectionType::Direct, &*cryptor, transit_key.clone(), ) .await?; Result::<_, crypto::TransitHandshakeError>::Ok(( - transit, - TransitInfo::Direct, + transit, finalizer, info, )) }; loop { @@ -1337,25 +1383,27 @@ impl TransitConnector { }) .map(|fut| { Box::pin(fut) - as BoxFuture< - Result< - (HandshakeResult, TransitInfo), - crypto::TransitHandshakeError, - >, - > + as BoxFuture> }), ), ) - as BoxIterator< - BoxFuture< - Result<(HandshakeResult, TransitInfo), crypto::TransitHandshakeError>, - >, - >; + as BoxIterator>>; } connectors.collect::>() } } +/// Trait abstracting our socket used for communicating over the wire. +/// +/// Will be primarily instantiated by either a TCP or web socket. Custom methods +/// will be added in the future. +pub(self) trait TransitTransport: + AsyncRead + AsyncWrite + std::any::Any + Unpin + Send +{ +} + +impl TransitTransport for T where T: AsyncRead + AsyncWrite + std::any::Any + Unpin + Send {} + /** * An established Transit connection. * @@ -1364,7 +1412,7 @@ impl TransitConnector { */ pub struct Transit { /** Raw transit connection */ - socket: TcpStream, + socket: Box, tx: Box, rx: Box, } @@ -1414,7 +1462,11 @@ impl Transit { } } -type HandshakeResult = (TcpStream, Box); +type HandshakeResult = ( + Box, + Box, + TransitInfo, +); /** * Do a transit handshake exchange, to establish a direct connection. @@ -1428,20 +1480,18 @@ type HandshakeResult = (TcpStream, Box); async fn handshake_exchange( is_leader: bool, tside: Arc, - socket: TcpStream, - host_type: &TransitInfo, + mut socket: Box, + host_type: &ConnectionType, cryptor: &dyn crypto::TransitCryptoInit, key: Arc>, -) -> Result { - /* Set proper read and write timeouts. This will temporarily set the socket into blocking mode :/ */ - // https://github.com/async-rs/async-std/issues/499 - let socket = std::net::TcpStream::try_from(socket) - .expect("Internal error: this should not fail because we never cloned the socket"); - socket.set_write_timeout(Some(std::time::Duration::from_secs(120)))?; - socket.set_read_timeout(Some(std::time::Duration::from_secs(120)))?; - let mut socket: TcpStream = socket.into(); - - if host_type != &TransitInfo::Direct { +) -> Result< + ( + Box, + Box, + ), + crypto::TransitHandshakeError, +> { + if host_type != &ConnectionType::Direct { log::trace!("initiating relay handshake"); let sub_key = key.derive_subkey_from_purpose::("transit_relay_token"); diff --git a/src/transit/crypto.rs b/src/transit/crypto.rs index 6cda14c8..6650a07b 100644 --- a/src/transit/crypto.rs +++ b/src/transit/crypto.rs @@ -95,7 +95,7 @@ async fn write_transit_message( pub(super) trait TransitCryptoInitFinalizer: Send { fn handshake_finalize( self: Box, - socket: &mut TcpStream, + socket: &mut dyn TransitTransport, ) -> BoxFuture>; } @@ -104,7 +104,7 @@ pub(super) trait TransitCryptoInitFinalizer: Send { impl TransitCryptoInitFinalizer for DynTransitCrypto { fn handshake_finalize( self: Box, - _socket: &mut TcpStream, + _socket: &mut dyn TransitTransport, ) -> BoxFuture> { Box::pin(futures::future::ready(Ok(*self))) } @@ -116,11 +116,11 @@ pub(super) trait TransitCryptoInit: Send + Sync { // Yes, this method returns a nested future. TODO explain async fn handshake_leader( &self, - socket: &mut TcpStream, + socket: &mut dyn TransitTransport, ) -> Result, TransitHandshakeError>; async fn handshake_follower( &self, - socket: &mut TcpStream, + socket: &mut dyn TransitTransport, ) -> Result, TransitHandshakeError>; } @@ -140,7 +140,7 @@ pub struct SecretboxInit { impl TransitCryptoInit for SecretboxInit { async fn handshake_leader( &self, - socket: &mut TcpStream, + mut socket: &mut dyn TransitTransport, ) -> Result, TransitHandshakeError> { // 9. create record keys let rkey = self @@ -171,7 +171,7 @@ impl TransitCryptoInit for SecretboxInit { .to_hex() ); assert_eq!(expected_rx_handshake.len(), 89); - read_expect(socket, expected_rx_handshake.as_bytes()).await?; + read_expect(&mut socket, expected_rx_handshake.as_bytes()).await?; struct Finalizer { skey: Key, @@ -181,7 +181,7 @@ impl TransitCryptoInit for SecretboxInit { impl TransitCryptoInitFinalizer for Finalizer { fn handshake_finalize( self: Box, - socket: &mut TcpStream, + socket: &mut dyn TransitTransport, ) -> BoxFuture> { Box::pin(async move { socket.write_all(b"go\n").await?; @@ -205,7 +205,7 @@ impl TransitCryptoInit for SecretboxInit { async fn handshake_follower( &self, - socket: &mut TcpStream, + mut socket: &mut dyn TransitTransport, ) -> Result, TransitHandshakeError> { // 9. create record keys /* The order here is correct. The "sender" and "receiver" side are a misnomer and should be called @@ -240,7 +240,7 @@ impl TransitCryptoInit for SecretboxInit { .to_hex(), ); assert_eq!(expected_tx_handshake.len(), 90); - read_expect(socket, expected_tx_handshake.as_bytes()).await?; + read_expect(&mut socket, expected_tx_handshake.as_bytes()).await?; Ok(Box::new(( Box::new(SecretboxCryptoEncrypt { @@ -279,12 +279,16 @@ pub struct NoiseInit { impl TransitCryptoInit for NoiseInit { async fn handshake_leader( &self, - socket: &mut TcpStream, + mut socket: &mut dyn TransitTransport, ) -> Result, TransitHandshakeError> { socket .write_all(b"Magic-Wormhole Dilation Handshake v1 Leader\n\n") .await?; - read_expect(socket, b"Magic-Wormhole Dilation Handshake v1 Follower\n\n").await?; + read_expect( + &mut socket, + b"Magic-Wormhole Dilation Handshake v1 Follower\n\n", + ) + .await?; let mut handshake: NoiseHandshakeState = { let mut builder = noise_protocol::HandshakeStateBuilder::new(); @@ -296,16 +300,17 @@ impl TransitCryptoInit for NoiseInit { handshake.push_psk(&*self.key); // → psk, e - write_transit_message(socket, &handshake.write_message_vec(&[])?).await?; + write_transit_message(&mut socket, &handshake.write_message_vec(&[])?).await?; // ← e, ee - handshake.read_message(&read_transit_message(socket).await?, &mut [])?; + handshake.read_message(&read_transit_message(&mut socket).await?, &mut [])?; assert!(handshake.completed()); let (tx, mut rx) = handshake.get_ciphers(); // ← "" - let peer_confirmation_message = rx.decrypt_vec(&read_transit_message(socket).await?)?; + let peer_confirmation_message = + rx.decrypt_vec(&read_transit_message(&mut socket).await?)?; ensure!( peer_confirmation_message.len() == 0, TransitHandshakeError::HandshakeFailed @@ -319,11 +324,11 @@ impl TransitCryptoInit for NoiseInit { impl TransitCryptoInitFinalizer for Finalizer { fn handshake_finalize( mut self: Box, - socket: &mut TcpStream, + mut socket: &mut dyn TransitTransport, ) -> BoxFuture> { Box::pin(async move { // → "" - write_transit_message(socket, &self.tx.encrypt_vec(&[])).await?; + write_transit_message(&mut socket, &self.tx.encrypt_vec(&[])).await?; Ok::<_, TransitHandshakeError>(( Box::new(NoiseCryptoEncrypt { tx: self.tx }) @@ -340,12 +345,16 @@ impl TransitCryptoInit for NoiseInit { async fn handshake_follower( &self, - socket: &mut TcpStream, + mut socket: &mut dyn TransitTransport, ) -> Result, TransitHandshakeError> { socket .write_all(b"Magic-Wormhole Dilation Handshake v1 Follower\n\n") .await?; - read_expect(socket, b"Magic-Wormhole Dilation Handshake v1 Leader\n\n").await?; + read_expect( + &mut socket, + b"Magic-Wormhole Dilation Handshake v1 Leader\n\n", + ) + .await?; let mut handshake: NoiseHandshakeState = { let mut builder = noise_protocol::HandshakeStateBuilder::new(); @@ -357,20 +366,21 @@ impl TransitCryptoInit for NoiseInit { handshake.push_psk(&*self.key); // ← psk, e - handshake.read_message(&read_transit_message(socket).await?, &mut [])?; + handshake.read_message(&read_transit_message(&mut socket).await?, &mut [])?; // → e, ee - write_transit_message(socket, &handshake.write_message_vec(&[])?).await?; + write_transit_message(&mut socket, &handshake.write_message_vec(&[])?).await?; assert!(handshake.completed()); // Warning: rx and tx are swapped here (read the `get_ciphers` doc carefully) let (mut rx, mut tx) = handshake.get_ciphers(); // → "" - write_transit_message(socket, &tx.encrypt_vec(&[])).await?; + write_transit_message(&mut socket, &tx.encrypt_vec(&[])).await?; // ← "" - let peer_confirmation_message = rx.decrypt_vec(&read_transit_message(socket).await?)?; + let peer_confirmation_message = + rx.decrypt_vec(&read_transit_message(&mut socket).await?)?; ensure!( peer_confirmation_message.len() == 0, TransitHandshakeError::HandshakeFailed From 72fb575908ba83157d76dac3bddb6d00ade13fb2 Mon Sep 17 00:00:00 2001 From: piegames Date: Tue, 31 Jan 2023 22:02:40 +0100 Subject: [PATCH 03/26] Bump MSRV to 1.66 --- .github/workflows/push.yml | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 2d4a1b00..4e7eb283 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -56,7 +56,7 @@ jobs: - ubuntu-latest - windows-latest rust: - - 1.61.0 # MSRV (also change in Cargo.toml) + - 1.66.0 # MSRV (also change in Cargo.toml) - stable - nightly steps: diff --git a/Cargo.toml b/Cargo.toml index 43869809..6d2b3b90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/magic-wormhole/magic-wormhole.rs" documentation = "https://docs.rs/magic-wormhole/latest/" license = "EUPL-1.2" edition = "2021" -rust-version = "1.61" # MSRV (also change in CI) +rust-version = "1.66" # MSRV (also change in CI) [dependencies] serde = { version = "1.0.120", features = ["rc"] } From 82ad7b0de9205f05fb1338984fff6daa7ef11249 Mon Sep 17 00:00:00 2001 From: piegames Date: Tue, 31 Jan 2023 22:04:38 +0100 Subject: [PATCH 04/26] Dependencies for WASM support --- Cargo.lock | 353 ++++++++++++++++++++++++++++++++------------------- Cargo.toml | 28 +++- changelog.md | 1 + 3 files changed, 245 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c05e8bb1..9f8687fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,9 +229,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", @@ -253,11 +253,22 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", +] + [[package]] name = "atomic-waker" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" +checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" [[package]] name = "atty" @@ -311,9 +322,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake2" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest 0.10.6", ] @@ -349,9 +360,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "bytecodec" @@ -377,9 +388,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cache-padded" @@ -389,9 +400,9 @@ checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -453,7 +464,7 @@ dependencies = [ "once_cell", "strsim 0.10.0", "termcolor", - "terminal_size 0.2.3", + "terminal_size", "textwrap", ] @@ -504,9 +515,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "4.4.2" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4ab1b92798304eedc095b53942963240037c0516452cb11aeba709d420b2219" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" dependencies = [ "error-code", "str-buf", @@ -542,25 +553,24 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" dependencies = [ "encode_unicode", "lazy_static", "libc", - "terminal_size 0.1.17", "unicode-width", - "winapi", + "windows-sys", ] [[package]] @@ -606,7 +616,7 @@ dependencies = [ "crossterm_winapi", "libc", "mio", - "parking_lot", + "parking_lot 0.12.1", "signal-hook", "signal-hook-mio", "winapi", @@ -656,7 +666,7 @@ version = "3.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" dependencies = [ - "nix 0.26.1", + "nix 0.26.2", "windows-sys", ] @@ -732,11 +742,12 @@ dependencies = [ [[package]] name = "dialoguer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92e7e37ecef6857fdc0c0c5d42fd5b0938e46590c2183cc92dd310a6d078eb1" +checksum = "af3c796f3b0b408d9fd581611b47fa850821fcb84aa640b83a3c1a5be2d691f2" dependencies = [ "console", + "shell-words", "tempfile", "zeroize", ] @@ -877,9 +888,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -892,9 +903,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -902,15 +913,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -919,9 +930,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-lite" @@ -940,9 +951,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -951,21 +962,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -1019,6 +1030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] @@ -1030,8 +1042,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1046,15 +1060,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" +checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" [[package]] name = "gloo-timers" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c4a8d6391675c6b2ee1a6c8d06e8e2d03605c44cec1270675985a4c2a5500b" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" dependencies = [ "futures-channel", "futures-core", @@ -1169,12 +1183,12 @@ dependencies = [ [[package]] name = "if-addrs" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" +checksum = "26b24dd0826eee92c56edcda7ff190f2cf52115c49eadb2c2da8063e2673a8c2" dependencies = [ "libc", - "winapi", + "windows-sys", ] [[package]] @@ -1195,9 +1209,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.2" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4295cbb7573c16d310e99e713cf9e75101eb190ab31fccd35f2d2691b4352b19" +checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" dependencies = [ "console", "number_prefix", @@ -1212,13 +1226,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] name = "io-lifetimes" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" dependencies = [ "libc", "windows-sys", @@ -1226,9 +1243,9 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" dependencies = [ "hermit-abi 0.2.6", "io-lifetimes", @@ -1238,9 +1255,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -1268,9 +1285,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.138" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "linux-raw-sys" @@ -1326,9 +1343,12 @@ dependencies = [ "eyre", "futures", "futures_ringbuf", + "getrandom 0.1.16", + "getrandom 0.2.8", "hex", "hkdf", "if-addrs", + "instant", "libc", "log", "noise-protocol", @@ -1347,6 +1367,8 @@ dependencies = [ "thiserror", "time", "url", + "wasm-timer", + "ws_stream_wasm", "xsalsa20poly1305", ] @@ -1421,9 +1443,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", "cfg-if", @@ -1458,9 +1480,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -1512,18 +1534,18 @@ dependencies = [ [[package]] name = "object" -version = "0.30.0" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "opaque-debug" @@ -1559,6 +1581,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1566,14 +1599,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.6", ] [[package]] name = "parking_lot_core" -version = "0.9.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" dependencies = [ "cfg-if", "libc", @@ -1584,9 +1631,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1c2c742266c2f1041c914ba65355a83ae8747b05f208319784083583494b4b" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "percent-encoding" @@ -1604,6 +1651,16 @@ dependencies = [ "indexmap", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] + [[package]] name = "pin-project" version = "1.0.12" @@ -1681,9 +1738,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bdd679d533107e090c2704a35982fc06302e30898e63ffa26a81155c012e92" +checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" [[package]] name = "ppv-lite86" @@ -1717,9 +1774,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] @@ -1745,9 +1802,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -1802,9 +1859,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -1889,9 +1946,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" dependencies = [ "bitflags", "errno", @@ -1916,9 +1973,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "salsa20" @@ -1948,24 +2005,30 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.150" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.150" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1974,9 +2037,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -2020,6 +2083,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "signal-hook" version = "0.3.14" @@ -2139,9 +2208,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -2176,23 +2245,13 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "terminal_size" version = "0.2.3" @@ -2209,23 +2268,23 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" dependencies = [ - "terminal_size 0.2.3", + "terminal_size", ] [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -2395,15 +2454,15 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -2566,6 +2625,21 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wayland-client" version = "0.29.5" @@ -2720,45 +2794,45 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "wl-clipboard-rs" @@ -2803,6 +2877,25 @@ dependencies = [ "url", ] +[[package]] +name = "ws_stream_wasm" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version", + "send_wrapper", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "x11-clipboard" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index 6d2b3b90..0e987745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,23 +32,18 @@ log = "0.4.13" base64 = "0.20.0" futures_ringbuf = "0.3.1" time = { version = "0.3.7", features = ["formatting"] } +instant = { version = "0.1.12", features = ["wasm-bindgen"] } derive_more = { version = "0.99.0", default-features = false, features = ["display", "deref", "from"] } thiserror = "1.0.24" futures = "0.3.12" -async-std = { version = "1.12.0", features = ["attributes", "unstable"] } -async-tungstenite = { version = "0.17.1", features = ["async-std-runtime", "async-tls"] } -async-io = "1.6.0" -libc = "0.2.101" url = { version = "2.2.2", features = ["serde"] } percent-encoding = { version = "2.1.0" } # Transit dependencies -socket2 = { version = "0.4.1", optional = true } stun_codec = { version = "0.2.0", optional = true } -if-addrs = { version = "0.7.0", optional = true } bytecodec = { version = "0.4.15", optional = true } async-trait = { version = "0.1.57", optional = true } noise-protocol = { version = "0.1.4", optional = true } @@ -56,13 +51,32 @@ noise-rust-crypto = { version = "0.5.0", optional = true } # Transfer dependencies -async-tar = { version = "0.4.2", optional = true } rmp-serde = { version = "1.0.0", optional = true } # Forwarding dependencies # rmp-serde = … # defined above +[target.'cfg(not(target_family = "wasm"))'.dependencies] +libc = "0.2.101" +async-std = { version = "1.12.0", features = ["attributes", "unstable"] } +async-tungstenite = { version = "0.17.1", features = ["async-std-runtime", "async-tls"] } +async-io = "1.6.0" + +# Transit +socket2 = { version = "0.4.1", optional = true } +if-addrs = { version = "0.8.0", optional = true } + +# Transfer + +async-tar = { version = "0.4.2", optional = true } + +[target.'cfg(target_family = "wasm")'.dependencies] +wasm-timer = "0.2.5" +ws_stream_wasm = "0.7.3" +getrandom = { version = "0.2.5", features = ["js"] } +getrandom_2 = { package = "getrandom", version = "0.1.16", features = ["js-sys"] } + # for some tests [dev-dependencies] env_logger = "0.10.0" diff --git a/changelog.md b/changelog.md index 45053253..c5978341 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ ## Unreleased +- Added compilation support for WASM targets. - \[lib\]\[breaking\] replaced `transit::TransitInfo` with a struct containing the address, the old enum has been renamed to `transit::ConnectionType`. ## Version 0.6.0 From d6a75a74cf1d66286ea9e362aeffc0a29e56e7b0 Mon Sep 17 00:00:00 2001 From: piegames Date: Tue, 31 Jan 2023 22:04:46 +0100 Subject: [PATCH 05/26] Core: add WASM support --- src/core/rendezvous.rs | 92 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/src/core/rendezvous.rs b/src/core/rendezvous.rs index ec999809..b013877b 100644 --- a/src/core/rendezvous.rs +++ b/src/core/rendezvous.rs @@ -2,6 +2,7 @@ //! //! Wormhole builds upon this, so you usually don't need to bother. +#[cfg(not(target_family = "wasm"))] use async_tungstenite::tungstenite as ws2; use futures::prelude::*; use std::collections::VecDeque; @@ -38,11 +39,19 @@ pub enum RendezvousError { _0 )] Login(Vec), + #[cfg(not(target_family = "wasm"))] #[error("Websocket IO error")] IO( #[from] #[source] - async_tungstenite::tungstenite::Error, + ws2::Error, + ), + #[cfg(target_family = "wasm")] + #[error("Websocket IO error")] + IO( + #[from] + #[source] + ws_stream_wasm::WsErr, ), } @@ -65,11 +74,19 @@ impl RendezvousError { type MessageQueue = VecDeque; +#[cfg(not(target_family = "wasm"))] struct WsConnection { connection: async_tungstenite::WebSocketStream, } +#[cfg(target_family = "wasm")] +struct WsConnection { + connection: ws_stream_wasm::WsStream, + meta: ws_stream_wasm::WsMeta, +} + impl WsConnection { + #[cfg(not(target_family = "wasm"))] async fn send_message( &mut self, message: &OutboundMessage, @@ -83,6 +100,22 @@ impl WsConnection { Ok(()) } + #[cfg(target_family = "wasm")] + async fn send_message( + &mut self, + message: &OutboundMessage, + queue: Option<&mut MessageQueue>, + ) -> Result<(), RendezvousError> { + log::debug!("Sending {:?}", message); + self.connection + .send(ws_stream_wasm::WsMessage::Text( + serde_json::to_string(message).unwrap(), + )) + .await?; + self.receive_ack(queue).await?; + Ok(()) + } + async fn receive_ack( &mut self, mut queue: Option<&mut MessageQueue>, @@ -160,6 +193,7 @@ impl WsConnection { } } + #[cfg(not(target_family = "wasm"))] async fn receive_message(&mut self) -> Result, RendezvousError> { let message = self .connection @@ -195,6 +229,42 @@ impl WsConnection { }, } } + + #[cfg(target_family = "wasm")] + async fn receive_message(&mut self) -> Result, RendezvousError> { + let message = self + .connection + .next() + .await + .expect("TODO this should always be Some"); + match message { + ws_stream_wasm::WsMessage::Text(message_plain) => { + let message = serde_json::from_str(&message_plain)?; + log::debug!("Received {:?}", message); + match message { + InboundMessage::Unknown => { + log::warn!("Got unknown message, ignoring: '{}'", message_plain); + Ok(None) + }, + InboundMessage::Error { error, orig: _ } => Err(RendezvousError::server(error)), + message => Ok(Some(message)), + } + }, + ws_stream_wasm::WsMessage::Binary(_) => Err(RendezvousError::protocol( + "WebSocket messages must be UTF-8 encoded text", + )), + } + } + + #[cfg(not(target_family = "wasm"))] + async fn close(&mut self) -> Result<(), ws2::Error> { + self.connection.close(None).await + } + + #[cfg(target_family = "wasm")] + async fn close(&mut self) -> Result { + self.meta.close().await + } } #[derive(Clone, Debug, derive_more::Display)] @@ -262,8 +332,22 @@ impl RendezvousServer { relay_url: &str, ) -> Result<(Self, Option), RendezvousError> { let side = MySide::generate(); - let (connection, _) = async_tungstenite::async_std::connect_async(relay_url).await?; - let mut connection = WsConnection { connection }; + let mut connection; + + #[cfg(not(target_arch = "wasm32"))] + { + let (stream, _) = async_tungstenite::async_std::connect_async(relay_url).await?; + connection = WsConnection { connection: stream }; + } + + #[cfg(target_arch = "wasm32")] + { + let (meta, stream) = ws_stream_wasm::WsMeta::connect(relay_url, None).await?; + connection = WsConnection { + meta, + connection: stream, + }; + } let welcome = match connection.receive_message_some().await? { InboundMessage::Welcome { welcome } => welcome, @@ -510,7 +594,7 @@ impl RendezvousServer { }; } - self.connection.connection.close(None).await?; + self.connection.close().await?; Ok(()) } } From 0e7ed60e60c0f174131e68ce535561eb14af182d Mon Sep 17 00:00:00 2001 From: piegames Date: Tue, 31 Jan 2023 22:06:47 +0100 Subject: [PATCH 06/26] Transit: add WASM support --- src/transit.rs | 507 +++++++++++++++------------------------ src/transit/crypto.rs | 148 +++++------- src/transit/transport.rs | 304 +++++++++++++++++++++++ 3 files changed, 560 insertions(+), 399 deletions(-) create mode 100644 src/transit/transport.rs diff --git a/src/transit.rs b/src/transit.rs index 5d1838a8..e455dd96 100644 --- a/src/transit.rs +++ b/src/transit.rs @@ -16,29 +16,33 @@ use crate::{Key, KeyPurpose}; use serde_derive::{Deserialize, Serialize}; -use async_std::{ - io::{prelude::WriteExt, ReadExt}, - net::{TcpListener, TcpStream}, -}; -use futures::io::{AsyncRead, AsyncWrite}; +#[cfg(not(target_family = "wasm"))] +use async_std::net::{TcpListener, TcpStream}; #[allow(unused_imports)] /* We need them for the docs */ -use futures::{future::TryFutureExt, Sink, SinkExt, Stream, StreamExt, TryStreamExt}; +use futures::{ + future::FutureExt, + future::TryFutureExt, + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + Sink, SinkExt, Stream, StreamExt, TryStreamExt, +}; use log::*; use std::{ collections::HashSet, - net::{IpAddr, SocketAddr, ToSocketAddrs}, + net::{IpAddr, SocketAddr}, sync::Arc, }; -use xsalsa20poly1305 as secretbox; -use xsalsa20poly1305::aead::{Aead, NewAead}; mod crypto; +mod transport; +use crypto::TransitHandshakeError; +use transport::{TransitTransport, TransitTransportRx, TransitTransportTx}; /// ULR to a default hosted relay server. Please don't abuse or DOS. pub const DEFAULT_RELAY_SERVER: &str = "tcp://transit.magic-wormhole.io:4001"; // No need to make public, it's hard-coded anyways (: // Open an issue if you want an API for this // Use for non-production testing +#[cfg(not(target_family = "wasm"))] const PUBLIC_STUN_SERVER: &str = "stun.piegames.de:3478"; #[derive(Debug)] @@ -65,6 +69,13 @@ pub enum TransitConnectError { #[source] std::io::Error, ), + #[cfg(target_family = "wasm")] + #[error("WASM error")] + WASM( + #[from] + #[source] + ws_stream_wasm::WsErr, + ), } #[derive(Debug, thiserror::Error)] @@ -80,6 +91,13 @@ pub enum TransitError { #[source] std::io::Error, ), + #[cfg(target_family = "wasm")] + #[error("WASM error")] + WASM( + #[from] + #[source] + ws_stream_wasm::WsErr, + ), } impl From<()> for TransitError { @@ -573,6 +591,7 @@ pub struct TransitInfo { pub conn_type: ConnectionType, /// Target address of our connection. This may be our peer, or the relay server. /// This says nothing about the actual transport protocol used. + #[cfg(not(target_family = "wasm"))] pub peer_addr: SocketAddr, // Prevent exhaustive destructuring for future proofing _unused: (), @@ -580,71 +599,7 @@ pub struct TransitInfo { type TransitConnection = (Box, TransitInfo); -fn set_socket_opts(socket: &socket2::Socket) -> std::io::Result<()> { - socket.set_nonblocking(true)?; - - /* See https://stackoverflow.com/a/14388707/6094756. - * On most BSD and Linux systems, we need both REUSEADDR and REUSEPORT; - * and if they don't support the latter we won't compile. - * On Windows, there is only REUSEADDR but it does what we want. - */ - socket.set_reuse_address(true)?; - #[cfg(all(unix, not(any(target_os = "solaris", target_os = "illumos"))))] - { - socket.set_reuse_port(true)?; - } - #[cfg(not(any( - all(unix, not(any(target_os = "solaris", target_os = "illumos"))), - target_os = "windows" - )))] - { - compile_error!("Your system is not supported yet, please raise an error"); - } - - Ok(()) -} - -/** - * Bind to a port with SO_REUSEADDR, connect to the destination and then hide the blood behind a pretty [`async_std::net::TcpStream`] - * - * We want an `async_std::net::TcpStream`, but with SO_REUSEADDR set. - * The former is just a wrapper around `async_io::Async`, of which we - * copy the `connect` method to add a statement that will set the socket flag. - * See https://github.com/smol-rs/async-net/issues/20. - */ -async fn connect_custom( - local_addr: &socket2::SockAddr, - dest_addr: &socket2::SockAddr, -) -> std::io::Result { - log::debug!("Binding to {}", local_addr.as_socket().unwrap()); - let socket = socket2::Socket::new(socket2::Domain::IPV6, socket2::Type::STREAM, None)?; - /* Set our custum options */ - set_socket_opts(&socket)?; - - socket.bind(local_addr)?; - - /* Initiate connect */ - match socket.connect(dest_addr) { - Ok(_) => {}, - #[cfg(unix)] - Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) => {}, - Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {}, - Err(err) => return Err(err), - } - - let stream = async_io::Async::new(std::net::TcpStream::from(socket))?; - /* The stream becomes writable when connected. */ - stream.writable().await?; - - /* Check if there was an error while connecting. */ - stream - .get_ref() - .take_error() - .and_then(|maybe_err| maybe_err.map_or(Ok(()), Result::Err))?; - /* Convert our mess to `async_std::net::TcpStream */ - Ok(stream.into_inner()?.into()) -} - +#[cfg(not(target_family = "wasm"))] #[derive(Debug, thiserror::Error)] enum StunError { #[error("No IPv4 addresses were found for the selected STUN server")] @@ -667,90 +622,6 @@ enum StunError { ), } -/** Perform a STUN query to get the external IP address */ -async fn get_external_ip() -> Result<(SocketAddr, TcpStream), StunError> { - let mut socket = connect_custom( - &"[::]:0".parse::().unwrap().into(), - &PUBLIC_STUN_SERVER - .to_socket_addrs()? - /* If you find yourself behind a NAT66, open an issue */ - .find(|x| x.is_ipv4()) - /* TODO add a helper method to stdlib for this */ - .map(|addr| match addr { - SocketAddr::V4(v4) => { - SocketAddr::new(IpAddr::V6(v4.ip().to_ipv6_mapped()), v4.port()) - }, - SocketAddr::V6(_) => unreachable!(), - }) - .ok_or(StunError::ServerIsV6Only)? - .into(), - ) - .await?; - - use bytecodec::{DecodeExt, EncodeExt}; - use stun_codec::{ - rfc5389::{ - self, - attributes::{MappedAddress, Software, XorMappedAddress}, - Attribute, - }, - Message, MessageClass, MessageDecoder, MessageEncoder, TransactionId, - }; - - fn get_binding_request() -> Result, bytecodec::Error> { - use rand::Rng; - let random_bytes = rand::thread_rng().gen::<[u8; 12]>(); - - let mut message = Message::new( - MessageClass::Request, - rfc5389::methods::BINDING, - TransactionId::new(random_bytes), - ); - - message.add_attribute(Attribute::Software(Software::new( - "magic-wormhole-rust".to_owned(), - )?)); - - // Encodes the message - let mut encoder = MessageEncoder::new(); - let bytes = encoder.encode_into_bytes(message.clone())?; - Ok(bytes) - } - - fn decode_address(buf: &[u8]) -> Result, bytecodec::Error> { - let mut decoder = MessageDecoder::::new(); - let decoded = decoder.decode_from_bytes(buf)??; - - let external_addr1 = decoded - .get_attribute::() - .map(|x| x.address()); - //let external_addr2 = decoded.get_attribute::().map(|x|x.address()); - let external_addr3 = decoded - .get_attribute::() - .map(|x| x.address()); - let external_addr = external_addr1 - // .or(external_addr2) - .or(external_addr3); - - Ok(external_addr) - } - - /* Connect the plugs */ - - socket.write_all(get_binding_request()?.as_ref()).await?; - - let mut buf = [0u8; 256]; - /* Read header first */ - socket.read_exact(&mut buf[..20]).await?; - let len: u16 = u16::from_be_bytes([buf[2], buf[3]]); - /* Read the rest of the message */ - socket.read_exact(&mut buf[20..][..len as usize]).await?; - let external_addr = - decode_address(&buf[..20 + len as usize])?.ok_or(StunError::ServerNoResponse)?; - - Ok((external_addr, socket)) -} - /// Utility method that logs information of the transit result /// /// Example usage: @@ -769,6 +640,7 @@ async fn get_external_ip() -> Result<(SocketAddr, TcpStream), StunError> { /// # Ok(()) /// # } /// ``` +#[cfg(not(target_family = "wasm"))] pub fn log_transit_connection(info: TransitInfo) { match info.conn_type { ConnectionType::Direct => { @@ -804,22 +676,24 @@ pub async fn init( relay_hints: Vec, ) -> Result { let mut our_hints = Hints::default(); - let mut listener = None; + #[cfg(not(target_family = "wasm"))] + let mut sockets = None; if let Some(peer_abilities) = peer_abilities { abilities = abilities.intersect(&peer_abilities); } /* Detect our IP addresses if the ability is enabled */ + #[cfg(not(target_family = "wasm"))] if abilities.can_direct() { let create_sockets = async { /* Do a STUN query to get our public IP. If it works, we must reuse the same socket (port) * so that we will be NATted to the same port again. If it doesn't, simply bind a new socket * and use that instead. */ - let socket: MaybeConnectedSocket = match async_std::future::timeout( + let socket: MaybeConnectedSocket = match timeout( std::time::Duration::from_secs(4), - get_external_ip(), + transport::tcp_get_external_ip(), ) .await .map_err(|_| StunError::Timeout) @@ -843,7 +717,7 @@ pub async fn init( log::warn!("Failed to get external address via STUN, {}", err); let socket = socket2::Socket::new(socket2::Domain::IPV6, socket2::Type::STREAM, None)?; - set_socket_opts(&socket)?; + transport::set_socket_opts(&socket)?; socket.bind(&"[::]:0".parse::().unwrap().into())?; log::debug!( @@ -861,11 +735,11 @@ pub async fn init( * the port. In theory, we could, but it really confused the kernel to the point * of `accept` calls never returning again. */ - let socket2 = TcpListener::bind("[::]:0").await?; + let listener = TcpListener::bind("[::]:0").await?; /* Find our ports, iterate all our local addresses, combine them with the ports and that's our hints */ let port = socket.local_addr()?.as_socket().unwrap().port(); - let port2 = socket2.local_addr()?.port(); + let port2 = listener.local_addr()?.port(); our_hints.direct_tcp.extend( if_addrs::get_if_addrs()? .iter() @@ -884,12 +758,12 @@ pub async fn init( .into_iter() }), ); - log::debug!("Our socket for listening is {}", socket2.local_addr()?); + log::debug!("Our socket for listening is {}", listener.local_addr()?); - Ok::<_, std::io::Error>((socket, socket2)) + Ok::<_, std::io::Error>((socket, listener)) }; - listener = create_sockets + sockets = create_sockets .await // TODO replace with inspect_err once stable .map_err(|err| { @@ -904,12 +778,15 @@ pub async fn init( } Ok(TransitConnector { - sockets: listener, + #[cfg(not(target_family = "wasm"))] + sockets, our_abilities: abilities, our_hints: Arc::new(our_hints), }) } +/// Bound socket, maybe also connected. Guaranteed to have SO_REUSEADDR. +#[cfg(not(target_family = "wasm"))] #[derive(derive_more::From)] enum MaybeConnectedSocket { #[from] @@ -918,6 +795,7 @@ enum MaybeConnectedSocket { Stream(TcpStream), } +#[cfg(not(target_family = "wasm"))] impl MaybeConnectedSocket { fn local_addr(&self) -> std::io::Result { match &self { @@ -939,6 +817,7 @@ pub struct TransitConnector { * The first socket is the port from which we will start connection attempts. * For in case the user is behind no firewalls, we must also listen to the second socket. */ + #[cfg(not(target_family = "wasm"))] sockets: Option<(MaybeConnectedSocket, TcpListener)>, our_abilities: Abilities, our_hints: Arc, @@ -964,13 +843,14 @@ impl TransitConnector { their_hints: Arc, ) -> Result<(Transit, TransitInfo), TransitConnectError> { let Self { + #[cfg(not(target_family = "wasm"))] sockets, our_abilities, our_hints, } = self; let transit_key = Arc::new(transit_key); - let start = std::time::Instant::now(); + let start = instant::Instant::now(); let mut connection_stream = Box::pin( Self::connect( true, @@ -979,6 +859,7 @@ impl TransitConnector { our_hints, their_abilities, their_hints, + #[cfg(not(target_family = "wasm"))] sockets, ) .filter_map(|result| async { @@ -992,16 +873,14 @@ impl TransitConnector { }), ); - let (mut transit, mut finalizer, mut conn_info) = async_std::future::timeout( - std::time::Duration::from_secs(60), - connection_stream.next(), - ) - .await - .map_err(|_| { - log::debug!("`leader_connect` timed out"); - TransitConnectError::Handshake - })? - .ok_or(TransitConnectError::Handshake)?; + let (mut transit, mut finalizer, mut conn_info) = + timeout(std::time::Duration::from_secs(60), connection_stream.next()) + .await + .map_err(|_| { + log::debug!("`leader_connect` timed out"); + TransitConnectError::Handshake + })? + .ok_or(TransitConnectError::Handshake)?; if conn_info.conn_type != ConnectionType::Direct && our_abilities.can_direct() { log::debug!( @@ -1017,7 +896,7 @@ impl TransitConnector { } else { elapsed.mul_f32(0.3) }; - let _ = async_std::future::timeout(to_wait, async { + let _ = timeout(to_wait, async { while let Some((new_transit, new_finalizer, new_conn_info)) = connection_stream.next().await { @@ -1050,7 +929,6 @@ impl TransitConnector { TransitConnectError::Handshake })?; - // let socket = Box::new(socket) as Box; Ok(( Transit { socket: transit, @@ -1071,6 +949,7 @@ impl TransitConnector { their_hints: Arc, ) -> Result<(Transit, TransitInfo), TransitConnectError> { let Self { + #[cfg(not(target_family = "wasm"))] sockets, our_abilities, our_hints, @@ -1085,6 +964,7 @@ impl TransitConnector { our_hints, their_abilities, their_hints, + #[cfg(not(target_family = "wasm"))] sockets, ) .filter_map(|result| async { @@ -1098,7 +978,7 @@ impl TransitConnector { }), ); - let transit = match async_std::future::timeout( + let transit = match timeout( std::time::Duration::from_secs(60), &mut connection_stream.next(), ) @@ -1112,7 +992,6 @@ impl TransitConnector { log::debug!("`handshake_finalize` failed: {e}"); TransitConnectError::Handshake })?; - // let socket = Box::new(socket) as Box; Ok((Transit { socket, tx, rx }, conn_info)) }, @@ -1146,34 +1025,11 @@ impl TransitConnector { our_hints: Arc, their_abilities: Abilities, their_hints: Arc, - socket: Option<(MaybeConnectedSocket, TcpListener)>, - ) -> impl Stream> + 'static { - /* Take a tcp connection and transform it into a `TransitConnection` (mainly set timeouts) */ - fn wrap_tcp_connection( - socket: TcpStream, - conn_type: ConnectionType, - ) -> Result { - /* Set proper read and write timeouts. This will temporarily set the socket into blocking mode :/ */ - // https://github.com/async-rs/async-std/issues/499 - let socket = std::net::TcpStream::try_from(socket) - .expect("Internal error: this should not fail because we never cloned the socket"); - socket.set_write_timeout(Some(std::time::Duration::from_secs(120)))?; - socket.set_read_timeout(Some(std::time::Duration::from_secs(120)))?; - let socket: TcpStream = socket.into(); - - let info = TransitInfo { - conn_type, - peer_addr: socket - .peer_addr() - .expect("Internal error: socket must be IP"), - _unused: (), - }; - - Ok((Box::new(socket), info)) - } - - /* Have socket => can direct */ - assert!(socket.is_none() || our_abilities.can_direct()); + #[cfg(not(target_family = "wasm"))] sockets: Option<(MaybeConnectedSocket, TcpListener)>, + ) -> impl Stream> + 'static { + /* Have Some(sockets) → Can direct */ + #[cfg(not(target_family = "wasm"))] + assert!(sockets.is_none() || our_abilities.can_direct()); let cryptor = if our_abilities.can_noise_crypto() && their_abilities.can_noise_crypto() { log::debug!("Using noise protocol for encryption"); @@ -1193,17 +1049,25 @@ impl TransitConnector { /* Iterator of futures yielding a connection. They'll be then mapped with the handshake, collected into * a Vec and polled concurrently. */ + #[cfg(not(target_family = "wasm"))] use futures::future::BoxFuture; + #[cfg(target_family = "wasm")] + use futures::future::LocalBoxFuture as BoxFuture; type BoxIterator = Box>; - type ConnectorFuture = - BoxFuture<'static, Result>; + type ConnectorFuture = BoxFuture<'static, Result>; let mut connectors: BoxIterator = Box::new(std::iter::empty()); - /* Create direct connection sockets, if we support it. If peer doesn't support it, their list of hints will - * be empty and no entries will be pushed. - */ - let socket2 = if let Some((socket, socket2)) = socket { - let local_addr = Arc::new(socket.local_addr().unwrap()); + #[cfg(not(target_family = "wasm"))] + let (socket, listener) = sockets.unzip(); + #[cfg(not(target_family = "wasm"))] + if our_abilities.can_direct() && their_abilities.can_direct() { + let local_addr = socket.map(|socket| { + Arc::new( + socket + .local_addr() + .expect("This is guaranteed to be an IP socket"), + ) + }); /* Connect to each hint of the peer */ connectors = Box::new( connectors.chain( @@ -1213,48 +1077,13 @@ impl TransitConnector { .into_iter() /* Nobody should have that many IP addresses, even with NATing */ .take(50) - .map(move |hint| { - let local_addr = local_addr.clone(); - async move { - let dest_addr = SocketAddr::try_from(&hint)?; - log::debug!("Connecting directly to {}", dest_addr); - let socket = connect_custom(&local_addr, &dest_addr.into()).await?; - log::debug!("Connected to {}!", dest_addr); - - wrap_tcp_connection(socket, ConnectionType::Direct) - } - }) + .map(move |hint| transport::connect_tcp_direct(local_addr.clone(), hint)) .map(|fut| Box::pin(fut) as ConnectorFuture), ), ) as BoxIterator; - Some(socket2) - } else if our_abilities.can_direct() { - /* Fallback: We did not manage to bind a listener but we can still connect to the peer's hints */ - connectors = Box::new( - connectors.chain( - their_hints - .direct_tcp - .clone() - .into_iter() - /* Nobody should have that many IP addresses, even with NATing */ - .take(50) - .map(move |hint| async move { - let dest_addr = SocketAddr::try_from(&hint)?; - log::debug!("Connecting directly to {}", dest_addr); - let socket = async_std::net::TcpStream::connect(&dest_addr).await?; - log::debug!("Connected to {}!", dest_addr); - - wrap_tcp_connection(socket, ConnectionType::Direct) - }) - .map(|fut| Box::pin(fut) as ConnectorFuture), - ), - ) as BoxIterator; - None - } else { - None - }; + } - /* Relay hints. Make sure that both sides advertize it, since it is fine to support it without providing own hints. */ + /* Relay hints. Make sure that both sides advertise it, since it is fine to support it without providing own hints. */ if our_abilities.can_relay() && their_abilities.can_relay() { /* Collect intermediate into HashSet for deduplication */ let mut relay_hints = Vec::::new(); @@ -1263,12 +1092,14 @@ impl TransitConnector { hint.merge_into(&mut relay_hints); } - connectors = Box::new( - connectors.chain( + #[cfg(not(target_family = "wasm"))] + { + connectors = Box::new( + connectors.chain( relay_hints .into_iter() /* A hint may have multiple addresses pointing towards the server. This may be multiple - * domain aliases or different ports or an IPv6 or IPv4 address. We only need + * domain aliases or different ports or an IPv6 or IPv4 address. We only need * to connect to one of them, since they are considered equivalent. However, we * also want to be prepared for the rare case of one failing, thus we try to reach * up to three different addresses. To not flood the system with requests, we @@ -1278,43 +1109,83 @@ impl TransitConnector { .flat_map(|hint| { /* If the hint has no name, take the first domain name as fallback */ let name = hint.name - .or_else(|| { - /* Try to parse as IP address. We are only interested in human readable names (the IP address will be printed anyways) */ - hint.tcp.iter() + .or_else(|| { + /* Try to parse as IP address. We are only interested in human readable names (the IP address will be printed anyways) */ + hint.tcp.iter() .filter_map(|hint| match url::Host::parse(&hint.hostname) { Ok(url::Host::Domain(_)) => Some(hint.hostname.clone()), _ => None, }) .next() - }); + }); hint.tcp .into_iter() .take(3) .enumerate() .map(move |(i, h)| (i, h, name.clone())) - }) - .map(|(index, host, name)| async move { - async_std::task::sleep(std::time::Duration::from_secs( - index as u64 * 5, - )) - .await; - log::debug!("Connecting to relay {}", host); - let socket = TcpStream::connect((host.hostname.as_str(), host.port)) - .err_into::() - .await?; - log::debug!("Connected to {}!", host); - - wrap_tcp_connection(socket, ConnectionType::Relay { name }) - }) - .map(|fut| Box::pin(fut) as ConnectorFuture), - ), - ) as BoxIterator; + }) + .map(|(index, host, name)| async move { + sleep(std::time::Duration::from_secs( + index as u64 * 5, + )) + .await; + transport::connect_tcp_relay(host, name).await + }) + .map(|fut| Box::pin(fut) as ConnectorFuture), + ), + ) as BoxIterator; + } + + #[cfg(target_family = "wasm")] + { + connectors = Box::new( + connectors.chain( + relay_hints + .into_iter() + /* A hint may have multiple addresses pointing towards the server. This may be multiple + * domain aliases or different ports or an IPv6 or IPv4 address. We only need + * to connect to one of them, since they are considered equivalent. However, we + * also want to be prepared for the rare case of one failing, thus we try to reach + * up to three different addresses. To not flood the system with requests, we + * start them in a 5 seconds interval spread. If one of them succeeds, the remaining ones + * will be cancelled anyways. Note that a hint might not necessarily be reachable via TCP. + */ + .flat_map(|hint| { + /* If the hint has no name, take the first domain name as fallback */ + let name = hint.name + .or_else(|| { + /* Try to parse as IP address. We are only interested in human readable names (the IP address will be printed anyways) */ + hint.tcp.iter() + .filter_map(|hint| match url::Host::parse(&hint.hostname) { + Ok(url::Host::Domain(_)) => Some(hint.hostname.clone()), + _ => None, + }) + .next() + }); + hint.ws + .into_iter() + .take(3) + .enumerate() + .map(move |(i, u)| (i, u, name.clone())) + }) + .map(|(index, url, name)| async move { + sleep(std::time::Duration::from_secs( + index as u64 * 5, + )) + .await; + transport::connect_ws_relay(url, name).await + }) + .map(|fut| Box::pin(fut) as ConnectorFuture), + ), + ) as BoxIterator; + } } /* Do a handshake on all our found connections */ let transit_key2 = transit_key.clone(); let tside2 = tside.clone(); let cryptor2 = cryptor.clone(); + #[allow(unused_mut)] // For WASM targets let mut connectors = Box::new( connectors .map(move |fut| { @@ -1336,14 +1207,14 @@ impl TransitConnector { } }) .map(|fut| { - Box::pin(fut) - as BoxFuture> + Box::pin(fut) as BoxFuture> }), ) - as BoxIterator>>; + as BoxIterator>>; /* Also listen on some port just in case. */ - if let Some(socket2) = socket2 { + #[cfg(not(target_family = "wasm"))] + if let Some(listener) = listener { connectors = Box::new( connectors.chain( std::iter::once(async move { @@ -1351,9 +1222,9 @@ impl TransitConnector { let tside = tside.clone(); let cryptor = cryptor.clone(); let connect = || async { - let (socket, peer) = socket2.accept().await?; + let (socket, peer) = listener.accept().await?; let (socket, info) = - wrap_tcp_connection(socket, ConnectionType::Direct)?; + transport::wrap_tcp_connection(socket, ConnectionType::Direct)?; log::debug!("Got connection from {}!", peer); let (transit, finalizer) = handshake_exchange( is_leader, @@ -1364,9 +1235,7 @@ impl TransitConnector { transit_key.clone(), ) .await?; - Result::<_, crypto::TransitHandshakeError>::Ok(( - transit, finalizer, info, - )) + Result::<_, TransitHandshakeError>::Ok((transit, finalizer, info)) }; loop { match connect().await { @@ -1382,28 +1251,16 @@ impl TransitConnector { } }) .map(|fut| { - Box::pin(fut) - as BoxFuture> + Box::pin(fut) as BoxFuture> }), ), ) - as BoxIterator>>; + as BoxIterator>>; } connectors.collect::>() } } -/// Trait abstracting our socket used for communicating over the wire. -/// -/// Will be primarily instantiated by either a TCP or web socket. Custom methods -/// will be added in the future. -pub(self) trait TransitTransport: - AsyncRead + AsyncWrite + std::any::Any + Unpin + Send -{ -} - -impl TransitTransport for T where T: AsyncRead + AsyncWrite + std::any::Any + Unpin + Send {} - /** * An established Transit connection. * @@ -1435,14 +1292,13 @@ impl Transit { } /** Convert the transit connection to a [`Stream`]/[`Sink`] pair */ + #[cfg(not(target_family = "wasm"))] pub fn split( self, ) -> ( impl futures::sink::Sink, Error = TransitError>, impl futures::stream::Stream, TransitError>>, ) { - use futures::io::AsyncReadExt; - let (reader, writer) = self.socket.split(); ( futures::sink::unfold( @@ -1489,7 +1345,7 @@ async fn handshake_exchange( Box, Box, ), - crypto::TransitHandshakeError, + TransitHandshakeError, > { if host_type != &ConnectionType::Direct { log::trace!("initiating relay handshake"); @@ -1501,10 +1357,7 @@ async fn handshake_exchange( let mut rx = [0u8; 3]; socket.read_exact(&mut rx).await?; let ok_msg: [u8; 3] = *b"ok\n"; - ensure!( - ok_msg == rx, - crypto::TransitHandshakeError::RelayHandshakeFailed - ); + ensure!(ok_msg == rx, TransitHandshakeError::RelayHandshakeFailed); } let finalizer = if is_leader { @@ -1516,6 +1369,40 @@ async fn handshake_exchange( Ok((socket, finalizer)) } +#[cfg(not(target_family = "wasm"))] +pub(super) async fn sleep(duration: std::time::Duration) { + async_std::task::sleep(duration).await +} + +#[cfg(target_family = "wasm")] +pub(super) async fn sleep(duration: std::time::Duration) { + /* Skip error handling. Waiting is best effort anyways */ + let _ = wasm_timer::Delay::new(duration).await; +} + +#[cfg(not(target_family = "wasm"))] +pub(super) async fn timeout( + duration: std::time::Duration, + future: F, +) -> Result +where + F: futures::Future, +{ + async_std::future::timeout(duration, future).await +} + +#[cfg(target_family = "wasm")] +pub(super) async fn timeout( + duration: std::time::Duration, + future: F, +) -> Result +where + F: futures::Future, +{ + use wasm_timer::TryFutureExt; + future.map(Result::Ok).timeout(duration).await +} + #[cfg(test)] mod test { use super::*; diff --git a/src/transit/crypto.rs b/src/transit/crypto.rs index 6650a07b..1b64c90c 100644 --- a/src/transit/crypto.rs +++ b/src/transit/crypto.rs @@ -1,12 +1,18 @@ -/// Cryptographic backbone of the Transit protocol -/// -/// This handles the encrypted handshakes during connection setup, then provides -/// a simple "encrypt/decrypt" abstraction that will be used for all messages. -use super::*; +//! Cryptographic backbone of the Transit protocol +//! +//! This handles the encrypted handshakes during connection setup, then provides +//! a simple "encrypt/decrypt" abstraction that will be used for all messages. + +use super::{ + TransitError, TransitKey, TransitRxKey, TransitTransport, TransitTransportRx, + TransitTransportTx, TransitTxKey, +}; use crate::Key; use async_trait::async_trait; -use futures::future::BoxFuture; +use futures::{future::BoxFuture, io::AsyncWriteExt}; use std::sync::Arc; +use xsalsa20poly1305 as secretbox; +use xsalsa20poly1305::aead::{Aead, NewAead}; /// Private, because we try multiple handshakes and only /// one needs to succeed @@ -37,6 +43,13 @@ pub(super) enum TransitHandshakeError { #[source] std::io::Error, ), + #[cfg(target_family = "wasm")] + #[error("WASM error")] + WASM( + #[from] + #[source] + ws_stream_wasm::WsErr, + ), } impl From<()> for TransitHandshakeError { @@ -45,51 +58,6 @@ impl From<()> for TransitHandshakeError { } } -/// Helper method for handshake: read a fixed number of bytes and make sure they are as expected -async fn read_expect( - socket: &mut (dyn futures::io::AsyncRead + Unpin + Send), - expected: &[u8], -) -> Result<(), TransitHandshakeError> { - let mut buffer = vec![0u8; expected.len()]; - socket.read_exact(&mut buffer).await?; - ensure!(buffer == expected, TransitHandshakeError::HandshakeFailed); - Ok(()) -} - -/// Helper method: read a four bytes length prefix then the appropriate number of bytes -async fn read_transit_message( - socket: &mut (dyn futures::io::AsyncRead + Unpin + Send), -) -> Result, std::io::Error> { - // 1. read 4 bytes from the stream. This represents the length of the encrypted packet. - let length = { - let mut length_arr: [u8; 4] = [0; 4]; - socket.read_exact(&mut length_arr[..]).await?; - u32::from_be_bytes(length_arr) as usize - }; - - // 2. read that many bytes into an array (or a vector?) - let mut buffer = Vec::with_capacity(length); - let len = socket.take(length as u64).read_to_end(&mut buffer).await?; - use std::io::{Error, ErrorKind}; - ensure!( - len == length, - Error::new(ErrorKind::UnexpectedEof, "failed to read whole message") - ); - Ok(buffer) -} - -/// Helper method: write the message length then the message -async fn write_transit_message( - socket: &mut (dyn futures::io::AsyncWrite + Unpin + Send), - message: &[u8], -) -> Result<(), std::io::Error> { - // send the encrypted record - socket - .write_all(&(message.len() as u32).to_be_bytes()) - .await?; - socket.write_all(message).await -} - /// The Transit protocol has the property that the last message of the handshake is from the leader /// and confirms the usage of that specific connection. This trait represents that specific type state. pub(super) trait TransitCryptoInitFinalizer: Send { @@ -140,7 +108,7 @@ pub struct SecretboxInit { impl TransitCryptoInit for SecretboxInit { async fn handshake_leader( &self, - mut socket: &mut dyn TransitTransport, + socket: &mut dyn TransitTransport, ) -> Result, TransitHandshakeError> { // 9. create record keys let rkey = self @@ -171,7 +139,7 @@ impl TransitCryptoInit for SecretboxInit { .to_hex() ); assert_eq!(expected_rx_handshake.len(), 89); - read_expect(&mut socket, expected_rx_handshake.as_bytes()).await?; + socket.read_expect(expected_rx_handshake.as_bytes()).await?; struct Finalizer { skey: Key, @@ -205,7 +173,7 @@ impl TransitCryptoInit for SecretboxInit { async fn handshake_follower( &self, - mut socket: &mut dyn TransitTransport, + socket: &mut dyn TransitTransport, ) -> Result, TransitHandshakeError> { // 9. create record keys /* The order here is correct. The "sender" and "receiver" side are a misnomer and should be called @@ -240,7 +208,7 @@ impl TransitCryptoInit for SecretboxInit { .to_hex(), ); assert_eq!(expected_tx_handshake.len(), 90); - read_expect(&mut socket, expected_tx_handshake.as_bytes()).await?; + socket.read_expect(expected_tx_handshake.as_bytes()).await?; Ok(Box::new(( Box::new(SecretboxCryptoEncrypt { @@ -279,16 +247,14 @@ pub struct NoiseInit { impl TransitCryptoInit for NoiseInit { async fn handshake_leader( &self, - mut socket: &mut dyn TransitTransport, + socket: &mut dyn TransitTransport, ) -> Result, TransitHandshakeError> { socket .write_all(b"Magic-Wormhole Dilation Handshake v1 Leader\n\n") .await?; - read_expect( - &mut socket, - b"Magic-Wormhole Dilation Handshake v1 Follower\n\n", - ) - .await?; + socket + .read_expect(b"Magic-Wormhole Dilation Handshake v1 Follower\n\n") + .await?; let mut handshake: NoiseHandshakeState = { let mut builder = noise_protocol::HandshakeStateBuilder::new(); @@ -300,17 +266,18 @@ impl TransitCryptoInit for NoiseInit { handshake.push_psk(&*self.key); // → psk, e - write_transit_message(&mut socket, &handshake.write_message_vec(&[])?).await?; + socket + .write_transit_message(&handshake.write_message_vec(&[])?) + .await?; // ← e, ee - handshake.read_message(&read_transit_message(&mut socket).await?, &mut [])?; + handshake.read_message(&socket.read_transit_message().await?, &mut [])?; assert!(handshake.completed()); let (tx, mut rx) = handshake.get_ciphers(); // ← "" - let peer_confirmation_message = - rx.decrypt_vec(&read_transit_message(&mut socket).await?)?; + let peer_confirmation_message = rx.decrypt_vec(&socket.read_transit_message().await?)?; ensure!( peer_confirmation_message.len() == 0, TransitHandshakeError::HandshakeFailed @@ -324,11 +291,13 @@ impl TransitCryptoInit for NoiseInit { impl TransitCryptoInitFinalizer for Finalizer { fn handshake_finalize( mut self: Box, - mut socket: &mut dyn TransitTransport, + socket: &mut dyn TransitTransport, ) -> BoxFuture> { Box::pin(async move { // → "" - write_transit_message(&mut socket, &self.tx.encrypt_vec(&[])).await?; + socket + .write_transit_message(&self.tx.encrypt_vec(&[])) + .await?; Ok::<_, TransitHandshakeError>(( Box::new(NoiseCryptoEncrypt { tx: self.tx }) @@ -345,16 +314,14 @@ impl TransitCryptoInit for NoiseInit { async fn handshake_follower( &self, - mut socket: &mut dyn TransitTransport, + socket: &mut dyn TransitTransport, ) -> Result, TransitHandshakeError> { socket .write_all(b"Magic-Wormhole Dilation Handshake v1 Follower\n\n") .await?; - read_expect( - &mut socket, - b"Magic-Wormhole Dilation Handshake v1 Leader\n\n", - ) - .await?; + socket + .read_expect(b"Magic-Wormhole Dilation Handshake v1 Leader\n\n") + .await?; let mut handshake: NoiseHandshakeState = { let mut builder = noise_protocol::HandshakeStateBuilder::new(); @@ -366,21 +333,22 @@ impl TransitCryptoInit for NoiseInit { handshake.push_psk(&*self.key); // ← psk, e - handshake.read_message(&read_transit_message(&mut socket).await?, &mut [])?; + handshake.read_message(&socket.read_transit_message().await?, &mut [])?; // → e, ee - write_transit_message(&mut socket, &handshake.write_message_vec(&[])?).await?; + socket + .write_transit_message(&handshake.write_message_vec(&[])?) + .await?; assert!(handshake.completed()); // Warning: rx and tx are swapped here (read the `get_ciphers` doc carefully) let (mut rx, mut tx) = handshake.get_ciphers(); // → "" - write_transit_message(&mut socket, &tx.encrypt_vec(&[])).await?; + socket.write_transit_message(&tx.encrypt_vec(&[])).await?; // ← "" - let peer_confirmation_message = - rx.decrypt_vec(&read_transit_message(&mut socket).await?)?; + let peer_confirmation_message = rx.decrypt_vec(&socket.read_transit_message().await?)?; ensure!( peer_confirmation_message.len() == 0, TransitHandshakeError::HandshakeFailed @@ -396,19 +364,19 @@ impl TransitCryptoInit for NoiseInit { type DynTransitCrypto = (Box, Box); #[async_trait] -pub trait TransitCryptoEncrypt: Send { +pub(super) trait TransitCryptoEncrypt: Send { async fn encrypt( &mut self, - socket: &mut (dyn futures::io::AsyncWrite + Unpin + Send), + socket: &mut dyn TransitTransportTx, plaintext: &[u8], ) -> Result<(), TransitError>; } #[async_trait] -pub trait TransitCryptoDecrypt: Send { +pub(super) trait TransitCryptoDecrypt: Send { async fn decrypt( &mut self, - socket: &mut (dyn futures::io::AsyncRead + Unpin + Send), + socket: &mut dyn TransitTransportRx, ) -> Result, TransitError>; } @@ -434,7 +402,7 @@ struct SecretboxCryptoDecrypt { impl TransitCryptoEncrypt for SecretboxCryptoEncrypt { async fn encrypt( &mut self, - socket: &mut (dyn futures::io::AsyncWrite + Unpin + Send), + socket: &mut dyn TransitTransportTx, plaintext: &[u8], ) -> Result<(), TransitError> { let nonce = &mut self.snonce; @@ -467,11 +435,11 @@ impl TransitCryptoEncrypt for SecretboxCryptoEncrypt { impl TransitCryptoDecrypt for SecretboxCryptoDecrypt { async fn decrypt( &mut self, - socket: &mut (dyn futures::io::AsyncRead + Unpin + Send), + socket: &mut dyn TransitTransportRx, ) -> Result, TransitError> { let nonce = &mut self.rnonce; - let enc_packet = read_transit_message(socket).await?; + let enc_packet = socket.read_transit_message().await?; use std::io::{Error, ErrorKind}; ensure!( @@ -518,10 +486,12 @@ struct NoiseCryptoDecrypt { impl TransitCryptoEncrypt for NoiseCryptoEncrypt { async fn encrypt( &mut self, - socket: &mut (dyn futures::io::AsyncWrite + Unpin + Send), + socket: &mut dyn TransitTransportTx, plaintext: &[u8], ) -> Result<(), TransitError> { - write_transit_message(socket, &self.tx.encrypt_vec(plaintext)).await?; + socket + .write_transit_message(&self.tx.encrypt_vec(plaintext)) + .await?; Ok(()) } } @@ -530,9 +500,9 @@ impl TransitCryptoEncrypt for NoiseCryptoEncrypt { impl TransitCryptoDecrypt for NoiseCryptoDecrypt { async fn decrypt( &mut self, - socket: &mut (dyn futures::io::AsyncRead + Unpin + Send), + socket: &mut dyn TransitTransportRx, ) -> Result, TransitError> { - let plaintext = self.rx.decrypt_vec(&read_transit_message(socket).await?)?; + let plaintext = self.rx.decrypt_vec(&socket.read_transit_message().await?)?; Ok(plaintext.into_boxed_slice()) } } diff --git a/src/transit/transport.rs b/src/transit/transport.rs new file mode 100644 index 00000000..1e3839ec --- /dev/null +++ b/src/transit/transport.rs @@ -0,0 +1,304 @@ +//! Helper functions abstracting away different transport protocols for Transit + +use super::{ConnectionType, TransitConnection, TransitHandshakeError, TransitInfo}; +#[cfg(not(target_family = "wasm"))] +use super::{DirectHint, StunError}; + +#[cfg(not(target_family = "wasm"))] +use async_std::net::TcpStream; +use async_trait::async_trait; +use futures::{ + future::TryFutureExt, + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, +}; +#[cfg(not(target_family = "wasm"))] +use std::{ + net::{IpAddr, SocketAddr, ToSocketAddrs}, + sync::Arc, +}; + +#[async_trait] +pub(super) trait TransitTransportRx: AsyncRead + std::any::Any + Unpin + Send { + /// Helper method for handshake: read a fixed number of bytes and make sure they are as expected + async fn read_expect(&mut self, expected: &[u8]) -> Result<(), TransitHandshakeError> { + let mut buffer = vec![0u8; expected.len()]; + self.read_exact(&mut buffer).await?; + ensure!(buffer == expected, TransitHandshakeError::HandshakeFailed); + Ok(()) + } + + /// Helper method: read a four bytes length prefix then the appropriate number of bytes + async fn read_transit_message(&mut self) -> Result, std::io::Error> { + // 1. read 4 bytes from the stream. This represents the length of the encrypted packet. + let length = { + let mut length_arr: [u8; 4] = [0; 4]; + self.read_exact(&mut length_arr[..]).await?; + u32::from_be_bytes(length_arr) as usize + }; + + // 2. read that many bytes into an array (or a vector?) + let mut buffer = Vec::with_capacity(length); + let len = self.take(length as u64).read_to_end(&mut buffer).await?; + use std::io::{Error, ErrorKind}; + ensure!( + len == length, + Error::new(ErrorKind::UnexpectedEof, "failed to read whole message") + ); + Ok(buffer) + } +} + +#[async_trait] +pub(super) trait TransitTransportTx: AsyncWrite + std::any::Any + Unpin + Send { + /// Helper method: write the message length then the message + async fn write_transit_message(&mut self, message: &[u8]) -> Result<(), std::io::Error> { + // send the encrypted record + self.write_all(&(message.len() as u32).to_be_bytes()) + .await?; + self.write_all(message).await + } +} + +/// Trait abstracting our socket used for communicating over the wire. +/// +/// Will be primarily instantiated by either a TCP or web socket. Custom methods +/// will be added in the future. +pub(super) trait TransitTransport: TransitTransportRx + TransitTransportTx {} + +impl TransitTransportRx for T where T: AsyncRead + std::any::Any + Unpin + Send {} +impl TransitTransportTx for T where T: AsyncWrite + std::any::Any + Unpin + Send {} +impl TransitTransport for T where T: AsyncRead + AsyncWrite + std::any::Any + Unpin + Send {} + +#[cfg(not(target_family = "wasm"))] +pub(super) fn set_socket_opts(socket: &socket2::Socket) -> std::io::Result<()> { + socket.set_nonblocking(true)?; + + /* See https://stackoverflow.com/a/14388707/6094756. + * On most BSD and Linux systems, we need both REUSEADDR and REUSEPORT; + * and if they don't support the latter we won't compile. + * On Windows, there is only REUSEADDR but it does what we want. + */ + socket.set_reuse_address(true)?; + #[cfg(all(unix, not(any(target_os = "solaris", target_os = "illumos"))))] + { + socket.set_reuse_port(true)?; + } + #[cfg(not(any( + all(unix, not(any(target_os = "solaris", target_os = "illumos"))), + target_os = "windows" + )))] + { + compile_error!("Your system is not supported yet, please raise an error"); + } + + Ok(()) +} + +/** Perform a STUN query to get the external IP address */ +#[cfg(not(target_family = "wasm"))] +pub(super) async fn tcp_get_external_ip() -> Result<(SocketAddr, TcpStream), StunError> { + let mut socket = tcp_connect_custom( + &"[::]:0".parse::().unwrap().into(), + &super::PUBLIC_STUN_SERVER + .to_socket_addrs()? + /* If you find yourself behind a NAT66, open an issue */ + .find(|x| x.is_ipv4()) + /* TODO add a helper method to stdlib for this */ + .map(|addr| match addr { + SocketAddr::V4(v4) => { + SocketAddr::new(IpAddr::V6(v4.ip().to_ipv6_mapped()), v4.port()) + }, + SocketAddr::V6(_) => unreachable!(), + }) + .ok_or(StunError::ServerIsV6Only)? + .into(), + ) + .await?; + + use bytecodec::{DecodeExt, EncodeExt}; + use stun_codec::{ + rfc5389::{ + self, + attributes::{MappedAddress, Software, XorMappedAddress}, + Attribute, + }, + Message, MessageClass, MessageDecoder, MessageEncoder, TransactionId, + }; + + fn get_binding_request() -> Result, bytecodec::Error> { + use rand::Rng; + let random_bytes = rand::thread_rng().gen::<[u8; 12]>(); + + let mut message = Message::new( + MessageClass::Request, + rfc5389::methods::BINDING, + TransactionId::new(random_bytes), + ); + + message.add_attribute(Attribute::Software(Software::new( + "magic-wormhole-rust".to_owned(), + )?)); + + // Encodes the message + let mut encoder = MessageEncoder::new(); + let bytes = encoder.encode_into_bytes(message.clone())?; + Ok(bytes) + } + + fn decode_address(buf: &[u8]) -> Result, bytecodec::Error> { + let mut decoder = MessageDecoder::::new(); + let decoded = decoder.decode_from_bytes(buf)??; + + let external_addr1 = decoded + .get_attribute::() + .map(|x| x.address()); + //let external_addr2 = decoded.get_attribute::().map(|x|x.address()); + let external_addr3 = decoded + .get_attribute::() + .map(|x| x.address()); + let external_addr = external_addr1 + // .or(external_addr2) + .or(external_addr3); + + Ok(external_addr) + } + + /* Connect the plugs */ + + socket.write_all(get_binding_request()?.as_ref()).await?; + + let mut buf = [0u8; 256]; + /* Read header first */ + socket.read_exact(&mut buf[..20]).await?; + let len: u16 = u16::from_be_bytes([buf[2], buf[3]]); + /* Read the rest of the message */ + socket.read_exact(&mut buf[20..][..len as usize]).await?; + let external_addr = + decode_address(&buf[..20 + len as usize])?.ok_or(StunError::ServerNoResponse)?; + + Ok((external_addr, socket)) +} + +/** + * Bind to a port with SO_REUSEADDR, connect to the destination and then hide the blood behind a pretty [`async_std::net::TcpStream`] + * + * We want an `async_std::net::TcpStream`, but with SO_REUSEADDR set. + * The former is just a wrapper around `async_io::Async`, of which we + * copy the `connect` method to add a statement that will set the socket flag. + * See https://github.com/smol-rs/async-net/issues/20. + */ +#[cfg(not(target_family = "wasm"))] +async fn tcp_connect_custom( + local_addr: &socket2::SockAddr, + dest_addr: &socket2::SockAddr, +) -> std::io::Result { + log::debug!("Binding to {}", local_addr.as_socket().unwrap()); + let socket = socket2::Socket::new(socket2::Domain::IPV6, socket2::Type::STREAM, None)?; + /* Set our custum options */ + set_socket_opts(&socket)?; + + socket.bind(local_addr)?; + + /* Initiate connect */ + match socket.connect(dest_addr) { + Ok(_) => {}, + #[cfg(unix)] + Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) => {}, + Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {}, + Err(err) => return Err(err), + } + + let stream = async_io::Async::new(std::net::TcpStream::from(socket))?; + /* The stream becomes writable when connected. */ + stream.writable().await?; + + /* Check if there was an error while connecting. */ + stream + .get_ref() + .take_error() + .and_then(|maybe_err| maybe_err.map_or(Ok(()), Result::Err))?; + /* Convert our mess to `async_std::net::TcpStream */ + Ok(stream.into_inner()?.into()) +} + +#[cfg(not(target_family = "wasm"))] +pub(super) async fn connect_tcp_direct( + local_addr: Option>, + hint: DirectHint, +) -> Result { + let dest_addr = SocketAddr::try_from(&hint)?; + log::debug!("Connecting directly to {}", dest_addr); + let socket; + + if let Some(local_addr) = local_addr { + socket = tcp_connect_custom(&local_addr, &dest_addr.into()).await?; + log::debug!("Connected to {}!", dest_addr); + } else { + socket = async_std::net::TcpStream::connect(&dest_addr).await?; + log::debug!("Connected to {}!", dest_addr); + } + + wrap_tcp_connection(socket, ConnectionType::Direct) +} + +/* Take a relay hint and try to connect to it */ +#[cfg(not(target_family = "wasm"))] +pub(super) async fn connect_tcp_relay( + host: DirectHint, + name: Option, +) -> Result { + log::debug!("Connecting to relay {}", host); + let socket = TcpStream::connect((host.hostname.as_str(), host.port)) + .err_into::() + .await?; + log::debug!("Connected to {}!", host); + + wrap_tcp_connection(socket, ConnectionType::Relay { name }) +} + +#[cfg(target_family = "wasm")] +pub(super) async fn connect_ws_relay( + url: url::Url, + name: Option, +) -> Result { + log::debug!("Connecting to relay {}", url); + let (_meta, transit) = ws_stream_wasm::WsMeta::connect(&url, None) + .err_into::() + .await?; + log::debug!("Connected to {}!", url); + + let transit = Box::new(transit.into_io()) as Box; + + Ok(( + transit, + TransitInfo { + conn_type: ConnectionType::Relay { name }, + _unused: (), + }, + )) +} + +/* Take a tcp connection and transform it into a `TransitConnection` (mainly set timeouts) */ +#[cfg(not(target_family = "wasm"))] +pub(super) fn wrap_tcp_connection( + socket: TcpStream, + conn_type: ConnectionType, +) -> Result { + /* Set proper read and write timeouts. This will temporarily set the socket into blocking mode :/ */ + // https://github.com/async-rs/async-std/issues/499 + let socket = std::net::TcpStream::try_from(socket) + .expect("Internal error: this should not fail because we never cloned the socket"); + socket.set_write_timeout(Some(std::time::Duration::from_secs(120)))?; + socket.set_read_timeout(Some(std::time::Duration::from_secs(120)))?; + let socket: TcpStream = socket.into(); + + let info = TransitInfo { + conn_type, + peer_addr: socket + .peer_addr() + .expect("Internal error: socket must be IP"), + _unused: (), + }; + + Ok((Box::new(socket), info)) +} From e18e0301cafb13c73ccd8d8e180704adf9b871d8 Mon Sep 17 00:00:00 2001 From: piegames Date: Tue, 31 Jan 2023 22:07:01 +0100 Subject: [PATCH 07/26] Transfer: add WASM support --- src/transfer.rs | 6 ++++-- src/transfer/v1.rs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/transfer.rs b/src/transfer.rs index 36f57437..a3fee776 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -201,6 +201,7 @@ impl TransitAck { } } +#[cfg(not(target_family = "wasm"))] pub async fn send_file_or_folder( wormhole: Wormhole, relay_hints: Vec, @@ -299,6 +300,7 @@ where /// This isn't a proper folder transfer as per the Wormhole protocol /// because it sends it in a way so that the receiver still has to manually /// unpack it. But it's better than nothing +#[cfg(not(target_family = "wasm"))] pub async fn send_folder( wormhole: Wormhole, relay_hints: Vec, @@ -515,7 +517,7 @@ async fn handle_run_result( result: Result<(Result<(), TransferError>, impl Future), crate::util::Cancelled>, ) -> Result<(), TransferError> { async fn wrap_timeout(run: impl Future, cancel: impl Future) { - let run = async_std::future::timeout(SHUTDOWN_TIME, run); + let run = transit::timeout(SHUTDOWN_TIME, run); futures::pin_mut!(run); match crate::util::cancellable(run, cancel).await { Ok(Ok(())) => {}, @@ -571,7 +573,7 @@ async fn handle_run_result( // and we should not only look for the next one but all have been received // and we should not interrupt a receive operation without making sure it leaves the connection // in a consistent state, otherwise the shutdown may cause protocol errors - if let Ok(Ok(Ok(PeerMessage::Error(e)))) = async_std::future::timeout(SHUTDOWN_TIME / 3, wormhole.receive_json()).await { + if let Ok(Ok(Ok(PeerMessage::Error(e)))) = transit::timeout(SHUTDOWN_TIME / 3, wormhole.receive_json()).await { error = TransferError::PeerError(e); } else { log::debug!("Failed to retrieve more specific error message from peer. Maybe it crashed?"); diff --git a/src/transfer/v1.rs b/src/transfer/v1.rs index be758490..15e31c17 100644 --- a/src/transfer/v1.rs +++ b/src/transfer/v1.rs @@ -108,6 +108,7 @@ where super::handle_run_result(wormhole, result).await } +#[cfg(not(target_family = "wasm"))] pub async fn send_folder( mut wormhole: Wormhole, relay_hints: Vec, From eab91a5d6d656d0613443a98e4bc7241d084ba05 Mon Sep 17 00:00:00 2001 From: piegames Date: Tue, 31 Jan 2023 22:07:25 +0100 Subject: [PATCH 08/26] CI: Test WASM target --- .github/workflows/push.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 4e7eb283..b233dc67 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -66,6 +66,7 @@ jobs: profile: minimal toolchain: ${{ matrix.rust }} override: true + target: wasm32-unknown-unknown - name: Cache ~/.cargo uses: actions/cache@v1 with: @@ -101,6 +102,11 @@ jobs: with: command: build args: --all-targets + - name: build WASM + uses: actions-rs/cargo@v1 + with: + command: build + args: --target wasm32-unknown-unknown --no-default-features --package magic-wormhole --features transit --features transfer - name: test uses: actions-rs/cargo@v1 with: From 485bf1962f200314dd53bb01b9bcc21c4c129262 Mon Sep 17 00:00:00 2001 From: piegames Date: Sun, 5 Feb 2023 21:23:16 +0100 Subject: [PATCH 09/26] Move some helper functions to `util` module --- src/transfer.rs | 6 +++--- src/transit.rs | 48 +++++++----------------------------------------- src/util.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/transfer.rs b/src/transfer.rs index a3fee776..8a0aeace 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -14,7 +14,7 @@ use serde_derive::{Deserialize, Serialize}; use serde_json::json; use std::sync::Arc; -use super::{core::WormholeError, transit, transit::Transit, AppID, Wormhole}; +use super::{core::WormholeError, transit, transit::Transit, util, AppID, Wormhole}; use futures::Future; use log::*; use std::{borrow::Cow, path::PathBuf}; @@ -517,7 +517,7 @@ async fn handle_run_result( result: Result<(Result<(), TransferError>, impl Future), crate::util::Cancelled>, ) -> Result<(), TransferError> { async fn wrap_timeout(run: impl Future, cancel: impl Future) { - let run = transit::timeout(SHUTDOWN_TIME, run); + let run = util::timeout(SHUTDOWN_TIME, run); futures::pin_mut!(run); match crate::util::cancellable(run, cancel).await { Ok(Ok(())) => {}, @@ -573,7 +573,7 @@ async fn handle_run_result( // and we should not only look for the next one but all have been received // and we should not interrupt a receive operation without making sure it leaves the connection // in a consistent state, otherwise the shutdown may cause protocol errors - if let Ok(Ok(Ok(PeerMessage::Error(e)))) = transit::timeout(SHUTDOWN_TIME / 3, wormhole.receive_json()).await { + if let Ok(Ok(Ok(PeerMessage::Error(e)))) = util::timeout(SHUTDOWN_TIME / 3, wormhole.receive_json()).await { error = TransferError::PeerError(e); } else { log::debug!("Failed to retrieve more specific error message from peer. Maybe it crashed?"); diff --git a/src/transit.rs b/src/transit.rs index e455dd96..a7678b02 100644 --- a/src/transit.rs +++ b/src/transit.rs @@ -13,7 +13,7 @@ //! **Notice:** while the resulting TCP connection is naturally bi-directional, the handshake is not symmetric. There *must* be one //! "leader" side and one "follower" side (formerly called "sender" and "receiver"). -use crate::{Key, KeyPurpose}; +use crate::{util, Key, KeyPurpose}; use serde_derive::{Deserialize, Serialize}; #[cfg(not(target_family = "wasm"))] @@ -691,7 +691,7 @@ pub async fn init( * so that we will be NATted to the same port again. If it doesn't, simply bind a new socket * and use that instead. */ - let socket: MaybeConnectedSocket = match timeout( + let socket: MaybeConnectedSocket = match util::timeout( std::time::Duration::from_secs(4), transport::tcp_get_external_ip(), ) @@ -874,7 +874,7 @@ impl TransitConnector { ); let (mut transit, mut finalizer, mut conn_info) = - timeout(std::time::Duration::from_secs(60), connection_stream.next()) + util::timeout(std::time::Duration::from_secs(60), connection_stream.next()) .await .map_err(|_| { log::debug!("`leader_connect` timed out"); @@ -896,7 +896,7 @@ impl TransitConnector { } else { elapsed.mul_f32(0.3) }; - let _ = timeout(to_wait, async { + let _ = util::timeout(to_wait, async { while let Some((new_transit, new_finalizer, new_conn_info)) = connection_stream.next().await { @@ -978,7 +978,7 @@ impl TransitConnector { }), ); - let transit = match timeout( + let transit = match util::timeout( std::time::Duration::from_secs(60), &mut connection_stream.next(), ) @@ -1125,7 +1125,7 @@ impl TransitConnector { .map(move |(i, h)| (i, h, name.clone())) }) .map(|(index, host, name)| async move { - sleep(std::time::Duration::from_secs( + util::sleep(std::time::Duration::from_secs( index as u64 * 5, )) .await; @@ -1169,7 +1169,7 @@ impl TransitConnector { .map(move |(i, u)| (i, u, name.clone())) }) .map(|(index, url, name)| async move { - sleep(std::time::Duration::from_secs( + util::sleep(std::time::Duration::from_secs( index as u64 * 5, )) .await; @@ -1369,40 +1369,6 @@ async fn handshake_exchange( Ok((socket, finalizer)) } -#[cfg(not(target_family = "wasm"))] -pub(super) async fn sleep(duration: std::time::Duration) { - async_std::task::sleep(duration).await -} - -#[cfg(target_family = "wasm")] -pub(super) async fn sleep(duration: std::time::Duration) { - /* Skip error handling. Waiting is best effort anyways */ - let _ = wasm_timer::Delay::new(duration).await; -} - -#[cfg(not(target_family = "wasm"))] -pub(super) async fn timeout( - duration: std::time::Duration, - future: F, -) -> Result -where - F: futures::Future, -{ - async_std::future::timeout(duration, future).await -} - -#[cfg(target_family = "wasm")] -pub(super) async fn timeout( - duration: std::time::Duration, - future: F, -) -> Result -where - F: futures::Future, -{ - use wasm_timer::TryFutureExt; - future.map(Result::Ok).timeout(duration).await -} - #[cfg(test)] mod test { use super::*; diff --git a/src/util.rs b/src/util.rs index dc7148b6..9ac5be1b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -79,6 +79,7 @@ impl std::fmt::Display for DisplayBytes<'_> { * TODO remove after https://github.com/quininer/memsec/issues/11 is resolved. * Original implementation: https://github.com/jedisct1/libsodium/blob/6d566070b48efd2fa099bbe9822914455150aba9/src/libsodium/sodium/utils.c#L262-L307 */ +#[allow(unused)] pub fn sodium_increment_le(n: &mut [u8]) { let mut c = 1u16; for b in n { @@ -209,3 +210,35 @@ impl std::fmt::Display for Cancelled { write!(f, "Task has been cancelled") } } + +#[cfg(not(target_family = "wasm"))] +pub async fn sleep(duration: std::time::Duration) { + async_std::task::sleep(duration).await +} + +#[cfg(target_family = "wasm")] +pub async fn sleep(duration: std::time::Duration) { + /* Skip error handling. Waiting is best effort anyways */ + let _ = wasm_timer::Delay::new(duration).await; +} + +#[cfg(not(target_family = "wasm"))] +pub async fn timeout( + duration: std::time::Duration, + future: F, +) -> Result +where + F: futures::Future, +{ + async_std::future::timeout(duration, future).await +} + +#[cfg(target_family = "wasm")] +pub async fn timeout(duration: std::time::Duration, future: F) -> Result +where + F: futures::Future, +{ + use futures::FutureExt; + use wasm_timer::TryFutureExt; + future.map(Result::Ok).timeout(duration).await +} From a1f81507d42f8ae8fd675a6a1295e194a7a242ac Mon Sep 17 00:00:00 2001 From: piegames Date: Sat, 11 Feb 2023 18:52:21 +0100 Subject: [PATCH 10/26] Transit: add `connect` method Sometimes you just want to pass in the leader/follower as a boolean at run time. --- src/transit.rs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/transit.rs b/src/transit.rs index a7678b02..45703861 100644 --- a/src/transit.rs +++ b/src/transit.rs @@ -833,6 +833,29 @@ impl TransitConnector { &self.our_hints } + /** + * Forwards to either [`leader_connect`] or [`follower_connect`]. + * + * It usually is better to call the respective functions directly by their name, as it makes + * them less easy to confuse (confusion may still happen though). Nevertheless, sometimes it + * is desirable to use the same code for both sides and only track the side with a boolean. + */ + pub async fn connect( + self, + is_leader: bool, + transit_key: Key, + their_abilities: Abilities, + their_hints: Arc, + ) -> Result<(Transit, TransitInfo), TransitConnectError> { + if is_leader { + self.leader_connect(transit_key, their_abilities, their_hints) + .await + } else { + self.follower_connect(transit_key, their_abilities, their_hints) + .await + } + } + /** * Connect to the other side, as sender. */ @@ -852,7 +875,7 @@ impl TransitConnector { let start = instant::Instant::now(); let mut connection_stream = Box::pin( - Self::connect( + Self::connect_inner( true, transit_key, our_abilities, @@ -957,7 +980,7 @@ impl TransitConnector { let transit_key = Arc::new(transit_key); let mut connection_stream = Box::pin( - Self::connect( + Self::connect_inner( false, transit_key, our_abilities, @@ -1018,7 +1041,7 @@ impl TransitConnector { * If the receiving end of the channel for the results is closed before all futures in the return * value are cancelled/dropped. */ - fn connect( + fn connect_inner( is_leader: bool, transit_key: Arc>, our_abilities: Abilities, From b9b53993578e2425c37379c7a4c6a111a1b1db55 Mon Sep 17 00:00:00 2001 From: Jon Zlotnik Date: Tue, 7 Mar 2023 08:09:36 -0500 Subject: [PATCH 11/26] add .vscode/ to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2bb17fd2..1417d84d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ **/*.rs.bk .idea/ +.vscode/ From a82a7aeb6b9ffdac33297a68582d12cf753979bd Mon Sep 17 00:00:00 2001 From: Jon Zlotnik Date: Tue, 7 Mar 2023 08:12:09 -0500 Subject: [PATCH 12/26] removed dependancy on actions-rs/tarpaulin --- .github/workflows/push.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index b233dc67..d1623ddc 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -160,6 +160,10 @@ jobs: with: toolchain: nightly override: true + - uses: actions-rs/install@v0.1 + with: + crate: cargo-tarpaulin + version: latest - name: Cache ~/.cargo uses: actions/cache@v1 with: @@ -170,9 +174,11 @@ jobs: with: path: target key: ${{ runner.os }}-coverage-cargo-build-target - - uses: actions-rs/tarpaulin@v0.1 + - name: Run tarpaulin + uses: actions-rs/cargo@v1 with: - args: --all + command: tarpaulin + args: --workspace --out Xml - name: upload coverage uses: codecov/codecov-action@v1 with: From 3daaded034a72d6f31423f5b82d2a1ade8b4c952 Mon Sep 17 00:00:00 2001 From: Justus Tumacder Date: Tue, 28 Feb 2023 11:03:22 +0800 Subject: [PATCH 13/26] Add the ability to check claimed nameplates - Adds `list_nameplates` which returns a list of currently claimed nameplates - Adds a new argument (`expect_claimed_nameplate`) to `connect_with_code`. When true, the function will return an error if the nameplate is not claimed --- cli/src/main.rs | 5 +++-- src/core.rs | 12 ++++++++++++ src/core/rendezvous.rs | 21 +++++++++++++++++++++ src/core/test.rs | 38 ++++++++++++++++++++++++++++++++------ 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index e7d07b1a..476a037a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -656,7 +656,7 @@ async fn parse_and_connect( )?; } let (server_welcome, wormhole) = - magic_wormhole::Wormhole::connect_with_code(app_config, code).await?; + magic_wormhole::Wormhole::connect_with_code(app_config, code, false).await?; print_welcome(term, &server_welcome)?; (wormhole, server_welcome.code) }, @@ -860,7 +860,8 @@ async fn send_many( } let (_server_welcome, wormhole) = - magic_wormhole::Wormhole::connect_with_code(transfer::APP_CONFIG, code.clone()).await?; + magic_wormhole::Wormhole::connect_with_code(transfer::APP_CONFIG, code.clone(), false) + .await?; send_in_background( relay_hints.clone(), Arc::clone(&file_path), diff --git a/src/core.rs b/src/core.rs index 27bf0d3c..282ad58a 100644 --- a/src/core.rs +++ b/src/core.rs @@ -42,6 +42,8 @@ pub enum WormholeError { PakeFailed, #[error("Cannot decrypt a received message")] Crypto, + #[error("Nameplate is unclaimed: {}", _0)] + UnclaimedNameplate(Nameplate), } impl WormholeError { @@ -165,6 +167,7 @@ impl Wormhole { pub async fn connect_with_code( config: AppConfig, code: Code, + expect_claimed_nameplate: bool, ) -> Result<(WormholeWelcome, Self), WormholeError> { let AppConfig { id: appid, @@ -174,6 +177,15 @@ impl Wormhole { let (mut server, welcome) = RendezvousServer::connect(&appid, &rendezvous_url).await?; let nameplate = code.nameplate(); + + if expect_claimed_nameplate { + let nameplate = code.nameplate(); + let nameplates = server.list_nameplates().await?; + if !nameplates.contains(&nameplate) { + return Err(WormholeError::UnclaimedNameplate(nameplate)); + } + } + let mailbox = server.claim_open(nameplate).await?; log::debug!("Connected to mailbox {}", mailbox); diff --git a/src/core/rendezvous.rs b/src/core/rendezvous.rs index b013877b..a107297c 100644 --- a/src/core/rendezvous.rs +++ b/src/core/rendezvous.rs @@ -74,6 +74,10 @@ impl RendezvousError { type MessageQueue = VecDeque; +#[derive(Clone, Debug, derive_more::Display)] +#[display(fmt = "{:?}", _0)] +struct NameplateList(Vec); + #[cfg(not(target_family = "wasm"))] struct WsConnection { connection: async_tungstenite::WebSocketStream, @@ -174,6 +178,9 @@ impl WsConnection { Some(InboundMessage::Error { error, orig: _ }) => { break Err(RendezvousError::Server(error.into())); }, + Some(InboundMessage::Nameplates { nameplates }) => { + break Ok(RendezvousReply::Nameplates(NameplateList(nameplates))) + }, Some(other) => { break Err(RendezvousError::protocol(format!( "Got unexpected message type from server '{}'", @@ -273,6 +280,7 @@ enum RendezvousReply { Released, Claimed(Mailbox), Closed, + Nameplates(NameplateList), } #[derive(Clone, Debug, derive_more::Display)] @@ -528,6 +536,19 @@ impl RendezvousServer { .is_some() } + /** + * Gets the list of currently claimed nameplates. + * This can be called at any time. + */ + pub async fn list_nameplates(&mut self) -> Result, RendezvousError> { + self.send_message(&OutboundMessage::List).await?; + let nameplate_reply = self.receive_reply().await?; + match nameplate_reply { + RendezvousReply::Nameplates(x) => Ok(x.0), + other => Err(RendezvousError::invalid_message("nameplates", other)), + } + } + pub async fn release_nameplate(&mut self) -> Result<(), RendezvousError> { let nameplate = &mut self .state diff --git a/src/core/test.rs b/src/core/test.rs index b060a3dc..5e9d99ed 100644 --- a/src/core/test.rs +++ b/src/core/test.rs @@ -75,7 +75,8 @@ pub async fn test_file_rust2rust() -> eyre::Result<()> { let code = code_rx.await?; log::info!("Got code over local: {}", &code); let (welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code).await?; + Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code, true) + .await?; if let Some(welcome) = &welcome.welcome { log::info!("Got welcome: {}", welcome); } @@ -150,7 +151,8 @@ pub async fn test_4096_file_rust2rust() -> eyre::Result<()> { let code = code_rx.await?; log::info!("Got code over local: {}", &code); let (welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code).await?; + Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code, true) + .await?; if let Some(welcome) = &welcome.welcome { log::info!("Got welcome: {}", welcome); } @@ -223,7 +225,8 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { let code = code_rx.await?; log::info!("Got code over local: {}", &code); let (welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code).await?; + Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code, true) + .await?; if let Some(welcome) = &welcome.welcome { log::info!("Got welcome: {}", welcome); } @@ -302,6 +305,7 @@ pub async fn test_send_many() -> eyre::Result<()> { let (_welcome, wormhole) = Wormhole::connect_with_code( transfer::APP_CONFIG.id(TEST_APPID), sender_code.clone(), + false, ) .await?; senders.push(async_std::task::spawn(async move { @@ -329,7 +333,8 @@ pub async fn test_send_many() -> eyre::Result<()> { for i in 0..5usize { log::info!("Receiving file #{}", i); let (_welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code.clone()).await?; + Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code.clone(), true) + .await?; log::info!("Got key: {}", &wormhole.key); let req = crate::transfer::request_file( wormhole, @@ -389,6 +394,7 @@ pub async fn test_wrong_code() -> eyre::Result<()> { APP_CONFIG, /* Making a wrong code here by appending bullshit */ Code::new(&nameplate, "foo-bar"), + true, ) .await; @@ -411,9 +417,9 @@ pub async fn test_crowded() -> eyre::Result<()> { let (welcome, connector1) = Wormhole::connect_without_code(APP_CONFIG, 2).await?; log::info!("This test's code is: {}", &welcome.code); - let connector2 = Wormhole::connect_with_code(APP_CONFIG, welcome.code.clone()); + let connector2 = Wormhole::connect_with_code(APP_CONFIG, welcome.code.clone(), true); - let connector3 = Wormhole::connect_with_code(APP_CONFIG, welcome.code.clone()); + let connector3 = Wormhole::connect_with_code(APP_CONFIG, welcome.code.clone(), true); match futures::try_join!(connector1, connector2, connector3).unwrap_err() { magic_wormhole::WormholeError::ServerError( @@ -427,6 +433,26 @@ pub async fn test_crowded() -> eyre::Result<()> { Ok(()) } +#[async_std::test] +pub async fn test_connect_with_code_expecting_nameplate() -> eyre::Result<()> { + // the max nameplate number is 999, so this will not impact a real nameplate + let code = Code("1000-guitarist-revenge".to_owned()); + let connector = Wormhole::connect_with_code(APP_CONFIG, code, true) + .await + .unwrap_err(); + match connector { + magic_wormhole::WormholeError::UnclaimedNameplate(x) => { + assert_eq!(x, magic_wormhole::core::Nameplate("1000".to_owned())); + }, + other => panic!( + "Got wrong error type {:?}. Expected `NameplateNotFound`", + other + ), + } + + Ok(()) +} + #[test] fn test_phase() { let p = Phase::PAKE; From e9ebb64e29155c538e7e72cdd90c1afd87b827b1 Mon Sep 17 00:00:00 2001 From: Adam Fontenot Date: Mon, 17 Apr 2023 04:31:09 -0400 Subject: [PATCH 14/26] Unify sending/receiving progress bar style Change the receiving end progress bar so that, like the sending end, it will only be updated every 250 ms. This avoids extremely rapid flickering that makes the ETA very hard to read. --- cli/src/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 476a037a..f2909699 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -980,7 +980,12 @@ async fn receive( let pb = create_progress_bar(req.filesize); - let on_progress = move |received, _total| { + let on_progress = move |received, total| { + if received == 0 { + pb.reset_elapsed(); + pb.set_length(total); + pb.enable_steady_tick(std::time::Duration::from_millis(250)); + } pb.set_position(received); }; From 3f52efc396e059cb2c328e9e874dd354327980ae Mon Sep 17 00:00:00 2001 From: Adam Fontenot Date: Sat, 22 Apr 2023 03:19:55 -0400 Subject: [PATCH 15/26] Refactor progress bar handler creation Unify progress bar closures in `create_progress_handler`. --- cli/src/main.rs | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index f2909699..4b6b836f 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -702,6 +702,17 @@ fn create_progress_bar(file_size: u64) -> ProgressBar { pb } +fn create_progress_handler(pb: ProgressBar) -> impl FnMut(u64, u64) { + move |sent, total| { + if sent == 0 { + pb.reset_elapsed(); + pb.set_length(total); + pb.enable_steady_tick(std::time::Duration::from_millis(250)); + } + pb.set_position(sent); + } +} + fn enter_code() -> eyre::Result { use dialoguer::Input; @@ -790,14 +801,7 @@ async fn send( file_name, transit_abilities, &transit::log_transit_connection, - move |sent, total| { - if sent == 0 { - pb.reset_elapsed(); - pb.set_length(total); - pb.enable_steady_tick(std::time::Duration::from_millis(250)); - } - pb.set_position(sent); - }, + create_progress_handler(pb), ctrl_c(), ) .await @@ -898,14 +902,7 @@ async fn send_many( file_name.deref(), transit_abilities, &transit::log_transit_connection, - move |sent, total| { - if sent == 0 { - pb2.reset_elapsed(); - pb2.set_length(total); - pb2.enable_steady_tick(std::time::Duration::from_millis(250)); - } - pb2.set_position(sent); - }, + create_progress_handler(pb2), cancel, ) .await?; @@ -980,15 +977,6 @@ async fn receive( let pb = create_progress_bar(req.filesize); - let on_progress = move |received, total| { - if received == 0 { - pb.reset_elapsed(); - pb.set_length(total); - pb.enable_steady_tick(std::time::Duration::from_millis(250)); - } - pb.set_position(received); - }; - /* Then, accept if the file exists */ if !file_path.exists() || noconfirm { let mut file = OpenOptions::new() @@ -1000,7 +988,7 @@ async fn receive( return req .accept( &transit::log_transit_connection, - on_progress, + create_progress_handler(pb), &mut file, ctrl_c(), ) @@ -1027,7 +1015,7 @@ async fn receive( Ok(req .accept( &transit::log_transit_connection, - on_progress, + create_progress_handler(pb), &mut file, ctrl_c(), ) From 187f585265ad6a2833c0e8515cab0f01e2decac7 Mon Sep 17 00:00:00 2001 From: Andreas Wuerl Date: Mon, 1 May 2023 01:22:00 +0200 Subject: [PATCH 16/26] two step connect methods --- cli/src/main.rs | 37 ++++--- src/core.rs | 260 ++++++++++++++++++++++++++++++++++------------ src/core/test.rs | 262 ++++++++++++++++++++++++++++++++++++----------- src/lib.rs | 3 +- 4 files changed, 414 insertions(+), 148 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 4b6b836f..f491fc3c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -18,7 +18,7 @@ use std::{ path::{Path, PathBuf}, }; -use magic_wormhole::{forwarding, transfer, transit, Wormhole}; +use magic_wormhole::{forwarding, transfer, transit, MailboxConnection, Wormhole}; fn install_ctrlc_handler( ) -> eyre::Result futures::future::BoxFuture<'static, ()> + Clone> { @@ -646,7 +646,7 @@ async fn parse_and_connect( uri_rendezvous = Some(rendezvous_server.clone()); app_config = app_config.rendezvous_url(rendezvous_server.to_string().into()); } - let (wormhole, code) = match code { + let mailbox_connection = match code { Some(code) => { if is_send { print_code.expect("`print_code` must be `Some` when `is_send` is `true`")( @@ -655,21 +655,16 @@ async fn parse_and_connect( &uri_rendezvous, )?; } - let (server_welcome, wormhole) = - magic_wormhole::Wormhole::connect_with_code(app_config, code, false).await?; - print_welcome(term, &server_welcome)?; - (wormhole, server_welcome.code) + MailboxConnection::connect(app_config, code, true).await? }, None => { - let numwords = code_length.unwrap(); + let mailbox_connection = + MailboxConnection::create(app_config, code_length.unwrap()).await?; - let (server_welcome, connector) = - magic_wormhole::Wormhole::connect_without_code(app_config, numwords).await?; - print_welcome(term, &server_welcome)?; /* Print code and also copy it to clipboard */ if is_send { if let Some(clipboard) = clipboard { - match clipboard.set_contents(server_welcome.code.to_string()) { + match clipboard.set_contents(mailbox_connection.code.to_string()) { Ok(()) => log::info!("Code copied to clipboard"), Err(err) => log::warn!("Failed to copy code to clipboard: {}", err), } @@ -677,14 +672,16 @@ async fn parse_and_connect( print_code.expect("`print_code` must be `Some` when `is_send` is `true`")( term, - &server_welcome.code, + &mailbox_connection.code, &uri_rendezvous, )?; } - let wormhole = connector.await?; - (wormhole, server_welcome.code) + mailbox_connection }, }; + print_welcome(term, &mailbox_connection.welcome)?; + let code = mailbox_connection.code.clone(); + let wormhole = Wormhole::connect(mailbox_connection).await?; eyre::Result::<_>::Ok((wormhole, code, relay_hints)) } @@ -722,8 +719,8 @@ fn enter_code() -> eyre::Result { .map_err(From::from) } -fn print_welcome(term: &mut Term, welcome: &magic_wormhole::WormholeWelcome) -> eyre::Result<()> { - if let Some(welcome) = &welcome.welcome { +fn print_welcome(term: &mut Term, welcome: &Option) -> eyre::Result<()> { + if let Some(welcome) = &welcome { writeln!(term, "Got welcome from server: {}", welcome)?; } Ok(()) @@ -863,9 +860,11 @@ async fn send_many( break; } - let (_server_welcome, wormhole) = - magic_wormhole::Wormhole::connect_with_code(transfer::APP_CONFIG, code.clone(), false) - .await?; + let wormhole = Wormhole::connect( + MailboxConnection::connect(transfer::APP_CONFIG, code.clone(), false).await?, + ) + .await?; + send_in_background( relay_hints.clone(), Arc::clone(&file_path), diff --git a/src/core.rs b/src/core.rs index 282ad58a..0d026607 100644 --- a/src/core.rs +++ b/src/core.rs @@ -63,6 +63,10 @@ impl From for WormholeError { * The result of the client-server handshake */ #[derive(Clone, Debug, PartialEq, Eq)] +#[deprecated( + since = "0.7.0", + note = "part of the response of `Wormhole::connect_without_code(...)` and `Wormhole::connect_with_code(...) please use 'MailboxConnection::create(...)`/`MailboxConnection::connect(..)` and `Wormhole::connect(mailbox_connection)' instead" +)] pub struct WormholeWelcome { /** A welcome message from the server (think of "message of the day"). Should be displayed to the user if present. */ pub welcome: Option, @@ -75,9 +79,8 @@ pub struct WormholeWelcome { * You can send and receive arbitrary messages in form of byte slices over it, using [`Wormhole::send`] and [`Wormhole::receive`]. * Everything else (including encryption) will be handled for you. * - * To create a wormhole, use the [`Wormhole::connect_without_code`], [`Wormhole::connect_with_code`] etc. methods, depending on - * which values you have. Typically, the sender side connects without a code (which will create one), and the receiver side - * has one (the user entered it, who got it from the sender). + * To create a wormhole, use the mailbox connection created via [`MailboxConnection::create`] or [`MailboxConnection::connect*`] with the [`Wormhole::connect`] method. + * Typically, the sender side connects without a code (which will create one), and the receiver side has one (the user entered it, who got it from the sender). * * # Clean shutdown * @@ -87,6 +90,151 @@ pub struct WormholeWelcome { * Maybe a better way to handle application level protocols is to create a trait for them and then * to paramterize over them. */ + +/// A `MailboxConnection` contains a `RendezvousServer` which is connected to the mailbox +pub struct MailboxConnection { + /// A copy of `AppConfig`, + config: AppConfig, + /// The `RendezvousServer` with an open mailbox connection + server: RendezvousServer, + /// The welcome message received from the mailbox server + pub welcome: Option, + /// The mailbox id of the created mailbox + pub mailbox: Mailbox, + /// The Code which is required to connect to the mailbox. + pub code: Code, +} + +impl MailboxConnection { + /// Create a connection to a mailbox which is configured with a `Code` starting with the nameplate and by a given number of wordlist based random words. + /// + /// # Arguments + /// + /// * `config`: Application configuration + /// * `code_length`: number of words used for the password. The words are taken from the default wordlist. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> eyre::Result<()> { async_std::task::block_on(async { + /// use magic_wormhole::{transfer::APP_CONFIG, AppConfig, MailboxConnection}; + /// let config = APP_CONFIG; + /// let mailbox_connection = MailboxConnection::create(config, 2).await?; + /// # Ok(()) })} + /// ``` + pub async fn create(config: AppConfig, code_length: usize) -> Result { + Self::create_with_password( + config, + &wordlist::default_wordlist(code_length).choose_words(), + ) + .await + } + + /// Create a connection to a mailbox which is configured with a `Code` containing the nameplate and the given password. + /// + /// # Arguments + /// + /// * `config`: Application configuration + /// * `password`: Free text password which will be appended to the nameplate number to form the `Code` + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> eyre::Result<()> { async_std::task::block_on(async { + /// use magic_wormhole::{transfer::APP_CONFIG, MailboxConnection}; + /// let config = APP_CONFIG; + /// let mailbox_connection = MailboxConnection::create_with_password(config, "secret").await?; + /// # Ok(()) })} + /// ``` + pub async fn create_with_password( + config: AppConfig, + password: &str, + ) -> Result { + let (mut server, welcome) = + RendezvousServer::connect(&config.id, &config.rendezvous_url).await?; + let (nameplate, mailbox) = server.allocate_claim_open().await?; + let code = Code::new(&nameplate, &password); + + Ok(MailboxConnection { + config, + server, + mailbox, + code, + welcome, + }) + } + + /// Create a connection to a mailbox defined by a `Code` which contains the `Nameplate` and the password to authorize the access. + /// + /// # Arguments + /// + /// * `config`: Application configuration + /// * `code`: The `Code` required to authorize to connect to an existing mailbox. + /// * `allocate`: `true`: Allocates a `Nameplate` if it does not exist. + /// `false`: The call fails with a `WormholeError::UnclaimedNameplate` when the `Nameplate` does not exist. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> eyre::Result<()> { async_std::task::block_on(async { + /// use magic_wormhole::{transfer::APP_CONFIG, Code, MailboxConnection, Nameplate}; + /// let config = APP_CONFIG; + /// let code = Code::new(&Nameplate::new("5"), "password"); + /// let mailbox_connection = MailboxConnection::connect(config, code, false).await?; + /// # Ok(()) })} + /// ``` + pub async fn connect( + config: AppConfig, + code: Code, + allocate: bool, + ) -> Result { + let (mut server, welcome) = + RendezvousServer::connect(&config.id, &config.rendezvous_url).await?; + let nameplate = code.nameplate(); + if !allocate { + let nameplates = server.list_nameplates().await?; + if !nameplates.contains(&nameplate) { + server.shutdown(Mood::Errory).await?; + return Err(WormholeError::UnclaimedNameplate(nameplate)); + } + } + let mailbox = server.claim_open(nameplate).await?; + + Ok(MailboxConnection { + config, + server, + mailbox, + code, + welcome, + }) + } + + /// Shut down the connection to the mailbox + /// + /// # Arguments + /// + /// * `mood`: `Mood` should give a hint of the reason of the shutdown + /// + /// # Examples + /// + /// ``` + /// # fn main() -> eyre::Result<()> { use magic_wormhole::WormholeError; + /// async_std::task::block_on(async { + /// use magic_wormhole::{transfer::APP_CONFIG, MailboxConnection, Mood}; + /// let config = APP_CONFIG; + /// let mailbox_connection = MailboxConnection::create_with_password(config, "secret") + /// .await?; + /// mailbox_connection.shutdown(Mood::Happy).await?; + /// # Ok(())})} + /// ``` + pub async fn shutdown(self, mood: Mood) -> Result<(), WormholeError> { + self.server + .shutdown(mood) + .await + .map_err(WormholeError::ServerError) + } +} + #[derive(Debug)] pub struct Wormhole { server: RendezvousServer, @@ -128,6 +276,11 @@ impl Wormhole { * do the rest of the client-client handshake and yield the [`Wormhole`] object * on success. */ + #[deprecated( + since = "0.7.0", + note = "please use 'MailboxConnection::create(...) and Wormhole::connect(mailbox_connection)' instead" + )] + #[allow(deprecated)] pub async fn connect_without_code( config: AppConfig, code_length: usize, @@ -138,87 +291,56 @@ impl Wormhole { ), WormholeError, > { - let AppConfig { - id: appid, - rendezvous_url, - app_version: versions, - } = config; - let (mut server, welcome) = RendezvousServer::connect(&appid, &rendezvous_url).await?; - let (nameplate, mailbox) = server.allocate_claim_open().await?; - log::debug!("Connected to mailbox {}", mailbox); - - let code = Code::new( - &nameplate, - &wordlist::default_wordlist(code_length).choose_words(), - ); - + let mailbox_connection = MailboxConnection::create(config, code_length).await?; Ok(( WormholeWelcome { - welcome, - code: code.clone(), + welcome: mailbox_connection.welcome.clone(), + code: mailbox_connection.code.clone(), }, - Self::connect_custom(server, appid, code.0, versions), + Self::connect(mailbox_connection), )) } /** * Connect to a peer with a code. */ + #[deprecated( + since = "0.7.0", + note = "please use 'MailboxConnection::connect(...) and Wormhole::connect(mailbox_connection)' instead" + )] + #[allow(deprecated)] pub async fn connect_with_code( config: AppConfig, code: Code, expect_claimed_nameplate: bool, ) -> Result<(WormholeWelcome, Self), WormholeError> { - let AppConfig { - id: appid, - rendezvous_url, - app_version: versions, - } = config; - let (mut server, welcome) = RendezvousServer::connect(&appid, &rendezvous_url).await?; - - let nameplate = code.nameplate(); - - if expect_claimed_nameplate { - let nameplate = code.nameplate(); - let nameplates = server.list_nameplates().await?; - if !nameplates.contains(&nameplate) { - return Err(WormholeError::UnclaimedNameplate(nameplate)); - } - } - - let mailbox = server.claim_open(nameplate).await?; - log::debug!("Connected to mailbox {}", mailbox); - - Ok(( + let mailbox_connection = + MailboxConnection::connect(config, code.clone(), !expect_claimed_nameplate).await?; + return Ok(( WormholeWelcome { - welcome, - code: code.clone(), + welcome: mailbox_connection.welcome.clone(), + code: code, }, - Self::connect_custom(server, appid, code.0, versions).await?, - )) + Self::connect(mailbox_connection).await?, + )); } - /** TODO */ - pub async fn connect_with_seed() { - todo!() - } - - /// Do only the client-client part of the connection setup - /// - /// The rendezvous server must already have an opened mailbox. - /// - /// # Panics + /// Set up a Wormhole which is the client-client part of the connection setup /// - /// If the [`RendezvousServer`] is not properly initialized, i.e. if the - /// mailbox is not open. - pub async fn connect_custom( - mut server: RendezvousServer, - appid: AppID, - password: String, - app_versions: impl serde::Serialize + Send + Sync + 'static, + /// The MailboxConnection already contains a rendezvous server with an opened mailbox. + pub async fn connect( + mailbox_connection: MailboxConnection, ) -> Result { + let MailboxConnection { + config, + mut server, + mailbox: _mailbox, + code, + welcome: _welcome, + } = mailbox_connection; + /* Send PAKE */ - let (pake_state, pake_msg_ser) = key::make_pake(&password, &appid); + let (pake_state, pake_msg_ser) = key::make_pake(&code.0, &config.id); server.send_peer_message(Phase::PAKE, pake_msg_ser).await?; /* Receive PAKE */ @@ -230,7 +352,7 @@ impl Wormhole { /* Send versions message */ let mut versions = key::VersionsMessage::new(); - versions.set_app_versions(serde_json::to_value(&app_versions).unwrap()); + versions.set_app_versions(serde_json::to_value(&config.app_version).unwrap()); let (version_phase, version_msg) = key::build_version_msg(server.side(), &key, &versions); server.send_peer_message(version_phase, version_msg).await?; let peer_version = server.next_peer_message_some().await?; @@ -254,15 +376,20 @@ impl Wormhole { /* We are now fully initialized! Up and running! :tada: */ Ok(Self { server, - appid, + appid: config.id, phase: 0, key: key::Key::new(key.into()), verifier: Box::new(key::derive_verifier(&key)), - our_version: Box::new(app_versions), + our_version: Box::new(config.app_version), peer_version, }) } + /** TODO */ + pub async fn connect_with_seed() { + todo!() + } + /** Send an encrypted message to peer */ pub async fn send(&mut self, plaintext: Vec) -> Result<(), WormholeError> { let phase_string = Phase::numeric(self.phase); @@ -522,6 +649,7 @@ pub struct Mailbox(pub String); #[deref(forward)] #[display(fmt = "{}", _0)] pub struct Nameplate(pub String); + impl Nameplate { pub fn new(n: &str) -> Self { Nameplate(String::from(n)) diff --git a/src/core/test.rs b/src/core/test.rs index 5e9d99ed..16ea8265 100644 --- a/src/core/test.rs +++ b/src/core/test.rs @@ -1,7 +1,12 @@ use super::{Mood, Phase}; +use rand::Rng; use std::{borrow::Cow, time::Duration}; -use crate::{self as magic_wormhole, AppConfig, AppID, Code, Wormhole}; +use crate::{ + self as magic_wormhole, + core::{MailboxConnection, Nameplate}, + AppConfig, AppID, Code, Wormhole, WormholeError, +}; #[cfg(feature = "transfer")] use crate::{transfer, transit}; @@ -35,10 +40,51 @@ fn default_relay_hints() -> Vec { ] } +#[async_std::test] +pub async fn test_connect_with_unknown_code_and_allocate_passes() -> eyre::Result<(), WormholeError> +{ + init_logger(); + + let code = generate_random_code(); + + let mailbox_connection = + MailboxConnection::connect(transfer::APP_CONFIG.id(TEST_APPID).clone(), code, true).await; + + assert!(mailbox_connection.is_ok()); + + mailbox_connection.unwrap().shutdown(Mood::Happy).await +} + +#[async_std::test] +pub async fn test_connect_with_unknown_code_and_no_allocate_fails() { + init_logger(); + + let code = generate_random_code(); + + let mailbox_connection = MailboxConnection::connect( + transfer::APP_CONFIG.id(TEST_APPID).clone(), + code.clone(), + false, + ) + .await; + + assert!(mailbox_connection.is_err()); + let error = mailbox_connection.err().unwrap(); + match error { + WormholeError::UnclaimedNameplate(nameplate) => { + assert_eq!(nameplate, code.nameplate()); + }, + _ => { + assert!(false); + }, + } +} + /** Send a file using the Rust implementation. This does not guarantee compatibility with Python! ;) */ #[cfg(feature = "transfer")] #[async_std::test] -pub async fn test_file_rust2rust() -> eyre::Result<()> { +#[allow(deprecated)] +pub async fn test_file_rust2rust_deprecated() -> eyre::Result<()> { init_logger(); let (code_tx, code_rx) = futures::channel::oneshot::channel(); @@ -46,14 +92,15 @@ pub async fn test_file_rust2rust() -> eyre::Result<()> { let sender_task = async_std::task::Builder::new() .name("sender".to_owned()) .spawn(async { - let (welcome, connector) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; + let (welcome, wormhole_future) = + Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID).clone(), 2) + .await?; if let Some(welcome) = &welcome.welcome { log::info!("Got welcome: {}", welcome); } log::info!("This wormhole's code is: {}", &welcome.code); - code_tx.send(welcome.code).unwrap(); - let wormhole = connector.await?; + code_tx.send(welcome.code.clone()).unwrap(); + let wormhole = wormhole_future.await?; eyre::Result::<_>::Ok( transfer::send_file( wormhole, @@ -74,12 +121,87 @@ pub async fn test_file_rust2rust() -> eyre::Result<()> { .spawn(async { let code = code_rx.await?; log::info!("Got code over local: {}", &code); + let config = transfer::APP_CONFIG.id(TEST_APPID); let (welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code, true) - .await?; + Wormhole::connect_with_code(config, code.clone(), true).await?; if let Some(welcome) = &welcome.welcome { log::info!("Got welcome: {}", welcome); } + log::info!("This wormhole's code is: {}", &welcome.code); + + let req = transfer::request_file( + wormhole, + default_relay_hints(), + magic_wormhole::transit::Abilities::ALL_ABILITIES, + futures::future::pending(), + ) + .await? + .unwrap(); + + let mut buffer = Vec::::new(); + req.accept( + &transit::log_transit_connection, + |_received, _total| {}, + &mut buffer, + futures::future::pending(), + ) + .await?; + Ok(buffer) + })?; + + sender_task.await?; + let original = std::fs::read("tests/example-file.bin")?; + let received: Vec = (receiver_task.await as eyre::Result>)?; + + assert_eq!(original, received, "Files differ"); + Ok(()) +} + +/** Send a file using the Rust implementation. This does not guarantee compatibility with Python! ;) */ +#[cfg(feature = "transfer")] +#[async_std::test] +pub async fn test_file_rust2rust() -> eyre::Result<()> { + init_logger(); + + let (code_tx, code_rx) = futures::channel::oneshot::channel(); + + let sender_task = async_std::task::Builder::new() + .name("sender".to_owned()) + .spawn(async { + let mailbox_connection = + MailboxConnection::create(transfer::APP_CONFIG.id(TEST_APPID).clone(), 2).await?; + if let Some(welcome) = &mailbox_connection.welcome { + log::info!("Got welcome: {}", welcome); + } + log::info!("This wormhole's code is: {}", &mailbox_connection.code); + code_tx.send(mailbox_connection.code.clone()).unwrap(); + let wormhole = Wormhole::connect(mailbox_connection).await?; + eyre::Result::<_>::Ok( + transfer::send_file( + wormhole, + default_relay_hints(), + &mut async_std::fs::File::open("tests/example-file.bin").await?, + "example-file.bin", + std::fs::metadata("tests/example-file.bin").unwrap().len(), + magic_wormhole::transit::Abilities::ALL_ABILITIES, + &transit::log_transit_connection, + |_sent, _total| {}, + futures::future::pending(), + ) + .await?, + ) + })?; + let receiver_task = async_std::task::Builder::new() + .name("receiver".to_owned()) + .spawn(async { + let code = code_rx.await?; + log::info!("Got code over local: {}", &code); + let config = transfer::APP_CONFIG.id(TEST_APPID); + let mailbox = MailboxConnection::connect(config, code.clone(), false).await?; + if let Some(welcome) = mailbox.welcome.clone() { + log::info!("Got welcome: {}", welcome); + } + let wormhole = Wormhole::connect(mailbox).await?; let req = transfer::request_file( wormhole, @@ -122,14 +244,14 @@ pub async fn test_4096_file_rust2rust() -> eyre::Result<()> { let sender_task = async_std::task::Builder::new() .name("sender".to_owned()) .spawn(async { - let (welcome, connector) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; - if let Some(welcome) = &welcome.welcome { + let config = transfer::APP_CONFIG.id(TEST_APPID); + let mailbox = MailboxConnection::create(config, 2).await?; + if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); } - log::info!("This wormhole's code is: {}", &welcome.code); - code_tx.send(welcome.code).unwrap(); - let wormhole = connector.await?; + log::info!("This wormhole's code is: {}", &mailbox.code); + code_tx.send(mailbox.code.clone()).unwrap(); + let wormhole = Wormhole::connect(mailbox).await?; eyre::Result::<_>::Ok( transfer::send_file( wormhole, @@ -150,12 +272,12 @@ pub async fn test_4096_file_rust2rust() -> eyre::Result<()> { .spawn(async { let code = code_rx.await?; log::info!("Got code over local: {}", &code); - let (welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code, true) - .await?; - if let Some(welcome) = &welcome.welcome { + let config = transfer::APP_CONFIG.id(TEST_APPID); + let mailbox = MailboxConnection::connect(config, code, false).await?; + if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); } + let wormhole = Wormhole::connect(mailbox).await?; let req = transfer::request_file( wormhole, @@ -196,14 +318,14 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { let sender_task = async_std::task::Builder::new() .name("sender".to_owned()) .spawn(async { - let (welcome, connector) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; - if let Some(welcome) = &welcome.welcome { + let mailbox = MailboxConnection::create(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; + if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); } - log::info!("This wormhole's code is: {}", &welcome.code); - code_tx.send(welcome.code).unwrap(); - let wormhole = connector.await?; + log::info!("This wormhole's code is: {}", &mailbox.code); + code_tx.send(mailbox.code.clone()).unwrap(); + let wormhole = Wormhole::connect(mailbox).await?; + eyre::Result::<_>::Ok( transfer::send_file( wormhole, @@ -224,12 +346,13 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { .spawn(async { let code = code_rx.await?; log::info!("Got code over local: {}", &code); - let (welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code, true) + let mailbox = + MailboxConnection::connect(transfer::APP_CONFIG.id(TEST_APPID), code, false) .await?; - if let Some(welcome) = &welcome.welcome { + if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); } + let wormhole = Wormhole::connect(mailbox).await?; let req = transfer::request_file( wormhole, @@ -265,10 +388,8 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { pub async fn test_send_many() -> eyre::Result<()> { init_logger(); - let (welcome, connector) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; - - let code = welcome.code; + let mailbox = MailboxConnection::create(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; + let code = mailbox.code.clone(); log::info!("The code is {:?}", code); let correct_data = std::fs::read("tests/example-file.bin")?; @@ -282,7 +403,7 @@ pub async fn test_send_many() -> eyre::Result<()> { /* The first time, we reuse the current session for sending */ { log::info!("Sending file #{}", 0); - let wormhole = connector.await?; + let wormhole = Wormhole::connect(mailbox).await?; senders.push(async_std::task::spawn(async move { default_relay_hints(); crate::transfer::send_file( @@ -302,10 +423,13 @@ pub async fn test_send_many() -> eyre::Result<()> { for i in 1..5usize { log::info!("Sending file #{}", i); - let (_welcome, wormhole) = Wormhole::connect_with_code( - transfer::APP_CONFIG.id(TEST_APPID), - sender_code.clone(), - false, + let wormhole = Wormhole::connect( + MailboxConnection::connect( + transfer::APP_CONFIG.id(TEST_APPID), + sender_code.clone(), + true, + ) + .await?, ) .await?; senders.push(async_std::task::spawn(async move { @@ -332,9 +456,11 @@ pub async fn test_send_many() -> eyre::Result<()> { /* Receive many */ for i in 0..5usize { log::info!("Receiving file #{}", i); - let (_welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code.clone(), true) - .await?; + let wormhole = Wormhole::connect( + MailboxConnection::connect(transfer::APP_CONFIG.id(TEST_APPID), code.clone(), true) + .await?, + ) + .await?; log::info!("Got key: {}", &wormhole.key); let req = crate::transfer::request_file( wormhole, @@ -373,14 +499,15 @@ pub async fn test_wrong_code() -> eyre::Result<()> { let sender_task = async_std::task::Builder::new() .name("sender".to_owned()) .spawn(async { - let (welcome, connector) = Wormhole::connect_without_code(APP_CONFIG, 2).await?; - if let Some(welcome) = &welcome.welcome { + let mailbox = MailboxConnection::create(APP_CONFIG, 2).await?; + if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); } - log::info!("This wormhole's code is: {}", &welcome.code); - code_tx.send(welcome.code.nameplate()).unwrap(); + let code = mailbox.code.clone(); + log::info!("This wormhole's code is: {}", &code); + code_tx.send(code.nameplate()).unwrap(); - let result = connector.await; + let result = Wormhole::connect(mailbox).await; /* This should have failed, due to the wrong code */ assert!(result.is_err()); eyre::Result::<_>::Ok(()) @@ -390,11 +517,14 @@ pub async fn test_wrong_code() -> eyre::Result<()> { .spawn(async { let nameplate = code_rx.await?; log::info!("Got nameplate over local: {}", &nameplate); - let result = Wormhole::connect_with_code( - APP_CONFIG, - /* Making a wrong code here by appending bullshit */ - Code::new(&nameplate, "foo-bar"), - true, + let result = Wormhole::connect( + MailboxConnection::connect( + APP_CONFIG, + /* Making a wrong code here by appending bullshit */ + Code::new(&nameplate, "foo-bar"), + true, + ) + .await?, ) .await; @@ -414,14 +544,17 @@ pub async fn test_wrong_code() -> eyre::Result<()> { pub async fn test_crowded() -> eyre::Result<()> { init_logger(); - let (welcome, connector1) = Wormhole::connect_without_code(APP_CONFIG, 2).await?; - log::info!("This test's code is: {}", &welcome.code); + let initial_mailbox_connection = MailboxConnection::create(APP_CONFIG, 2).await?; + log::info!("This test's code is: {}", &initial_mailbox_connection.code); + let code = initial_mailbox_connection.code.clone(); - let connector2 = Wormhole::connect_with_code(APP_CONFIG, welcome.code.clone(), true); + let mailbox_connection_1 = MailboxConnection::connect(APP_CONFIG.clone(), code.clone(), false); + let mailbox_connection_2 = MailboxConnection::connect(APP_CONFIG.clone(), code.clone(), false); - let connector3 = Wormhole::connect_with_code(APP_CONFIG, welcome.code.clone(), true); - - match futures::try_join!(connector1, connector2, connector3).unwrap_err() { + match futures::try_join!(mailbox_connection_1, mailbox_connection_2) + .err() + .unwrap() + { magic_wormhole::WormholeError::ServerError( magic_wormhole::rendezvous::RendezvousError::Server(error), ) => { @@ -435,14 +568,12 @@ pub async fn test_crowded() -> eyre::Result<()> { #[async_std::test] pub async fn test_connect_with_code_expecting_nameplate() -> eyre::Result<()> { - // the max nameplate number is 999, so this will not impact a real nameplate - let code = Code("1000-guitarist-revenge".to_owned()); - let connector = Wormhole::connect_with_code(APP_CONFIG, code, true) - .await - .unwrap_err(); - match connector { + let code = generate_random_code(); + let result = MailboxConnection::connect(APP_CONFIG, code.clone(), false).await; + let error = result.err().unwrap(); + match error { magic_wormhole::WormholeError::UnclaimedNameplate(x) => { - assert_eq!(x, magic_wormhole::core::Nameplate("1000".to_owned())); + assert_eq!(x, code.nameplate()); }, other => panic!( "Got wrong error type {:?}. Expected `NameplateNotFound`", @@ -453,6 +584,13 @@ pub async fn test_connect_with_code_expecting_nameplate() -> eyre::Result<()> { Ok(()) } +fn generate_random_code() -> Code { + let mut rng = rand::thread_rng(); + let nameplate_string = format!("{}-guitarist-revenge", rng.gen_range(1000..10000)); + let nameplate = Nameplate::new(&nameplate_string); + Code::new(&nameplate, "guitarist-revenge") +} + #[test] fn test_phase() { let p = Phase::PAKE; diff --git a/src/lib.rs b/src/lib.rs index 937b43c5..78b3fb64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,5 +37,6 @@ pub mod uri; pub use crate::core::{ key::{GenericKey, Key, KeyPurpose, WormholeKey}, - rendezvous, AppConfig, AppID, Code, Wormhole, WormholeError, WormholeWelcome, + rendezvous, AppConfig, AppID, Code, MailboxConnection, Mood, Nameplate, Wormhole, + WormholeError, }; From 802bcf3d2082dfe54a44d2882c78f8495b027b21 Mon Sep 17 00:00:00 2001 From: Ramakrishnan Muthukrishnan Date: Mon, 20 Mar 2023 14:14:13 +0530 Subject: [PATCH 17/26] Initial dilation support --- Cargo.toml | 3 +- cli/src/main.rs | 49 +++++++---- src/core.rs | 99 +++++++++++++++++---- src/core/key.rs | 37 ++++++-- src/core/rendezvous.rs | 2 +- src/core/test.rs | 78 ++++++++--------- src/dilation.rs | 163 +++++++++++++++++++++++++++++++++++ src/dilation/api.rs | 24 ++++++ src/dilation/events.rs | 109 +++++++++++++++++++++++ src/dilation/manager.rs | 186 ++++++++++++++++++++++++++++++++++++++++ src/forwarding.rs | 4 +- src/lib.rs | 2 + src/transfer.rs | 42 +++++++-- src/transfer/v1.rs | 8 +- 14 files changed, 712 insertions(+), 94 deletions(-) create mode 100644 src/dilation.rs create mode 100644 src/dilation/api.rs create mode 100644 src/dilation/events.rs create mode 100644 src/dilation/manager.rs diff --git a/Cargo.toml b/Cargo.toml index 0e987745..dccad29a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,8 +85,9 @@ eyre = "0.6.5" [features] transit = ["socket2", "stun_codec", "if-addrs", "bytecodec", "async-trait", "noise-protocol", "noise-rust-crypto"] transfer = ["transit", "async-tar", "rmp-serde"] +dilation = ["transfer"] forwarding = ["transit", "rmp-serde"] -default = ["transit", "transfer"] +default = ["transfer", "dilation"] all = ["default", "forwarding"] [profile.release] diff --git a/cli/src/main.rs b/cli/src/main.rs index f491fc3c..363da473 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -17,8 +17,9 @@ use std::{ io::Write, path::{Path, PathBuf}, }; +use serde_json::Value; -use magic_wormhole::{forwarding, transfer, transit, MailboxConnection, Wormhole}; +use magic_wormhole::{forwarding, transfer, transit, dilation, MailboxConnection, Wormhole}; fn install_ctrlc_handler( ) -> eyre::Result futures::future::BoxFuture<'static, ()> + Clone> { @@ -335,7 +336,7 @@ async fn main() -> eyre::Result<()> { code, Some(code_length), true, - transfer::APP_CONFIG, + dilation::APP_CONFIG_SEND, Some(&sender_print_code), clipboard.as_mut(), )), @@ -373,7 +374,7 @@ async fn main() -> eyre::Result<()> { code, Some(code_length), true, - transfer::APP_CONFIG, + dilation::APP_CONFIG_SEND, Some(&sender_print_code), clipboard.as_mut(), )); @@ -412,14 +413,14 @@ async fn main() -> eyre::Result<()> { .. } => { let transit_abilities = parse_transit_args(&common); - let (wormhole, _code, relay_hints) = { + let (mut wormhole, _code, relay_hints) = { let connect_fut = Box::pin(parse_and_connect( &mut term, common, code, None, false, - transfer::APP_CONFIG, + dilation::APP_CONFIG_RECEIVE, None, clipboard.as_mut(), )); @@ -429,16 +430,30 @@ async fn main() -> eyre::Result<()> { } }; - Box::pin(receive( - wormhole, - relay_hints, - file_path.as_os_str(), - file_name.map(std::ffi::OsString::from).as_deref(), - noconfirm, - transit_abilities, - ctrl_c, - )) - .await?; + if wormhole.enable_dilation && peer_allows_dilation(&wormhole.peer_version) { + let dilated_wormhole = wormhole.dilate().await?; // need to pass transit relay URL + // TODO receive via dilated wormhole + + match dilated_wormhole.wormhole.receive_json().await { + Ok(dilation::events::ManagerEvent::RxPlease{side}) => { + println!("received a please message with side: {:?}", side); + }, + other => { + println!("received a message: {:?}", other); + } + }; + } else { + Box::pin(receive( + wormhole, + relay_hints, + file_path.as_os_str(), + file_name.map(std::ffi::OsString::from).as_deref(), + noconfirm, + transit_abilities, + ctrl_c, + )) + .await?; + } }, WormholeCommand::Forward(ForwardCommand::Serve { targets, @@ -591,6 +606,10 @@ async fn main() -> eyre::Result<()> { Ok(()) } +fn peer_allows_dilation(version: &Value) -> bool { + true +} + fn parse_transit_args(args: &CommonArgs) -> transit::Abilities { match (args.force_direct, args.force_relay) { (false, false) => transit::Abilities::ALL_ABILITIES, diff --git a/src/core.rs b/src/core.rs index 0d026607..7527db8c 100644 --- a/src/core.rs +++ b/src/core.rs @@ -8,6 +8,9 @@ mod wordlist; use serde_derive::{Deserialize, Serialize}; use std::borrow::Cow; +use crate::dilation; +use crate::dilation::events::create_channels; + use self::rendezvous::*; pub(self) use self::server_messages::EncryptedMessage; use log::*; @@ -44,6 +47,8 @@ pub enum WormholeError { Crypto, #[error("Nameplate is unclaimed: {}", _0)] UnclaimedNameplate(Nameplate), + #[error("Dilation version mismatch")] + DilationVersion, } impl WormholeError { @@ -264,6 +269,12 @@ pub struct Wormhole { * (e.g. by the file transfer API). */ pub peer_version: serde_json::Value, + /** + * Enable dilatation. This is off by default. Any application that + * would want to use a dilated wormhole would turn it on, which + * results in app specific exchange in the "version" message. + */ + pub enable_dilation: bool, } impl Wormhole { @@ -284,6 +295,7 @@ impl Wormhole { pub async fn connect_without_code( config: AppConfig, code_length: usize, + enable_dilation: bool, ) -> Result< ( WormholeWelcome, @@ -313,6 +325,7 @@ impl Wormhole { config: AppConfig, code: Code, expect_claimed_nameplate: bool, + enable_dilation: bool, ) -> Result<(WormholeWelcome, Self), WormholeError> { let mailbox_connection = MailboxConnection::connect(config, code.clone(), !expect_claimed_nameplate).await?; @@ -351,8 +364,9 @@ impl Wormhole { .map(|key| *secretbox::Key::from_slice(&key))?; /* Send versions message */ - let mut versions = key::VersionsMessage::new(); + let mut versions = key::VersionsMessage::new(enable_dilation); versions.set_app_versions(serde_json::to_value(&config.app_version).unwrap()); + println!("versions msg: {}", serde_json::to_value(&app_versions).unwrap()); let (version_phase, version_msg) = key::build_version_msg(server.side(), &key, &versions); server.send_peer_message(version_phase, version_msg).await?; let peer_version = server.next_peer_message_some().await?; @@ -382,6 +396,7 @@ impl Wormhole { verifier: Box::new(key::derive_verifier(&key)), our_version: Box::new(config.app_version), peer_version, + enable_dilation, }) } @@ -426,16 +441,24 @@ impl Wormhole { Some(peer_message) => peer_message, None => continue, }; - if peer_message.phase.to_num().is_none() { - // TODO: log and ignore, for future expansion - todo!("log and ignore, for future expansion"); - } + println!("peer message: {:?}", peer_message.to_string()); + // if peer_message.phase.to_num().is_none() { + // // TODO: log and ignore, for future expansion + // todo!("log and ignore, for future expansion"); + // } // TODO maybe reorder incoming messages by phase numeral? let decrypted_message = peer_message .decrypt(&self.key) .ok_or(WormholeError::Crypto)?; + if peer_message.phase.to_string().starts_with("dilate-") { + // decrypt the payload and send it to dilation module + println!("Got a dilation message: {:?}", decrypted_message); + // XXX push the decrypted_message to the channel that accepts messages + // to dilation core. + } + // Send to client return Ok(decrypted_message); } @@ -448,19 +471,30 @@ impl Wormhole { * used by upper layer protocols. We distinguish between the different layers * on which a serialization error happened, hence the double `Result`. */ - pub async fn receive_json(&mut self) -> Result, WormholeError> + pub async fn receive_json(&mut self) -> Result where T: for<'a> serde::Deserialize<'a>, { - self.receive().await.map(|data: Vec| { - serde_json::from_slice(&data).map_err(|e| { - log::error!( - "Received invalid data from peer: '{}'", - String::from_utf8_lossy(&data) - ); - e - }) - }) + let received_msg = self.receive().await; + match received_msg { + Ok(msg) => { + let val = serde_json::from_slice(&msg); + match val { + Ok(v) => Ok(v), + Err(e) => Err(WormholeError::ProtocolJson(e)), + } + }, + Err(e) => { Err(e) }, + } + // self.receive().await.map(|data: Vec| { + // serde_json::from_slice(&data).map_err(|e| { + // log::error!( + // "Received invalid data from peer: '{}'", + // String::from_utf8_lossy(&data) + // ); + // e + // }) + // }) } pub async fn close(self) -> Result<(), WormholeError> { @@ -483,6 +517,25 @@ impl Wormhole { pub fn key(&self) -> &key::Key { &self.key } + + /** + * create a dilated wormhole + */ + pub async fn dilate(&mut self) -> Result { + // XXX: create endpoints? + // get versions from the other side and check if they support dilation. + let can_they_dilate = &self.peer_version["can-dilate"]; + if !can_they_dilate.is_null() && can_they_dilate[0] != "1" { + return Err(WormholeError::DilationVersion) + } + + let (connection_channels, manager_channels) = create_channels(); + Ok(DilatedWormhole { + wormhole: self, + side: MySide::generate(8), + dilation_core: dilation::DilationCore::new(connection_channels, manager_channels), + }) + } } // the serialized forms of these variants are part of the wire protocol, so @@ -570,10 +623,10 @@ impl From for AppID { pub struct MySide(EitherSide); impl MySide { - pub fn generate() -> MySide { + pub fn generate(n: usize) -> MySide { use rand::{rngs::OsRng, RngCore}; - let mut bytes: [u8; 5] = [0; 5]; + let mut bytes = vec![0; n]; OsRng.fill_bytes(&mut bytes); MySide(EitherSide(hex::encode(bytes))) @@ -688,3 +741,15 @@ impl Code { Nameplate::new(self.0.splitn(2, '-').next().unwrap()) } } + +pub struct DilatedWormhole<'a> { + pub wormhole: & 'a mut Wormhole, + side: MySide, + dilation_core: dilation::DilationCore, +} + +fn connection_handler() { + +} + +// XXX dilation API should be member functions of DilatedWormhole type diff --git a/src/core/key.rs b/src/core/key.rs index eb3dab7b..62f2c7c0 100644 --- a/src/core/key.rs +++ b/src/core/key.rs @@ -102,18 +102,42 @@ pub fn make_pake(password: &str, appid: &AppID) -> (Spake2, Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] pub struct VersionsMessage { - #[serde(default)] - pub abilities: Vec, - #[serde(default)] + //#[serde(default)] + pub can_dilate: Option<[Cow<'static, str>; 1]>, + //#[serde(default)] + pub dilation_abilities: Cow<'static, [Ability; 2]>, + //#[serde(default)] + #[serde(rename = "app_versions")] pub app_versions: serde_json::Value, // resume: Option, } impl VersionsMessage { - pub fn new() -> Self { - Default::default() + pub fn new(enable_dilation: bool) -> Self { + // Default::default() + Self{ + can_dilate: + if enable_dilation { + Some([std::borrow::Cow::Borrowed("1")]) + } else { + None + }, + dilation_abilities: std::borrow::Cow::Borrowed(&[ + Ability{ ty: std::borrow::Cow::Borrowed("direct-tcp-v1") }, + Ability{ ty: std::borrow::Cow::Borrowed("relay-v1") }, + ]), + app_versions: serde_json::Value::Null, + } } pub fn set_app_versions(&mut self, versions: serde_json::Value) { @@ -133,6 +157,7 @@ pub fn build_version_msg( let phase = Phase::VERSION; let data_key = derive_phase_key(side, key, &phase); let plaintext = serde_json::to_vec(versions).unwrap(); + println!("versions message before encryption: {:?}", plaintext); let (_nonce, encrypted) = encrypt_data(&data_key, &plaintext); (phase, encrypted) } diff --git a/src/core/rendezvous.rs b/src/core/rendezvous.rs index a107297c..09181af1 100644 --- a/src/core/rendezvous.rs +++ b/src/core/rendezvous.rs @@ -339,7 +339,7 @@ impl RendezvousServer { appid: &AppID, relay_url: &str, ) -> Result<(Self, Option), RendezvousError> { - let side = MySide::generate(); + let side = MySide::generate(5); let mut connection; #[cfg(not(target_arch = "wasm32"))] diff --git a/src/core/test.rs b/src/core/test.rs index 16ea8265..403ab9df 100644 --- a/src/core/test.rs +++ b/src/core/test.rs @@ -93,7 +93,7 @@ pub async fn test_file_rust2rust_deprecated() -> eyre::Result<()> { .name("sender".to_owned()) .spawn(async { let (welcome, wormhole_future) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID).clone(), 2) + Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID).clone(), 2, false) .await?; if let Some(welcome) = &welcome.welcome { log::info!("Got welcome: {}", welcome); @@ -318,8 +318,9 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { let sender_task = async_std::task::Builder::new() .name("sender".to_owned()) .spawn(async { - let mailbox = MailboxConnection::create(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; - if let Some(welcome) = &mailbox.welcome { + let (welcome, connector) = + Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; + if let Some(welcome) = &welcome.welcome { log::info!("Got welcome: {}", welcome); } log::info!("This wormhole's code is: {}", &mailbox.code); @@ -346,8 +347,8 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { .spawn(async { let code = code_rx.await?; log::info!("Got code over local: {}", &code); - let mailbox = - MailboxConnection::connect(transfer::APP_CONFIG.id(TEST_APPID), code, false) + let (welcome, wormhole) = + Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code, true) .await?; if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); @@ -388,8 +389,10 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { pub async fn test_send_many() -> eyre::Result<()> { init_logger(); - let mailbox = MailboxConnection::create(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; - let code = mailbox.code.clone(); + let (welcome, connector) = + Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; + + let code = welcome.code; log::info!("The code is {:?}", code); let correct_data = std::fs::read("tests/example-file.bin")?; @@ -423,13 +426,10 @@ pub async fn test_send_many() -> eyre::Result<()> { for i in 1..5usize { log::info!("Sending file #{}", i); - let wormhole = Wormhole::connect( - MailboxConnection::connect( - transfer::APP_CONFIG.id(TEST_APPID), - sender_code.clone(), - true, - ) - .await?, + let (_welcome, wormhole) = Wormhole::connect_with_code( + transfer::APP_CONFIG.id(TEST_APPID), + sender_code.clone(), + false, ) .await?; senders.push(async_std::task::spawn(async move { @@ -456,11 +456,9 @@ pub async fn test_send_many() -> eyre::Result<()> { /* Receive many */ for i in 0..5usize { log::info!("Receiving file #{}", i); - let wormhole = Wormhole::connect( - MailboxConnection::connect(transfer::APP_CONFIG.id(TEST_APPID), code.clone(), true) - .await?, - ) - .await?; + let (_welcome, wormhole) = + Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code.clone(), true) + .await?; log::info!("Got key: {}", &wormhole.key); let req = crate::transfer::request_file( wormhole, @@ -499,8 +497,8 @@ pub async fn test_wrong_code() -> eyre::Result<()> { let sender_task = async_std::task::Builder::new() .name("sender".to_owned()) .spawn(async { - let mailbox = MailboxConnection::create(APP_CONFIG, 2).await?; - if let Some(welcome) = &mailbox.welcome { + let (welcome, connector) = Wormhole::connect_without_code(APP_CONFIG, 2).await?; + if let Some(welcome) = &welcome.welcome { log::info!("Got welcome: {}", welcome); } let code = mailbox.code.clone(); @@ -517,14 +515,11 @@ pub async fn test_wrong_code() -> eyre::Result<()> { .spawn(async { let nameplate = code_rx.await?; log::info!("Got nameplate over local: {}", &nameplate); - let result = Wormhole::connect( - MailboxConnection::connect( - APP_CONFIG, - /* Making a wrong code here by appending bullshit */ - Code::new(&nameplate, "foo-bar"), - true, - ) - .await?, + let result = Wormhole::connect_with_code( + APP_CONFIG, + /* Making a wrong code here by appending bullshit */ + Code::new(&nameplate, "foo-bar"), + true, ) .await; @@ -544,17 +539,14 @@ pub async fn test_wrong_code() -> eyre::Result<()> { pub async fn test_crowded() -> eyre::Result<()> { init_logger(); - let initial_mailbox_connection = MailboxConnection::create(APP_CONFIG, 2).await?; - log::info!("This test's code is: {}", &initial_mailbox_connection.code); - let code = initial_mailbox_connection.code.clone(); + let (welcome, connector1) = Wormhole::connect_without_code(APP_CONFIG, 2).await?; + log::info!("This test's code is: {}", &welcome.code); + + let connector2 = Wormhole::connect_with_code(APP_CONFIG, welcome.code.clone(), true); - let mailbox_connection_1 = MailboxConnection::connect(APP_CONFIG.clone(), code.clone(), false); - let mailbox_connection_2 = MailboxConnection::connect(APP_CONFIG.clone(), code.clone(), false); + let connector3 = Wormhole::connect_with_code(APP_CONFIG, welcome.code.clone(), true); - match futures::try_join!(mailbox_connection_1, mailbox_connection_2) - .err() - .unwrap() - { + match futures::try_join!(connector1, connector2, connector3).unwrap_err() { magic_wormhole::WormholeError::ServerError( magic_wormhole::rendezvous::RendezvousError::Server(error), ) => { @@ -568,10 +560,12 @@ pub async fn test_crowded() -> eyre::Result<()> { #[async_std::test] pub async fn test_connect_with_code_expecting_nameplate() -> eyre::Result<()> { - let code = generate_random_code(); - let result = MailboxConnection::connect(APP_CONFIG, code.clone(), false).await; - let error = result.err().unwrap(); - match error { + // the max nameplate number is 999, so this will not impact a real nameplate + let code = Code("1000-guitarist-revenge".to_owned()); + let connector = Wormhole::connect_with_code(APP_CONFIG, code, true) + .await + .unwrap_err(); + match connector { magic_wormhole::WormholeError::UnclaimedNameplate(x) => { assert_eq!(x, code.nameplate()); }, diff --git a/src/dilation.rs b/src/dilation.rs new file mode 100644 index 00000000..9d17f06b --- /dev/null +++ b/src/dilation.rs @@ -0,0 +1,163 @@ +use std::borrow::Cow; +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; + +use serde_derive::{Deserialize, Serialize}; + +use crate::dilation::events::{ConnectionChannels, Event, ManagerChannels, Events}; + +use super::AppID; + +mod manager; +pub mod events; +mod api; + + +const APPID_RAW: &str = "lothar.com/wormhole/text-or-file-xfer"; + +// XXX define an dilation::APP_CONFIG +pub const APP_CONFIG_SEND: crate::AppConfig = crate::AppConfig:: { + id: AppID(Cow::Borrowed(APPID_RAW)), + rendezvous_url: Cow::Borrowed(crate::rendezvous::DEFAULT_RENDEZVOUS_SERVER), + app_version: AppVersion::new(FileTransferV2Mode::Send), +}; + +pub const APP_CONFIG_RECEIVE: crate::AppConfig = crate::AppConfig:: { + id: AppID(Cow::Borrowed(APPID_RAW)), + rendezvous_url: Cow::Borrowed(crate::rendezvous::DEFAULT_RENDEZVOUS_SERVER), + app_version: AppVersion::new(FileTransferV2Mode::Receive), +}; + + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(rename = "transfer")] +enum FileTransferV2Mode { + Send, + Receive, + Connect, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +struct DilatedTransfer { + mode: FileTransferV2Mode, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct AppVersion { + // #[serde(default)] + // abilities: Cow<'static, [Cow<'static, str>]>, + // #[serde(default)] + // transfer_v2: Option, + + // XXX: we don't want to send "can-dilate" key for non-dilated + // wormhole, would making this an Option help? i.e. when the value + // is a None, we don't serialize that into the json and do it only + // when it is a "Some" value? + // overall versions payload is of the form: + // b'{"can-dilate": ["1"], "dilation-abilities": [{"type": "direct-tcp-v1"}, {"type": "relay-v1"}], "app_versions": {"transfer": {"mode": "send", "features": {}}}}' + + //can_dilate: Option<[Cow<'static, str>; 1]>, + //dilation_abilities: Cow<'static, [Ability; 2]>, + #[serde(rename = "transfer")] + app_versions: DilatedTransfer, +} + + +impl AppVersion { + const fn new(mode: FileTransferV2Mode) -> Self { + // let can_dilate: Option<[Cow<'static, str>; 1]> = if enable_dilation { + // Some([std::borrow::Cow::Borrowed("1")]) + // } else { + // None + // }; + + Self { + // abilities: Cow::Borrowed([Cow::Borrowed("transfer-v1"), Cow::Borrowed("transfer-v2")]), + // transfer_v2: Some(AppVersionTransferV2Hint::new()) + // can_dilate: can_dilate, + // dilation_abilities: std::borrow::Cow::Borrowed(&[ + // Ability{ ty: std::borrow::Cow::Borrowed("direct-tcp-v1") }, + // Ability{ ty: std::borrow::Cow::Borrowed("relay-v1") }, + // ]), + app_versions: DilatedTransfer { + mode, + } + } + } +} + +impl Default for AppVersion { + fn default() -> Self { + Self::new(FileTransferV2Mode::Send) + } +} + +type Queue = Arc>>; + +pub struct DilationCore { + manager: manager::ManagerMachine, + connection_channels: ConnectionChannels, + manager_channels: ManagerChannels, +} + +impl DilationCore { + pub fn new(connection_channels: ConnectionChannels, manager_channels: ManagerChannels) -> DilationCore { + // XXX: generate side + DilationCore { + manager: manager::ManagerMachine::new(), + connection_channels, + manager_channels, + } + } + + pub fn do_io(&mut self, event: api::IOEvent) -> Vec { + let events: events::Events = self.manager.process_io(event); + + self.execute(events) + } + + fn run(&mut self) { + while let event_result = self.manager_channels.inbound.recv() { + let actions = match event_result { + Ok(Event::Manager(manager_event)) => + self.manager.process(manager_event), + Ok(Event::IO(io_action)) => { + println!("manager received IOAction {}", io_action); + // XXX to be filled in later + Events::new() + }, + Err(error) => { + eprintln!("received error {}", error); + Events::new() + } + }; + // XXX do something with "actions", some of them could + // be input to other state machines, some of them could + // be IO actions. + } + } + + fn execute(&mut self, events: events::Events) -> Vec { + // process until all the events are consumed and produce a + // bunch of Actions. + let mut action_queue: Vec = Vec::new(); + let mut event_queue: VecDeque = VecDeque::new(); + + event_queue.append(&mut VecDeque::from(events.events)); + + while let Some(event) = event_queue.pop_front() { + let actions: events::Events = match event { + events::Event::IO(_) => todo!(), + events::Event::Manager(e) => self.manager.process(e), + }; + for a in actions.events { + event_queue.push_back(a) + } + } + + action_queue + } +} diff --git a/src/dilation/api.rs b/src/dilation/api.rs new file mode 100644 index 00000000..a09e664f --- /dev/null +++ b/src/dilation/api.rs @@ -0,0 +1,24 @@ +use derive_more::Display; + +pub enum APIAction { +} + +// from IO to DilationCore +#[derive(Debug, Display)] +pub enum IOEvent { + WormholeMessageReceived(String), + TCPConnectionLost, + TCPConnectionMade, +} + +// from DilationCore to IO +#[derive(Debug, Clone, PartialEq, Display)] +pub enum IOAction { + SendPlease, +} + +pub enum Action { + // outbound events to IO layer + // XXX: include API calls to IO layer + IO(IOAction), +} diff --git a/src/dilation/events.rs b/src/dilation/events.rs new file mode 100644 index 00000000..bf24e522 --- /dev/null +++ b/src/dilation/events.rs @@ -0,0 +1,109 @@ +use std::sync::mpsc::{channel, Receiver, Sender}; +use derive_more::Display; +use serde_derive::{Deserialize}; + +use crate::core::TheirSide; +use crate::dilation::api::Action; + +use super::api::{APIAction, IOAction}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Event { + IO(IOAction), + // All state machine events + Manager(ManagerEvent), +} + +impl From for Event { + fn from(r: IOAction) -> Event { + Event::IO(r) + } +} + +impl From for Event { + fn from(r: ManagerEvent) -> Event { + Event::Manager(r) + } +} + +// individual fsm events +#[derive(Debug, Clone, PartialEq, Display, Deserialize)] +#[serde(tag = "type")] +pub enum ManagerEvent { + Start, + #[serde(rename = "please")] + RxPlease { side: TheirSide }, + RxHints, + RxReconnect, + RxReconnecting, + ConnectionMade, + ConnectionLostLeader, + ConnectionLostFollower, + Stop, +} + +// XXX: for Connector fsm events +// ... +// XXX + + +#[derive(Debug)] +pub struct Events { + pub events: Vec, +} + +impl Events { + pub fn new() -> Self { + Events { + events: Vec::new(), + } + } + + pub fn addEvent(&mut self, event: Event) { + self.events.push(event); + } +} + +pub struct ManagerChannels { + pub inbound: Receiver, + pub outbound: Sender, +} + +impl ManagerChannels { + pub fn new(inbound: Receiver, outbound: Sender) -> Self { + ManagerChannels { inbound, outbound } + } +} + +pub struct ConnectionChannels { + pub inbound: Receiver, + pub outbound: Sender, +} + +impl ConnectionChannels { + pub fn new(inbound: Receiver, outbound: Sender) -> Self { + ConnectionChannels { inbound, outbound } + } +} + +pub fn create_channels() -> (ConnectionChannels, ManagerChannels) { + let (event_sender, event_receiver) = channel(); + let (action_sender, action_receiver) = channel(); + + return ( + ConnectionChannels::new(action_receiver, event_sender), + ManagerChannels::new(event_receiver, action_sender) + ); +} + +#[test] +fn test_create_channels() { + let (connection_channels, manager_channels) = create_channels(); + + let event = Event::Manager(ManagerEvent::Start); + connection_channels.outbound.send(event.clone()).expect("send failed"); + + let result = manager_channels.inbound.recv().expect("receive failed"); + + assert_eq!(&event, &result); +} diff --git a/src/dilation/manager.rs b/src/dilation/manager.rs new file mode 100644 index 00000000..59ef712d --- /dev/null +++ b/src/dilation/manager.rs @@ -0,0 +1,186 @@ +use super::events::{Events, Event}; +use super::events::ManagerEvent; +use super::api::{IOAction, IOEvent}; + +pub struct ManagerMachine { + state: Option +} + +#[derive(Debug, PartialEq, Clone, Copy)] +enum State { + Waiting, + Wanting, + Connecting, + Connected, + Abandoning, + Flushing, + Lonely, + Stopping, + Stopped, +} + +impl ManagerMachine { + pub fn new() -> Self { + ManagerMachine { + state: Some(State::Waiting), + } + } + + pub fn process_io(&mut self, event: IOEvent) -> Events { + // XXX: which Manager states process IO events? + let mut actions = Events::new(); + + // XXX: big match expression here + + actions + } + + pub fn process(&mut self, event: ManagerEvent) -> Events { + // given the event and the current state, generate output + // event and move to the new state + use State::*; + let mut actions = Events::new(); + let current_state = self.state.unwrap(); + let new_state = match current_state { + Waiting => match event { + ManagerEvent::Start => { + actions.addEvent(Event::from(IOAction::SendPlease)); + Wanting + } + ManagerEvent::Stop => { + // actions.addAction(NotifyStopped) + Stopped + } + _ => { + panic!{"unexpected event {:?} for state {:?}", current_state, event } + } + }, + Wanting => match event { + ManagerEvent::RxPlease { side: theirSide } => { + Connecting + } + ManagerEvent::Stop => { + Stopped + } + ManagerEvent::RxHints => { + current_state + } + _ => { + panic!{"unexpected event {:?} for state {:?}", current_state, event } + } + }, + Connecting => match event { + ManagerEvent::RxHints => { + current_state + }, + ManagerEvent::Stop => { + Stopped + }, + ManagerEvent::ConnectionMade => { + Connected + } + ManagerEvent::RxReconnect => { + current_state + } + _ => { + panic!{"unexpected event {:?} for state {:?}", current_state, event } + } + }, + Connected => match event { + ManagerEvent::RxReconnect => { + Abandoning + }, + ManagerEvent::RxHints => { + current_state + }, + ManagerEvent::ConnectionLostFollower => { + Lonely + }, + ManagerEvent::ConnectionLostLeader => { + Flushing + }, + ManagerEvent::Stop => { + Stopped + }, + _ => { + panic!{"unexpected event {:?} for state {:?}", current_state, event } + } + }, + Abandoning => match event { + ManagerEvent::RxHints => { + current_state + }, + ManagerEvent::ConnectionLostFollower => { + Connecting + }, + ManagerEvent::Stop => { + Stopped + }, + _ => { + panic!{"unexpected event {:?} for state {:?}", current_state, event } + } + }, + Flushing => match event { + ManagerEvent::RxReconnecting => { + Connecting + }, + ManagerEvent::Stop => { + Stopped + }, + ManagerEvent::RxHints => { + current_state + }, + _ => { + panic!{"unexpected event {:?} for state {:?}", current_state, event } + } + }, + Lonely => match event { + ManagerEvent::RxReconnect => { + Connecting + }, + ManagerEvent::Stop => { + Stopped + }, + ManagerEvent::RxHints => { + current_state + }, + _ => { + panic!{"unexpected event {:?} for state {:?}", current_state, event } + } + }, + Stopping => match event { + ManagerEvent::RxHints => { + current_state + }, + ManagerEvent::ConnectionLostFollower => { + Stopped + }, + ManagerEvent::ConnectionLostLeader => { + Stopped + }, + _ => { + panic!{"unexpected event {:?} for state {:?}", current_state, event } + } + }, + Stopped => current_state + }; + self.state = Some(new_state); + + actions + } + + fn get_current_state(&self) -> Option { + self.state + } +} + +#[test] +fn test_manager_machine() { + let mut manager_fsm = ManagerMachine::new(); + + // generate an input Event and see if we get the desired state and output Actions + assert_eq!(manager_fsm.get_current_state(), Some(State::Waiting)); + + let actions = manager_fsm.process(ManagerEvent::Start); + assert_eq!(manager_fsm.get_current_state(), Some(State::Wanting)); +} diff --git a/src/forwarding.rs b/src/forwarding.rs index 03dc3a8e..77176a03 100644 --- a/src/forwarding.rs +++ b/src/forwarding.rs @@ -173,7 +173,7 @@ pub async fn serve( .collect(); /* Receive their transit hints */ - let their_hints: transit::Hints = match wormhole.receive_json().await?? { + let their_hints: transit::Hints = match wormhole.receive_json().await? { PeerMessage::Transit { hints } => { log::debug!("Received transit message: {:?}", hints); hints @@ -544,7 +544,7 @@ pub async fn connect( .await?; /* Receive their transit hints */ - let their_hints: transit::Hints = match wormhole.receive_json().await?? { + let their_hints: transit::Hints = match wormhole.receive_json().await? { PeerMessage::Transit { hints } => { log::debug!("Received transit message: {:?}", hints); hints diff --git a/src/lib.rs b/src/lib.rs index 78b3fb64..a6972cbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,8 @@ pub mod transfer; pub mod transit; #[cfg(feature = "transfer")] pub mod uri; +#[cfg(feature = "dilation")] +pub mod dilation; pub use crate::core::{ key::{GenericKey, Key, KeyPurpose, WormholeKey}, diff --git a/src/transfer.rs b/src/transfer.rs index 8a0aeace..94dc5fb6 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -14,6 +14,8 @@ use serde_derive::{Deserialize, Serialize}; use serde_json::json; use std::sync::Arc; +use crate::dilation; + use super::{core::WormholeError, transit, transit::Transit, util, AppID, Wormhole}; use futures::Future; use log::*; @@ -37,7 +39,7 @@ pub const APPID: AppID = AppID(Cow::Borrowed(APPID_RAW)); pub const APP_CONFIG: crate::AppConfig = crate::AppConfig:: { id: AppID(Cow::Borrowed(APPID_RAW)), rendezvous_url: Cow::Borrowed(crate::rendezvous::DEFAULT_RENDEZVOUS_SERVER), - app_version: AppVersion::new(), + app_version: AppVersion::new(false), }; // TODO be more extensible on the JSON enum types (i.e. recognize unknown variants) @@ -117,6 +119,13 @@ impl TransferError { } } +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct Ability { + #[serde(rename = "type")] + ty: Cow<'static, str>, +} + /** * The application specific version information for this protocol. * @@ -129,15 +138,36 @@ pub struct AppVersion { // abilities: Cow<'static, [Cow<'static, str>]>, // #[serde(default)] // transfer_v2: Option, + + // XXX: we don't want to send "can-dilate" key for non-dilated + // wormhole, would making this an Option help? i.e. when the value + // is a None, we don't serialize that into the json and do it only + // when it is a "Some" value? + // overall versions payload is of the form: + // b'{"can-dilate": ["1"], "dilation-abilities": [{"type": "direct-tcp-v1"}, {"type": "relay-v1"}], "app_versions": {"transfer": {"mode": "send", "features": {}}}}' + + can_dilate: Option<[Cow<'static, str>; 1]>, + dilation_abilities: Cow<'static, [Ability; 2]> } // TODO check invariants during deserialization impl AppVersion { - const fn new() -> Self { + const fn new(enable_dilation: bool) -> Self { + let can_dilate: Option<[Cow<'static, str>; 1]> = if enable_dilation { + Some([std::borrow::Cow::Borrowed("1")]) + } else { + None + }; + Self { // abilities: Cow::Borrowed([Cow::Borrowed("transfer-v1"), Cow::Borrowed("transfer-v2")]), // transfer_v2: Some(AppVersionTransferV2Hint::new()) + can_dilate: can_dilate, + dilation_abilities: std::borrow::Cow::Borrowed(&[ + Ability{ ty: std::borrow::Cow::Borrowed("direct-tcp-v1") }, + Ability{ ty: std::borrow::Cow::Borrowed("relay-v1") }, + ]) } } @@ -150,7 +180,7 @@ impl AppVersion { impl Default for AppVersion { fn default() -> Self { - Self::new() + Self::new(false) } } @@ -359,7 +389,7 @@ pub async fn request_file( // receive transit message let (their_abilities, their_hints): (transit::Abilities, transit::Hints) = - match wormhole.receive_json().await?? { + match wormhole.receive_json().await? { PeerMessage::Transit(transit) => { debug!("received transit message: {:?}", transit); (transit.abilities_v1, transit.hints_v1) @@ -373,7 +403,7 @@ pub async fn request_file( }; // 3. receive file offer message from peer - let (filename, filesize) = match wormhole.receive_json().await?? { + let (filename, filesize) = match wormhole.receive_json().await? { PeerMessage::Offer(offer_type) => match offer_type { Offer::File { filename, filesize } => (filename, filesize), Offer::Directory { @@ -573,7 +603,7 @@ async fn handle_run_result( // and we should not only look for the next one but all have been received // and we should not interrupt a receive operation without making sure it leaves the connection // in a consistent state, otherwise the shutdown may cause protocol errors - if let Ok(Ok(Ok(PeerMessage::Error(e)))) = util::timeout(SHUTDOWN_TIME / 3, wormhole.receive_json()).await { + if let Ok(Ok(PeerMessage::Error(e))) = util::timeout(SHUTDOWN_TIME / 3, wormhole.receive_json()).await { error = TransferError::PeerError(e); } else { log::debug!("Failed to retrieve more specific error message from peer. Maybe it crashed?"); diff --git a/src/transfer/v1.rs b/src/transfer/v1.rs index 15e31c17..fff9fdcd 100644 --- a/src/transfer/v1.rs +++ b/src/transfer/v1.rs @@ -42,7 +42,7 @@ where // Wait for their transit response let (their_abilities, their_hints): (transit::Abilities, transit::Hints) = - match wormhole.receive_json().await?? { + match wormhole.receive_json().await? { PeerMessage::Transit(transit) => { debug!("Received transit message: {:?}", transit); (transit.abilities_v1, transit.hints_v1) @@ -57,7 +57,7 @@ where { // Wait for file_ack - let fileack_msg = wormhole.receive_json().await??; + let fileack_msg = wormhole.receive_json().await?; debug!("Received file ack message: {:?}", fileack_msg); match fileack_msg { @@ -199,7 +199,7 @@ where // Wait for their transit response let (their_abilities, their_hints): (transit::Abilities, transit::Hints) = - match wormhole.receive_json().await?? { + match wormhole.receive_json().await? { PeerMessage::Transit(transit) => { debug!("received transit message: {:?}", transit); (transit.abilities_v1, transit.hints_v1) @@ -213,7 +213,7 @@ where }; // Wait for file_ack - match wormhole.receive_json().await?? { + match wormhole.receive_json().await? { PeerMessage::Answer(Answer::FileAck(msg)) => { ensure!(msg == "ok", TransferError::AckError); }, From a5359c77f497f219249c65b8108d94a2f9ce95f8 Mon Sep 17 00:00:00 2001 From: Andreas Wuerl Date: Fri, 28 Apr 2023 14:06:48 +0200 Subject: [PATCH 18/26] Integrate Manager machine --- .github/workflows/push.yml | 6 +- Cargo.lock | 112 ++++++++++++++++ Cargo.toml | 1 + cli/src/main.rs | 20 +-- sonar-project.properties | 1 + src/core.rs | 130 ++++++++---------- src/core/key.rs | 13 +- src/core/test.rs | 16 ++- src/dilation.rs | 136 ++++++++++--------- src/dilation/api.rs | 26 ++-- src/dilation/events.rs | 99 ++++---------- src/dilation/manager.rs | 264 +++++++++++++++++++++---------------- src/lib.rs | 10 +- src/transfer.rs | 15 ++- 14 files changed, 479 insertions(+), 370 deletions(-) create mode 100644 sonar-project.properties diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index d1623ddc..6dd6200e 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -2,7 +2,7 @@ name: Rust on: push: - branches: [master, dev] + branches: [master, dev, dilation-1] pull_request: branches: [master, dev] @@ -183,6 +183,10 @@ jobs: uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} + - uses: sonarsource/sonarqube-scan-action@master + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} cargo-deny: name: Cargo deny diff --git a/Cargo.lock b/Cargo.lock index 9f8687fe..81f0316a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -752,6 +752,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.9.0" @@ -772,12 +778,24 @@ dependencies = [ "subtle", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "downcast-rs" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -871,6 +889,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -886,6 +913,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "futures" version = "0.3.26" @@ -1253,6 +1286,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.5" @@ -1351,6 +1393,7 @@ dependencies = [ "instant", "libc", "log", + "mockall", "noise-protocol", "noise-rust-crypto", "percent-encoding", @@ -1429,6 +1472,33 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "mockall" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "nix" version = "0.24.3" @@ -1488,6 +1558,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num-traits" version = "0.2.15" @@ -1748,6 +1824,36 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2262,6 +2368,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "textwrap" version = "0.16.0" diff --git a/Cargo.toml b/Cargo.toml index dccad29a..0135301c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,7 @@ getrandom_2 = { package = "getrandom", version = "0.1.16", features = ["js-sys"] [dev-dependencies] env_logger = "0.10.0" eyre = "0.6.5" +mockall = "0.11" [features] transit = ["socket2", "stun_codec", "if-addrs", "bytecodec", "async-trait", "noise-protocol", "noise-rust-crypto"] diff --git a/cli/src/main.rs b/cli/src/main.rs index 363da473..f1495890 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -13,13 +13,13 @@ use color_eyre::{eyre, eyre::Context}; use console::{style, Term}; use futures::{future::Either, Future, FutureExt}; use indicatif::{MultiProgress, ProgressBar}; +use serde_json::Value; use std::{ io::Write, path::{Path, PathBuf}, }; -use serde_json::Value; -use magic_wormhole::{forwarding, transfer, transit, dilation, MailboxConnection, Wormhole}; +use magic_wormhole::{dilate, dilation, forwarding, transfer, transit, MailboxConnection, Wormhole}; fn install_ctrlc_handler( ) -> eyre::Result futures::future::BoxFuture<'static, ()> + Clone> { @@ -431,17 +431,9 @@ async fn main() -> eyre::Result<()> { }; if wormhole.enable_dilation && peer_allows_dilation(&wormhole.peer_version) { - let dilated_wormhole = wormhole.dilate().await?; // need to pass transit relay URL - // TODO receive via dilated wormhole - - match dilated_wormhole.wormhole.receive_json().await { - Ok(dilation::events::ManagerEvent::RxPlease{side}) => { - println!("received a please message with side: {:?}", side); - }, - other => { - println!("received a message: {:?}", other); - } - }; + log::debug!("dilate wormhole"); + let mut dilated_wormhole = dilate(wormhole)?; // need to pass transit relay URL + dilated_wormhole.run().await; } else { Box::pin(receive( wormhole, @@ -452,7 +444,7 @@ async fn main() -> eyre::Result<()> { transit_abilities, ctrl_c, )) - .await?; + .await?; } }, WormholeCommand::Forward(ForwardCommand::Serve { diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..0da19140 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1 @@ +sonar.projectKey=LeastAuthority_magic-wormhole.rs_AYfWiG9h4b06GJhIj4na diff --git a/src/core.rs b/src/core.rs index 7527db8c..099f5f5c 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,21 +1,23 @@ -pub(super) mod key; -pub mod rendezvous; -mod server_messages; -#[cfg(test)] -mod test; -mod wordlist; - -use serde_derive::{Deserialize, Serialize}; use std::borrow::Cow; -use crate::dilation; -use crate::dilation::events::create_channels; +#[cfg(feature = "dilation")] +use crate::dilation::DilatedWormhole; +use log::*; +#[cfg(test)] +use mockall::automock; +use serde; +use serde_derive::{Deserialize, Serialize}; +use xsalsa20poly1305 as secretbox; use self::rendezvous::*; pub(self) use self::server_messages::EncryptedMessage; -use log::*; -use xsalsa20poly1305 as secretbox; +pub(super) mod key; +pub mod rendezvous; +mod server_messages; +#[cfg(test)] +mod test; +mod wordlist; #[derive(Debug, thiserror::Error)] #[non_exhaustive] @@ -273,7 +275,7 @@ pub struct Wormhole { * Enable dilatation. This is off by default. Any application that * would want to use a dilated wormhole would turn it on, which * results in app specific exchange in the "version" message. - */ + */ pub enable_dilation: bool, } @@ -366,7 +368,10 @@ impl Wormhole { /* Send versions message */ let mut versions = key::VersionsMessage::new(enable_dilation); versions.set_app_versions(serde_json::to_value(&config.app_version).unwrap()); - println!("versions msg: {}", serde_json::to_value(&app_versions).unwrap()); + println!( + "versions msg: {}", + serde_json::to_value(&app_versions).unwrap() + ); let (version_phase, version_msg) = key::build_version_msg(server.side(), &key, &versions); server.send_peer_message(version_phase, version_msg).await?; let peer_version = server.next_peer_message_some().await?; @@ -427,7 +432,7 @@ impl Wormhole { * * If the serialization fails */ - pub async fn send_json( + pub async fn send_json( &mut self, message: &T, ) -> Result<(), WormholeError> { @@ -441,24 +446,12 @@ impl Wormhole { Some(peer_message) => peer_message, None => continue, }; - println!("peer message: {:?}", peer_message.to_string()); - // if peer_message.phase.to_num().is_none() { - // // TODO: log and ignore, for future expansion - // todo!("log and ignore, for future expansion"); - // } // TODO maybe reorder incoming messages by phase numeral? let decrypted_message = peer_message .decrypt(&self.key) .ok_or(WormholeError::Crypto)?; - if peer_message.phase.to_string().starts_with("dilate-") { - // decrypt the payload and send it to dilation module - println!("Got a dilation message: {:?}", decrypted_message); - // XXX push the decrypted_message to the channel that accepts messages - // to dilation core. - } - // Send to client return Ok(decrypted_message); } @@ -475,26 +468,15 @@ impl Wormhole { where T: for<'a> serde::Deserialize<'a>, { - let received_msg = self.receive().await; - match received_msg { - Ok(msg) => { - let val = serde_json::from_slice(&msg); - match val { - Ok(v) => Ok(v), - Err(e) => Err(WormholeError::ProtocolJson(e)), - } - }, - Err(e) => { Err(e) }, - } - // self.receive().await.map(|data: Vec| { - // serde_json::from_slice(&data).map_err(|e| { - // log::error!( - // "Received invalid data from peer: '{}'", - // String::from_utf8_lossy(&data) - // ); - // e - // }) - // }) + self.receive().await.and_then(|data: Vec| { + serde_json::from_slice(&data).map_err(|e| { + log::error!( + "Received invalid data from peer: '{}'", + String::from_utf8_lossy(&data) + ); + WormholeError::ProtocolJson(e) + }) + }) } pub async fn close(self) -> Result<(), WormholeError> { @@ -517,30 +499,34 @@ impl Wormhole { pub fn key(&self) -> &key::Key { &self.key } +} - /** - * create a dilated wormhole - */ - pub async fn dilate(&mut self) -> Result { - // XXX: create endpoints? - // get versions from the other side and check if they support dilation. - let can_they_dilate = &self.peer_version["can-dilate"]; - if !can_they_dilate.is_null() && can_they_dilate[0] != "1" { - return Err(WormholeError::DilationVersion) - } - - let (connection_channels, manager_channels) = create_channels(); - Ok(DilatedWormhole { - wormhole: self, - side: MySide::generate(8), - dilation_core: dilation::DilationCore::new(connection_channels, manager_channels), - }) +/** + * create a dilated wormhole + */ +#[cfg(feature = "dilation")] +pub fn dilate(wormhole: Wormhole) -> Result { + // XXX: create endpoints? + // get versions from the other side and check if they support dilation. + let can_they_dilate = &wormhole.peer_version["can-dilate"]; + if !can_they_dilate.is_null() && can_they_dilate[0] != "1" { + return Err(WormholeError::DilationVersion); } + + Ok(DilatedWormhole::new(wormhole, MySide::generate(8))) } // the serialized forms of these variants are part of the wire protocol, so // they must be spelled exactly as shown -#[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize, derive_more::Display)] +#[derive( + Debug, + PartialEq, + Copy, + Clone, + serde_derive::Deserialize, + serde_derive::Serialize, + derive_more::Display, +)] pub enum Mood { #[serde(rename = "happy")] Happy, @@ -555,7 +541,7 @@ pub enum Mood { } /** - * Wormhole configuration corresponding to an uppler layer protocol + * Wormhole configuration corresponding to an upper layer protocol * * There are multiple different protocols built on top of the core * Wormhole protocol. They are identified by a unique URI-like ID string @@ -741,15 +727,3 @@ impl Code { Nameplate::new(self.0.splitn(2, '-').next().unwrap()) } } - -pub struct DilatedWormhole<'a> { - pub wormhole: & 'a mut Wormhole, - side: MySide, - dilation_core: dilation::DilationCore, -} - -fn connection_handler() { - -} - -// XXX dilation API should be member functions of DilatedWormhole type diff --git a/src/core/key.rs b/src/core/key.rs index 62f2c7c0..14fc94a1 100644 --- a/src/core/key.rs +++ b/src/core/key.rs @@ -125,16 +125,19 @@ pub struct VersionsMessage { impl VersionsMessage { pub fn new(enable_dilation: bool) -> Self { // Default::default() - Self{ - can_dilate: - if enable_dilation { + Self { + can_dilate: if enable_dilation { Some([std::borrow::Cow::Borrowed("1")]) } else { None }, dilation_abilities: std::borrow::Cow::Borrowed(&[ - Ability{ ty: std::borrow::Cow::Borrowed("direct-tcp-v1") }, - Ability{ ty: std::borrow::Cow::Borrowed("relay-v1") }, + Ability { + ty: std::borrow::Cow::Borrowed("direct-tcp-v1"), + }, + Ability { + ty: std::borrow::Cow::Borrowed("relay-v1"), + }, ]), app_versions: serde_json::Value::Null, } diff --git a/src/core/test.rs b/src/core/test.rs index 403ab9df..1d5e5e3e 100644 --- a/src/core/test.rs +++ b/src/core/test.rs @@ -245,7 +245,8 @@ pub async fn test_4096_file_rust2rust() -> eyre::Result<()> { .name("sender".to_owned()) .spawn(async { let config = transfer::APP_CONFIG.id(TEST_APPID); - let mailbox = MailboxConnection::create(config, 2).await?; + let mailbox = MailboxConnection::create(config, 2) + .await?; if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); } @@ -319,7 +320,8 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { .name("sender".to_owned()) .spawn(async { let (welcome, connector) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; + Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2) + .await?; if let Some(welcome) = &welcome.welcome { log::info!("Got welcome: {}", welcome); } @@ -456,9 +458,13 @@ pub async fn test_send_many() -> eyre::Result<()> { /* Receive many */ for i in 0..5usize { log::info!("Receiving file #{}", i); - let (_welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code.clone(), true) - .await?; + let (_welcome, wormhole) = Wormhole::connect_with_code( + transfer::APP_CONFIG.id(TEST_APPID), + code.clone(), + true, + false, + ) + .await?; log::info!("Got key: {}", &wormhole.key); let req = crate::transfer::request_file( wormhole, diff --git a/src/dilation.rs b/src/dilation.rs index 9d17f06b..706265f7 100644 --- a/src/dilation.rs +++ b/src/dilation.rs @@ -1,17 +1,27 @@ -use std::borrow::Cow; -use std::collections::VecDeque; -use std::sync::{Arc, Mutex}; +use std::{ + borrow::Cow, + cell::{RefCell, RefMut}, + ops::Deref, + rc::Rc, +}; +use async_trait::async_trait; +use futures::executor; +#[cfg(test)] +use mockall::{automock, mock, predicate::*}; use serde_derive::{Deserialize, Serialize}; -use crate::dilation::events::{ConnectionChannels, Event, ManagerChannels, Events}; +use crate::{ + core::MySide, + dilation::{api::ManagerCommand, manager::ManagerMachine}, + Wormhole, WormholeError, +}; use super::AppID; -mod manager; -pub mod events; mod api; - +mod events; +mod manager; const APPID_RAW: &str = "lothar.com/wormhole/text-or-file-xfer"; @@ -28,7 +38,6 @@ pub const APP_CONFIG_RECEIVE: crate::AppConfig = crate::AppConfig::< app_version: AppVersion::new(FileTransferV2Mode::Receive), }; - #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[serde(rename = "transfer")] @@ -65,7 +74,6 @@ pub struct AppVersion { app_versions: DilatedTransfer, } - impl AppVersion { const fn new(mode: FileTransferV2Mode) -> Self { // let can_dilate: Option<[Cow<'static, str>; 1]> = if enable_dilation { @@ -82,9 +90,7 @@ impl AppVersion { // Ability{ ty: std::borrow::Cow::Borrowed("direct-tcp-v1") }, // Ability{ ty: std::borrow::Cow::Borrowed("relay-v1") }, // ]), - app_versions: DilatedTransfer { - mode, - } + app_versions: DilatedTransfer { mode }, } } } @@ -95,69 +101,77 @@ impl Default for AppVersion { } } -type Queue = Arc>>; - -pub struct DilationCore { - manager: manager::ManagerMachine, - connection_channels: ConnectionChannels, - manager_channels: ManagerChannels, +pub struct DilatedWormhole { + wormhole: Rc>, + side: MySide, + manager: ManagerMachine, } -impl DilationCore { - pub fn new(connection_channels: ConnectionChannels, manager_channels: ManagerChannels) -> DilationCore { - // XXX: generate side - DilationCore { - manager: manager::ManagerMachine::new(), - connection_channels, - manager_channels, +impl DilatedWormhole { + pub fn new(wormhole: Wormhole, side: MySide) -> Self { + let wormhole_ref = &wormhole; + DilatedWormhole { + wormhole: Rc::new(RefCell::new(wormhole)), + side, + manager: ManagerMachine::new(), } } - pub fn do_io(&mut self, event: api::IOEvent) -> Vec { - let events: events::Events = self.manager.process_io(event); + pub async fn run(&mut self) { + log::debug!( + "start state machine: state={}", + &self.manager.state.unwrap() + ); - self.execute(events) - } + let mut command_handler = |cmd| Self::execute_command(self.wormhole.borrow_mut(), cmd); - fn run(&mut self) { - while let event_result = self.manager_channels.inbound.recv() { - let actions = match event_result { - Ok(Event::Manager(manager_event)) => - self.manager.process(manager_event), - Ok(Event::IO(io_action)) => { - println!("manager received IOAction {}", io_action); - // XXX to be filled in later - Events::new() - }, + loop { + let event_result = self.wormhole.borrow_mut().receive_json().await; + + match event_result { + Ok(manager_event) => self.manager.process(manager_event, &mut command_handler), Err(error) => { - eprintln!("received error {}", error); - Events::new() - } + log::warn!("received error {}", error); + }, }; - // XXX do something with "actions", some of them could - // be input to other state machines, some of them could - // be IO actions. + + if self.manager.is_done() { + break; + } } } - fn execute(&mut self, events: events::Events) -> Vec { - // process until all the events are consumed and produce a - // bunch of Actions. - let mut action_queue: Vec = Vec::new(); - let mut event_queue: VecDeque = VecDeque::new(); + fn execute_command( + mut wormhole: RefMut, + command: ManagerCommand, + ) -> Result<(), WormholeError> { + log::debug!("execute_command"); + match command { + ManagerCommand::Protocol(protocol_command) => { + log::debug!(" command: {}", protocol_command); + executor::block_on(wormhole.send_json(&protocol_command)) + }, + ManagerCommand::IO(io_command) => { + println!("io command: {}", io_command); + Ok(()) + }, + } + } +} - event_queue.append(&mut VecDeque::from(events.events)); +#[cfg(test)] +mod test { + use crate::dilation::{ + api::ProtocolCommand, events::ManagerEvent, manager::MockManagerMachine, + }; - while let Some(event) = event_queue.pop_front() { - let actions: events::Events = match event { - events::Event::IO(_) => todo!(), - events::Event::Manager(e) => self.manager.process(e), - }; - for a in actions.events { - event_queue.push_back(a) - } - } + use super::*; - action_queue + #[test] + fn test_dilated_wormhole() { + let mut manager = MockManagerMachine::default(); + // let mut wormhole = MockWormhole::default(); + // + // let dilated_wormhole = DilatedWormhole { manager, side: MySide::generate(23), wormhole }; } } diff --git a/src/dilation/api.rs b/src/dilation/api.rs index a09e664f..cb726045 100644 --- a/src/dilation/api.rs +++ b/src/dilation/api.rs @@ -1,24 +1,30 @@ use derive_more::Display; - -pub enum APIAction { -} +use serde_derive::{Deserialize, Serialize}; // from IO to DilationCore -#[derive(Debug, Display)] +#[derive(Debug, Clone, PartialEq, Display, Deserialize)] pub enum IOEvent { WormholeMessageReceived(String), TCPConnectionLost, TCPConnectionMade, } -// from DilationCore to IO +/// Commands to be executed #[derive(Debug, Clone, PartialEq, Display)] -pub enum IOAction { +pub enum ManagerCommand { + // XXX: include API calls to IO layer + Protocol(ProtocolCommand), + IO(IOCommand), +} + +/// Protocol level commands +#[derive(Debug, Clone, PartialEq, Display, Serialize)] +pub enum ProtocolCommand { SendPlease, } -pub enum Action { - // outbound events to IO layer - // XXX: include API calls to IO layer - IO(IOAction), +/// Protocol level commands +#[derive(Debug, Clone, PartialEq, Display)] +pub enum IOCommand { + CloseConnection, } diff --git a/src/dilation/events.rs b/src/dilation/events.rs index bf24e522..24fb7ca8 100644 --- a/src/dilation/events.rs +++ b/src/dilation/events.rs @@ -1,22 +1,24 @@ -use std::sync::mpsc::{channel, Receiver, Sender}; use derive_more::Display; -use serde_derive::{Deserialize}; +use serde_derive::Deserialize; -use crate::core::TheirSide; -use crate::dilation::api::Action; +use crate::{ + core::TheirSide, + dilation::api::{IOEvent, ManagerCommand}, +}; -use super::api::{APIAction, IOAction}; +use super::api::ProtocolCommand; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub enum Event { - IO(IOAction), + //IO(IOAction), // All state machine events Manager(ManagerEvent), + Connection(IOEvent), } -impl From for Event { - fn from(r: IOAction) -> Event { - Event::IO(r) +impl From for ManagerCommand { + fn from(r: ProtocolCommand) -> ManagerCommand { + ManagerCommand::Protocol(r) } } @@ -32,7 +34,9 @@ impl From for Event { pub enum ManagerEvent { Start, #[serde(rename = "please")] - RxPlease { side: TheirSide }, + RxPlease { + side: TheirSide, + }, RxHints, RxReconnect, RxReconnecting, @@ -42,68 +46,19 @@ pub enum ManagerEvent { Stop, } -// XXX: for Connector fsm events -// ... -// XXX - - -#[derive(Debug)] -pub struct Events { - pub events: Vec, -} - -impl Events { - pub fn new() -> Self { - Events { - events: Vec::new(), +#[test] +fn test_manager_event_deserialisation() { + let result: ManagerEvent = + serde_json::from_str(r#"{"type": "please", "side": "f91dcdaccc7cc336"}"#) + .expect("parse error"); + assert_eq!( + result, + ManagerEvent::RxPlease { + side: TheirSide::from("f91dcdaccc7cc336") } - } - - pub fn addEvent(&mut self, event: Event) { - self.events.push(event); - } -} - -pub struct ManagerChannels { - pub inbound: Receiver, - pub outbound: Sender, -} - -impl ManagerChannels { - pub fn new(inbound: Receiver, outbound: Sender) -> Self { - ManagerChannels { inbound, outbound } - } -} - -pub struct ConnectionChannels { - pub inbound: Receiver, - pub outbound: Sender, -} - -impl ConnectionChannels { - pub fn new(inbound: Receiver, outbound: Sender) -> Self { - ConnectionChannels { inbound, outbound } - } -} - -pub fn create_channels() -> (ConnectionChannels, ManagerChannels) { - let (event_sender, event_receiver) = channel(); - let (action_sender, action_receiver) = channel(); - - return ( - ConnectionChannels::new(action_receiver, event_sender), - ManagerChannels::new(event_receiver, action_sender) ); } -#[test] -fn test_create_channels() { - let (connection_channels, manager_channels) = create_channels(); - - let event = Event::Manager(ManagerEvent::Start); - connection_channels.outbound.send(event.clone()).expect("send failed"); - - let result = manager_channels.inbound.recv().expect("receive failed"); - - assert_eq!(&event, &result); -} +// XXX: for Connector fsm events +// ... +// XXX diff --git a/src/dilation/manager.rs b/src/dilation/manager.rs index 59ef712d..595a251a 100644 --- a/src/dilation/manager.rs +++ b/src/dilation/manager.rs @@ -1,13 +1,21 @@ -use super::events::{Events, Event}; -use super::events::ManagerEvent; -use super::api::{IOAction, IOEvent}; +use async_trait::async_trait; +use derive_more::Display; +#[cfg(test)] +use mockall::automock; + +use crate::{core::TheirSide, dilation::api::ManagerCommand, WormholeError}; + +use super::{ + api::{IOEvent, ProtocolCommand}, + events::ManagerEvent, +}; pub struct ManagerMachine { - state: Option + pub state: Option, } -#[derive(Debug, PartialEq, Clone, Copy)] -enum State { +#[derive(Debug, PartialEq, Clone, Copy, Display)] +pub enum State { Waiting, Wanting, Connecting, @@ -19,168 +27,194 @@ enum State { Stopped, } +#[cfg_attr(test, automock)] impl ManagerMachine { pub fn new() -> Self { - ManagerMachine { - state: Some(State::Waiting), - } + let mut machine = ManagerMachine { + state: Some(State::Wanting), + }; + machine + } + + pub fn is_waiting(&self) -> bool { + self.state == Some(State::Waiting) } - pub fn process_io(&mut self, event: IOEvent) -> Events { + pub fn process_io(&mut self, event: IOEvent) -> Vec { // XXX: which Manager states process IO events? - let mut actions = Events::new(); + let mut actions = Vec::::new(); // XXX: big match expression here actions } - pub fn process(&mut self, event: ManagerEvent) -> Events { + pub fn get_current_state(&self) -> Option { + self.state + } + + pub fn process( + &mut self, + event: ManagerEvent, + command_handler: &mut dyn FnMut(ManagerCommand) -> Result<(), WormholeError>, + ) { + log::debug!( + "processing event: state={}, event={}", + self.state.unwrap(), + &event + ); // given the event and the current state, generate output // event and move to the new state use State::*; - let mut actions = Events::new(); + let mut command = None; let current_state = self.state.unwrap(); let new_state = match current_state { Waiting => match event { ManagerEvent::Start => { - actions.addEvent(Event::from(IOAction::SendPlease)); + command = Some(ManagerCommand::from(ProtocolCommand::SendPlease)); Wanting - } + }, ManagerEvent::Stop => { // actions.addAction(NotifyStopped) Stopped - } + }, _ => { - panic!{"unexpected event {:?} for state {:?}", current_state, event } - } + panic! {"unexpected event {:?} for state {:?}", current_state, event} + }, }, Wanting => match event { - ManagerEvent::RxPlease { side: theirSide } => { + ManagerEvent::RxPlease { side: theirSide } => { + command = Some(ManagerCommand::from(ProtocolCommand::SendPlease)); Connecting - } - ManagerEvent::Stop => { - Stopped - } - ManagerEvent::RxHints => { - current_state - } + }, + ManagerEvent::Stop => Stopped, + ManagerEvent::RxHints => current_state, _ => { - panic!{"unexpected event {:?} for state {:?}", current_state, event } - } + panic! {"unexpected event {:?} for state {:?}", current_state, event} + }, }, Connecting => match event { - ManagerEvent::RxHints => { - current_state - }, - ManagerEvent::Stop => { - Stopped - }, - ManagerEvent::ConnectionMade => { - Connected - } - ManagerEvent::RxReconnect => { - current_state - } + ManagerEvent::RxHints => current_state, + ManagerEvent::Stop => Stopped, + ManagerEvent::ConnectionMade => Connected, + ManagerEvent::RxReconnect => current_state, _ => { - panic!{"unexpected event {:?} for state {:?}", current_state, event } - } + panic! {"unexpected event {:?} for state {:?}", current_state, event} + }, }, Connected => match event { - ManagerEvent::RxReconnect => { - Abandoning - }, - ManagerEvent::RxHints => { - current_state - }, - ManagerEvent::ConnectionLostFollower => { - Lonely - }, - ManagerEvent::ConnectionLostLeader => { - Flushing - }, - ManagerEvent::Stop => { - Stopped - }, + ManagerEvent::RxReconnect => Abandoning, + ManagerEvent::RxHints => current_state, + ManagerEvent::ConnectionLostFollower => Lonely, + ManagerEvent::ConnectionLostLeader => Flushing, + ManagerEvent::Stop => Stopped, _ => { - panic!{"unexpected event {:?} for state {:?}", current_state, event } - } + panic! {"unexpected event {:?} for state {:?}", current_state, event} + }, }, Abandoning => match event { - ManagerEvent::RxHints => { - current_state - }, - ManagerEvent::ConnectionLostFollower => { - Connecting - }, - ManagerEvent::Stop => { - Stopped - }, + ManagerEvent::RxHints => current_state, + ManagerEvent::ConnectionLostFollower => Connecting, + ManagerEvent::Stop => Stopped, _ => { - panic!{"unexpected event {:?} for state {:?}", current_state, event } - } + panic! {"unexpected event {:?} for state {:?}", current_state, event} + }, }, Flushing => match event { - ManagerEvent::RxReconnecting => { - Connecting - }, - ManagerEvent::Stop => { - Stopped - }, - ManagerEvent::RxHints => { - current_state - }, + ManagerEvent::RxReconnecting => Connecting, + ManagerEvent::Stop => Stopped, + ManagerEvent::RxHints => current_state, _ => { - panic!{"unexpected event {:?} for state {:?}", current_state, event } - } + panic! {"unexpected event {:?} for state {:?}", current_state, event} + }, }, Lonely => match event { - ManagerEvent::RxReconnect => { - Connecting - }, - ManagerEvent::Stop => { - Stopped - }, - ManagerEvent::RxHints => { - current_state - }, + ManagerEvent::RxReconnect => Connecting, + ManagerEvent::Stop => Stopped, + ManagerEvent::RxHints => current_state, _ => { - panic!{"unexpected event {:?} for state {:?}", current_state, event } - } + panic! {"unexpected event {:?} for state {:?}", current_state, event} + }, }, Stopping => match event { - ManagerEvent::RxHints => { - current_state - }, - ManagerEvent::ConnectionLostFollower => { - Stopped - }, - ManagerEvent::ConnectionLostLeader => { - Stopped - }, + ManagerEvent::RxHints => current_state, + ManagerEvent::ConnectionLostFollower => Stopped, + ManagerEvent::ConnectionLostLeader => Stopped, _ => { - panic!{"unexpected event {:?} for state {:?}", current_state, event } - } + panic! {"unexpected event {:?} for state {:?}", current_state, event} + }, }, - Stopped => current_state + Stopped => current_state, }; - self.state = Some(new_state); - actions + let command_result = match command.clone() { + Some(command) => command_handler(command), + None => Ok(()), + }; + + match command_result { + Ok(result) => { + self.state = Some(new_state); + log::debug!( + "processing event finished: state={}, command={}", + self.state.unwrap(), + command + .clone() + .map(|cmd| cmd.to_string()) + .unwrap_or("n/a".to_string()) + ); + }, + Err(wormhole_error) => { + log::warn!("processing event errored: {}", wormhole_error); + }, + }; } - fn get_current_state(&self) -> Option { - self.state + pub(crate) fn is_done(&self) -> bool { + self.state == Option::from(State::Stopped) } } -#[test] -fn test_manager_machine() { - let mut manager_fsm = ManagerMachine::new(); +#[cfg(test)] +mod test { + use super::*; + + struct TestHandler { + command: Option, + } + + impl TestHandler { + fn new() -> Self { + TestHandler { command: None } + } + + fn handle_command(&mut self, command: ManagerCommand) -> Result<(), WormholeError> { + self.command = Some(command); + Ok(()) + } + } + + #[test] + fn test_manager_machine() { + // Sends Start event during construction: + let mut manager_fsm = ManagerMachine::new(); - // generate an input Event and see if we get the desired state and output Actions - assert_eq!(manager_fsm.get_current_state(), Some(State::Waiting)); + // generate an input Event and see if we get the desired state and output Actions + assert_eq!(manager_fsm.get_current_state(), Some(State::Wanting)); - let actions = manager_fsm.process(ManagerEvent::Start); - assert_eq!(manager_fsm.get_current_state(), Some(State::Wanting)); + let mut handler = TestHandler::new(); + + manager_fsm.process( + ManagerEvent::RxPlease { + side: TheirSide::from("test"), + }, + &mut |cmd| handler.handle_command(cmd), + ); + + assert_eq!(manager_fsm.get_current_state(), Some(State::Connecting)); + assert_eq!( + handler.command, + Some(ManagerCommand::Protocol(ProtocolCommand::SendPlease)) + ) + } } diff --git a/src/lib.rs b/src/lib.rs index a6972cbb..c385e5d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,8 @@ #[macro_use] mod util; mod core; +#[cfg(feature = "dilation")] +pub mod dilation; #[cfg(feature = "forwarding")] pub mod forwarding; #[cfg(feature = "transfer")] @@ -34,11 +36,15 @@ pub mod transfer; pub mod transit; #[cfg(feature = "transfer")] pub mod uri; -#[cfg(feature = "dilation")] -pub mod dilation; pub use crate::core::{ key::{GenericKey, Key, KeyPurpose, WormholeKey}, rendezvous, AppConfig, AppID, Code, MailboxConnection, Mood, Nameplate, Wormhole, WormholeError, }; + +#[cfg(feature = "dilation")] +pub use crate::core::dilate; + +#[cfg(feature = "dilation")] +pub use crate::dilation::DilatedWormhole; diff --git a/src/transfer.rs b/src/transfer.rs index 94dc5fb6..94297fd4 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -14,8 +14,6 @@ use serde_derive::{Deserialize, Serialize}; use serde_json::json; use std::sync::Arc; -use crate::dilation; - use super::{core::WormholeError, transit, transit::Transit, util, AppID, Wormhole}; use futures::Future; use log::*; @@ -145,9 +143,8 @@ pub struct AppVersion { // when it is a "Some" value? // overall versions payload is of the form: // b'{"can-dilate": ["1"], "dilation-abilities": [{"type": "direct-tcp-v1"}, {"type": "relay-v1"}], "app_versions": {"transfer": {"mode": "send", "features": {}}}}' - can_dilate: Option<[Cow<'static, str>; 1]>, - dilation_abilities: Cow<'static, [Ability; 2]> + dilation_abilities: Cow<'static, [Ability; 2]>, } // TODO check invariants during deserialization @@ -165,9 +162,13 @@ impl AppVersion { // transfer_v2: Some(AppVersionTransferV2Hint::new()) can_dilate: can_dilate, dilation_abilities: std::borrow::Cow::Borrowed(&[ - Ability{ ty: std::borrow::Cow::Borrowed("direct-tcp-v1") }, - Ability{ ty: std::borrow::Cow::Borrowed("relay-v1") }, - ]) + Ability { + ty: std::borrow::Cow::Borrowed("direct-tcp-v1"), + }, + Ability { + ty: std::borrow::Cow::Borrowed("relay-v1"), + }, + ]), } } From 4f72d70f356fafb32b6c2cbdfec8fa921b2533c0 Mon Sep 17 00:00:00 2001 From: Ramakrishnan Muthukrishnan Date: Wed, 3 May 2023 16:42:22 +0530 Subject: [PATCH 19/26] Initial communication for dilation --- src/core.rs | 25 +++++++++++++++++++++++++ src/dilation.rs | 7 +++++-- src/dilation/api.rs | 5 ++++- src/dilation/manager.rs | 21 ++++++++++++++++----- src/transfer.rs | 2 +- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/core.rs b/src/core.rs index 099f5f5c..4205c68b 100644 --- a/src/core.rs +++ b/src/core.rs @@ -422,6 +422,18 @@ impl Wormhole { Ok(()) } + /** Send an encrypted dilation phase message to peer */ + pub async fn send_dilation_message(&mut self, plaintext: Vec) -> Result<(), WormholeError> { + let phase_string = Phase::dilation(self.phase); + self.phase += 1; + let data_key = key::derive_phase_key(self.server.side(), &self.key, &phase_string); + let (_nonce, encrypted) = key::encrypt_data(&data_key, &plaintext); + self.server + .send_peer_message(phase_string, encrypted) + .await?; + Ok(()) + } + /** * Serialize and send an encrypted message to peer * @@ -439,6 +451,15 @@ impl Wormhole { self.send(serde_json::to_vec(message).unwrap()).await } + // XXX this function's name could be better than this.. + pub async fn send_json_dilation_message( + &mut self, + message: &T, + ) -> Result<(), WormholeError> { + self.send_dilation_message(serde_json::to_vec(message).unwrap()) + .await + } + /** Receive an encrypted message from peer */ pub async fn receive(&mut self) -> Result, WormholeError> { loop { @@ -666,6 +687,10 @@ impl Phase { Phase(phase.to_string().into()) } + pub fn dilation(phase: u64) -> Self { + Phase(format!("dilate-{}", phase.to_string()).to_string().into()) + } + pub fn is_version(&self) -> bool { self == &Self::VERSION } diff --git a/src/dilation.rs b/src/dilation.rs index 706265f7..7858ac35 100644 --- a/src/dilation.rs +++ b/src/dilation.rs @@ -129,7 +129,10 @@ impl DilatedWormhole { let event_result = self.wormhole.borrow_mut().receive_json().await; match event_result { - Ok(manager_event) => self.manager.process(manager_event, &mut command_handler), + Ok(manager_event) => { + self.manager + .process(manager_event, &self.side, &mut command_handler) + }, Err(error) => { log::warn!("received error {}", error); }, @@ -149,7 +152,7 @@ impl DilatedWormhole { match command { ManagerCommand::Protocol(protocol_command) => { log::debug!(" command: {}", protocol_command); - executor::block_on(wormhole.send_json(&protocol_command)) + executor::block_on(wormhole.send_json_dilation_message(&protocol_command)) }, ManagerCommand::IO(io_command) => { println!("io command: {}", io_command); diff --git a/src/dilation/api.rs b/src/dilation/api.rs index cb726045..d790d5f4 100644 --- a/src/dilation/api.rs +++ b/src/dilation/api.rs @@ -1,3 +1,4 @@ +use crate::core::MySide; use derive_more::Display; use serde_derive::{Deserialize, Serialize}; @@ -19,8 +20,10 @@ pub enum ManagerCommand { /// Protocol level commands #[derive(Debug, Clone, PartialEq, Display, Serialize)] +#[serde(tag = "type")] pub enum ProtocolCommand { - SendPlease, + #[serde(rename = "please")] + SendPlease { side: MySide }, } /// Protocol level commands diff --git a/src/dilation/manager.rs b/src/dilation/manager.rs index 595a251a..b00e030b 100644 --- a/src/dilation/manager.rs +++ b/src/dilation/manager.rs @@ -3,7 +3,7 @@ use derive_more::Display; #[cfg(test)] use mockall::automock; -use crate::{core::TheirSide, dilation::api::ManagerCommand, WormholeError}; +use crate::{core::MySide, dilation::api::ManagerCommand, WormholeError}; use super::{ api::{IOEvent, ProtocolCommand}, @@ -56,6 +56,7 @@ impl ManagerMachine { pub fn process( &mut self, event: ManagerEvent, + side: &MySide, command_handler: &mut dyn FnMut(ManagerCommand) -> Result<(), WormholeError>, ) { log::debug!( @@ -71,7 +72,9 @@ impl ManagerMachine { let new_state = match current_state { Waiting => match event { ManagerEvent::Start => { - command = Some(ManagerCommand::from(ProtocolCommand::SendPlease)); + command = Some(ManagerCommand::from(ProtocolCommand::SendPlease { + side: side.clone(), + })); Wanting }, ManagerEvent::Stop => { @@ -83,8 +86,10 @@ impl ManagerMachine { }, }, Wanting => match event { - ManagerEvent::RxPlease { side: theirSide } => { - command = Some(ManagerCommand::from(ProtocolCommand::SendPlease)); + ManagerEvent::RxPlease { side: _their_side } => { + command = Some(ManagerCommand::from(ProtocolCommand::SendPlease { + side: side.clone(), + })); Connecting }, ManagerEvent::Stop => Stopped, @@ -177,6 +182,8 @@ impl ManagerMachine { #[cfg(test)] mod test { + use crate::core::TheirSide; + use super::*; struct TestHandler { @@ -198,6 +205,7 @@ mod test { fn test_manager_machine() { // Sends Start event during construction: let mut manager_fsm = ManagerMachine::new(); + let side = MySide::generate(8); // generate an input Event and see if we get the desired state and output Actions assert_eq!(manager_fsm.get_current_state(), Some(State::Wanting)); @@ -208,13 +216,16 @@ mod test { ManagerEvent::RxPlease { side: TheirSide::from("test"), }, + &side, &mut |cmd| handler.handle_command(cmd), ); assert_eq!(manager_fsm.get_current_state(), Some(State::Connecting)); assert_eq!( handler.command, - Some(ManagerCommand::Protocol(ProtocolCommand::SendPlease)) + Some(ManagerCommand::Protocol(ProtocolCommand::SendPlease { + side: side, + })) ) } } diff --git a/src/transfer.rs b/src/transfer.rs index 94297fd4..572a6f5d 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -160,7 +160,7 @@ impl AppVersion { Self { // abilities: Cow::Borrowed([Cow::Borrowed("transfer-v1"), Cow::Borrowed("transfer-v2")]), // transfer_v2: Some(AppVersionTransferV2Hint::new()) - can_dilate: can_dilate, + can_dilate, dilation_abilities: std::borrow::Cow::Borrowed(&[ Ability { ty: std::borrow::Cow::Borrowed("direct-tcp-v1"), From ebd157a848698c766007cd3ce9cf7d7e6fa62001 Mon Sep 17 00:00:00 2001 From: Andreas Wuerl Date: Wed, 3 May 2023 19:56:55 +0200 Subject: [PATCH 20/26] add --with-dilation command line switch (OP#2392) --- Cargo.lock | 13 ++ Cargo.toml | 3 +- cli/src/main.rs | 30 +++-- src/core.rs | 94 +++++++++------ src/core/key.rs | 24 +++- src/core/test.rs | 85 ++++++------- src/dilation.rs | 257 ++++++++++++++++++++++++++++++++++++---- src/dilation/events.rs | 24 +++- src/dilation/manager.rs | 70 ++++++++--- src/forwarding.rs | 1 + src/transfer.rs | 1 + src/transit.rs | 2 +- 12 files changed, 469 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81f0316a..3e981b55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1394,6 +1394,7 @@ dependencies = [ "libc", "log", "mockall", + "mockall_double", "noise-protocol", "noise-rust-crypto", "percent-encoding", @@ -1499,6 +1500,18 @@ dependencies = [ "syn", ] +[[package]] +name = "mockall_double" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae71c7bb287375187c775cf82e2dcf1bef3388aaf58f0789a77f9c7ab28466f6" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "nix" version = "0.24.3" diff --git a/Cargo.toml b/Cargo.toml index 0135301c..ef1f17b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ base64 = "0.20.0" futures_ringbuf = "0.3.1" time = { version = "0.3.7", features = ["formatting"] } instant = { version = "0.1.12", features = ["wasm-bindgen"] } +mockall_double = "0.3.0" derive_more = { version = "0.99.0", default-features = false, features = ["display", "deref", "from"] } thiserror = "1.0.24" @@ -81,7 +82,7 @@ getrandom_2 = { package = "getrandom", version = "0.1.16", features = ["js-sys"] [dev-dependencies] env_logger = "0.10.0" eyre = "0.6.5" -mockall = "0.11" +mockall = "0.11.4" [features] transit = ["socket2", "stun_codec", "if-addrs", "bytecodec", "async-trait", "noise-protocol", "noise-rust-crypto"] diff --git a/cli/src/main.rs b/cli/src/main.rs index f1495890..71246a7a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -19,7 +19,9 @@ use std::{ path::{Path, PathBuf}, }; -use magic_wormhole::{dilate, dilation, forwarding, transfer, transit, MailboxConnection, Wormhole}; +use magic_wormhole::{ + dilate, dilation, forwarding, transfer, transit, MailboxConnection, Wormhole, +}; fn install_ctrlc_handler( ) -> eyre::Result futures::future::BoxFuture<'static, ()> + Clone> { @@ -254,6 +256,9 @@ struct WormholeCli { /// Enable logging to stdout, for debugging purposes #[clap(short = 'v', long = "verbose", alias = "log", global = true)] log: bool, + /// Enable dilation + #[clap(long = "with-dilation", alias = "with-dilation", global = true)] + with_dilation: bool, #[clap(subcommand)] command: WormholeCommand, } @@ -336,7 +341,7 @@ async fn main() -> eyre::Result<()> { code, Some(code_length), true, - dilation::APP_CONFIG_SEND, + dilation::APP_CONFIG_SEND.with_dilation(app.with_dilation), Some(&sender_print_code), clipboard.as_mut(), )), @@ -374,7 +379,7 @@ async fn main() -> eyre::Result<()> { code, Some(code_length), true, - dilation::APP_CONFIG_SEND, + dilation::APP_CONFIG_SEND.with_dilation(app.with_dilation), Some(&sender_print_code), clipboard.as_mut(), )); @@ -413,14 +418,14 @@ async fn main() -> eyre::Result<()> { .. } => { let transit_abilities = parse_transit_args(&common); - let (mut wormhole, _code, relay_hints) = { + let (wormhole, _code, relay_hints) = { let connect_fut = Box::pin(parse_and_connect( &mut term, common, code, None, false, - dilation::APP_CONFIG_RECEIVE, + dilation::APP_CONFIG_RECEIVE.with_dilation(app.with_dilation), None, clipboard.as_mut(), )); @@ -430,7 +435,7 @@ async fn main() -> eyre::Result<()> { } }; - if wormhole.enable_dilation && peer_allows_dilation(&wormhole.peer_version) { + if app.with_dilation && peer_allows_dilation(&wormhole.peer_version) { log::debug!("dilate wormhole"); let mut dilated_wormhole = dilate(wormhole)?; // need to pass transit relay URL dilated_wormhole.run().await; @@ -493,7 +498,7 @@ async fn main() -> eyre::Result<()> { }) .collect::, _>>()?; loop { - let mut app_config = forwarding::APP_CONFIG; + let mut app_config = forwarding::APP_CONFIG.with_dilation(app.with_dilation); app_config.app_version.transit_abilities = parse_transit_args(&common); let connect_fut = Box::pin(parse_and_connect( &mut term, @@ -529,7 +534,7 @@ async fn main() -> eyre::Result<()> { }) => { // TODO make fancy log::warn!("This is an unstable feature. Make sure that your peer is running the exact same version of the program as you. Also, please report all bugs and crashes."); - let mut app_config = forwarding::APP_CONFIG; + let mut app_config = forwarding::APP_CONFIG.with_dilation(app.with_dilation); app_config.app_version.transit_abilities = parse_transit_args(&common); let (wormhole, _code, relay_hints) = parse_and_connect( &mut term, @@ -599,6 +604,7 @@ async fn main() -> eyre::Result<()> { } fn peer_allows_dilation(version: &Value) -> bool { + // TODO needs to be implemented true } @@ -871,11 +877,9 @@ async fn send_many( break; } - let wormhole = Wormhole::connect( - MailboxConnection::connect(transfer::APP_CONFIG, code.clone(), false).await?, - ) - .await?; - + let wormhole = + Wormhole::connect(MailboxConnection::connect(transfer::APP_CONFIG, code.clone(), false).await?) + .await?; send_in_background( relay_hints.clone(), Arc::clone(&file_path), diff --git a/src/core.rs b/src/core.rs index 4205c68b..18c9fa5b 100644 --- a/src/core.rs +++ b/src/core.rs @@ -16,7 +16,7 @@ pub(super) mod key; pub mod rendezvous; mod server_messages; #[cfg(test)] -mod test; +pub(crate) mod test; mod wordlist; #[derive(Debug, thiserror::Error)] @@ -271,12 +271,6 @@ pub struct Wormhole { * (e.g. by the file transfer API). */ pub peer_version: serde_json::Value, - /** - * Enable dilatation. This is off by default. Any application that - * would want to use a dilated wormhole would turn it on, which - * results in app specific exchange in the "version" message. - */ - pub enable_dilation: bool, } impl Wormhole { @@ -297,7 +291,6 @@ impl Wormhole { pub async fn connect_without_code( config: AppConfig, code_length: usize, - enable_dilation: bool, ) -> Result< ( WormholeWelcome, @@ -327,7 +320,6 @@ impl Wormhole { config: AppConfig, code: Code, expect_claimed_nameplate: bool, - enable_dilation: bool, ) -> Result<(WormholeWelcome, Self), WormholeError> { let mailbox_connection = MailboxConnection::connect(config, code.clone(), !expect_claimed_nameplate).await?; @@ -366,12 +358,11 @@ impl Wormhole { .map(|key| *secretbox::Key::from_slice(&key))?; /* Send versions message */ - let mut versions = key::VersionsMessage::new(enable_dilation); + let mut versions = key::VersionsMessage::new(); versions.set_app_versions(serde_json::to_value(&config.app_version).unwrap()); - println!( - "versions msg: {}", - serde_json::to_value(&app_versions).unwrap() - ); + if config.with_dilation { + versions.enable_dilation(); + } let (version_phase, version_msg) = key::build_version_msg(server.side(), &key, &versions); server.send_peer_message(version_phase, version_msg).await?; let peer_version = server.next_peer_message_some().await?; @@ -401,7 +392,6 @@ impl Wormhole { verifier: Box::new(key::derive_verifier(&key)), our_version: Box::new(config.app_version), peer_version, - enable_dilation, }) } @@ -412,24 +402,20 @@ impl Wormhole { /** Send an encrypted message to peer */ pub async fn send(&mut self, plaintext: Vec) -> Result<(), WormholeError> { - let phase_string = Phase::numeric(self.phase); - self.phase += 1; - let data_key = key::derive_phase_key(self.server.side(), &self.key, &phase_string); - let (_nonce, encrypted) = key::encrypt_data(&data_key, &plaintext); - self.server - .send_peer_message(phase_string, encrypted) - .await?; - Ok(()) + self.send_with_phase(plaintext, Phase::numeric).await } - /** Send an encrypted dilation phase message to peer */ - pub async fn send_dilation_message(&mut self, plaintext: Vec) -> Result<(), WormholeError> { - let phase_string = Phase::dilation(self.phase); + pub async fn send_with_phase( + &mut self, + plaintext: Vec, + phase_provider: PhaseProvider, + ) -> Result<(), WormholeError> { + let current_phase = phase_provider(self.phase); self.phase += 1; - let data_key = key::derive_phase_key(self.server.side(), &self.key, &phase_string); + let data_key = key::derive_phase_key(self.server.side(), &self.key, ¤t_phase); let (_nonce, encrypted) = key::encrypt_data(&data_key, &plaintext); self.server - .send_peer_message(phase_string, encrypted) + .send_peer_message(current_phase, encrypted) .await?; Ok(()) } @@ -448,15 +434,15 @@ impl Wormhole { &mut self, message: &T, ) -> Result<(), WormholeError> { - self.send(serde_json::to_vec(message).unwrap()).await + self.send_json_with_phase(message, Phase::numeric).await } - // XXX this function's name could be better than this.. - pub async fn send_json_dilation_message( + pub async fn send_json_with_phase( &mut self, message: &T, + phase_provider: PhaseProvider, ) -> Result<(), WormholeError> { - self.send_dilation_message(serde_json::to_vec(message).unwrap()) + self.send_with_phase(serde_json::to_vec(message).unwrap(), phase_provider) .await } @@ -577,6 +563,7 @@ pub struct AppConfig { pub id: AppID, pub rendezvous_url: Cow<'static, str>, pub app_version: V, + pub with_dilation: bool, } impl AppConfig { @@ -589,6 +576,11 @@ impl AppConfig { self.rendezvous_url = rendezvous_url; self } + + pub fn with_dilation(mut self, with_dilation: bool) -> Self { + self.with_dilation = with_dilation; + self + } } impl AppConfig { @@ -623,7 +615,15 @@ impl From for AppID { // MySide is used for the String that we send in all our outbound messages #[derive( - PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref, + PartialOrd, + PartialEq, + Eq, + Clone, + Debug, + Deserialize, + Serialize, + derive_more::Display, + derive_more::Deref, )] #[serde(transparent)] #[display(fmt = "MySide({})", "&*_0")] @@ -649,7 +649,15 @@ impl MySide { // TheirSide is used for the string that arrives inside inbound messages #[derive( - PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref, + PartialOrd, + PartialEq, + Eq, + Clone, + Debug, + Deserialize, + Serialize, + derive_more::Display, + derive_more::Deref, )] #[serde(transparent)] #[display(fmt = "TheirSide({})", "&*_0")] @@ -662,7 +670,15 @@ impl> From for TheirSide { } #[derive( - PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref, + PartialOrd, + PartialEq, + Eq, + Clone, + Debug, + Deserialize, + Serialize, + derive_more::Display, + derive_more::Deref, )] #[serde(transparent)] #[deref(forward)] @@ -675,6 +691,12 @@ impl> From for EitherSide { } } +impl From for TheirSide { + fn from(side: MySide) -> TheirSide { + TheirSide(side.0.into()) + } +} + #[derive(PartialEq, Eq, Clone, Debug, Hash, Deserialize, Serialize, derive_more::Display)] #[serde(transparent)] pub struct Phase(pub Cow<'static, str>); @@ -702,6 +724,8 @@ impl Phase { } } +type PhaseProvider = fn(u64) -> Phase; + #[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display)] #[serde(transparent)] pub struct Mailbox(pub String); diff --git a/src/core/key.rs b/src/core/key.rs index 14fc94a1..67f7fdcc 100644 --- a/src/core/key.rs +++ b/src/core/key.rs @@ -3,6 +3,7 @@ use hkdf::Hkdf; use serde_derive::{Deserialize, Serialize}; use sha2::{digest::FixedOutput, Digest, Sha256}; use spake2::{Ed25519Group, Identity, Password, Spake2}; +use std::ptr::addr_of_mut; use xsalsa20poly1305 as secretbox; use xsalsa20poly1305::{ aead::{generic_array::GenericArray, Aead, AeadCore, NewAead}, @@ -123,14 +124,10 @@ pub struct VersionsMessage { } impl VersionsMessage { - pub fn new(enable_dilation: bool) -> Self { + pub fn new() -> Self { // Default::default() Self { - can_dilate: if enable_dilation { - Some([std::borrow::Cow::Borrowed("1")]) - } else { - None - }, + can_dilate: None, dilation_abilities: std::borrow::Cow::Borrowed(&[ Ability { ty: std::borrow::Cow::Borrowed("direct-tcp-v1"), @@ -147,6 +144,10 @@ impl VersionsMessage { self.app_versions = versions; } + pub fn enable_dilation(&mut self) { + self.can_dilate = Some([std::borrow::Cow::Borrowed("1")]) + } + // pub fn add_resume_ability(&mut self, _resume: ()) { // self.abilities.push("resume-v1".into()) // } @@ -390,4 +391,15 @@ mod test { // None => panic!(), // } // } + + #[test] + fn test_versions_message_can_dilate() { + let mut message = VersionsMessage::new(); + + assert_eq!(message.can_dilate, None); + + message.enable_dilation(); + + assert_eq!(message.can_dilate, Some([std::borrow::Cow::Borrowed("1")])); + } } diff --git a/src/core/test.rs b/src/core/test.rs index 1d5e5e3e..7db6be58 100644 --- a/src/core/test.rs +++ b/src/core/test.rs @@ -18,11 +18,12 @@ pub const APP_CONFIG: AppConfig<()> = AppConfig::<()> { id: TEST_APPID, rendezvous_url: Cow::Borrowed(crate::rendezvous::DEFAULT_RENDEZVOUS_SERVER), app_version: (), + with_dilation: false, }; const TIMEOUT: Duration = Duration::from_secs(60); -fn init_logger() { +pub fn init_logger() { /* Ignore errors from succeedent initialization tries */ let _ = env_logger::builder() .filter_level(log::LevelFilter::Debug) @@ -93,7 +94,7 @@ pub async fn test_file_rust2rust_deprecated() -> eyre::Result<()> { .name("sender".to_owned()) .spawn(async { let (welcome, wormhole_future) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID).clone(), 2, false) + Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID).clone(), 2) .await?; if let Some(welcome) = &welcome.welcome { log::info!("Got welcome: {}", welcome); @@ -245,8 +246,7 @@ pub async fn test_4096_file_rust2rust() -> eyre::Result<()> { .name("sender".to_owned()) .spawn(async { let config = transfer::APP_CONFIG.id(TEST_APPID); - let mailbox = MailboxConnection::create(config, 2) - .await?; + let mailbox = MailboxConnection::create(config, 2).await?; if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); } @@ -319,10 +319,8 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { let sender_task = async_std::task::Builder::new() .name("sender".to_owned()) .spawn(async { - let (welcome, connector) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2) - .await?; - if let Some(welcome) = &welcome.welcome { + let mailbox = MailboxConnection::create(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; + if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); } log::info!("This wormhole's code is: {}", &mailbox.code); @@ -349,8 +347,8 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { .spawn(async { let code = code_rx.await?; log::info!("Got code over local: {}", &code); - let (welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code, true) + let mailbox = + MailboxConnection::connect(transfer::APP_CONFIG.id(TEST_APPID), code, false) .await?; if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); @@ -391,10 +389,8 @@ pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { pub async fn test_send_many() -> eyre::Result<()> { init_logger(); - let (welcome, connector) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; - - let code = welcome.code; + let mailbox = MailboxConnection::create(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; + let code = mailbox.code.clone(); log::info!("The code is {:?}", code); let correct_data = std::fs::read("tests/example-file.bin")?; @@ -428,10 +424,13 @@ pub async fn test_send_many() -> eyre::Result<()> { for i in 1..5usize { log::info!("Sending file #{}", i); - let (_welcome, wormhole) = Wormhole::connect_with_code( - transfer::APP_CONFIG.id(TEST_APPID), - sender_code.clone(), - false, + let wormhole = Wormhole::connect( + MailboxConnection::connect( + transfer::APP_CONFIG.id(TEST_APPID), + sender_code.clone(), + true, + ) + .await?, ) .await?; senders.push(async_std::task::spawn(async move { @@ -458,11 +457,9 @@ pub async fn test_send_many() -> eyre::Result<()> { /* Receive many */ for i in 0..5usize { log::info!("Receiving file #{}", i); - let (_welcome, wormhole) = Wormhole::connect_with_code( - transfer::APP_CONFIG.id(TEST_APPID), - code.clone(), - true, - false, + let wormhole = Wormhole::connect( + MailboxConnection::connect(transfer::APP_CONFIG.id(TEST_APPID), code.clone(), true) + .await?, ) .await?; log::info!("Got key: {}", &wormhole.key); @@ -503,8 +500,8 @@ pub async fn test_wrong_code() -> eyre::Result<()> { let sender_task = async_std::task::Builder::new() .name("sender".to_owned()) .spawn(async { - let (welcome, connector) = Wormhole::connect_without_code(APP_CONFIG, 2).await?; - if let Some(welcome) = &welcome.welcome { + let mailbox = MailboxConnection::create(APP_CONFIG, 2).await?; + if let Some(welcome) = &mailbox.welcome { log::info!("Got welcome: {}", welcome); } let code = mailbox.code.clone(); @@ -521,11 +518,14 @@ pub async fn test_wrong_code() -> eyre::Result<()> { .spawn(async { let nameplate = code_rx.await?; log::info!("Got nameplate over local: {}", &nameplate); - let result = Wormhole::connect_with_code( - APP_CONFIG, - /* Making a wrong code here by appending bullshit */ - Code::new(&nameplate, "foo-bar"), - true, + let result = Wormhole::connect( + MailboxConnection::connect( + APP_CONFIG, + /* Making a wrong code here by appending bullshit */ + Code::new(&nameplate, "foo-bar"), + true, + ) + .await?, ) .await; @@ -545,14 +545,17 @@ pub async fn test_wrong_code() -> eyre::Result<()> { pub async fn test_crowded() -> eyre::Result<()> { init_logger(); - let (welcome, connector1) = Wormhole::connect_without_code(APP_CONFIG, 2).await?; - log::info!("This test's code is: {}", &welcome.code); - - let connector2 = Wormhole::connect_with_code(APP_CONFIG, welcome.code.clone(), true); + let initial_mailbox_connection = MailboxConnection::create(APP_CONFIG, 2).await?; + log::info!("This test's code is: {}", &initial_mailbox_connection.code); + let code = initial_mailbox_connection.code.clone(); - let connector3 = Wormhole::connect_with_code(APP_CONFIG, welcome.code.clone(), true); + let mailbox_connection_1 = MailboxConnection::connect(APP_CONFIG.clone(), code.clone(), false); + let mailbox_connection_2 = MailboxConnection::connect(APP_CONFIG.clone(), code.clone(), false); - match futures::try_join!(connector1, connector2, connector3).unwrap_err() { + match futures::try_join!(mailbox_connection_1, mailbox_connection_2) + .err() + .unwrap() + { magic_wormhole::WormholeError::ServerError( magic_wormhole::rendezvous::RendezvousError::Server(error), ) => { @@ -566,12 +569,10 @@ pub async fn test_crowded() -> eyre::Result<()> { #[async_std::test] pub async fn test_connect_with_code_expecting_nameplate() -> eyre::Result<()> { - // the max nameplate number is 999, so this will not impact a real nameplate - let code = Code("1000-guitarist-revenge".to_owned()); - let connector = Wormhole::connect_with_code(APP_CONFIG, code, true) - .await - .unwrap_err(); - match connector { + let code = generate_random_code(); + let result = MailboxConnection::connect(APP_CONFIG, code.clone(), false).await; + let error = result.err().unwrap(); + match error { magic_wormhole::WormholeError::UnclaimedNameplate(x) => { assert_eq!(x, code.nameplate()); }, diff --git a/src/dilation.rs b/src/dilation.rs index 7858ac35..29e7ef61 100644 --- a/src/dilation.rs +++ b/src/dilation.rs @@ -1,7 +1,6 @@ use std::{ borrow::Cow, cell::{RefCell, RefMut}, - ops::Deref, rc::Rc, }; @@ -9,14 +8,18 @@ use async_trait::async_trait; use futures::executor; #[cfg(test)] use mockall::{automock, mock, predicate::*}; +use mockall_double::double; use serde_derive::{Deserialize, Serialize}; use crate::{ - core::MySide, - dilation::{api::ManagerCommand, manager::ManagerMachine}, + core::{MySide, Phase}, + dilation::api::{ManagerCommand, ProtocolCommand}, Wormhole, WormholeError, }; +#[double] +use crate::dilation::manager::ManagerMachine; + use super::AppID; mod api; @@ -30,12 +33,14 @@ pub const APP_CONFIG_SEND: crate::AppConfig = crate::AppConfig:: = crate::AppConfig:: { id: AppID(Cow::Borrowed(APPID_RAW)), rendezvous_url: Cow::Borrowed(crate::rendezvous::DEFAULT_RENDEZVOUS_SERVER), app_version: AppVersion::new(FileTransferV2Mode::Receive), + with_dilation: false, }; #[derive(Clone, Serialize, Deserialize)] @@ -101,58 +106,92 @@ impl Default for AppVersion { } } -pub struct DilatedWormhole { +#[double] +type WormholeConnection = WormholeConnectionDefault; + +pub struct WormholeConnectionDefault { wormhole: Rc>, +} + +#[cfg_attr(test, automock)] +impl WormholeConnectionDefault { + fn new(wormhole: Wormhole) -> Self { + Self { + wormhole: Rc::new(RefCell::new(wormhole)), + } + } + + async fn receive_json(&self) -> Result + where + T: for<'a> serde::Deserialize<'a> + 'static, + { + let message = self.wormhole.borrow_mut().receive_json().await; + message + } + + async fn send_json(&self, command: &ProtocolCommand) -> Result<(), WormholeError> { + self.wormhole + .borrow_mut() + .send_json_with_phase(command, Phase::dilation) + .await + } +} + +pub struct DilatedWormhole { + wormhole: WormholeConnection, side: MySide, manager: ManagerMachine, } impl DilatedWormhole { pub fn new(wormhole: Wormhole, side: MySide) -> Self { - let wormhole_ref = &wormhole; DilatedWormhole { - wormhole: Rc::new(RefCell::new(wormhole)), - side, - manager: ManagerMachine::new(), + wormhole: WormholeConnection::new(wormhole), + side: side.clone(), + manager: ManagerMachine::new(side.clone()), } } pub async fn run(&mut self) { - log::debug!( + log::info!( "start state machine: state={}", - &self.manager.state.unwrap() + &self.manager.current_state().unwrap() ); - let mut command_handler = |cmd| Self::execute_command(self.wormhole.borrow_mut(), cmd); + let mut command_handler = |cmd| Self::execute_command(&self.wormhole, cmd); loop { - let event_result = self.wormhole.borrow_mut().receive_json().await; + log::debug!("wait for next event"); + let event_result = self.wormhole.receive_json().await; match event_result { Ok(manager_event) => { + log::debug!("received event"); self.manager .process(manager_event, &self.side, &mut command_handler) }, Err(error) => { log::warn!("received error {}", error); + continue; }, }; if self.manager.is_done() { + log::debug!("exiting"); break; } } } fn execute_command( - mut wormhole: RefMut, + wormhole: &WormholeConnection, command: ManagerCommand, ) -> Result<(), WormholeError> { log::debug!("execute_command"); match command { ManagerCommand::Protocol(protocol_command) => { log::debug!(" command: {}", protocol_command); - executor::block_on(wormhole.send_json_dilation_message(&protocol_command)) + executor::block_on(wormhole.send_json(&protocol_command)) }, ManagerCommand::IO(io_command) => { println!("io command: {}", io_command); @@ -164,17 +203,193 @@ impl DilatedWormhole { #[cfg(test)] mod test { - use crate::dilation::{ - api::ProtocolCommand, events::ManagerEvent, manager::MockManagerMachine, + use crate::{ + core::test::init_logger, + dilation::{ + api::{IOCommand, ProtocolCommand}, + events::ManagerEvent, + manager::{MockManagerMachine, State}, + }, + rendezvous::RendezvousError, }; + use std::sync::{Arc, Mutex}; use super::*; + #[async_std::test] + async fn test_dilated_wormhole() { + init_logger(); + + let mut manager = ManagerMachine::default(); + let mut wormhole = WormholeConnection::default(); + + let my_side = MySide::generate(23); + + manager + .expect_current_state() + .return_once(|| Some(State::Wanting)); + + wormhole + .expect_receive_json() + .return_once(|| Ok(ManagerEvent::Start)); + + manager + .expect_process() + .with(eq(ManagerEvent::Start), eq(my_side.clone()), always()) + .times(1) + .return_once(|_, _, _| ()); + + manager.expect_is_done().return_once(|| true); + + let mut dilated_wormhole = DilatedWormhole { + manager, + side: my_side, + wormhole, + }; + + dilated_wormhole.run().await; + } + + #[async_std::test] + async fn test_dilated_wormhole_receving_error() { + init_logger(); + + let mut manager = ManagerMachine::default(); + let mut wormhole = WormholeConnection::default(); + + let my_side = MySide::generate(23); + + manager + .expect_current_state() + .return_once(|| Some(State::Wanting)); + + let mut events = vec![Ok(ManagerEvent::Start), Err(WormholeError::DilationVersion)]; + wormhole + .expect_receive_json() + .returning(move || events.pop().unwrap()); + + manager + .expect_process() + .with(eq(ManagerEvent::Start), eq(my_side.clone()), always()) + .times(1) + .return_once(|_, _, _| ()); + + manager.expect_is_done().return_once(|| true); + + let mut dilated_wormhole = DilatedWormhole { + manager, + side: my_side, + wormhole, + }; + + dilated_wormhole.run().await; + } + + #[async_std::test] + async fn test_dilated_wormhole_two_iterations() { + init_logger(); + + let mut manager = ManagerMachine::default(); + let mut wormhole = WormholeConnection::default(); + + let my_side = MySide::generate(23); + + manager + .expect_current_state() + .return_once(|| Some(State::Wanting)); + + let mut events = vec![Ok(ManagerEvent::Stop), Ok(ManagerEvent::Start)]; + wormhole + .expect_receive_json() + .times(2) + .returning(move || events.pop().unwrap()); + + let mut verify_events = Arc::new(Mutex::new(vec![ManagerEvent::Stop, ManagerEvent::Start])); + let verify_my_side = my_side.clone(); + manager + .expect_process() + .withf(move |event, side, handler| { + *event == verify_events.lock().unwrap().pop().unwrap() && side == &verify_my_side + }) + .times(2) + .returning(|_, _, _| ()); + + let mut returns = vec![true, false]; + manager + .expect_is_done() + .returning(move || returns.pop().unwrap()); + + let mut dilated_wormhole = DilatedWormhole { + manager, + side: my_side.clone(), + wormhole, + }; + + dilated_wormhole.run().await; + } + #[test] - fn test_dilated_wormhole() { - let mut manager = MockManagerMachine::default(); - // let mut wormhole = MockWormhole::default(); - // - // let dilated_wormhole = DilatedWormhole { manager, side: MySide::generate(23), wormhole }; + fn test_dilated_wormhole_execute_protocol_command() { + init_logger(); + + let mut wormhole = WormholeConnection::default(); + + let protocol_command = ProtocolCommand::SendPlease { + side: MySide::generate(2), + }; + + wormhole + .expect_send_json() + .with(eq(protocol_command.clone())) + .return_once(|_| Ok(())) + .times(1); + + let result = DilatedWormhole::execute_command( + &mut wormhole, + ManagerCommand::Protocol(protocol_command), + ); + + assert!(result.is_ok()) + } + + #[test] + fn test_dilated_wormhole_execute_protocol_command_failure() { + init_logger(); + + let mut wormhole = WormholeConnection::default(); + + let protocol_command = ProtocolCommand::SendPlease { + side: MySide::generate(2), + }; + + let protocol_command_ref = protocol_command.clone(); + wormhole + .expect_send_json() + .with(eq(protocol_command_ref)) + .return_once(|_| Err(WormholeError::Crypto)) + .times(1); + + let result = DilatedWormhole::execute_command( + &mut wormhole, + ManagerCommand::Protocol(protocol_command.clone()), + ); + + assert!(result.is_err()) + } + + #[test] + fn test_dilated_wormhole_execute_io_command() { + init_logger(); + + let mut wormhole = WormholeConnection::default(); + + wormhole.expect_send_json().times(0); + + let result = DilatedWormhole::execute_command( + &mut wormhole, + ManagerCommand::IO(IOCommand::CloseConnection), + ); + + assert!(result.is_ok()) } } diff --git a/src/dilation/events.rs b/src/dilation/events.rs index 24fb7ca8..8358466f 100644 --- a/src/dilation/events.rs +++ b/src/dilation/events.rs @@ -4,6 +4,7 @@ use serde_derive::Deserialize; use crate::{ core::TheirSide, dilation::api::{IOEvent, ManagerCommand}, + transit, }; use super::api::ProtocolCommand; @@ -29,7 +30,7 @@ impl From for Event { } // individual fsm events -#[derive(Debug, Clone, PartialEq, Display, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(tag = "type")] pub enum ManagerEvent { Start, @@ -37,7 +38,10 @@ pub enum ManagerEvent { RxPlease { side: TheirSide, }, - RxHints, + #[serde(rename = "connection-hints")] + RxHints { + hints: transit::Hints, + }, RxReconnect, RxReconnecting, ConnectionMade, @@ -46,6 +50,22 @@ pub enum ManagerEvent { Stop, } +impl std::fmt::Display for ManagerEvent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + ManagerEvent::Start => write!(f, "start"), + ManagerEvent::RxPlease { side } => write!(f, "please"), + ManagerEvent::RxHints { hints } => write!(f, "connection-hints"), + ManagerEvent::RxReconnect => write!(f, "reconnect"), + ManagerEvent::RxReconnecting => write!(f, "reconnecting"), + ManagerEvent::ConnectionMade => write!(f, "connection-made"), + ManagerEvent::ConnectionLostLeader => write!(f, "connection-lost-leader"), + ManagerEvent::ConnectionLostFollower => write!(f, "connection-lost-follower"), + ManagerEvent::Stop => write!(f, "stop"), + } + } +} + #[test] fn test_manager_event_deserialisation() { let result: ManagerEvent = diff --git a/src/dilation/manager.rs b/src/dilation/manager.rs index b00e030b..7fdeae08 100644 --- a/src/dilation/manager.rs +++ b/src/dilation/manager.rs @@ -3,14 +3,26 @@ use derive_more::Display; #[cfg(test)] use mockall::automock; -use crate::{core::MySide, dilation::api::ManagerCommand, WormholeError}; +use crate::{ + core::{MySide, TheirSide}, + dilation::api::ManagerCommand, + WormholeError, +}; use super::{ api::{IOEvent, ProtocolCommand}, events::ManagerEvent, }; +#[derive(Debug, PartialEq, Display)] +pub enum Role { + Leader, + Follower, +} + pub struct ManagerMachine { + pub side: MySide, + pub role: Role, pub state: Option, } @@ -29,13 +41,19 @@ pub enum State { #[cfg_attr(test, automock)] impl ManagerMachine { - pub fn new() -> Self { + pub fn new(side: MySide) -> Self { let mut machine = ManagerMachine { + side, + role: Role::Follower, state: Some(State::Wanting), }; machine } + pub fn current_state(&self) -> Option { + self.state + } + pub fn is_waiting(&self) -> bool { self.state == Some(State::Waiting) } @@ -53,6 +71,15 @@ impl ManagerMachine { self.state } + fn choose_role(&self, theirside: &TheirSide) -> Role { + let myside: TheirSide = self.side.clone().into(); + if myside > *theirside { + Role::Leader + } else { + Role::Follower + } + } + pub fn process( &mut self, event: ManagerEvent, @@ -86,20 +113,34 @@ impl ManagerMachine { }, }, Wanting => match event { - ManagerEvent::RxPlease { side: _their_side } => { + ManagerEvent::RxPlease { side: their_side } => { command = Some(ManagerCommand::from(ProtocolCommand::SendPlease { side: side.clone(), })); + let role = self.choose_role(&their_side.clone()); + log::debug!( + "role: {}", + if role == Role::Leader { + "leader" + } else { + "follower" + } + ); + self.role = role; Connecting }, ManagerEvent::Stop => Stopped, - ManagerEvent::RxHints => current_state, + ManagerEvent::RxHints { hints } => current_state, _ => { panic! {"unexpected event {:?} for state {:?}", current_state, event} }, }, Connecting => match event { - ManagerEvent::RxHints => current_state, + ManagerEvent::RxHints { hints } => { + log::debug!("received connection hints: {:?}", hints); + // TODO store the other side's hints + current_state + }, ManagerEvent::Stop => Stopped, ManagerEvent::ConnectionMade => Connected, ManagerEvent::RxReconnect => current_state, @@ -109,7 +150,7 @@ impl ManagerMachine { }, Connected => match event { ManagerEvent::RxReconnect => Abandoning, - ManagerEvent::RxHints => current_state, + ManagerEvent::RxHints { hints } => current_state, ManagerEvent::ConnectionLostFollower => Lonely, ManagerEvent::ConnectionLostLeader => Flushing, ManagerEvent::Stop => Stopped, @@ -118,7 +159,7 @@ impl ManagerMachine { }, }, Abandoning => match event { - ManagerEvent::RxHints => current_state, + ManagerEvent::RxHints { hints } => current_state, ManagerEvent::ConnectionLostFollower => Connecting, ManagerEvent::Stop => Stopped, _ => { @@ -128,7 +169,7 @@ impl ManagerMachine { Flushing => match event { ManagerEvent::RxReconnecting => Connecting, ManagerEvent::Stop => Stopped, - ManagerEvent::RxHints => current_state, + ManagerEvent::RxHints { hints } => current_state, _ => { panic! {"unexpected event {:?} for state {:?}", current_state, event} }, @@ -136,13 +177,13 @@ impl ManagerMachine { Lonely => match event { ManagerEvent::RxReconnect => Connecting, ManagerEvent::Stop => Stopped, - ManagerEvent::RxHints => current_state, + ManagerEvent::RxHints { hints } => current_state, _ => { panic! {"unexpected event {:?} for state {:?}", current_state, event} }, }, Stopping => match event { - ManagerEvent::RxHints => current_state, + ManagerEvent::RxHints { hints } => current_state, ManagerEvent::ConnectionLostFollower => Stopped, ManagerEvent::ConnectionLostLeader => Stopped, _ => { @@ -170,7 +211,7 @@ impl ManagerMachine { ); }, Err(wormhole_error) => { - log::warn!("processing event errored: {}", wormhole_error); + panic!("processing event errored: {}", wormhole_error); }, }; } @@ -182,7 +223,7 @@ impl ManagerMachine { #[cfg(test)] mod test { - use crate::core::TheirSide; + use crate::core::{MySide, TheirSide}; use super::*; @@ -204,14 +245,15 @@ mod test { #[test] fn test_manager_machine() { // Sends Start event during construction: - let mut manager_fsm = ManagerMachine::new(); + let mut manager_fsm = + ManagerMachine::new(MySide::unchecked_from_string("test123".to_string())); let side = MySide::generate(8); - // generate an input Event and see if we get the desired state and output Actions assert_eq!(manager_fsm.get_current_state(), Some(State::Wanting)); let mut handler = TestHandler::new(); + // generate an input Event and see if we get the desired state and output Actions manager_fsm.process( ManagerEvent::RxPlease { side: TheirSide::from("test"), diff --git a/src/forwarding.rs b/src/forwarding.rs index 77176a03..1391e841 100644 --- a/src/forwarding.rs +++ b/src/forwarding.rs @@ -41,6 +41,7 @@ pub const APP_CONFIG: crate::AppConfig = crate::AppConfig:: = crate::AppConfig::, From df724bea552e09cfba8432f8096e1c19a5a6338a Mon Sep 17 00:00:00 2001 From: Ramakrishnan Muthukrishnan Date: Wed, 10 May 2023 15:14:37 +0530 Subject: [PATCH 21/26] reduce the number of Ability type definitions by one --- src/core/key.rs | 19 ++++--------------- src/transfer.rs | 17 +++-------------- src/transit.rs | 24 ++++++++++++------------ 3 files changed, 19 insertions(+), 41 deletions(-) diff --git a/src/core/key.rs b/src/core/key.rs index 67f7fdcc..a26586e6 100644 --- a/src/core/key.rs +++ b/src/core/key.rs @@ -1,4 +1,4 @@ -use crate::core::*; +use crate::{core::*, transit}; use hkdf::Hkdf; use serde_derive::{Deserialize, Serialize}; use sha2::{digest::FixedOutput, Digest, Sha256}; @@ -103,20 +103,13 @@ pub fn make_pake(password: &str, appid: &AppID) -> (Spake2, Vec, -} - #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct VersionsMessage { //#[serde(default)] pub can_dilate: Option<[Cow<'static, str>; 1]>, //#[serde(default)] - pub dilation_abilities: Cow<'static, [Ability; 2]>, + pub dilation_abilities: Cow<'static, [transit::Ability; 2]>, //#[serde(default)] #[serde(rename = "app_versions")] pub app_versions: serde_json::Value, @@ -129,12 +122,8 @@ impl VersionsMessage { Self { can_dilate: None, dilation_abilities: std::borrow::Cow::Borrowed(&[ - Ability { - ty: std::borrow::Cow::Borrowed("direct-tcp-v1"), - }, - Ability { - ty: std::borrow::Cow::Borrowed("relay-v1"), - }, + transit::Ability::DirectTcpV1, + transit::Ability::RelayV1, ]), app_versions: serde_json::Value::Null, } diff --git a/src/transfer.rs b/src/transfer.rs index 9831cca4..c3140a11 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -118,13 +118,6 @@ impl TransferError { } } -#[derive(Clone, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct Ability { - #[serde(rename = "type")] - ty: Cow<'static, str>, -} - /** * The application specific version information for this protocol. * @@ -145,7 +138,7 @@ pub struct AppVersion { // overall versions payload is of the form: // b'{"can-dilate": ["1"], "dilation-abilities": [{"type": "direct-tcp-v1"}, {"type": "relay-v1"}], "app_versions": {"transfer": {"mode": "send", "features": {}}}}' can_dilate: Option<[Cow<'static, str>; 1]>, - dilation_abilities: Cow<'static, [Ability; 2]>, + dilation_abilities: Cow<'static, [transit::Ability; 2]>, } // TODO check invariants during deserialization @@ -163,12 +156,8 @@ impl AppVersion { // transfer_v2: Some(AppVersionTransferV2Hint::new()) can_dilate, dilation_abilities: std::borrow::Cow::Borrowed(&[ - Ability { - ty: std::borrow::Cow::Borrowed("direct-tcp-v1"), - }, - Ability { - ty: std::borrow::Cow::Borrowed("relay-v1"), - }, + transit::Ability::DirectTcpV1, + transit::Ability::RelayV1, ]), } } diff --git a/src/transit.rs b/src/transit.rs index 2c424a1f..d87fd443 100644 --- a/src/transit.rs +++ b/src/transit.rs @@ -209,23 +209,23 @@ impl serde::Serialize for Abilities { } } +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "kebab-case", tag = "type")] +pub enum Ability { + DirectTcpV1, + RelayV1, + RelayV2, + #[cfg(all())] + NoiseCryptoV1, + #[serde(other)] + Other, +} + impl<'de> serde::Deserialize<'de> for Abilities { fn deserialize(de: D) -> Result where D: serde::Deserializer<'de>, { - #[derive(Deserialize)] - #[serde(rename_all = "kebab-case", tag = "type")] - enum Ability { - DirectTcpV1, - RelayV1, - RelayV2, - #[cfg(all())] - NoiseCryptoV1, - #[serde(other)] - Other, - } - let mut abilities = Self::default(); /* Specifying a hint multiple times is undefined behavior. Here, we simply merge all features. */ for ability in as serde::Deserialize>::deserialize(de)? { From ff78cbbd0c661612b32a951d042c9eda81ca735f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Thu, 11 May 2023 22:35:05 +0200 Subject: [PATCH 22/26] review related improvements --- .github/workflows/push.yml | 9 +- Cargo.lock | 723 +++++++++++++++------------ Cargo.toml | 8 +- cli/src/main.rs | 56 ++- sonar-project.properties | 1 - src/core.rs | 166 +++--- src/core/key.rs | 17 +- src/core/protocol.rs | 155 ++++++ src/core/rendezvous.rs | 19 +- src/core/server_messages.rs | 59 ++- src/core/test.rs | 2 +- src/core/wordlist.rs | 4 +- src/dilated_transfer/mod.rs | 77 +++ src/dilation/events.rs | 69 +-- src/dilation/manager.rs | 73 ++- src/{dilation.rs => dilation/mod.rs} | 224 +++++---- src/forwarding.rs | 37 +- src/lib.rs | 5 +- src/transfer.rs | 42 +- src/transfer/v1.rs | 8 +- src/transit.rs | 17 +- 21 files changed, 1032 insertions(+), 739 deletions(-) delete mode 100644 sonar-project.properties create mode 100644 src/core/protocol.rs create mode 100644 src/dilated_transfer/mod.rs rename src/{dilation.rs => dilation/mod.rs} (67%) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 6dd6200e..8fc04b22 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -97,6 +97,11 @@ jobs: with: command: build args: -p magic-wormhole --no-default-features --features=forwarding + - name: build library (features=dilation) + uses: actions-rs/cargo@v1 + with: + command: build + args: -p magic-wormhole --no-default-features --features=dilation - name: build CLI uses: actions-rs/cargo@v1 with: @@ -183,10 +188,6 @@ jobs: uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} - - uses: sonarsource/sonarqube-scan-action@master - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} cargo-deny: name: Cargo deny diff --git a/Cargo.lock b/Cargo.lock index 3e981b55..b78275e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,9 +55,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -75,7 +75,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -91,9 +91,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ "async-lock", "async-task", @@ -120,39 +120,38 @@ dependencies = [ [[package]] name = "async-io" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg", + "cfg-if", "concurrent-queue", "futures-lite", - "libc", "log", "parking", "polling", + "rustix", "slab", "socket2", "waker-fn", - "windows-sys", ] [[package]] name = "async-lock" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" dependencies = [ "event-listener", - "futures-lite", ] [[package]] name = "async-process" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io", "async-lock", @@ -161,9 +160,9 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "libc", + "rustix", "signal-hook", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -204,15 +203,15 @@ dependencies = [ "filetime", "libc", "pin-project", - "redox_syscall", + "redox_syscall 0.2.16", "xattr", ] [[package]] name = "async-task" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-tls" @@ -229,13 +228,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -266,9 +265,9 @@ dependencies = [ [[package]] name = "atomic-waker" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atty" @@ -326,7 +325,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -337,18 +336,18 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "blocking" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", "async-lock", @@ -356,13 +355,14 @@ dependencies = [ "atomic-waker", "fastrand", "futures-lite", + "log", ] [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytecodec" @@ -394,9 +394,9 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cache-padded" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" [[package]] name = "cc" @@ -452,9 +452,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags", @@ -479,15 +479,15 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.18" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -553,31 +553,31 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.5" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -599,9 +599,9 @@ checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", ] @@ -641,16 +641,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "ctr" version = "0.8.0" @@ -662,12 +652,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.4" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" +checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" dependencies = [ "nix 0.26.2", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -704,7 +694,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.9.3", - "syn", + "syn 1.0.109", ] [[package]] @@ -715,7 +705,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -726,7 +716,7 @@ checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -737,14 +727,14 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "dialoguer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3c796f3b0b408d9fd581611b47fa850821fcb84aa640b83a3c1a5be2d691f2" +checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" dependencies = [ "console", "shell-words", @@ -769,9 +759,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -817,13 +807,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -864,23 +854,23 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "filetime" -version = "0.2.19" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", ] [[package]] @@ -921,9 +911,9 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "futures" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -936,9 +926,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -946,15 +936,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -963,15 +953,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -984,32 +974,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -1038,9 +1028,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1070,9 +1060,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "js-sys", @@ -1093,9 +1083,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "gloo-timers" @@ -1117,9 +1107,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -1132,12 +1122,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "hex" @@ -1163,7 +1150,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -1177,9 +1164,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -1221,7 +1208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b24dd0826eee92c56edcda7ff190f2cf52115c49eadb2c2da8063e2673a8c2" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1232,9 +1219,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -1242,11 +1229,12 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.3" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" +checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" dependencies = [ "console", + "instant", "number_prefix", "portable-atomic", "unicode-width", @@ -1266,24 +1254,25 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.4" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1297,15 +1286,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -1327,15 +1316,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" @@ -1349,11 +1338,10 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" dependencies = [ - "cfg-if", "value-bag", ] @@ -1366,7 +1354,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1386,7 +1374,7 @@ dependencies = [ "futures", "futures_ringbuf", "getrandom 0.1.16", - "getrandom 0.2.8", + "getrandom 0.2.9", "hex", "hkdf", "if-addrs", @@ -1463,14 +1451,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1497,7 +1485,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1509,7 +1497,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1554,7 +1542,7 @@ dependencies = [ "aes-gcm", "blake2", "chacha20poly1305", - "getrandom 0.2.8", + "getrandom 0.2.9", "noise-protocol", "sha2", "x25519-dalek", @@ -1632,9 +1620,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -1644,19 +1632,19 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "os_pipe" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a252f1f8c11e84b3ab59d7a488e48e4478a93937e027076638c49536204639" +checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "os_str_bytes" -version = "6.4.1" +version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "owo-colors" @@ -1666,9 +1654,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -1688,7 +1676,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.6", + "parking_lot_core 0.9.7", ] [[package]] @@ -1700,29 +1688,29 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "paste" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "percent-encoding" @@ -1732,9 +1720,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", "indexmap", @@ -1752,22 +1740,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -1784,22 +1772,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "polling" -version = "2.5.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", + "bitflags", "cfg-if", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "windows-sys", + "pin-project-lite", + "windows-sys 0.48.0", ] [[package]] @@ -1827,9 +1817,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "0.3.19" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" +checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" [[package]] name = "ppv-lite86" @@ -1876,7 +1866,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -1893,9 +1883,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] @@ -1921,9 +1911,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1964,7 +1954,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", ] [[package]] @@ -1976,11 +1966,20 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" -version = "1.7.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" dependencies = [ "aho-corasick", "memchr", @@ -1989,18 +1988,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "ring" @@ -2050,9 +2040,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc_version" @@ -2065,16 +2055,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.7" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2092,9 +2082,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "salsa20" @@ -2124,9 +2114,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "send_wrapper" @@ -2136,29 +2126,29 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -2173,7 +2163,7 @@ checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2190,7 +2180,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2210,9 +2200,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", @@ -2231,18 +2221,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -2255,9 +2245,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -2316,7 +2306,7 @@ dependencies = [ "crc", "hmac-sha1", "md5", - "trackable 1.2.0", + "trackable 1.3.0", ] [[package]] @@ -2327,9 +2317,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -2337,29 +2327,27 @@ dependencies = [ ] [[package]] -name = "synstructure" -version = "0.12.6" +name = "syn" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", - "syn", - "unicode-xid", + "unicode-ident", ] [[package]] name = "tempfile" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", ] [[package]] @@ -2373,12 +2361,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2398,38 +2386,39 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] [[package]] name = "time" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "itoa", "serde", @@ -2439,15 +2428,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -2463,9 +2452,9 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tracing" @@ -2480,9 +2469,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -2500,9 +2489,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "sharded-slab", "thread_local", @@ -2515,15 +2504,15 @@ version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98abb9e7300b9ac902cc04920945a874c1973e08c310627cc4458c04b70dd32" dependencies = [ - "trackable 1.2.0", + "trackable 1.3.0", "trackable_derive", ] [[package]] name = "trackable" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017e2a1a93718e4e8386d037cfb8add78f1d690467f4350fb582f55af1203167" +checksum = "b15bd114abb99ef8cee977e517c8f37aee63f184f2d08e3e6ceca092373369ae" dependencies = [ "trackable_derive", ] @@ -2535,7 +2524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2579,15 +2568,15 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -2604,12 +2593,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "universal-hash" version = "0.4.1" @@ -2652,13 +2635,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.9" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] +checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" [[package]] name = "version_check" @@ -2686,9 +2665,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2696,24 +2675,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -2723,9 +2702,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2733,22 +2712,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "wasm-timer" @@ -2826,9 +2805,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2853,15 +2832,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2908,56 +2878,146 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "wl-clipboard-rs" @@ -3023,9 +3083,9 @@ dependencies = [ [[package]] name = "x11-clipboard" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0827f86aa910c4e73329a4f619deabe88ebb4b042370bf023c2d5d8b4eb54695" +checksum = "980b9aa9226c3b7de8e2adb11bf20124327c054e0e5812d2aac0b5b5a87e7464" dependencies = [ "x11rb", ] @@ -3074,9 +3134,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.4" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +checksum = "52839dc911083a8ef63efa4d039d1f58b5e409f923e44c80828f206f66e5541c" [[package]] name = "xsalsa20poly1305" @@ -3103,12 +3163,11 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.18", ] diff --git a/Cargo.toml b/Cargo.toml index ef1f17b5..97f27c0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,9 +31,10 @@ log = "0.4.13" # zeroize = { version = "1.2.0", features = ["zeroize_derive"] } base64 = "0.20.0" futures_ringbuf = "0.3.1" +async-trait = "0.1.57" +mockall_double = "0.3.0" time = { version = "0.3.7", features = ["formatting"] } instant = { version = "0.1.12", features = ["wasm-bindgen"] } -mockall_double = "0.3.0" derive_more = { version = "0.99.0", default-features = false, features = ["display", "deref", "from"] } thiserror = "1.0.24" @@ -46,7 +47,6 @@ percent-encoding = { version = "2.1.0" } stun_codec = { version = "0.2.0", optional = true } bytecodec = { version = "0.4.15", optional = true } -async-trait = { version = "0.1.57", optional = true } noise-protocol = { version = "0.1.4", optional = true } noise-rust-crypto = { version = "0.5.0", optional = true } @@ -85,9 +85,9 @@ eyre = "0.6.5" mockall = "0.11.4" [features] -transit = ["socket2", "stun_codec", "if-addrs", "bytecodec", "async-trait", "noise-protocol", "noise-rust-crypto"] +transit = ["socket2", "stun_codec", "if-addrs", "bytecodec", "noise-protocol", "noise-rust-crypto"] transfer = ["transit", "async-tar", "rmp-serde"] -dilation = ["transfer"] +dilation = ["transit"] forwarding = ["transit", "rmp-serde"] default = ["transfer", "dilation"] all = ["default", "forwarding"] diff --git a/cli/src/main.rs b/cli/src/main.rs index 71246a7a..d3d8f245 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -13,14 +13,13 @@ use color_eyre::{eyre, eyre::Context}; use console::{style, Term}; use futures::{future::Either, Future, FutureExt}; use indicatif::{MultiProgress, ProgressBar}; -use serde_json::Value; use std::{ io::Write, path::{Path, PathBuf}, }; use magic_wormhole::{ - dilate, dilation, forwarding, transfer, transit, MailboxConnection, Wormhole, + dilated_transfer, forwarding, transfer, transit, MailboxConnection, Wormhole, }; fn install_ctrlc_handler( @@ -61,6 +60,14 @@ fn install_ctrlc_handler( }) } +// send, receive, +#[derive(Debug, Args)] +struct CommonTransferArgs { + /// Enable dilation + #[clap(long = "with-dilation", alias = "with-dilation")] + with_dilation: bool, +} + // send, send-many #[derive(Debug, Args)] struct CommonSenderArgs { @@ -183,6 +190,8 @@ enum WormholeCommand { common_leader: CommonLeaderArgs, #[clap(flatten)] common_send: CommonSenderArgs, + #[clap(flatten)] + common_transfer: CommonTransferArgs, }, /// Send a file to many recipients. READ HELP PAGE FIRST! #[clap( @@ -225,6 +234,8 @@ enum WormholeCommand { common_follower: CommonFollowerArgs, #[clap(flatten)] common_receiver: CommonReceiverArgs, + #[clap(flatten)] + common_transfer: CommonTransferArgs, }, /// Forward ports from one machine to another #[clap(subcommand)] @@ -256,9 +267,6 @@ struct WormholeCli { /// Enable logging to stdout, for debugging purposes #[clap(short = 'v', long = "verbose", alias = "log", global = true)] log: bool, - /// Enable dilation - #[clap(long = "with-dilation", alias = "with-dilation", global = true)] - with_dilation: bool, #[clap(subcommand)] command: WormholeCommand, } @@ -327,6 +335,7 @@ async fn main() -> eyre::Result<()> { file_name, file: file_path, }, + common_transfer: CommonTransferArgs { with_dilation: _ }, .. } => { let file_name = concat_file_name(&file_path, file_name.as_ref())?; @@ -341,7 +350,7 @@ async fn main() -> eyre::Result<()> { code, Some(code_length), true, - dilation::APP_CONFIG_SEND.with_dilation(app.with_dilation), + transfer::APP_CONFIG, Some(&sender_print_code), clipboard.as_mut(), )), @@ -379,7 +388,7 @@ async fn main() -> eyre::Result<()> { code, Some(code_length), true, - dilation::APP_CONFIG_SEND.with_dilation(app.with_dilation), + transfer::APP_CONFIG, Some(&sender_print_code), clipboard.as_mut(), )); @@ -415,17 +424,31 @@ async fn main() -> eyre::Result<()> { file_name, file_path, }, + common_transfer: CommonTransferArgs { with_dilation }, .. } => { + if with_dilation { + log::warn!("The dilation feature is still work in progress. Please remove the `--with-dilation` argument to avoid this."); + } + let transit_abilities = parse_transit_args(&common); let (wormhole, _code, relay_hints) = { + let app_config = dilated_transfer::APP_CONFIG.with_dilation(with_dilation); + let app_config = if with_dilation { + app_config.app_version(dilated_transfer::AppVersion::new(Some( + dilated_transfer::FileTransferV2Mode::Receive, + ))) + } else { + app_config + }; + let connect_fut = Box::pin(parse_and_connect( &mut term, common, code, None, false, - dilation::APP_CONFIG_RECEIVE.with_dilation(app.with_dilation), + app_config, None, clipboard.as_mut(), )); @@ -435,9 +458,9 @@ async fn main() -> eyre::Result<()> { } }; - if app.with_dilation && peer_allows_dilation(&wormhole.peer_version) { + if with_dilation && peer_allows_dilation(&wormhole.peer_version()) { log::debug!("dilate wormhole"); - let mut dilated_wormhole = dilate(wormhole)?; // need to pass transit relay URL + let mut dilated_wormhole = wormhole.dilate()?; // need to pass transit relay URL dilated_wormhole.run().await; } else { Box::pin(receive( @@ -498,7 +521,7 @@ async fn main() -> eyre::Result<()> { }) .collect::, _>>()?; loop { - let mut app_config = forwarding::APP_CONFIG.with_dilation(app.with_dilation); + let mut app_config = forwarding::APP_CONFIG; app_config.app_version.transit_abilities = parse_transit_args(&common); let connect_fut = Box::pin(parse_and_connect( &mut term, @@ -534,7 +557,7 @@ async fn main() -> eyre::Result<()> { }) => { // TODO make fancy log::warn!("This is an unstable feature. Make sure that your peer is running the exact same version of the program as you. Also, please report all bugs and crashes."); - let mut app_config = forwarding::APP_CONFIG.with_dilation(app.with_dilation); + let mut app_config = forwarding::APP_CONFIG; app_config.app_version.transit_abilities = parse_transit_args(&common); let (wormhole, _code, relay_hints) = parse_and_connect( &mut term, @@ -603,7 +626,7 @@ async fn main() -> eyre::Result<()> { Ok(()) } -fn peer_allows_dilation(version: &Value) -> bool { +fn peer_allows_dilation(_version: &serde_json::Value) -> bool { // TODO needs to be implemented true } @@ -877,9 +900,10 @@ async fn send_many( break; } - let wormhole = - Wormhole::connect(MailboxConnection::connect(transfer::APP_CONFIG, code.clone(), false).await?) - .await?; + let wormhole = Wormhole::connect( + MailboxConnection::connect(transfer::APP_CONFIG, code.clone(), false).await?, + ) + .await?; send_in_background( relay_hints.clone(), Arc::clone(&file_path), diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 0da19140..00000000 --- a/sonar-project.properties +++ /dev/null @@ -1 +0,0 @@ -sonar.projectKey=LeastAuthority_magic-wormhole.rs_AYfWiG9h4b06GJhIj4na diff --git a/src/core.rs b/src/core.rs index 18c9fa5b..f99d53df 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,18 +1,19 @@ -use std::borrow::Cow; +use std::{any::Any, borrow::Cow}; +use crate::core::protocol::{WormholeProtocol, WormholeProtocolDefault}; #[cfg(feature = "dilation")] use crate::dilation::DilatedWormhole; use log::*; -#[cfg(test)] -use mockall::automock; use serde; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value; use xsalsa20poly1305 as secretbox; use self::rendezvous::*; pub(self) use self::server_messages::EncryptedMessage; pub(super) mod key; +pub(crate) mod protocol; pub mod rendezvous; mod server_messages; #[cfg(test)] @@ -187,7 +188,7 @@ impl MailboxConnection { /// use magic_wormhole::{transfer::APP_CONFIG, Code, MailboxConnection, Nameplate}; /// let config = APP_CONFIG; /// let code = Code::new(&Nameplate::new("5"), "password"); - /// let mailbox_connection = MailboxConnection::connect(config, code, false).await?; + /// let mut mailbox_connection = MailboxConnection::connect(config, code, false).await?; /// # Ok(()) })} /// ``` pub async fn connect( @@ -229,12 +230,12 @@ impl MailboxConnection { /// async_std::task::block_on(async { /// use magic_wormhole::{transfer::APP_CONFIG, MailboxConnection, Mood}; /// let config = APP_CONFIG; - /// let mailbox_connection = MailboxConnection::create_with_password(config, "secret") + /// let mut mailbox_connection = MailboxConnection::create_with_password(config, "secret") /// .await?; /// mailbox_connection.shutdown(Mood::Happy).await?; /// # Ok(())})} /// ``` - pub async fn shutdown(self, mood: Mood) -> Result<(), WormholeError> { + pub async fn shutdown(&mut self, mood: Mood) -> Result<(), WormholeError> { self.server .shutdown(mood) .await @@ -244,36 +245,15 @@ impl MailboxConnection { #[derive(Debug)] pub struct Wormhole { - server: RendezvousServer, - phase: u64, - key: key::Key, - appid: AppID, - /** - * If you're paranoid, let both sides check that they calculated the same verifier. - * - * PAKE hardens a standard key exchange with a password ("password authenticated") in order - * to mitigate potential man in the middle attacks that would otherwise be possible. Since - * the passwords usually are not of hight entropy, there is a low-probability possible of - * an attacker guessing the password correctly, enabling them to MitM the connection. - * - * Not only is that probability low, but they also have only one try per connection and a failed - * attempts will be noticed by both sides. Nevertheless, comparing the verifier mitigates that - * attack vector. - */ - pub verifier: Box, - /** - * Our "app version" information that we sent. See the [`peer_version`] for more information. - */ - pub our_version: Box, - /** - * Protocol version information from the other side. - * This is bound by the [`AppID`]'s protocol and thus shall be handled on a higher level - * (e.g. by the file transfer API). - */ - pub peer_version: serde_json::Value, + protocol: Box, } impl Wormhole { + #[cfg(test)] + pub fn new(protocol: Box) -> Self { + Wormhole { protocol } + } + /** * Generate a code and connect to the rendezvous server. * @@ -360,6 +340,7 @@ impl Wormhole { /* Send versions message */ let mut versions = key::VersionsMessage::new(); versions.set_app_versions(serde_json::to_value(&config.app_version).unwrap()); + #[cfg(feature = "dilation")] if config.with_dilation { versions.enable_dilation(); } @@ -385,13 +366,12 @@ impl Wormhole { /* We are now fully initialized! Up and running! :tada: */ Ok(Self { - server, - appid: config.id, - phase: 0, - key: key::Key::new(key.into()), - verifier: Box::new(key::derive_verifier(&key)), - our_version: Box::new(config.app_version), - peer_version, + protocol: Box::new(WormholeProtocolDefault::new( + server, + config, + key::Key::new(key.into()), + peer_version, + )), }) } @@ -400,24 +380,19 @@ impl Wormhole { todo!() } - /** Send an encrypted message to peer */ - pub async fn send(&mut self, plaintext: Vec) -> Result<(), WormholeError> { - self.send_with_phase(plaintext, Phase::numeric).await - } + /** + * create a dilated wormhole + */ + #[cfg(feature = "dilation")] + pub fn dilate(self) -> Result { + // XXX: create endpoints? + // get versions from the other side and check if they support dilation. + let can_they_dilate = &self.protocol.peer_version()["can-dilate"]; + if !can_they_dilate.is_null() && can_they_dilate[0] != "1" { + return Err(WormholeError::DilationVersion); + } - pub async fn send_with_phase( - &mut self, - plaintext: Vec, - phase_provider: PhaseProvider, - ) -> Result<(), WormholeError> { - let current_phase = phase_provider(self.phase); - self.phase += 1; - let data_key = key::derive_phase_key(self.server.side(), &self.key, ¤t_phase); - let (_nonce, encrypted) = key::encrypt_data(&data_key, &plaintext); - self.server - .send_peer_message(current_phase, encrypted) - .await?; - Ok(()) + Ok(DilatedWormhole::new(self, MySide::generate(8))) } /** @@ -442,28 +417,11 @@ impl Wormhole { message: &T, phase_provider: PhaseProvider, ) -> Result<(), WormholeError> { - self.send_with_phase(serde_json::to_vec(message).unwrap(), phase_provider) + self.protocol + .send_with_phase(serde_json::to_vec(message).unwrap(), phase_provider) .await } - /** Receive an encrypted message from peer */ - pub async fn receive(&mut self) -> Result, WormholeError> { - loop { - let peer_message = match self.server.next_peer_message().await? { - Some(peer_message) => peer_message, - None => continue, - }; - - // TODO maybe reorder incoming messages by phase numeral? - let decrypted_message = peer_message - .decrypt(&self.key) - .ok_or(WormholeError::Crypto)?; - - // Send to client - return Ok(decrypted_message); - } - } - /** * Receive an encrypted message from peer * @@ -471,24 +429,23 @@ impl Wormhole { * used by upper layer protocols. We distinguish between the different layers * on which a serialization error happened, hence the double `Result`. */ - pub async fn receive_json(&mut self) -> Result + pub async fn receive_json(&mut self) -> Result, WormholeError> where T: for<'a> serde::Deserialize<'a>, { - self.receive().await.and_then(|data: Vec| { + self.protocol.receive().await.map(|data: Vec| { serde_json::from_slice(&data).map_err(|e| { log::error!( "Received invalid data from peer: '{}'", String::from_utf8_lossy(&data) ); - WormholeError::ProtocolJson(e) + e }) }) } - pub async fn close(self) -> Result<(), WormholeError> { - log::debug!("Closing Wormhole…"); - self.server.shutdown(Mood::Happy).await.map_err(Into::into) + pub async fn close(&mut self) -> Result<(), WormholeError> { + self.protocol.close().await } /** @@ -496,7 +453,7 @@ impl Wormhole { * This determines the upper-layer protocol. Only wormholes with the same value can talk to each other. */ pub fn appid(&self) -> &AppID { - &self.appid + self.protocol.appid() } /** @@ -504,23 +461,16 @@ impl Wormhole { * Can be used to derive sub-keys for different purposes. */ pub fn key(&self) -> &key::Key { - &self.key + self.protocol.key() } -} -/** - * create a dilated wormhole - */ -#[cfg(feature = "dilation")] -pub fn dilate(wormhole: Wormhole) -> Result { - // XXX: create endpoints? - // get versions from the other side and check if they support dilation. - let can_they_dilate = &wormhole.peer_version["can-dilate"]; - if !can_they_dilate.is_null() && can_they_dilate[0] != "1" { - return Err(WormholeError::DilationVersion); + pub fn peer_version(&self) -> &Value { + self.protocol.peer_version() } - Ok(DilatedWormhole::new(wormhole, MySide::generate(8))) + pub fn our_version(&self) -> &Box { + &self.protocol.our_version() + } } // the serialized forms of these variants are part of the wire protocol, so @@ -547,6 +497,8 @@ pub enum Mood { Unwelcome, } +pub const APPID_RAW: &str = "lothar.com/wormhole/text-or-file-xfer"; + /** * Wormhole configuration corresponding to an upper layer protocol * @@ -559,14 +511,14 @@ pub enum Mood { * See [`crate::transfer::APP_CONFIG`], which entails */ #[derive(PartialEq, Eq, Clone, Debug)] -pub struct AppConfig { +pub struct AppConfig { pub id: AppID, pub rendezvous_url: Cow<'static, str>, pub app_version: V, pub with_dilation: bool, } -impl AppConfig { +impl AppConfig { pub fn id(mut self, id: AppID) -> Self { self.id = id; self @@ -581,9 +533,7 @@ impl AppConfig { self.with_dilation = with_dilation; self } -} -impl AppConfig { pub fn app_version(mut self, app_version: V) -> Self { self.app_version = app_version; self @@ -630,10 +580,10 @@ impl From for AppID { pub struct MySide(EitherSide); impl MySide { - pub fn generate(n: usize) -> MySide { + pub fn generate(length: usize) -> MySide { use rand::{rngs::OsRng, RngCore}; - let mut bytes = vec![0; n]; + let mut bytes = vec![0; length]; OsRng.fill_bytes(&mut bytes); MySide(EitherSide(hex::encode(bytes))) @@ -776,3 +726,15 @@ impl Code { Nameplate::new(self.0.splitn(2, '-').next().unwrap()) } } + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "kebab-case", tag = "type")] +pub enum Ability { + DirectTcpV1, + RelayV1, + RelayV2, + #[cfg(all())] + NoiseCryptoV1, + #[serde(other)] + Other, +} diff --git a/src/core/key.rs b/src/core/key.rs index a26586e6..2fd0140a 100644 --- a/src/core/key.rs +++ b/src/core/key.rs @@ -1,9 +1,8 @@ -use crate::{core::*, transit}; +use crate::core::*; use hkdf::Hkdf; use serde_derive::{Deserialize, Serialize}; use sha2::{digest::FixedOutput, Digest, Sha256}; use spake2::{Ed25519Group, Identity, Password, Spake2}; -use std::ptr::addr_of_mut; use xsalsa20poly1305 as secretbox; use xsalsa20poly1305::{ aead::{generic_array::GenericArray, Aead, AeadCore, NewAead}, @@ -106,10 +105,12 @@ pub fn make_pake(password: &str, appid: &AppID) -> (Spake2, Vec, //#[serde(default)] pub can_dilate: Option<[Cow<'static, str>; 1]>, //#[serde(default)] - pub dilation_abilities: Cow<'static, [transit::Ability; 2]>, + pub dilation_abilities: Option>, //#[serde(default)] #[serde(rename = "app_versions")] pub app_versions: serde_json::Value, @@ -120,11 +121,12 @@ impl VersionsMessage { pub fn new() -> Self { // Default::default() Self { + abilities: vec![], can_dilate: None, - dilation_abilities: std::borrow::Cow::Borrowed(&[ - transit::Ability::DirectTcpV1, - transit::Ability::RelayV1, - ]), + dilation_abilities: Some(std::borrow::Cow::Borrowed(&[ + Ability::DirectTcpV1, + Ability::RelayV1, + ])), app_versions: serde_json::Value::Null, } } @@ -150,7 +152,6 @@ pub fn build_version_msg( let phase = Phase::VERSION; let data_key = derive_phase_key(side, key, &phase); let plaintext = serde_json::to_vec(versions).unwrap(); - println!("versions message before encryption: {:?}", plaintext); let (_nonce, encrypted) = encrypt_data(&data_key, &plaintext); (phase, encrypted) } diff --git a/src/core/protocol.rs b/src/core/protocol.rs new file mode 100644 index 00000000..38c99b21 --- /dev/null +++ b/src/core/protocol.rs @@ -0,0 +1,155 @@ +use async_trait::async_trait; +use std::{any::Any, fmt::Debug}; + +#[cfg(test)] +use mockall::automock; + +use crate::{ + core::{ + key::{derive_phase_key, derive_verifier, encrypt_data}, + Phase, PhaseProvider, + }, + rendezvous::RendezvousServer, + AppConfig, AppID, Key, Mood, WormholeError, WormholeKey, +}; + +#[derive(Debug)] +pub struct WormholeProtocolDefault { + server: RendezvousServer, + phase: u64, + key: Key, + appid: AppID, + /** + * If you're paranoid, let both sides check that they calculated the same verifier. + * + * PAKE hardens a standard key exchange with a password ("password authenticated") in order + * to mitigate potential man in the middle attacks that would otherwise be possible. Since + * the passwords usually are not of hight entropy, there is a low-probability possible of + * an attacker guessing the password correctly, enabling them to MitM the connection. + * + * Not only is that probability low, but they also have only one try per connection and a failed + * attempts will be noticed by both sides. Nevertheless, comparing the verifier mitigates that + * attack vector. + */ + pub verifier: Box, + /** + * Our "app version" information that we sent. See the [`peer_version`] for more information. + */ + pub our_version: Box, + /** + * Protocol version information from the other side. + * This is bound by the [`AppID`]'s protocol and thus shall be handled on a higher level + * (e.g. by the file transfer API). + */ + pub peer_version: serde_json::Value, +} + +impl WormholeProtocolDefault { + pub fn new( + server: RendezvousServer, + config: AppConfig, + key: Key, + peer_version: serde_json::Value, + ) -> Self + where + T: serde::Serialize + Send + Sync + Sized + 'static, + { + let verifier = Box::new(derive_verifier(&key)); + Self { + server, + appid: config.id, + phase: 0, + key, + verifier, + our_version: Box::new(config.app_version), + peer_version, + } + } +} + +#[async_trait] +impl WormholeProtocol for WormholeProtocolDefault { + /** Send an encrypted message to peer */ + async fn send(&mut self, plaintext: Vec) -> Result<(), WormholeError> { + self.send_with_phase(plaintext, Phase::numeric).await + } + + async fn send_with_phase( + &mut self, + plaintext: Vec, + phase_provider: PhaseProvider, + ) -> Result<(), WormholeError> { + let current_phase = phase_provider(self.phase); + self.phase += 1; + let data_key = derive_phase_key(self.server.side(), &self.key, ¤t_phase); + let (_nonce, encrypted) = encrypt_data(&data_key, &plaintext); + self.server + .send_peer_message(current_phase, encrypted) + .await?; + Ok(()) + } + + /** Receive an encrypted message from peer */ + async fn receive(&mut self) -> Result, WormholeError> { + loop { + let peer_message = match self.server.next_peer_message().await? { + Some(peer_message) => peer_message, + None => continue, + }; + + // TODO maybe reorder incoming messages by phase numeral? + let decrypted_message = peer_message + .decrypt(&self.key) + .ok_or(WormholeError::Crypto)?; + + // Send to client + return Ok(decrypted_message); + } + } + + async fn close(&mut self) -> Result<(), WormholeError> { + log::debug!("Closing Wormhole…"); + self.server.shutdown(Mood::Happy).await.map_err(Into::into) + } + + /** + * The `AppID` this wormhole is bound to. + * This determines the upper-layer protocol. Only wormholes with the same value can talk to each other. + */ + fn appid(&self) -> &AppID { + &self.appid + } + + /** + * The symmetric encryption key used by this connection. + * Can be used to derive sub-keys for different purposes. + */ + fn key(&self) -> &Key { + &self.key + } + + fn peer_version(&self) -> &serde_json::Value { + &self.peer_version + } + + fn our_version(&self) -> &Box { + &self.our_version + } +} + +#[cfg_attr(test, automock)] +#[async_trait] +pub trait WormholeProtocol: Debug + Send + Sync { + async fn send(&mut self, plaintext: Vec) -> Result<(), WormholeError>; + async fn send_with_phase( + &mut self, + plaintext: Vec, + phase_provider: PhaseProvider, + ) -> Result<(), WormholeError>; + async fn receive(&mut self) -> Result, WormholeError>; + async fn close(&mut self) -> Result<(), WormholeError>; + fn appid(&self) -> &AppID; + fn key(&self) -> &Key; + fn peer_version(&self) -> &serde_json::Value; + fn our_version(&self) -> &Box; +} diff --git a/src/core/rendezvous.rs b/src/core/rendezvous.rs index 09181af1..a1acc183 100644 --- a/src/core/rendezvous.rs +++ b/src/core/rendezvous.rs @@ -588,28 +588,31 @@ impl RendezvousServer { Ok(()) } - pub async fn shutdown(mut self, mood: Mood) -> Result<(), RendezvousError> { + pub async fn shutdown(&mut self, mood: Mood) -> Result<(), RendezvousError> { if let Some(MailboxMachine { - nameplate, - mailbox, - mut queue, + ref nameplate, + ref mailbox, + ref mut queue, .. }) = self.state { if let Some(nameplate) = nameplate { self.connection - .send_message(&OutboundMessage::release(nameplate), Some(&mut queue)) + .send_message(&OutboundMessage::release(nameplate.to_owned()), Some(queue)) .await?; - match self.connection.receive_reply(Some(&mut queue)).await? { + match self.connection.receive_reply(Some(queue)).await? { RendezvousReply::Released => (), other => return Err(RendezvousError::invalid_message("released", other)), }; } self.connection - .send_message(&OutboundMessage::close(mailbox, mood), Some(&mut queue)) + .send_message( + &OutboundMessage::close(mailbox.to_owned(), mood), + Some(queue), + ) .await?; - match self.connection.receive_reply(Some(&mut queue)).await? { + match self.connection.receive_reply(Some(queue)).await? { RendezvousReply::Closed => (), other => return Err(RendezvousError::invalid_message("closed", other)), }; diff --git a/src/core/server_messages.rs b/src/core/server_messages.rs index d0fa5264..cd6a69ee 100644 --- a/src/core/server_messages.rs +++ b/src/core/server_messages.rs @@ -253,7 +253,6 @@ pub enum InboundMessage { #[cfg(test)] mod test { use super::*; - use serde_json::{from_str, json, Value}; #[test] fn test_bind() { @@ -262,10 +261,10 @@ mod test { MySide::unchecked_from_string(String::from("side1")), ); let s = serde_json::to_string(&m1).unwrap(); - let m2: Value = from_str(&s).unwrap(); + let m2: serde_json::Value = serde_json::from_str(&s).unwrap(); assert_eq!( m2, - json!({"type": "bind", "appid": "appid", + serde_json::json!({"type": "bind", "appid": "appid", "side": "side1"}) ); } @@ -274,50 +273,59 @@ mod test { fn test_list() { let m1 = OutboundMessage::List; let s = serde_json::to_string(&m1).unwrap(); - let m2: Value = from_str(&s).unwrap(); - assert_eq!(m2, json!({"type": "list"})); + let m2: serde_json::Value = serde_json::from_str(&s).unwrap(); + assert_eq!(m2, serde_json::json!({"type": "list"})); } #[test] fn test_allocate() { let m1 = OutboundMessage::Allocate; let s = serde_json::to_string(&m1).unwrap(); - let m2: Value = from_str(&s).unwrap(); - assert_eq!(m2, json!({"type": "allocate"})); + let m2: serde_json::Value = serde_json::from_str(&s).unwrap(); + assert_eq!(m2, serde_json::json!({"type": "allocate"})); } #[test] fn test_claim() { let m1 = OutboundMessage::claim("nameplate1"); let s = serde_json::to_string(&m1).unwrap(); - let m2: Value = from_str(&s).unwrap(); - assert_eq!(m2, json!({"type": "claim", "nameplate": "nameplate1"})); + let m2: serde_json::Value = serde_json::from_str(&s).unwrap(); + assert_eq!( + m2, + serde_json::json!({"type": "claim", "nameplate": "nameplate1"}) + ); } #[test] fn test_release() { let m1 = OutboundMessage::release("nameplate1"); let s = serde_json::to_string(&m1).unwrap(); - let m2: Value = from_str(&s).unwrap(); - assert_eq!(m2, json!({"type": "release", "nameplate": "nameplate1"})); + let m2: serde_json::Value = serde_json::from_str(&s).unwrap(); + assert_eq!( + m2, + serde_json::json!({"type": "release", "nameplate": "nameplate1"}) + ); } #[test] fn test_open() { let m1 = OutboundMessage::open(Mailbox(String::from("mailbox1"))); let s = serde_json::to_string(&m1).unwrap(); - let m2: Value = from_str(&s).unwrap(); - assert_eq!(m2, json!({"type": "open", "mailbox": "mailbox1"})); + let m2: serde_json::Value = serde_json::from_str(&s).unwrap(); + assert_eq!( + m2, + serde_json::json!({"type": "open", "mailbox": "mailbox1"}) + ); } #[test] fn test_add() { let m1 = OutboundMessage::add(Phase("phase1".into()), b"body".to_vec()); let s = serde_json::to_string(&m1).unwrap(); - let m2: Value = from_str(&s).unwrap(); + let m2: serde_json::Value = serde_json::from_str(&s).unwrap(); assert_eq!( m2, - json!({"type": "add", "phase": "phase1", + serde_json::json!({"type": "add", "phase": "phase1", "body": "626f6479"}) ); // body is hex-encoded } @@ -326,10 +334,10 @@ mod test { fn test_close() { let m1 = OutboundMessage::close(Mailbox(String::from("mailbox1")), Mood::Happy); let s = serde_json::to_string(&m1).unwrap(); - let m2: Value = from_str(&s).unwrap(); + let m2: serde_json::Value = serde_json::from_str(&s).unwrap(); assert_eq!( m2, - json!({"type": "close", "mailbox": "mailbox1", + serde_json::json!({"type": "close", "mailbox": "mailbox1", "mood": "happy"}) ); } @@ -338,10 +346,10 @@ mod test { fn test_close_errory() { let m1 = OutboundMessage::close(Mailbox(String::from("mailbox1")), Mood::Errory); let s = serde_json::to_string(&m1).unwrap(); - let m2: Value = from_str(&s).unwrap(); + let m2: serde_json::Value = serde_json::from_str(&s).unwrap(); assert_eq!( m2, - json!({"type": "close", "mailbox": "mailbox1", + serde_json::json!({"type": "close", "mailbox": "mailbox1", "mood": "errory"}) ); } @@ -350,10 +358,10 @@ mod test { fn test_close_scared() { let m1 = OutboundMessage::close(Mailbox(String::from("mailbox1")), Mood::Scared); let s = serde_json::to_string(&m1).unwrap(); - let m2: Value = from_str(&s).unwrap(); + let m2: serde_json::Value = serde_json::from_str(&s).unwrap(); assert_eq!( m2, - json!({"type": "close", "mailbox": "mailbox1", + serde_json::json!({"type": "close", "mailbox": "mailbox1", "mood": "scary"}) ); } @@ -422,9 +430,12 @@ mod test { bits: 6, resource: "resource-string".into(), }), - other: [("dark-ritual".to_string(), json!({ "hocrux": true }))] - .into_iter() - .collect() + other: [( + "dark-ritual".to_string(), + serde_json::json!({ "hocrux": true }) + )] + .into_iter() + .collect() }), current_cli_version: None, error: None, diff --git a/src/core/test.rs b/src/core/test.rs index 7db6be58..6a2c01d8 100644 --- a/src/core/test.rs +++ b/src/core/test.rs @@ -462,7 +462,7 @@ pub async fn test_send_many() -> eyre::Result<()> { .await?, ) .await?; - log::info!("Got key: {}", &wormhole.key); + log::info!("Got key: {}", &wormhole.key()); let req = crate::transfer::request_file( wormhole, default_relay_hints(), diff --git a/src/core/wordlist.rs b/src/core/wordlist.rs index 01cc1f42..34bf5cb0 100644 --- a/src/core/wordlist.rs +++ b/src/core/wordlist.rs @@ -1,5 +1,4 @@ use rand::{rngs::OsRng, seq::SliceRandom}; -use serde_json::{self, Value}; use std::fmt; #[derive(PartialEq)] @@ -70,7 +69,8 @@ impl Wordlist { } fn load_pgpwords() -> Vec> { - let raw_words_value: Value = serde_json::from_str(include_str!("pgpwords.json")).unwrap(); + let raw_words_value: serde_json::Value = + serde_json::from_str(include_str!("pgpwords.json")).unwrap(); let raw_words = raw_words_value.as_object().unwrap(); let mut even_words: Vec = Vec::with_capacity(256); even_words.resize(256, String::from("")); diff --git a/src/dilated_transfer/mod.rs b/src/dilated_transfer/mod.rs new file mode 100644 index 00000000..7bb54d66 --- /dev/null +++ b/src/dilated_transfer/mod.rs @@ -0,0 +1,77 @@ +use crate::{core::APPID_RAW, AppID}; +use std::borrow::Cow; + +pub const APP_CONFIG: crate::AppConfig = crate::AppConfig:: { + id: AppID(Cow::Borrowed(APPID_RAW)), + rendezvous_url: Cow::Borrowed(crate::rendezvous::DEFAULT_RENDEZVOUS_SERVER), + app_version: AppVersion::new(Some(FileTransferV2Mode::Send)), + with_dilation: false, +}; + +#[derive(Clone, serde_derive::Serialize, serde_derive::Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(rename = "transfer")] +pub enum FileTransferV2Mode { + Send, + Receive, + Connect, +} + +#[derive(Clone, serde_derive::Serialize, serde_derive::Deserialize)] +#[serde(rename_all = "kebab-case")] +struct DilatedTransfer { + mode: FileTransferV2Mode, +} + +#[derive(Clone, serde_derive::Serialize, serde_derive::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct AppVersion { + // #[serde(default)] + // abilities: Cow<'static, [Cow<'static, str>]>, + // #[serde(default)] + // transfer_v2: Option, + + // XXX: we don't want to send "can-dilate" key for non-dilated + // wormhole, would making this an Option help? i.e. when the value + // is a None, we don't serialize that into the json and do it only + // when it is a "Some" value? + // overall versions payload is of the form: + // b'{"can-dilate": ["1"], "dilation-abilities": [{"type": "direct-tcp-v1"}, {"type": "relay-v1"}], "app_versions": {"transfer": {"mode": "send", "features": {}}}}' + + //can_dilate: Option<[Cow<'static, str>; 1]>, + //dilation_abilities: Cow<'static, [Ability; 2]>, + #[serde(rename = "transfer")] + app_versions: Option, +} + +impl AppVersion { + pub const fn new(mode: Option) -> Self { + // let can_dilate: Option<[Cow<'static, str>; 1]> = if enable_dilation { + // Some([std::borrow::Cow::Borrowed("1")]) + // } else { + // None + // }; + + let option = match mode { + Some(mode) => Some(DilatedTransfer { mode }), + None => None, + }; + + Self { + // abilities: Cow::Borrowed([Cow::Borrowed("transfer-v1"), Cow::Borrowed("transfer-v2")]), + // transfer_v2: Some(AppVersionTransferV2Hint::new()) + // can_dilate: can_dilate, + // dilation_abilities: std::borrow::Cow::Borrowed(&[ + // Ability{ ty: std::borrow::Cow::Borrowed("direct-tcp-v1") }, + // Ability{ ty: std::borrow::Cow::Borrowed("relay-v1") }, + // ]), + app_versions: option, + } + } +} + +impl Default for AppVersion { + fn default() -> Self { + Self::new(Some(FileTransferV2Mode::Send)) + } +} diff --git a/src/dilation/events.rs b/src/dilation/events.rs index 8358466f..a465b97c 100644 --- a/src/dilation/events.rs +++ b/src/dilation/events.rs @@ -4,7 +4,7 @@ use serde_derive::Deserialize; use crate::{ core::TheirSide, dilation::api::{IOEvent, ManagerCommand}, - transit, + transit::Hints, }; use super::api::ProtocolCommand; @@ -30,9 +30,10 @@ impl From for Event { } // individual fsm events -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Display, Debug, Clone, PartialEq, Deserialize)] #[serde(tag = "type")] pub enum ManagerEvent { + #[serde(rename = "start")] Start, #[serde(rename = "please")] RxPlease { @@ -40,7 +41,7 @@ pub enum ManagerEvent { }, #[serde(rename = "connection-hints")] RxHints { - hints: transit::Hints, + hints: Hints, }, RxReconnect, RxReconnecting, @@ -50,35 +51,39 @@ pub enum ManagerEvent { Stop, } -impl std::fmt::Display for ManagerEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self { - ManagerEvent::Start => write!(f, "start"), - ManagerEvent::RxPlease { side } => write!(f, "please"), - ManagerEvent::RxHints { hints } => write!(f, "connection-hints"), - ManagerEvent::RxReconnect => write!(f, "reconnect"), - ManagerEvent::RxReconnecting => write!(f, "reconnecting"), - ManagerEvent::ConnectionMade => write!(f, "connection-made"), - ManagerEvent::ConnectionLostLeader => write!(f, "connection-lost-leader"), - ManagerEvent::ConnectionLostFollower => write!(f, "connection-lost-follower"), - ManagerEvent::Stop => write!(f, "stop"), - } - } -} - -#[test] -fn test_manager_event_deserialisation() { - let result: ManagerEvent = - serde_json::from_str(r#"{"type": "please", "side": "f91dcdaccc7cc336"}"#) - .expect("parse error"); - assert_eq!( - result, - ManagerEvent::RxPlease { - side: TheirSide::from("f91dcdaccc7cc336") - } - ); -} - // XXX: for Connector fsm events // ... // XXX + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_display_please_event() { + let event = ManagerEvent::RxPlease { + side: TheirSide::from("f91dcdaccc7cc336"), + }; + assert_eq!(format!("{}", event), "TheirSide(f91dcdaccc7cc336)"); + } + + #[test] + fn test_manager_event_deserialisation_start() { + let result: ManagerEvent = + serde_json::from_str(r#"{"type": "start"}"#).expect("parse error"); + assert_eq!(result, ManagerEvent::Start); + } + + #[test] + fn test_manager_event_deserialisation_rxplease() { + let result: ManagerEvent = + serde_json::from_str(r#"{"type": "please", "side": "f91dcdaccc7cc336"}"#) + .expect("parse error"); + assert_eq!( + result, + ManagerEvent::RxPlease { + side: TheirSide::from("f91dcdaccc7cc336") + } + ); + } +} diff --git a/src/dilation/manager.rs b/src/dilation/manager.rs index 7fdeae08..e3a5cbee 100644 --- a/src/dilation/manager.rs +++ b/src/dilation/manager.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use derive_more::Display; #[cfg(test)] use mockall::automock; @@ -9,10 +8,7 @@ use crate::{ WormholeError, }; -use super::{ - api::{IOEvent, ProtocolCommand}, - events::ManagerEvent, -}; +use super::{api::ProtocolCommand, events::ManagerEvent}; #[derive(Debug, PartialEq, Display)] pub enum Role { @@ -20,12 +16,6 @@ pub enum Role { Follower, } -pub struct ManagerMachine { - pub side: MySide, - pub role: Role, - pub state: Option, -} - #[derive(Debug, PartialEq, Clone, Copy, Display)] pub enum State { Waiting, @@ -39,10 +29,16 @@ pub enum State { Stopped, } +pub struct ManagerMachine { + pub side: MySide, + pub role: Role, + pub state: Option, +} + #[cfg_attr(test, automock)] impl ManagerMachine { pub fn new(side: MySide) -> Self { - let mut machine = ManagerMachine { + let machine = ManagerMachine { side, role: Role::Follower, state: Some(State::Wanting), @@ -54,23 +50,6 @@ impl ManagerMachine { self.state } - pub fn is_waiting(&self) -> bool { - self.state == Some(State::Waiting) - } - - pub fn process_io(&mut self, event: IOEvent) -> Vec { - // XXX: which Manager states process IO events? - let mut actions = Vec::::new(); - - // XXX: big match expression here - - actions - } - - pub fn get_current_state(&self) -> Option { - self.state - } - fn choose_role(&self, theirside: &TheirSide) -> Role { let myside: TheirSide = self.side.clone().into(); if myside > *theirside { @@ -130,7 +109,7 @@ impl ManagerMachine { Connecting }, ManagerEvent::Stop => Stopped, - ManagerEvent::RxHints { hints } => current_state, + ManagerEvent::RxHints { hints: _ } => current_state, _ => { panic! {"unexpected event {:?} for state {:?}", current_state, event} }, @@ -150,7 +129,7 @@ impl ManagerMachine { }, Connected => match event { ManagerEvent::RxReconnect => Abandoning, - ManagerEvent::RxHints { hints } => current_state, + ManagerEvent::RxHints { hints: _ } => current_state, ManagerEvent::ConnectionLostFollower => Lonely, ManagerEvent::ConnectionLostLeader => Flushing, ManagerEvent::Stop => Stopped, @@ -159,7 +138,7 @@ impl ManagerMachine { }, }, Abandoning => match event { - ManagerEvent::RxHints { hints } => current_state, + ManagerEvent::RxHints { hints: _ } => current_state, ManagerEvent::ConnectionLostFollower => Connecting, ManagerEvent::Stop => Stopped, _ => { @@ -169,7 +148,7 @@ impl ManagerMachine { Flushing => match event { ManagerEvent::RxReconnecting => Connecting, ManagerEvent::Stop => Stopped, - ManagerEvent::RxHints { hints } => current_state, + ManagerEvent::RxHints { hints: _ } => current_state, _ => { panic! {"unexpected event {:?} for state {:?}", current_state, event} }, @@ -177,13 +156,13 @@ impl ManagerMachine { Lonely => match event { ManagerEvent::RxReconnect => Connecting, ManagerEvent::Stop => Stopped, - ManagerEvent::RxHints { hints } => current_state, + ManagerEvent::RxHints { hints: _ } => current_state, _ => { panic! {"unexpected event {:?} for state {:?}", current_state, event} }, }, Stopping => match event { - ManagerEvent::RxHints { hints } => current_state, + ManagerEvent::RxHints { hints: _ } => current_state, ManagerEvent::ConnectionLostFollower => Stopped, ManagerEvent::ConnectionLostLeader => Stopped, _ => { @@ -199,7 +178,7 @@ impl ManagerMachine { }; match command_result { - Ok(result) => { + Ok(_result) => { self.state = Some(new_state); log::debug!( "processing event finished: state={}, command={}", @@ -249,7 +228,8 @@ mod test { ManagerMachine::new(MySide::unchecked_from_string("test123".to_string())); let side = MySide::generate(8); - assert_eq!(manager_fsm.get_current_state(), Some(State::Wanting)); + assert_eq!(manager_fsm.current_state(), Some(State::Wanting)); + assert_eq!(manager_fsm.is_done(), false); let mut handler = TestHandler::new(); @@ -262,7 +242,7 @@ mod test { &mut |cmd| handler.handle_command(cmd), ); - assert_eq!(manager_fsm.get_current_state(), Some(State::Connecting)); + assert_eq!(manager_fsm.current_state(), Some(State::Connecting)); assert_eq!( handler.command, Some(ManagerCommand::Protocol(ProtocolCommand::SendPlease { @@ -270,4 +250,21 @@ mod test { })) ) } + + #[test] + #[should_panic(expected = "Protocol error: foo")] + fn test_manager_machine_handle_error() { + let side = MySide::generate(8); + let mut manager_fsm = ManagerMachine { + side: side.clone(), + role: Role::Follower, + state: Some(State::Waiting), + }; + + assert_eq!(manager_fsm.current_state(), Some(State::Waiting)); + + manager_fsm.process(ManagerEvent::Start, &side, &mut |cmd| { + Err(WormholeError::Protocol("foo".into())) + }); + } } diff --git a/src/dilation.rs b/src/dilation/mod.rs similarity index 67% rename from src/dilation.rs rename to src/dilation/mod.rs index 29e7ef61..c96763e8 100644 --- a/src/dilation.rs +++ b/src/dilation/mod.rs @@ -1,15 +1,6 @@ -use std::{ - borrow::Cow, - cell::{RefCell, RefMut}, - rc::Rc, -}; +use std::{cell::RefCell, rc::Rc}; -use async_trait::async_trait; use futures::executor; -#[cfg(test)] -use mockall::{automock, mock, predicate::*}; -use mockall_double::double; -use serde_derive::{Deserialize, Serialize}; use crate::{ core::{MySide, Phase}, @@ -17,103 +8,24 @@ use crate::{ Wormhole, WormholeError, }; -#[double] -use crate::dilation::manager::ManagerMachine; +#[cfg(test)] +use crate::core::protocol::MockWormholeProtocol; -use super::AppID; +#[mockall_double::double] +use crate::dilation::manager::ManagerMachine; mod api; mod events; mod manager; -const APPID_RAW: &str = "lothar.com/wormhole/text-or-file-xfer"; - -// XXX define an dilation::APP_CONFIG -pub const APP_CONFIG_SEND: crate::AppConfig = crate::AppConfig:: { - id: AppID(Cow::Borrowed(APPID_RAW)), - rendezvous_url: Cow::Borrowed(crate::rendezvous::DEFAULT_RENDEZVOUS_SERVER), - app_version: AppVersion::new(FileTransferV2Mode::Send), - with_dilation: false, -}; - -pub const APP_CONFIG_RECEIVE: crate::AppConfig = crate::AppConfig:: { - id: AppID(Cow::Borrowed(APPID_RAW)), - rendezvous_url: Cow::Borrowed(crate::rendezvous::DEFAULT_RENDEZVOUS_SERVER), - app_version: AppVersion::new(FileTransferV2Mode::Receive), - with_dilation: false, -}; - -#[derive(Clone, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -#[serde(rename = "transfer")] -enum FileTransferV2Mode { - Send, - Receive, - Connect, -} - -#[derive(Clone, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -struct DilatedTransfer { - mode: FileTransferV2Mode, -} - -#[derive(Clone, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct AppVersion { - // #[serde(default)] - // abilities: Cow<'static, [Cow<'static, str>]>, - // #[serde(default)] - // transfer_v2: Option, - - // XXX: we don't want to send "can-dilate" key for non-dilated - // wormhole, would making this an Option help? i.e. when the value - // is a None, we don't serialize that into the json and do it only - // when it is a "Some" value? - // overall versions payload is of the form: - // b'{"can-dilate": ["1"], "dilation-abilities": [{"type": "direct-tcp-v1"}, {"type": "relay-v1"}], "app_versions": {"transfer": {"mode": "send", "features": {}}}}' - - //can_dilate: Option<[Cow<'static, str>; 1]>, - //dilation_abilities: Cow<'static, [Ability; 2]>, - #[serde(rename = "transfer")] - app_versions: DilatedTransfer, -} - -impl AppVersion { - const fn new(mode: FileTransferV2Mode) -> Self { - // let can_dilate: Option<[Cow<'static, str>; 1]> = if enable_dilation { - // Some([std::borrow::Cow::Borrowed("1")]) - // } else { - // None - // }; - - Self { - // abilities: Cow::Borrowed([Cow::Borrowed("transfer-v1"), Cow::Borrowed("transfer-v2")]), - // transfer_v2: Some(AppVersionTransferV2Hint::new()) - // can_dilate: can_dilate, - // dilation_abilities: std::borrow::Cow::Borrowed(&[ - // Ability{ ty: std::borrow::Cow::Borrowed("direct-tcp-v1") }, - // Ability{ ty: std::borrow::Cow::Borrowed("relay-v1") }, - // ]), - app_versions: DilatedTransfer { mode }, - } - } -} - -impl Default for AppVersion { - fn default() -> Self { - Self::new(FileTransferV2Mode::Send) - } -} - -#[double] +#[mockall_double::double] type WormholeConnection = WormholeConnectionDefault; pub struct WormholeConnectionDefault { wormhole: Rc>, } -#[cfg_attr(test, automock)] +#[cfg_attr(test, mockall::automock)] impl WormholeConnectionDefault { fn new(wormhole: Wormhole) -> Self { Self { @@ -126,7 +38,13 @@ impl WormholeConnectionDefault { T: for<'a> serde::Deserialize<'a> + 'static, { let message = self.wormhole.borrow_mut().receive_json().await; - message + match message { + Ok(result) => match result { + Ok(result) => Ok(result), + Err(error) => Err(WormholeError::ProtocolJson(error)), + }, + Err(error) => Err(error), + } } async fn send_json(&self, command: &ProtocolCommand) -> Result<(), WormholeError> { @@ -210,12 +128,120 @@ mod test { events::ManagerEvent, manager::{MockManagerMachine, State}, }, - rendezvous::RendezvousError, }; use std::sync::{Arc, Mutex}; use super::*; + use mockall::predicate::{always, eq}; + + #[async_std::test] + async fn test_wormhole_connection_send() { + let mut protocol = MockWormholeProtocol::default(); + let command = ProtocolCommand::SendPlease { + side: MySide::generate(2), + }; + + let serialized_bytes = serde_json::to_vec(&command).unwrap(); + + protocol + .expect_send_with_phase() + .withf(move |bytes, provider| { + bytes == &serialized_bytes && provider(0) == Phase::dilation(0) + }) + .return_once(|_, _| Ok(())); + + let connection = WormholeConnectionDefault::new(Wormhole::new(Box::new(protocol))); + + let result = connection.send_json(&command).await; + + assert!(result.is_ok()) + } + + #[async_std::test] + async fn test_wormhole_connection_send_error() { + let mut protocol = MockWormholeProtocol::default(); + let command = ProtocolCommand::SendPlease { + side: MySide::generate(2), + }; + + protocol + .expect_send_with_phase() + .return_once(|_, _| Err(WormholeError::Protocol(Box::from("foo")))); + + let connection = WormholeConnectionDefault::new(Wormhole::new(Box::new(protocol))); + + let result = connection.send_json(&command).await; + + assert!(result.is_err()) + } + + #[async_std::test] + async fn test_wormhole_connection_receive() { + let mut protocol = MockWormholeProtocol::default(); + + let serialized_bytes = r#"{"type": "start"}"#.as_bytes().to_vec(); + + protocol + .expect_receive() + .return_once(|| Ok(serialized_bytes)); + + let connection = WormholeConnectionDefault::new(Wormhole::new(Box::new(protocol))); + + let result = connection.receive_json::().await; + + assert!(result.is_ok()) + } + + #[async_std::test] + async fn test_wormhole_connection_receive_error() { + let mut protocol = MockWormholeProtocol::default(); + + protocol + .expect_receive() + .return_once(|| Err(WormholeError::Protocol(Box::from("foo")))); + + let connection = WormholeConnectionDefault::new(Wormhole::new(Box::new(protocol))); + + let result = connection.receive_json::().await; + + assert!(result.is_err()) + } + + #[async_std::test] + async fn test_wormhole_connection_receive_deserialization_error() { + let mut protocol = MockWormholeProtocol::default(); + + let serialized_bytes = r#"{"type": "foo"}"#.as_bytes().to_vec(); + + protocol + .expect_receive() + .return_once(|| Ok(serialized_bytes)); + + let connection = WormholeConnectionDefault::new(Wormhole::new(Box::new(protocol))); + + let result = connection.receive_json::().await; + + assert!(result.is_err()) + } + + #[async_std::test] + async fn test_dilated_wormhole_new() { + let protocol = MockWormholeProtocol::default(); + + let wc_ctx = MockWormholeConnectionDefault::new_context(); + wc_ctx + .expect() + .with(always()) + .return_once(move |_| WormholeConnection::default()); + + let mm_ctx = MockManagerMachine::new_context(); + mm_ctx + .expect() + .with(always()) + .return_once(move |_| ManagerMachine::default()); + } + #[async_std::test] async fn test_dilated_wormhole() { init_logger(); @@ -304,11 +330,11 @@ mod test { .times(2) .returning(move || events.pop().unwrap()); - let mut verify_events = Arc::new(Mutex::new(vec![ManagerEvent::Stop, ManagerEvent::Start])); + let verify_events = Arc::new(Mutex::new(vec![ManagerEvent::Stop, ManagerEvent::Start])); let verify_my_side = my_side.clone(); manager .expect_process() - .withf(move |event, side, handler| { + .withf(move |event, side, _| { *event == verify_events.lock().unwrap().pop().unwrap() && side == &verify_my_side }) .times(2) diff --git a/src/forwarding.rs b/src/forwarding.rs index 1391e841..8c055fd2 100644 --- a/src/forwarding.rs +++ b/src/forwarding.rs @@ -13,18 +13,21 @@ //! and received as they come in, no additional buffering is applied. (Under the assumption that those applications //! that need buffering already do it on their side, and those who don't, don't.) -use super::*; -use async_std::net::{TcpListener, TcpStream}; -use futures::{AsyncReadExt, AsyncWriteExt, Future, SinkExt, StreamExt, TryStreamExt}; -use serde::{Deserialize, Serialize}; use std::{ borrow::Cow, collections::{HashMap, HashSet}, rc::Rc, sync::Arc, }; + +use async_std::net::{TcpListener, TcpStream}; +use futures::{AsyncReadExt, AsyncWriteExt, Future, SinkExt, StreamExt, TryStreamExt}; +use serde::{Deserialize, Serialize}; + use transit::{TransitConnectError, TransitError}; +use super::*; + const APPID_RAW: &str = "piegames.de/wormhole/port-forwarding"; /// The App ID associated with this protocol. @@ -141,18 +144,18 @@ pub async fn serve( targets: Vec<(Option, u16)>, cancel: impl Future, ) -> Result<(), ForwardingError> { - let our_version: &AppVersion = wormhole - .our_version - .downcast_ref() - .expect("You may only use a Wormhole instance with the correct AppVersion type!"); - let peer_version: AppVersion = serde_json::from_value(wormhole.peer_version.clone())?; + let our_version = wormhole + .our_version() + .downcast_ref::() + .expect("You may only use a Wormhole instance with the correct AppVersion type!") + .to_owned(); + let peer_version: AppVersion = serde_json::from_value(wormhole.peer_version().to_owned())?; let connector = transit::init( - our_version.transit_abilities, - Some(peer_version.transit_abilities), + our_version.transit_abilities.clone(), + Some(peer_version.transit_abilities.clone()), relay_hints, ) .await?; - /* Send our transit hints */ wormhole .send_json(&PeerMessage::Transit { @@ -168,13 +171,13 @@ pub async fn serve( log::warn!("It seems like you are trying to forward a remote HTTP target ('{}'). Due to HTTP being host-aware this will very likely fail!", host); } (format!("{}:{}", host, port), (Some(host), port)) - }, + } None => (port.to_string(), (host, port)), }) .collect(); /* Receive their transit hints */ - let their_hints: transit::Hints = match wormhole.receive_json().await? { + let their_hints: transit::Hints = match wormhole.receive_json().await?? { PeerMessage::Transit { hints } => { log::debug!("Received transit message: {:?}", hints); hints @@ -525,10 +528,10 @@ pub async fn connect( custom_ports: &[u16], ) -> Result { let our_version: &AppVersion = wormhole - .our_version + .our_version() .downcast_ref() .expect("You may only use a Wormhole instance with the correct AppVersion type!"); - let peer_version: AppVersion = serde_json::from_value(wormhole.peer_version.clone())?; + let peer_version: AppVersion = serde_json::from_value(wormhole.peer_version().to_owned())?; let connector = transit::init( our_version.transit_abilities, Some(peer_version.transit_abilities), @@ -545,7 +548,7 @@ pub async fn connect( .await?; /* Receive their transit hints */ - let their_hints: transit::Hints = match wormhole.receive_json().await? { + let their_hints: transit::Hints = match wormhole.receive_json().await?? { PeerMessage::Transit { hints } => { log::debug!("Received transit message: {:?}", hints); hints diff --git a/src/lib.rs b/src/lib.rs index c385e5d5..3be0651c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,8 @@ mod util; mod core; #[cfg(feature = "dilation")] +pub mod dilated_transfer; +#[cfg(feature = "dilation")] pub mod dilation; #[cfg(feature = "forwarding")] pub mod forwarding; @@ -43,8 +45,5 @@ pub use crate::core::{ WormholeError, }; -#[cfg(feature = "dilation")] -pub use crate::core::dilate; - #[cfg(feature = "dilation")] pub use crate::dilation::DilatedWormhole; diff --git a/src/transfer.rs b/src/transfer.rs index c3140a11..d753f759 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -21,12 +21,12 @@ use std::{borrow::Cow, path::PathBuf}; use transit::{TransitConnectError, TransitConnector, TransitError}; mod messages; +use crate::core::APPID_RAW; use messages::*; + mod v1; mod v2; -const APPID_RAW: &str = "lothar.com/wormhole/text-or-file-xfer"; - /// The App ID associated with this protocol. pub const APPID: AppID = AppID(Cow::Borrowed(APPID_RAW)); @@ -37,7 +37,7 @@ pub const APPID: AppID = AppID(Cow::Borrowed(APPID_RAW)); pub const APP_CONFIG: crate::AppConfig = crate::AppConfig:: { id: AppID(Cow::Borrowed(APPID_RAW)), rendezvous_url: Cow::Borrowed(crate::rendezvous::DEFAULT_RENDEZVOUS_SERVER), - app_version: AppVersion::new(false), + app_version: AppVersion::new(), with_dilation: false, }; @@ -130,35 +130,15 @@ pub struct AppVersion { // abilities: Cow<'static, [Cow<'static, str>]>, // #[serde(default)] // transfer_v2: Option, - - // XXX: we don't want to send "can-dilate" key for non-dilated - // wormhole, would making this an Option help? i.e. when the value - // is a None, we don't serialize that into the json and do it only - // when it is a "Some" value? - // overall versions payload is of the form: - // b'{"can-dilate": ["1"], "dilation-abilities": [{"type": "direct-tcp-v1"}, {"type": "relay-v1"}], "app_versions": {"transfer": {"mode": "send", "features": {}}}}' - can_dilate: Option<[Cow<'static, str>; 1]>, - dilation_abilities: Cow<'static, [transit::Ability; 2]>, } // TODO check invariants during deserialization impl AppVersion { - const fn new(enable_dilation: bool) -> Self { - let can_dilate: Option<[Cow<'static, str>; 1]> = if enable_dilation { - Some([std::borrow::Cow::Borrowed("1")]) - } else { - None - }; - + const fn new() -> Self { Self { // abilities: Cow::Borrowed([Cow::Borrowed("transfer-v1"), Cow::Borrowed("transfer-v2")]), // transfer_v2: Some(AppVersionTransferV2Hint::new()) - can_dilate, - dilation_abilities: std::borrow::Cow::Borrowed(&[ - transit::Ability::DirectTcpV1, - transit::Ability::RelayV1, - ]), } } @@ -171,7 +151,7 @@ impl AppVersion { impl Default for AppVersion { fn default() -> Self { - Self::new(false) + Self::new() } } @@ -179,14 +159,14 @@ impl Default for AppVersion { // #[serde(rename_all = "kebab-case")] // pub struct AppVersionTransferV2Hint { // supported_formats: Vec>, -// transit_abilities: Vec, +// transit_abilities: Vec, // } // impl AppVersionTransferV2Hint { // const fn new() -> Self { // Self { // supported_formats: vec![Cow::Borrowed("tar.zst")], -// transit_abilities: transit::Ability::all_abilities(), +// transit_abilities: Ability::all_abilities(), // } // } // } @@ -296,7 +276,7 @@ where G: FnOnce(transit::TransitInfo), H: FnMut(u64, u64) + 'static, { - let _peer_version: AppVersion = serde_json::from_value(wormhole.peer_version.clone())?; + let _peer_version: AppVersion = serde_json::from_value(wormhole.peer_version().to_owned())?; // if peer_version.supports_v2() && false { // v2::send_file(wormhole, relay_url, file, file_name, file_size, progress_handler, peer_version).await // } else { @@ -380,7 +360,7 @@ pub async fn request_file( // receive transit message let (their_abilities, their_hints): (transit::Abilities, transit::Hints) = - match wormhole.receive_json().await? { + match wormhole.receive_json().await?? { PeerMessage::Transit(transit) => { debug!("received transit message: {:?}", transit); (transit.abilities_v1, transit.hints_v1) @@ -394,7 +374,7 @@ pub async fn request_file( }; // 3. receive file offer message from peer - let (filename, filesize) = match wormhole.receive_json().await? { + let (filename, filesize) = match wormhole.receive_json().await?? { PeerMessage::Offer(offer_type) => match offer_type { Offer::File { filename, filesize } => (filename, filesize), Offer::Directory { @@ -594,7 +574,7 @@ async fn handle_run_result( // and we should not only look for the next one but all have been received // and we should not interrupt a receive operation without making sure it leaves the connection // in a consistent state, otherwise the shutdown may cause protocol errors - if let Ok(Ok(PeerMessage::Error(e))) = util::timeout(SHUTDOWN_TIME / 3, wormhole.receive_json()).await { + if let Ok(Ok(Ok(PeerMessage::Error(e)))) = util::timeout(SHUTDOWN_TIME / 3, wormhole.receive_json()).await { error = TransferError::PeerError(e); } else { log::debug!("Failed to retrieve more specific error message from peer. Maybe it crashed?"); diff --git a/src/transfer/v1.rs b/src/transfer/v1.rs index fff9fdcd..15e31c17 100644 --- a/src/transfer/v1.rs +++ b/src/transfer/v1.rs @@ -42,7 +42,7 @@ where // Wait for their transit response let (their_abilities, their_hints): (transit::Abilities, transit::Hints) = - match wormhole.receive_json().await? { + match wormhole.receive_json().await?? { PeerMessage::Transit(transit) => { debug!("Received transit message: {:?}", transit); (transit.abilities_v1, transit.hints_v1) @@ -57,7 +57,7 @@ where { // Wait for file_ack - let fileack_msg = wormhole.receive_json().await?; + let fileack_msg = wormhole.receive_json().await??; debug!("Received file ack message: {:?}", fileack_msg); match fileack_msg { @@ -199,7 +199,7 @@ where // Wait for their transit response let (their_abilities, their_hints): (transit::Abilities, transit::Hints) = - match wormhole.receive_json().await? { + match wormhole.receive_json().await?? { PeerMessage::Transit(transit) => { debug!("received transit message: {:?}", transit); (transit.abilities_v1, transit.hints_v1) @@ -213,7 +213,7 @@ where }; // Wait for file_ack - match wormhole.receive_json().await? { + match wormhole.receive_json().await?? { PeerMessage::Answer(Answer::FileAck(msg)) => { ensure!(msg == "ok", TransferError::AckError); }, diff --git a/src/transit.rs b/src/transit.rs index d87fd443..f2c3f6cd 100644 --- a/src/transit.rs +++ b/src/transit.rs @@ -14,6 +14,7 @@ //! "leader" side and one "follower" side (formerly called "sender" and "receiver"). use crate::{util, Key, KeyPurpose}; +use derive_more::Display; use serde_derive::{Deserialize, Serialize}; #[cfg(not(target_family = "wasm"))] @@ -209,18 +210,6 @@ impl serde::Serialize for Abilities { } } -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "kebab-case", tag = "type")] -pub enum Ability { - DirectTcpV1, - RelayV1, - RelayV2, - #[cfg(all())] - NoiseCryptoV1, - #[serde(other)] - Other, -} - impl<'de> serde::Deserialize<'de> for Abilities { fn deserialize(de: D) -> Result where @@ -259,7 +248,8 @@ enum HintSerde { } /** Information about how to find a peer */ -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Display, Debug, Default, PartialEq)] +#[display(fmt = "Hints(direct: {:?}, relay: {:?})", "&direct_tcp", "&relay")] pub struct Hints { /** Hints for direct connection */ pub direct_tcp: HashSet, @@ -551,6 +541,7 @@ impl<'de> serde::Deserialize<'de> for RelayHint { } } +use crate::core::Ability; use std::convert::{TryFrom, TryInto}; impl TryFrom<&DirectHint> for IpAddr { From 3c288066d49504ba6221f49a07068ea2c3416b47 Mon Sep 17 00:00:00 2001 From: piegames Date: Sat, 11 Feb 2023 18:54:28 +0100 Subject: [PATCH 23/26] Transfer refactoring, start of transfer-v2 This moves us towards the unified API that will be used in the future for both v1 and v2 transfers. The latter already has bits of code, but it is mostly a prototype and currently disabled. Additionally, this commit gives great improvements to the cancellation handling; the improved code for that has been moved out into its own submodule. --- Cargo.lock | 55 ++ Cargo.toml | 6 +- cli/Cargo.toml | 1 + cli/src/main.rs | 313 ++++++--- src/core/server_messages.rs | 3 +- src/core/test.rs | 422 ++++++------- src/transfer.rs | 1195 ++++++++++++++++++++++------------- src/transfer/cancel.rs | 293 +++++++++ src/transfer/messages.rs | 252 -------- src/transfer/v1.rs | 773 ++++++++++++++++------ src/transfer/v2.rs | 695 +++++++++++++++++--- src/util.rs | 41 -- 12 files changed, 2674 insertions(+), 1375 deletions(-) create mode 100644 src/transfer/cancel.rs delete mode 100644 src/transfer/messages.rs diff --git a/Cargo.lock b/Cargo.lock index 9f8687fe..80824276 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,6 +403,9 @@ name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -1259,6 +1262,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +[[package]] +name = "jobserver" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -1364,12 +1376,14 @@ dependencies = [ "socket2", "spake2", "stun_codec", + "tar", "thiserror", "time", "url", "wasm-timer", "ws_stream_wasm", "xsalsa20poly1305", + "zstd", ] [[package]] @@ -2229,6 +2243,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -2871,6 +2896,7 @@ dependencies = [ "magic-wormhole", "number_prefix", "qr2term", + "rand", "serde", "serde_derive", "serde_json", @@ -2987,3 +3013,32 @@ dependencies = [ "syn", "synstructure", ] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.4+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0" +dependencies = [ + "cc", + "libc", +] diff --git a/Cargo.toml b/Cargo.toml index 0e987745..b345ab3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ noise-rust-crypto = { version = "0.5.0", optional = true } # Transfer dependencies rmp-serde = { version = "1.0.0", optional = true } +tar = { version = "0.4.33", optional = true } # Forwarding dependencies @@ -69,7 +70,8 @@ if-addrs = { version = "0.8.0", optional = true } # Transfer -async-tar = { version = "0.4.2", optional = true } +async-tar = { version = "0.4", optional = true } +zstd = { version = "0.11.1", optional = true } [target.'cfg(target_family = "wasm")'.dependencies] wasm-timer = "0.2.5" @@ -84,7 +86,7 @@ eyre = "0.6.5" [features] transit = ["socket2", "stun_codec", "if-addrs", "bytecodec", "async-trait", "noise-protocol", "noise-rust-crypto"] -transfer = ["transit", "async-tar", "rmp-serde"] +transfer = ["transit", "tar", "async-tar", "rmp-serde", "zstd"] forwarding = ["transit", "rmp-serde"] default = ["transit", "transfer"] all = ["default", "forwarding"] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 094f0c5a..92f4bc80 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,6 +11,7 @@ log = "0.4.13" url = { version = "2.2.2", features = ["serde"] } futures = "0.3.12" async-std = { version = "1.12.0", features = ["attributes", "unstable"] } +rand = "0.8.3" # CLI specific dependencies magic-wormhole = { path = "..", features = ["all"] } diff --git a/cli/src/main.rs b/cli/src/main.rs index e7d07b1a..ca7d2625 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,22 +1,16 @@ #![allow(clippy::too_many_arguments)] mod util; -use std::{ - ops::Deref, - time::{Duration, Instant}, -}; +use std::time::{Duration, Instant}; -use async_std::{fs::OpenOptions, sync::Arc}; +use async_std::sync::Arc; use clap::{Args, CommandFactory, Parser, Subcommand}; use cli_clipboard::{ClipboardContext, ClipboardProvider}; use color_eyre::{eyre, eyre::Context}; use console::{style, Term}; use futures::{future::Either, Future, FutureExt}; use indicatif::{MultiProgress, ProgressBar}; -use std::{ - io::Write, - path::{Path, PathBuf}, -}; +use std::{io::Write, path::PathBuf}; use magic_wormhole::{forwarding, transfer, transit, Wormhole}; @@ -62,10 +56,17 @@ fn install_ctrlc_handler( #[derive(Debug, Args)] struct CommonSenderArgs { /// Suggest a different name to the receiver to keep the file's actual name secret. + /// Not allowed when sending more than one file. #[clap(long = "rename", visible_alias = "name", value_name = "FILE_NAME")] - file_name: Option, - #[clap(index = 1, required = true, value_name = "FILENAME|DIRNAME", value_hint = clap::ValueHint::AnyPath)] - file: PathBuf, + file_name: Option, + #[clap( + index = 1, + required = true, + min_values = 1, + value_name = "FILENAME|DIRNAME", + value_hint = clap::ValueHint::AnyPath, + )] + files: Vec, } // send, send-many, serve @@ -82,9 +83,6 @@ struct CommonLeaderArgs { // receive #[derive(Debug, Args)] struct CommonReceiverArgs { - /// Rename the received file or folder, overriding the name suggested by the sender. - #[clap(long = "rename", visible_alias = "name", value_name = "FILE_NAME")] - file_name: Option, /// Store transferred file or folder in the specified directory. Defaults to $PWD. #[clap(long = "out-dir", value_name = "PATH", default_value = ".", value_hint = clap::ValueHint::DirPath)] file_path: PathBuf, @@ -173,7 +171,6 @@ enum WormholeCommand { mut_arg("help", |a| a.help("Print this help message")), )] Send { - /// The file or directory to send #[clap(flatten)] common: CommonArgs, #[clap(flatten)] @@ -289,43 +286,14 @@ async fn main() -> eyre::Result<()> { }) .ok(); - let concat_file_name = |file_path: &Path, file_name: Option<_>| { - // TODO this has gotten out of hand (it ugly) - // The correct solution would be to make `file_name` an Option everywhere and - // move the ".tar" part further down the line. - // The correct correct solution would be to have working file transfer instead - // of sending stupid archives. - file_name - .map(std::ffi::OsString::from) - .or_else(|| { - let mut name = file_path.file_name().map(std::ffi::OsString::from); - if file_path.is_dir() { - name = name.map(|mut name| { - name.push(".tar"); - name - }); - } - name - }) - .ok_or_else(|| { - eyre::format_err!("You can't send a file without a name. Maybe try --rename") - }) - }; - match app.command { WormholeCommand::Send { common, common_leader: CommonLeaderArgs { code, code_length }, - common_send: - CommonSenderArgs { - file_name, - file: file_path, - }, + common_send: CommonSenderArgs { file_name, files }, .. } => { - let file_name = concat_file_name(&file_path, file_name.as_ref())?; - - eyre::ensure!(file_path.exists(), "{} does not exist", file_path.display()); + let offer = make_send_offer(files, file_name).await?; let transit_abilities = parse_transit_args(&common); let (wormhole, _code, relay_hints) = match util::cancellable( @@ -350,19 +318,19 @@ async fn main() -> eyre::Result<()> { Box::pin(send( wormhole, relay_hints, - file_path.as_ref(), - &file_name, + offer, transit_abilities, ctrl_c.clone(), )) .await?; }, + #[allow(unused_variables)] WormholeCommand::SendMany { tries, timeout, common, common_leader: CommonLeaderArgs { code, code_length }, - common_send: CommonSenderArgs { file_name, file }, + common_send: CommonSenderArgs { file_name, files }, .. } => { let transit_abilities = parse_transit_args(&common); @@ -384,13 +352,11 @@ async fn main() -> eyre::Result<()> { }; let timeout = Duration::from_secs(timeout * 60); - let file_name = concat_file_name(&file, file_name.as_ref())?; - Box::pin(send_many( relay_hints, &code, - file.as_ref(), - &file_name, + files, + file_name, tries, timeout, wormhole, @@ -404,11 +370,7 @@ async fn main() -> eyre::Result<()> { noconfirm, common, common_follower: CommonFollowerArgs { code }, - common_receiver: - CommonReceiverArgs { - file_name, - file_path, - }, + common_receiver: CommonReceiverArgs { file_path }, .. } => { let transit_abilities = parse_transit_args(&common); @@ -432,8 +394,7 @@ async fn main() -> eyre::Result<()> { Box::pin(receive( wormhole, relay_hints, - file_path.as_os_str(), - file_name.map(std::ffi::OsString::from).as_deref(), + &file_path, noconfirm, transit_abilities, ctrl_c, @@ -688,6 +649,55 @@ async fn parse_and_connect( eyre::Result::<_>::Ok((wormhole, code, relay_hints)) } +async fn make_send_offer( + mut files: Vec, + file_name: Option, +) -> eyre::Result { + for file in &files { + eyre::ensure!( + async_std::path::Path::new(&file).exists().await, + "{} does not exist", + file.display() + ); + } + log::trace!("Making send offer in {files:?}, with name {file_name:?}"); + + match (files.len(), file_name) { + (0, _) => unreachable!("Already checked by CLI parser"), + (1, Some(file_name)) => { + let file = files.remove(0); + Ok(transfer::OfferSend::new_file_or_folder(file_name, file).await?) + }, + (1, None) => { + let file = files.remove(0); + let file_name = file + .file_name() + .ok_or_else(|| { + eyre::format_err!("You can't send a file without a name. Maybe try --rename") + })? + .to_str() + .ok_or_else(|| eyre::format_err!("File path must be a valid UTF-8 string"))? + .to_owned(); + Ok(transfer::OfferSend::new_file_or_folder(file_name, file).await?) + }, + (_, Some(_)) => Err(eyre::format_err!( + "Can't customize file name when sending multiple files" + )), + (_, None) => { + let mut names = std::collections::BTreeMap::new(); + for path in &files { + eyre::ensure!(path.file_name().is_some(), "'{}' has no name. You need to send it separately and use the --rename flag, or rename it on the file system", path.display()); + if let Some(old) = names.insert(path.file_name(), path) { + eyre::bail!( + "'{}' and '{}' have the same file name. Rename one of them on disk, or send them in separate transfers", old.display(), path.display(), + ); + } + } + Ok(transfer::OfferSend::new_paths(files).await?) + }, + } +} + fn create_progress_bar(file_size: u64) -> ProgressBar { use indicatif::ProgressStyle; @@ -776,19 +786,17 @@ fn server_print_code( async fn send( wormhole: Wormhole, relay_hints: Vec, - file_path: &std::ffi::OsStr, - file_name: &std::ffi::OsStr, + offer: transfer::OfferSend, transit_abilities: transit::Abilities, ctrl_c: impl Fn() -> futures::future::BoxFuture<'static, ()>, ) -> eyre::Result<()> { let pb = create_progress_bar(0); let pb2 = pb.clone(); - transfer::send_file_or_folder( + transfer::send( wormhole, relay_hints, - file_path, - file_name, transit_abilities, + offer, &transit::log_transit_connection, move |sent, total| { if sent == 0 { @@ -809,8 +817,8 @@ async fn send( async fn send_many( relay_hints: Vec, code: &magic_wormhole::Code, - file_path: &std::ffi::OsStr, - file_name: &std::ffi::OsStr, + files: Vec, + file_name: Option, max_tries: u64, timeout: Duration, wormhole: Wormhole, @@ -825,19 +833,12 @@ async fn send_many( * for us at the moment, so we'll have to do without for now. */ let mp = MultiProgress::new(); - - let file_path = Arc::new(file_path.to_owned()); - let file_name = Arc::new(file_name.to_owned()); - // TODO go back to reference counting again - //let url = Arc::new(relay_server); - let time = Instant::now(); /* Special-case the first send with reusing the existing connection */ send_in_background( relay_hints.clone(), - Arc::clone(&file_path), - Arc::clone(&file_name), + make_send_offer(files.clone(), file_name.clone()).await?, wormhole, term.clone(), &mp, @@ -863,8 +864,7 @@ async fn send_many( magic_wormhole::Wormhole::connect_with_code(transfer::APP_CONFIG, code.clone()).await?; send_in_background( relay_hints.clone(), - Arc::clone(&file_path), - Arc::clone(&file_name), + make_send_offer(files.clone(), file_name.clone()).await?, wormhole, term.clone(), &mp, @@ -876,8 +876,7 @@ async fn send_many( async fn send_in_background( relay_hints: Vec, - file_name: Arc, - file_path: Arc, + offer: transfer::OfferSend, wormhole: Wormhole, mut term: Term, mp: &MultiProgress, @@ -890,12 +889,11 @@ async fn send_many( async_std::task::spawn(async move { let pb2 = pb.clone(); let result = async move { - transfer::send_file_or_folder( + transfer::send( wormhole, relay_hints, - file_path.deref(), - file_name.deref(), transit_abilities, + offer, &transit::log_transit_connection, move |sent, total| { if sent == 0 { @@ -930,20 +928,33 @@ async fn send_many( async fn receive( wormhole: Wormhole, relay_hints: Vec, - target_dir: &std::ffi::OsStr, - file_name: Option<&std::ffi::OsStr>, + target_dir: &std::path::Path, noconfirm: bool, transit_abilities: transit::Abilities, ctrl_c: impl Fn() -> futures::future::BoxFuture<'static, ()>, ) -> eyre::Result<()> { - let req = transfer::request_file(wormhole, relay_hints, transit_abilities, ctrl_c()) + let req = transfer::request(wormhole, relay_hints, transit_abilities, ctrl_c()) .await .context("Could not get an offer")?; /* If None, the task got cancelled */ - let req = match req { - Some(req) => req, - None => return Ok(()), - }; + match req { + Some(transfer::ReceiveRequest::V1(req)) => { + receive_inner_v1(req, target_dir, noconfirm, ctrl_c).await + }, + Some(transfer::ReceiveRequest::V2(req)) => { + receive_inner_v2(req, target_dir, noconfirm, ctrl_c).await + }, + None => Ok(()), + } +} + +async fn receive_inner_v1( + req: transfer::ReceiveRequestV1, + target_dir: &std::path::Path, + noconfirm: bool, + ctrl_c: impl Fn() -> futures::future::BoxFuture<'static, ()>, +) -> eyre::Result<()> { + use async_std::fs::OpenOptions; /* * Control flow is a bit tricky here: @@ -958,7 +969,7 @@ async fn receive( || util::ask_user( format!( "Receive file '{}' ({})?", - req.filename.display(), + req.filename, match NumberPrefix::binary(req.filesize as f64) { NumberPrefix::Standalone(bytes) => format!("{} bytes", bytes), NumberPrefix::Prefixed(prefix, n) => @@ -972,17 +983,11 @@ async fn receive( return req.reject().await.context("Could not reject offer"); } - let file_name = file_name - .or_else(|| req.filename.file_name()) - .ok_or_else(|| eyre::format_err!("The sender did not specify a valid file name, and neither did you. Try using --rename."))?; - let file_path = Path::new(target_dir).join(file_name); + // TODO validate untrusted input here + let file_path = std::path::Path::new(target_dir).join(&req.filename); let pb = create_progress_bar(req.filesize); - let on_progress = move |received, _total| { - pb.set_position(received); - }; - /* Then, accept if the file exists */ if !file_path.exists() || noconfirm { let mut file = OpenOptions::new() @@ -994,8 +999,10 @@ async fn receive( return req .accept( &transit::log_transit_connection, - on_progress, &mut file, + move |received, _total| { + pb.set_position(received); + }, ctrl_c(), ) .await @@ -1021,14 +1028,118 @@ async fn receive( Ok(req .accept( &transit::log_transit_connection, - on_progress, &mut file, + move |received, _total| { + pb.set_position(received); + }, ctrl_c(), ) .await .context("Receive process failed")?) } +async fn receive_inner_v2( + req: transfer::ReceiveRequestV2, + target_dir: &std::path::Path, + noconfirm: bool, + ctrl_c: impl Fn() -> futures::future::BoxFuture<'static, ()>, +) -> eyre::Result<()> { + let offer = req.offer(); + let file_size = offer.total_size(); + let offer_name = offer.offer_name(); + + use number_prefix::NumberPrefix; + if !(noconfirm + || util::ask_user( + format!( + "Receive {} ({})?", + offer_name, + match NumberPrefix::binary(file_size as f64) { + NumberPrefix::Standalone(bytes) => format!("{} bytes", bytes), + NumberPrefix::Prefixed(prefix, n) => + format!("{:.1} {}B in size", n, prefix.symbol()), + }, + ), + true, + ) + .await) + { + return req.reject().await.context("Could not reject offer"); + } + + let pb = create_progress_bar(file_size); + + let on_progress = move |received, _total| { + pb.set_position(received); + }; + + /* Create a temporary directory for receiving */ + use rand::Rng; + let tmp_dir = target_dir.join(&format!( + "wormhole-tmp-{:06}", + rand::thread_rng().gen_range(0..1_000_000) + )); + async_std::fs::create_dir_all(&tmp_dir) + .await + .context("Failed to create temporary directory for receiving")?; + + /* Prepare the receive by creating all directories */ + offer.create_directories(&tmp_dir).await?; + + /* Accept the offer and receive it */ + let answer = offer.accept_all(&tmp_dir); + req.accept( + &transit::log_transit_connection, + answer, + on_progress, + ctrl_c(), + ) + .await + .context("Receive process failed")?; + + /* Put in all the symlinks last, this greatly reduces the attack surface */ + offer.create_symlinks(&tmp_dir).await?; + + /* TODO walk the output directory and delete things we did not accept; this will be important for resumption */ + + /* Move the received files to their target location */ + use futures::TryStreamExt; + async_std::fs::read_dir(&tmp_dir) + .await? + .map_err(Into::into) + .and_then(|file| { + let tmp_dir = tmp_dir.clone(); + async move { + let path = file.path(); + let name = path.file_name().expect("Internal error: this should never happen"); + let target_path = target_dir.join(name); + + /* This suffers some TOCTTOU, sorry about that: https://internals.rust-lang.org/t/rename-file-without-overriding-existing-target/17637 */ + if async_std::path::Path::new(&target_path).exists().await { + eyre::bail!( + "Target destination {} exists, you can manually extract the file from {}", + target_path.display(), + tmp_dir.display(), + ); + } else { + async_std::fs::rename(&path, &target_path).await?; + } + Ok(()) + }}) + .try_collect::<()>() + .await?; + + /* Delete the temporary directory */ + async_std::fs::remove_dir_all(&tmp_dir) + .await + .context(format!( + "Failed to delete {}, please do it manually", + tmp_dir.display() + ))?; + + Ok(()) +} + #[cfg(test)] mod test { use super::*; diff --git a/src/core/server_messages.rs b/src/core/server_messages.rs index d0fa5264..8ad353f8 100644 --- a/src/core/server_messages.rs +++ b/src/core/server_messages.rs @@ -244,7 +244,8 @@ pub enum InboundMessage { #[display(fmt = "Error {{ error: {:?}, .. }}", error)] Error { error: String, - orig: Box, + /// A copy of the original message that caused the error. + orig: Box, }, #[serde(other)] Unknown, diff --git a/src/core/test.rs b/src/core/test.rs index b060a3dc..5d4860b3 100644 --- a/src/core/test.rs +++ b/src/core/test.rs @@ -6,7 +6,7 @@ use crate::{self as magic_wormhole, AppConfig, AppID, Code, Wormhole}; use crate::{transfer, transit}; pub const TEST_APPID: AppID = AppID(std::borrow::Cow::Borrowed( - "lothar.com/wormhole/rusty-wormhole-test", + "piegames.de/wormhole/rusty-wormhole-test", )); pub const APP_CONFIG: AppConfig<()> = AppConfig::<()> { @@ -35,227 +35,169 @@ fn default_relay_hints() -> Vec { ] } -/** Send a file using the Rust implementation. This does not guarantee compatibility with Python! ;) */ -#[cfg(feature = "transfer")] -#[async_std::test] -pub async fn test_file_rust2rust() -> eyre::Result<()> { - init_logger(); - - let (code_tx, code_rx) = futures::channel::oneshot::channel(); - - let sender_task = async_std::task::Builder::new() - .name("sender".to_owned()) - .spawn(async { - let (welcome, connector) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; - if let Some(welcome) = &welcome.welcome { - log::info!("Got welcome: {}", welcome); - } - log::info!("This wormhole's code is: {}", &welcome.code); - code_tx.send(welcome.code).unwrap(); - let wormhole = connector.await?; - eyre::Result::<_>::Ok( - transfer::send_file( - wormhole, - default_relay_hints(), - &mut async_std::fs::File::open("tests/example-file.bin").await?, - "example-file.bin", - std::fs::metadata("tests/example-file.bin").unwrap().len(), - magic_wormhole::transit::Abilities::ALL_ABILITIES, - &transit::log_transit_connection, - |_sent, _total| {}, - futures::future::pending(), - ) - .await?, - ) - })?; - let receiver_task = async_std::task::Builder::new() - .name("receiver".to_owned()) - .spawn(async { - let code = code_rx.await?; - log::info!("Got code over local: {}", &code); - let (welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code).await?; - if let Some(welcome) = &welcome.welcome { - log::info!("Got welcome: {}", welcome); - } - - let req = transfer::request_file( - wormhole, - default_relay_hints(), - magic_wormhole::transit::Abilities::ALL_ABILITIES, - futures::future::pending(), - ) +/** Generate common offers for testing, together with a pre-made answer that checks the received content */ +async fn file_offers() -> eyre::Result> { + async fn offer(name: &str) -> eyre::Result<(transfer::OfferSend, transfer::OfferAccept)> { + let path = format!("tests/{name}"); + let offer = transfer::OfferSend::new_file_or_folder(name.into(), &path).await?; + let answer = transfer::OfferSend::new_file_or_folder(name.into(), &path) .await? - .unwrap(); - - let mut buffer = Vec::::new(); - req.accept( - &transit::log_transit_connection, - |_received, _total| {}, - &mut buffer, - futures::future::pending(), - ) - .await?; - Ok(buffer) - })?; - - sender_task.await?; - let original = std::fs::read("tests/example-file.bin")?; - let received: Vec = (receiver_task.await as eyre::Result>)?; + .set_content(|_path| { + use std::{ + io, + pin::Pin, + task::{Context, Poll}, + }; + + let path = path.clone(); + let content = transfer::new_accept_content(move |_append| { + struct Writer { + closed: bool, + send_bytes: Vec, + receive_bytes: Vec, + } + + impl futures::io::AsyncWrite for Writer { + fn poll_write( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + self.receive_bytes.extend_from_slice(buf); + Poll::Ready(Ok(buf.len())) + } + + fn poll_close( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll> { + self.closed = true; + if self.send_bytes == self.receive_bytes { + Poll::Ready(Ok(())) + } else { + Poll::Ready(Err(io::Error::new( + io::ErrorKind::Other, + "Send and receive are not the same", + ))) + } + } + + fn poll_flush( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + } + + impl Drop for Writer { + fn drop(&mut self) { + assert!(self.closed, "Implementation forgot to close Writer"); + } + } + + let path = path.clone(); + async move { + Ok(Writer { + closed: false, + send_bytes: async_std::fs::read(&path).await?, + receive_bytes: Vec::new(), + }) + } + }); + transfer::AcceptInner { + content, + offset: 0, + sha256: None, + } + }); + + Ok((offer, answer)) + } - assert_eq!(original, received, "Files differ"); - Ok(()) + Ok(vec![ + offer("example-file.bin").await?, + /* Empty file: https://github.com/magic-wormhole/magic-wormhole.rs/issues/160 */ + offer("example-file-empty").await?, + /* 4k file: https://github.com/magic-wormhole/magic-wormhole.rs/issues/152 */ + offer("example-file-4096.bin").await?, + ]) } -/** Send a file using the Rust implementation that has exactly 4096 bytes (our chunk size) */ +/** Send a file using the Rust implementation. This does not guarantee compatibility with Python! ;) */ #[cfg(feature = "transfer")] #[async_std::test] -pub async fn test_4096_file_rust2rust() -> eyre::Result<()> { +pub async fn test_file_rust2rust() -> eyre::Result<()> { init_logger(); - let (code_tx, code_rx) = futures::channel::oneshot::channel(); - - const FILENAME: &str = "tests/example-file-4096.bin"; - - let sender_task = async_std::task::Builder::new() - .name("sender".to_owned()) - .spawn(async { - let (welcome, connector) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; - if let Some(welcome) = &welcome.welcome { - log::info!("Got welcome: {}", welcome); - } - log::info!("This wormhole's code is: {}", &welcome.code); - code_tx.send(welcome.code).unwrap(); - let wormhole = connector.await?; - eyre::Result::<_>::Ok( - transfer::send_file( + for (offer, answer) in file_offers().await? { + let (code_tx, code_rx) = futures::channel::oneshot::channel(); + + let sender_task = async_std::task::Builder::new() + .name("sender".to_owned()) + .spawn(async { + let (welcome, connector) = + Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; + if let Some(welcome) = &welcome.welcome { + log::info!("Got welcome: {}", welcome); + } + log::info!("This wormhole's code is: {}", &welcome.code); + code_tx.send(welcome.code).unwrap(); + let wormhole = connector.await?; + eyre::Result::<_>::Ok( + transfer::send( + wormhole, + default_relay_hints(), + magic_wormhole::transit::Abilities::ALL_ABILITIES, + offer, + &transit::log_transit_connection, + |_sent, _total| {}, + futures::future::pending(), + ) + .await?, + ) + })?; + let receiver_task = async_std::task::Builder::new() + .name("receiver".to_owned()) + .spawn(async { + let code = code_rx.await?; + log::info!("Got code over local: {}", &code); + let (welcome, wormhole) = + Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code).await?; + if let Some(welcome) = &welcome.welcome { + log::info!("Got welcome: {}", welcome); + } + + // Hacky v1-compat conversion for now + let mut answer = + (answer.into_iter_files().next().unwrap().1.content)(false).await?; + + let transfer::ReceiveRequest::V1(req) = transfer::request( wormhole, default_relay_hints(), - &mut async_std::fs::File::open(FILENAME).await?, - "example-file.bin", - std::fs::metadata(FILENAME).unwrap().len(), magic_wormhole::transit::Abilities::ALL_ABILITIES, - &transit::log_transit_connection, - |_sent, _total| {}, futures::future::pending(), ) - .await?, - ) - })?; - let receiver_task = async_std::task::Builder::new() - .name("receiver".to_owned()) - .spawn(async { - let code = code_rx.await?; - log::info!("Got code over local: {}", &code); - let (welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code).await?; - if let Some(welcome) = &welcome.welcome { - log::info!("Got welcome: {}", welcome); - } - - let req = transfer::request_file( - wormhole, - default_relay_hints(), - magic_wormhole::transit::Abilities::ALL_ABILITIES, - futures::future::pending(), - ) - .await? - .unwrap(); - - let mut buffer = Vec::::new(); - req.accept( - &transit::log_transit_connection, - |_received, _total| {}, - &mut buffer, - futures::future::pending(), - ) - .await?; - Ok(buffer) - })?; - - sender_task.await?; - let original = std::fs::read(FILENAME)?; - let received: Vec = (receiver_task.await as eyre::Result>)?; - - assert_eq!(original, received, "Files differ"); - Ok(()) -} - -/** https://github.com/magic-wormhole/magic-wormhole.rs/issues/160 */ -#[cfg(feature = "transfer")] -#[async_std::test] -pub async fn test_empty_file_rust2rust() -> eyre::Result<()> { - init_logger(); - - let (code_tx, code_rx) = futures::channel::oneshot::channel(); - - let sender_task = async_std::task::Builder::new() - .name("sender".to_owned()) - .spawn(async { - let (welcome, connector) = - Wormhole::connect_without_code(transfer::APP_CONFIG.id(TEST_APPID), 2).await?; - if let Some(welcome) = &welcome.welcome { - log::info!("Got welcome: {}", welcome); - } - log::info!("This wormhole's code is: {}", &welcome.code); - code_tx.send(welcome.code).unwrap(); - let wormhole = connector.await?; - eyre::Result::<_>::Ok( - transfer::send_file( - wormhole, - default_relay_hints(), - &mut async_std::fs::File::open("tests/example-file-empty").await?, - "example-file-empty", - std::fs::metadata("tests/example-file-empty").unwrap().len(), - magic_wormhole::transit::Abilities::ALL_ABILITIES, + .await? + .unwrap() + else {panic!("v2 should be disabled for now")}; + req.accept( &transit::log_transit_connection, - |_sent, _total| {}, + &mut answer, + |_received, _total| {}, futures::future::pending(), ) - .await?, - ) - })?; - let receiver_task = async_std::task::Builder::new() - .name("receiver".to_owned()) - .spawn(async { - let code = code_rx.await?; - log::info!("Got code over local: {}", &code); - let (welcome, wormhole) = - Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code).await?; - if let Some(welcome) = &welcome.welcome { - log::info!("Got welcome: {}", welcome); - } + .await?; + eyre::Result::<_>::Ok(()) + })?; - let req = transfer::request_file( - wormhole, - default_relay_hints(), - magic_wormhole::transit::Abilities::ALL_ABILITIES, - futures::future::pending(), - ) - .await? - .unwrap(); - - let mut buffer = Vec::::new(); - req.accept( - &transit::log_transit_connection, - |_received, _total| {}, - &mut buffer, - futures::future::pending(), - ) - .await?; - eyre::Result::>::Ok(buffer) - })?; - - sender_task.await?; - - assert!(&receiver_task.await?.is_empty()); + sender_task.await?; + receiver_task.await?; + } Ok(()) } -/** Test the functionality used by the `send-many` subcommand. It logically builds upon the - * `test_eventloop_exit` tests. We send us a file five times, and check if it arrived. +/** Test the functionality used by the `send-many` subcommand. */ #[cfg(feature = "transfer")] #[async_std::test] @@ -268,32 +210,37 @@ pub async fn test_send_many() -> eyre::Result<()> { let code = welcome.code; log::info!("The code is {:?}", code); - let correct_data = std::fs::read("tests/example-file.bin")?; + async fn gen_offer() -> eyre::Result { + file_offers().await.map(|mut vec| vec.remove(0).0) + } + + async fn gen_accept() -> eyre::Result { + file_offers().await.map(|mut vec| vec.remove(0).1) + } /* Send many */ let sender_code = code.clone(); let senders = async_std::task::spawn(async move { // let mut senders = Vec::, eyre::Error>>>::new(); - let mut senders = Vec::new(); + let mut senders: Vec>> = Vec::new(); /* The first time, we reuse the current session for sending */ { log::info!("Sending file #{}", 0); let wormhole = connector.await?; senders.push(async_std::task::spawn(async move { - default_relay_hints(); - crate::transfer::send_file( - wormhole, - default_relay_hints(), - &mut async_std::fs::File::open("tests/example-file.bin").await?, - "example-file.bin", - std::fs::metadata("tests/example-file.bin").unwrap().len(), - magic_wormhole::transit::Abilities::ALL_ABILITIES, - &transit::log_transit_connection, - |_, _| {}, - futures::future::pending(), + eyre::Result::Ok( + crate::transfer::send( + wormhole, + default_relay_hints(), + magic_wormhole::transit::Abilities::ALL_ABILITIES, + gen_offer().await?, + &transit::log_transit_connection, + |_, _| {}, + futures::future::pending(), + ) + .await?, ) - .await })); } @@ -304,20 +251,20 @@ pub async fn test_send_many() -> eyre::Result<()> { sender_code.clone(), ) .await?; + let gen_offer = gen_offer.clone(); senders.push(async_std::task::spawn(async move { - default_relay_hints(); - crate::transfer::send_file( - wormhole, - default_relay_hints(), - &mut async_std::fs::File::open("tests/example-file.bin").await?, - "example-file.bin", - std::fs::metadata("tests/example-file.bin").unwrap().len(), - magic_wormhole::transit::Abilities::ALL_ABILITIES, - &transit::log_transit_connection, - |_, _| {}, - futures::future::pending(), + eyre::Result::Ok( + crate::transfer::send( + wormhole, + default_relay_hints(), + magic_wormhole::transit::Abilities::ALL_ABILITIES, + gen_offer().await?, + &transit::log_transit_connection, + |_, _| {}, + futures::future::pending(), + ) + .await?, ) - .await })); } eyre::Result::<_>::Ok(senders) @@ -331,24 +278,33 @@ pub async fn test_send_many() -> eyre::Result<()> { let (_welcome, wormhole) = Wormhole::connect_with_code(transfer::APP_CONFIG.id(TEST_APPID), code.clone()).await?; log::info!("Got key: {}", &wormhole.key); - let req = crate::transfer::request_file( + let transfer::ReceiveRequest::V1(req) = crate::transfer::request( wormhole, default_relay_hints(), magic_wormhole::transit::Abilities::ALL_ABILITIES, futures::future::pending(), ) .await? - .unwrap(); + .unwrap() + else {panic!("v2 should be disabled for now")}; + + // Hacky v1-compat conversion for now + let mut answer = (gen_accept() + .await? + .into_iter_files() + .next() + .unwrap() + .1 + .content)(false) + .await?; - let mut buffer = Vec::::new(); req.accept( &transit::log_transit_connection, + &mut answer, |_, _| {}, - &mut buffer, futures::future::pending(), ) .await?; - assert_eq!(correct_data, buffer, "Files #{} differ", i); } for sender in senders.await? { diff --git a/src/transfer.rs b/src/transfer.rs index 8a0aeace..7a53107c 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -8,23 +8,31 @@ //! At its core, "peer messages" are exchanged over an established wormhole connection with the other side. //! They are used to set up a [transit] portal and to exchange a file offer/accept. Then, the file is transmitted over the transit relay. -use futures::{AsyncRead, AsyncWrite}; +use futures::{AsyncRead, AsyncSeek, AsyncWrite}; use serde_derive::{Deserialize, Serialize}; #[cfg(test)] use serde_json::json; use std::sync::Arc; -use super::{core::WormholeError, transit, transit::Transit, util, AppID, Wormhole}; +use super::{core::WormholeError, transit, AppID, Wormhole}; use futures::Future; use log::*; -use std::{borrow::Cow, path::PathBuf}; -use transit::{TransitConnectError, TransitConnector, TransitError}; +use std::{ + borrow::Cow, + collections::BTreeMap, + path::{Path, PathBuf}, +}; +use transit::{ + Abilities as TransitAbilities, Transit, TransitConnectError, TransitConnector, TransitError, +}; -mod messages; -use messages::*; +mod cancel; mod v1; mod v2; +pub use v1::ReceiveRequest as ReceiveRequestV1; +pub use v2::ReceiveRequest as ReceiveRequestV2; + const APPID_RAW: &str = "lothar.com/wormhole/text-or-file-xfer"; /// The App ID associated with this protocol. @@ -77,11 +85,11 @@ pub enum TransferError { #[error("Protocol error: {}", _0)] Protocol(Box), #[error( - "Unexpected message (protocol error): Expected '{}', but got: {:?}", + "Unexpected message (protocol error): Expected '{}', but got: '{}'", _0, _1 )] - ProtocolUnexpectedMessage(Box, Box), + ProtocolUnexpectedMessage(Box, Box), #[error("Wormhole connection error")] Wormhole( #[from] @@ -111,40 +119,39 @@ pub enum TransferError { impl TransferError { pub(self) fn unexpected_message( expected: impl Into>, - got: impl std::fmt::Debug + Send + Sync + 'static, + got: impl std::fmt::Display, ) -> Self { - Self::ProtocolUnexpectedMessage(expected.into(), Box::new(got)) + Self::ProtocolUnexpectedMessage(expected.into(), got.to_string().into()) } } /** * The application specific version information for this protocol. - * - * At the moment, this always is an empty object, but this will likely change in the future. */ #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct AppVersion { - // #[serde(default)] - // abilities: Cow<'static, [Cow<'static, str>]>, - // #[serde(default)] - // transfer_v2: Option, + #[serde(default)] + abilities: Cow<'static, [Cow<'static, str>]>, + #[serde(default)] + transfer_v2: Option, } // TODO check invariants during deserialization - impl AppVersion { const fn new() -> Self { Self { - // abilities: Cow::Borrowed([Cow::Borrowed("transfer-v1"), Cow::Borrowed("transfer-v2")]), - // transfer_v2: Some(AppVersionTransferV2Hint::new()) + // Dont advertize v2 for now + abilities: Cow::Borrowed(&[ + Cow::Borrowed("transfer-v1"), /* Cow::Borrowed("transfer-v2") */ + ]), + transfer_v2: Some(AppVersionTransferV2Hint::new()), } } #[allow(dead_code)] fn supports_v2(&self) -> bool { - false - // self.abilities.contains(&"transfer-v2".into()) + self.abilities.contains(&"transfer-v2".into()) } } @@ -154,180 +161,677 @@ impl Default for AppVersion { } } -// #[derive(Clone, Debug, Serialize, Deserialize)] -// #[serde(rename_all = "kebab-case")] -// pub struct AppVersionTransferV2Hint { -// supported_formats: Vec>, -// transit_abilities: Vec, -// } - -// impl AppVersionTransferV2Hint { -// const fn new() -> Self { -// Self { -// supported_formats: vec![Cow::Borrowed("tar.zst")], -// transit_abilities: transit::Ability::all_abilities(), -// } -// } -// } - -// impl Default for AppVersionTransferV2Hint { -// fn default() -> Self { -// Self::new() -// } -// } - -#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] -struct TransitAck { - pub ack: String, - pub sha256: String, +pub struct AppVersionTransferV2Hint { + supported_formats: Cow<'static, [Cow<'static, str>]>, + transit_abilities: transit::Abilities, } -impl TransitAck { - pub fn new(msg: impl Into, sha256: impl Into) -> Self { - TransitAck { - ack: msg.into(), - sha256: sha256.into(), +impl AppVersionTransferV2Hint { + const fn new() -> Self { + Self { + supported_formats: Cow::Borrowed(&[Cow::Borrowed("plain"), Cow::Borrowed("tar")]), + transit_abilities: transit::Abilities::ALL_ABILITIES, } } +} - #[cfg(test)] - pub fn serialize(&self) -> String { - json!(self).to_string() +impl Default for AppVersionTransferV2Hint { + fn default() -> Self { + Self::new() } +} + +pub trait AsyncReadSeek: AsyncRead + AsyncSeek {} + +impl AsyncReadSeek for T where T: AsyncRead + AsyncSeek {} + +pub trait AsyncWriteSeek: AsyncWrite + AsyncSeek {} + +impl AsyncWriteSeek for T where T: AsyncWrite + AsyncSeek {} - pub fn serialize_vec(&self) -> Vec { +/** + * The type of message exchanged over the wormhole for this protocol + */ +#[derive(Deserialize, Serialize, derive_more::Display, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +#[non_exhaustive] +pub enum PeerMessage { + /* V1 */ + #[display(fmt = "transit")] + Transit(v1::TransitV1), + #[display(fmt = "offer")] + Offer(v1::OfferMessage), + #[display(fmt = "answer")] + Answer(v1::AnswerMessage), + /* V2 */ + #[display(fmt = "transit-v2")] + TransitV2(v2::TransitV2), + + /** Tell the other side you got an error */ + #[display(fmt = "error")] + Error(String), + #[display(fmt = "unknown")] + #[serde(other)] + Unknown, +} + +impl PeerMessage { + #[allow(unused)] + fn offer_message_v1(msg: impl Into) -> Self { + PeerMessage::Offer(v1::OfferMessage::Message(msg.into())) + } + + fn offer_file_v1(name: impl Into, size: u64) -> Self { + PeerMessage::Offer(v1::OfferMessage::File { + filename: name.into(), + filesize: size, + }) + } + + #[allow(dead_code)] + fn offer_directory_v1( + name: impl Into, + mode: impl Into, + compressed_size: u64, + numbytes: u64, + numfiles: u64, + ) -> Self { + PeerMessage::Offer(v1::OfferMessage::Directory { + dirname: name.into(), + mode: mode.into(), + zipsize: compressed_size, + numbytes, + numfiles, + }) + } + + #[allow(dead_code)] + fn message_ack_v1(msg: impl Into) -> Self { + PeerMessage::Answer(v1::AnswerMessage::MessageAck(msg.into())) + } + + fn file_ack_v1(msg: impl Into) -> Self { + PeerMessage::Answer(v1::AnswerMessage::FileAck(msg.into())) + } + + fn error_message(msg: impl Into) -> Self { + PeerMessage::Error(msg.into()) + } + + fn transit_v1(abilities: TransitAbilities, hints: transit::Hints) -> Self { + PeerMessage::Transit(v1::TransitV1 { + abilities_v1: abilities, + hints_v1: hints, + }) + } + + fn transit_v2(hints_v2: transit::Hints) -> Self { + PeerMessage::TransitV2(v2::TransitV2 { hints_v2 }) + } + + fn check_err(&self) -> Result { + match self { + Self::Error(err) => Err(TransferError::PeerError(err.clone())), + other => Ok(other.clone()), + } + } + + #[allow(dead_code)] + fn ser_json(&self) -> Vec { serde_json::to_vec(self).unwrap() } } -#[cfg(not(target_family = "wasm"))] -pub async fn send_file_or_folder( +pub type OfferSend = Offer; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(bound(deserialize = "T: Default"))] +pub struct Offer { + content: BTreeMap>, +} + +impl OfferSend { + /// Offer a single path (file or folder) + #[cfg(not(target_family = "wasm"))] + pub async fn new_file_or_folder( + offer_name: String, + path: impl AsRef, + ) -> std::io::Result { + let path = path.as_ref(); + log::trace!( + "OfferSend::new_file_or_folder: {offer_name}, {}", + path.display() + ); + let mut content = BTreeMap::new(); + content.insert(offer_name, OfferSendEntry::new(path).await?); + Ok(Self { content }) + } + + /// Offer list of paths (files and folders) + /// Panics if any of the paths does not have a name (like `/`). + /// Panics if any two or more of the paths have the same name. + #[cfg(not(target_family = "wasm"))] + pub async fn new_paths(paths: impl IntoIterator) -> std::io::Result { + let mut content = BTreeMap::new(); + for path in paths { + let offer_name = path.file_name().expect("Path must have a name"); + let offer_name = offer_name + .to_str() + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "{} is not UTF-8 encoded", + (offer_name.as_ref() as &Path).display() + ), + ) + })? + .to_owned(); + let old = content.insert(offer_name, OfferSendEntry::new(path).await?); + assert!(old.is_none(), "Duplicate names found"); + } + Ok(Self { content }) + } + + /// Offer a single file with custom content + /// + /// You must ensure that the Reader contains exactly as many bytes + /// as advertized in file_size. + pub fn new_file_custom(offer_name: String, size: u64, content: OfferContent) -> Self { + let mut content_ = BTreeMap::new(); + content_.insert(offer_name, OfferSendEntry::RegularFile { size, content }); + Self { content: content_ } + } +} + +impl Offer { + pub fn top_level_paths(&self) -> impl Iterator + '_ { + self.content.keys() + } + + pub fn get(&self, path: &[String]) -> Option<&OfferEntry> { + match path { + [] => None, + [start, rest @ ..] => self.content.get(start).and_then(|inner| inner.get(rest)), + } + } + + pub fn get_file(&self, path: &[String]) -> Option<(&T, u64)> { + match path { + [] => None, + [start, rest @ ..] => self + .content + .get(start) + .and_then(|inner| inner.get_file(rest)), + } + } + + /** Recursively list all file paths, without directory names or symlinks. */ + pub fn iter_file_paths(&self) -> impl Iterator> + '_ { + self.iter_files().map(|val| val.0) + } + + /** Recursively list all files, without directory names or symlinks. */ + pub fn iter_files(&self) -> impl Iterator, &T, u64)> + '_ { + self.content.iter().flat_map(|(name, offer)| { + let name = name.clone(); + offer.iter_files().map(move |mut val| { + val.0.insert(0, name.clone()); + val + }) + }) + } + + pub fn total_size(&self) -> u64 { + self.iter_files().map(|v| v.2).sum() + } + + #[cfg(not(target_family = "wasm"))] + pub fn accept_all(&self, target_dir: &Path) -> OfferAccept { + self.set_content(|path| { + let full_path: PathBuf = target_dir.join(path.join("/")); + let content = new_accept_content(move |append| { + let full_path = full_path.clone(); + async_std::fs::OpenOptions::new() + .write(true) + .create(true) + .append(append) + .truncate(!append) + .open(full_path) + }); + AcceptInner { + content: Box::new(content) as _, + offset: 0, + sha256: None, + } + }) + } + + #[cfg(not(target_family = "wasm"))] + pub async fn create_directories(&self, target_path: &Path) -> std::io::Result<()> { + // TODO this could be made more efficient by passing around just one buffer + for (name, file) in &self.content { + file.create_directories(&target_path.join(name)).await?; + } + Ok(()) + } + + #[cfg(not(target_family = "wasm"))] + pub async fn create_symlinks(&self, target_path: &Path) -> std::io::Result<()> { + // TODO this could be made more efficient by passing around just one buffer + for (name, file) in &self.content { + file.create_symlinks(&target_path.join(name)).await?; + } + Ok(()) + } + + pub fn offer_name(&self) -> String { + let (name, entry) = self.content.iter().next().unwrap(); + if self.is_multiple() { + format!( + "{name} and {} other files or directories", + self.content.len() - 1 + ) + } else { + if self.is_directory() { + let count = entry.iter_files().count(); + format!("{name} with {count} files inside") + } else { + name.clone() + } + } + } + + pub fn is_multiple(&self) -> bool { + self.content.len() > 1 + } + + pub fn is_directory(&self) -> bool { + self.is_multiple() + || self + .content + .values() + .any(|f| matches!(f, OfferEntry::Directory { .. })) + } + + pub fn set_content(&self, mut f: impl FnMut(&[String]) -> U) -> Offer { + Offer { + content: self + .content + .iter() + .map(|(k, v)| (k.clone(), v.set_content(&mut vec![k.clone()], &mut f))) + .collect(), + } + } +} + +impl Offer { + /** Recursively list all files, without directory names or symlinks. */ + pub fn into_iter_files(self) -> impl Iterator, T, u64)> + Send { + self.content.into_iter().flat_map(|(name, offer)| { + offer.into_iter_files().map(move |mut val| { + val.0.insert(0, name.clone()); + val + }) + }) + } +} + +impl From<&Offer> for Offer { + fn from(from: &Offer) -> Self { + from.set_content(|_| ()) + } +} + +/// The signature is basically just `() -> io::Result`, but in async +/// +/// This may be called multiple times during the send process, an imlementations that generate their +/// output dynamically must ensure all invocations produce the same result — independently of each other +/// (things may be concurrent). +pub type OfferContent = Box< + dyn Fn() -> futures::future::BoxFuture< + 'static, + std::io::Result>, + > + Send, +>; + +pub fn new_offer_content(content: F) -> OfferContent +where + F: Fn() -> G + Send + 'static, + G: Future> + Send + 'static, + H: AsyncReadSeek + Unpin + Send + 'static, +{ + let wrap_fun = move || { + use futures::TryFutureExt; + + let fut = content(); + let wrap_fut = fut.map_ok(|read| Box::new(read) as Box); + + Box::pin(wrap_fut) as futures::future::BoxFuture<'static, _> + }; + Box::new(wrap_fun) as _ +} + +pub type OfferSendEntry = OfferEntry; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +#[serde(tag = "type")] +#[serde(bound(deserialize = "T: Default"))] +pub enum OfferEntry { + RegularFile { + size: u64, + #[serde(skip)] + content: T, + }, + Directory { + content: BTreeMap, + }, + Symlink { + target: String, + }, +} + +impl OfferSendEntry { + #[cfg(not(target_family = "wasm"))] + async fn new(path: impl AsRef) -> std::io::Result { + // Workaround for https://github.com/rust-lang/rust/issues/78649 + #[inline(always)] + fn new_recurse<'a>( + path: impl AsRef + 'a + Send, + ) -> futures::future::BoxFuture<'a, std::io::Result> { + Box::pin(OfferSendEntry::new(path)) + } + + let path = path.as_ref(); + let metadata = async_std::fs::symlink_metadata(path).await?; + // let mtime = metadata.modified()? + // .duration_since(std::time::SystemTime::UNIX_EPOCH) + // .unwrap_or_default() + // .as_secs(); + if metadata.is_file() { + log::trace!("OfferSendEntry::new {path:?} is file"); + let path = path.to_owned(); + Ok(Self::RegularFile { + size: metadata.len(), + content: new_offer_content(move || { + let path = path.clone(); + async_std::fs::File::open(path) + }), + }) + } else if metadata.is_symlink() { + log::trace!("OfferSendEntry::new {path:?} is symlink"); + let target = async_std::fs::read_link(path).await?; + Ok(Self::Symlink { + target: target + .to_str() + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("{} is not UTF-8 encoded", target.display()), + ) + })? + .to_string(), + }) + } else if metadata.is_dir() { + use futures::TryStreamExt; + log::trace!("OfferSendEntry::new {path:?} is directory"); + + let content: BTreeMap = async_std::fs::read_dir(path) + .await? + .and_then(|file| async move { + let path = file.path(); + let name = path + .file_name() + .expect("Internal error: non-root paths should always have a name") + .to_str() + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("{} is not UTF-8 encoded", path.display()), + ) + })? + .to_owned(); + let offer = new_recurse(path).await?; + Ok((name, offer)) + }) + .try_collect() + .await?; + Ok(Self::Directory { content }) + } else { + unreachable!() + } + } +} + +impl OfferEntry { + /** Recursively list all files, without directory names or symlinks. */ + fn iter_files(&self) -> impl Iterator, &T, u64)> + '_ { + // TODO I couldn't think up a less efficient way to do this ^^ + match self { + Self::Directory { content, .. } => { + let iter = content.iter().flat_map(|(name, offer)| { + let name = name.clone(); + offer.iter_files().map(move |mut val| { + val.0.insert(0, name.clone()); + val + }) + }); + Box::new(iter) as Box> + }, + Self::RegularFile { content, size } => { + Box::new(std::iter::once((vec![], content, *size))) as Box> + }, + Self::Symlink { .. } => Box::new(std::iter::empty()) as Box>, + } + } + + fn get(&self, path: &[String]) -> Option<&Self> { + match path { + [] => Some(self), + [start, rest @ ..] => match self { + Self::Directory { content, .. } => { + content.get(start).and_then(|inner| inner.get(rest)) + }, + _ => None, + }, + } + } + + fn get_file(&self, path: &[String]) -> Option<(&T, u64)> { + match path { + [] => match self { + Self::RegularFile { content, size } => Some((content, *size)), + _ => None, + }, + [start, rest @ ..] => match self { + Self::Directory { content, .. } => { + content.get(start).and_then(|inner| inner.get_file(rest)) + }, + _ => None, + }, + } + } + + #[cfg(not(target_family = "wasm"))] + async fn create_directories(&self, target_path: &Path) -> std::io::Result<()> { + #[inline(always)] + fn recurse<'a, T>( + this: &'a OfferEntry, + path: &'a Path, + ) -> futures::future::LocalBoxFuture<'a, std::io::Result<()>> { + Box::pin(OfferEntry::create_directories(this, path)) + } + match self { + Self::Directory { content, .. } => { + async_std::fs::create_dir(target_path).await?; + for (name, file) in content { + recurse(file, &target_path.join(name)).await?; + } + Ok(()) + }, + _ => Ok(()), + } + } + + #[cfg(not(target_family = "wasm"))] + async fn create_symlinks(&self, target_path: &Path) -> std::io::Result<()> { + #[inline(always)] + fn recurse<'a, T>( + this: &'a OfferEntry, + path: &'a Path, + ) -> futures::future::LocalBoxFuture<'a, std::io::Result<()>> { + Box::pin(OfferEntry::create_symlinks(this, path)) + } + match self { + Self::Symlink { target } => { + todo!() + }, + Self::Directory { content, .. } => { + for (name, file) in content { + recurse(file, &target_path.join(name)).await?; + } + Ok(()) + }, + _ => Ok(()), + } + } + + fn set_content( + &self, + base_path: &mut Vec, + f: &mut impl FnMut(&[String]) -> U, + ) -> OfferEntry { + match self { + OfferEntry::RegularFile { size, .. } => OfferEntry::RegularFile { + size: *size, + content: f(&base_path), + }, + OfferEntry::Directory { content } => OfferEntry::Directory { + content: content + .into_iter() + .map(|(k, v)| { + base_path.push(k.clone()); + let v = v.set_content(base_path, f); + base_path.pop(); + (k.clone(), v) + }) + .collect(), + }, + OfferEntry::Symlink { target } => OfferEntry::Symlink { + target: target.clone(), + }, + } + } +} + +impl OfferEntry { + /** Recursively list all files, without directory names or symlinks. */ + fn into_iter_files(self) -> impl Iterator, T, u64)> + Send { + // TODO I couldn't think up a less efficient way to do this ^^ + match self { + Self::Directory { content, .. } => { + let iter = content.into_iter().flat_map(|(name, offer)| { + offer.into_iter_files().map(move |mut val| { + val.0.insert(0, name.clone()); + val + }) + }); + Box::new(iter) as Box + Send> + }, + Self::RegularFile { content, size } => { + Box::new(std::iter::once((vec![], content, size))) + as Box + Send> + }, + Self::Symlink { .. } => { + Box::new(std::iter::empty()) as Box + Send> + }, + } + } +} + +impl From<&OfferEntry> for OfferEntry { + fn from(from: &OfferEntry) -> Self { + /* Note: this violates some invariants and only works because our mapper discards the path argument */ + from.set_content(&mut vec![], &mut |_| ()) + } +} + +/// The signature is basically just `bool -> io::Result`, but in async +/// +/// The boolean parameter dictates whether we start from scratch or not: +/// true: Append to existing files +/// false: Truncate if necessary +pub type AcceptContent = Box< + dyn FnOnce( + bool, + ) -> futures::future::BoxFuture< + 'static, + std::io::Result>, + > + Send, +>; + +pub fn new_accept_content(content: F) -> AcceptContent +where + F: Fn(bool) -> G + Send + 'static, + G: Future> + Send + 'static, + H: AsyncWrite + Unpin + Send + 'static, +{ + let wrap_fun = move |append| { + use futures::TryFutureExt; + + let fut = content(append); + let wrap_fut = fut.map_ok(|write| Box::new(write) as Box); + + Box::pin(wrap_fut) as futures::future::BoxFuture<'static, _> + }; + Box::new(wrap_fun) as _ +} + +pub type OfferAccept = Offer; + +pub struct AcceptInner { + pub offset: u64, + pub sha256: Option<[u8; 32]>, + pub content: AcceptContent, +} + +pub async fn send( wormhole: Wormhole, relay_hints: Vec, - file_path: N, - file_name: M, transit_abilities: transit::Abilities, - transit_handler: G, - progress_handler: H, + offer: OfferSend, + transit_handler: impl FnOnce(transit::TransitInfo), + progress_handler: impl FnMut(u64, u64) + 'static, cancel: impl Future, -) -> Result<(), TransferError> -where - N: AsRef, - M: AsRef, - G: FnOnce(transit::TransitInfo), - H: FnMut(u64, u64) + 'static, -{ - use async_std::fs::File; - let file_path = file_path.as_ref(); - let file_name = file_name.as_ref(); - - let mut file = File::open(file_path).await?; - let metadata = file.metadata().await?; - if metadata.is_dir() { - send_folder( +) -> Result<(), TransferError> { + let peer_version: AppVersion = serde_json::from_value(wormhole.peer_version.clone())?; + if peer_version.supports_v2() { + v2::send( wormhole, relay_hints, - file_path, - file_name, transit_abilities, - transit_handler, + offer, progress_handler, + peer_version, cancel, ) - .await?; + .await } else { - let file_size = metadata.len(); - send_file( + v1::send( wormhole, relay_hints, - &mut file, - file_name, - file_size, transit_abilities, - transit_handler, + offer, progress_handler, + transit_handler, + peer_version, cancel, ) - .await?; + .await } - Ok(()) -} - -/// Send a file to the other side -/// -/// You must ensure that the Reader contains exactly as many bytes -/// as advertized in file_size. -pub async fn send_file( - wormhole: Wormhole, - relay_hints: Vec, - file: &mut F, - file_name: N, - file_size: u64, - transit_abilities: transit::Abilities, - transit_handler: G, - progress_handler: H, - cancel: impl Future, -) -> Result<(), TransferError> -where - F: AsyncRead + Unpin, - N: Into, - G: FnOnce(transit::TransitInfo), - H: FnMut(u64, u64) + 'static, -{ - let _peer_version: AppVersion = serde_json::from_value(wormhole.peer_version.clone())?; - // if peer_version.supports_v2() && false { - // v2::send_file(wormhole, relay_url, file, file_name, file_size, progress_handler, peer_version).await - // } else { - // log::info!("TODO"); - v1::send_file( - wormhole, - relay_hints, - file, - file_name, - file_size, - transit_abilities, - transit_handler, - progress_handler, - cancel, - ) - .await - // } -} - -/// Send a folder to the other side -/// -/// This isn't a proper folder transfer as per the Wormhole protocol -/// because it sends it in a way so that the receiver still has to manually -/// unpack it. But it's better than nothing -#[cfg(not(target_family = "wasm"))] -pub async fn send_folder( - wormhole: Wormhole, - relay_hints: Vec, - folder_path: N, - folder_name: M, - transit_abilities: transit::Abilities, - transit_handler: G, - progress_handler: H, - cancel: impl Future, -) -> Result<(), TransferError> -where - N: Into, - M: Into, - G: FnOnce(transit::TransitInfo), - H: FnMut(u64, u64) + 'static, -{ - v1::send_folder( - wormhole, - relay_hints, - folder_path, - folder_name, - transit_abilities, - transit_handler, - progress_handler, - cancel, - ) - .await } /** @@ -338,300 +842,119 @@ where * * Returns `None` if the task got cancelled. */ -pub async fn request_file( - mut wormhole: Wormhole, +pub async fn request( + wormhole: Wormhole, relay_hints: Vec, transit_abilities: transit::Abilities, cancel: impl Future, ) -> Result, TransferError> { - // Error handling - let run = Box::pin(async { - let connector = transit::init(transit_abilities, None, relay_hints).await?; - - // send the transit message - debug!("Sending transit message '{:?}", connector.our_hints()); - wormhole - .send_json(&PeerMessage::transit( - *connector.our_abilities(), - (**connector.our_hints()).clone(), - )) - .await?; - - // receive transit message - let (their_abilities, their_hints): (transit::Abilities, transit::Hints) = - match wormhole.receive_json().await?? { - PeerMessage::Transit(transit) => { - debug!("received transit message: {:?}", transit); - (transit.abilities_v1, transit.hints_v1) - }, - PeerMessage::Error(err) => { - bail!(TransferError::PeerError(err)); - }, - other => { - bail!(TransferError::unexpected_message("transit", other)); - }, - }; - - // 3. receive file offer message from peer - let (filename, filesize) = match wormhole.receive_json().await?? { - PeerMessage::Offer(offer_type) => match offer_type { - Offer::File { filename, filesize } => (filename, filesize), - Offer::Directory { - mut dirname, - zipsize, - .. - } => { - dirname.set_extension("zip"); - (dirname, zipsize) - }, - _ => bail!(TransferError::UnsupportedOffer), - }, - PeerMessage::Error(err) => { - bail!(TransferError::PeerError(err)); - }, - other => { - bail!(TransferError::unexpected_message("offer", other)); - }, - }; - - Ok((filename, filesize, connector, their_abilities, their_hints)) - }); - - match crate::util::cancellable(run, cancel).await { - Ok(Ok((filename, filesize, connector, their_abilities, their_hints))) => { - Ok(Some(ReceiveRequest { - wormhole, - filename, - filesize, - connector, - their_abilities, - their_hints: Arc::new(their_hints), - })) - }, - Ok(Err(error @ TransferError::PeerError(_))) => Err(error), - Ok(Err(error)) => { - let _ = wormhole - .send_json(&PeerMessage::Error(format!("{}", error))) - .await; - Err(error) - }, - Err(cancelled) => { - let _ = wormhole - .send_json(&PeerMessage::Error(format!("{}", cancelled))) - .await; - Ok(None) - }, + let peer_version: AppVersion = serde_json::from_value(wormhole.peer_version.clone())?; + if peer_version.supports_v2() { + v2::request( + wormhole, + relay_hints, + peer_version, + transit_abilities, + cancel, + ) + .await + .map(|req| req.map(ReceiveRequest::V2)) + } else { + v1::request(wormhole, relay_hints, transit_abilities, cancel) + .await + .map(|req| req.map(ReceiveRequest::V1)) } } /** * A pending files send offer from the other side * - * You *should* consume this object, either by calling [`accept`](ReceiveRequest::accept) or [`reject`](ReceiveRequest::reject). + * You *should* consume this object, by matching on the protocol version and then calling either `accept` or `reject`. */ #[must_use] -pub struct ReceiveRequest { - wormhole: Wormhole, - connector: TransitConnector, - /// **Security warning:** this is untrusted and unverified input - pub filename: PathBuf, - pub filesize: u64, - their_abilities: transit::Abilities, - their_hints: Arc, +pub enum ReceiveRequest { + V1(ReceiveRequestV1), + V2(ReceiveRequestV2), } -impl ReceiveRequest { - /** - * Accept the file offer - * - * This will transfer the file and save it on disk. - */ - pub async fn accept( - mut self, - transit_handler: G, - progress_handler: F, - content_handler: &mut W, - cancel: impl Future, - ) -> Result<(), TransferError> - where - F: FnMut(u64, u64) + 'static, - G: FnOnce(transit::TransitInfo), - W: AsyncWrite + Unpin, - { - let run = Box::pin(async { - // send file ack. - debug!("Sending ack"); - self.wormhole - .send_json(&PeerMessage::file_ack("ok")) - .await?; - - let (mut transit, info) = self - .connector - .follower_connect( - self.wormhole - .key() - .derive_transit_key(self.wormhole.appid()), - self.their_abilities, - self.their_hints.clone(), - ) - .await?; - transit_handler(info); - - debug!("Beginning file transfer"); - v1::tcp_file_receive( - &mut transit, - self.filesize, - progress_handler, - content_handler, - ) - .await?; - Ok(()) - }); - - futures::pin_mut!(cancel); - let result = crate::util::cancellable_2(run, cancel).await; - handle_run_result(self.wormhole, result).await - } - - /** - * Reject the file offer - * - * This will send an error message to the other side so that it knows the transfer failed. - */ - pub async fn reject(mut self) -> Result<(), TransferError> { - self.wormhole - .send_json(&PeerMessage::error_message("transfer rejected")) - .await?; - self.wormhole.close().await?; +#[cfg(test)] +mod test { + use super::*; + use transit::{Abilities, DirectHint, RelayHint}; - Ok(()) + #[test] + fn test_transit() { + let abilities = Abilities::ALL_ABILITIES; + let hints = transit::Hints::new( + [DirectHint::new("192.168.1.8", 46295)], + [RelayHint::new( + None, + [DirectHint::new("magic-wormhole-transit.debian.net", 4001)], + [], + )], + ); + assert_eq!( + serde_json::json!(crate::transfer::PeerMessage::transit_v1(abilities, hints)), + serde_json::json!({ + "transit": { + "abilities-v1": [{"type":"direct-tcp-v1"},{"type":"relay-v1"}], + "hints-v1": [ + {"hostname":"192.168.1.8","port":46295,"type":"direct-tcp-v1"}, + { + "type": "relay-v1", + "hints": [ + {"type": "direct-tcp-v1", "hostname": "magic-wormhole-transit.debian.net", "port": 4001} + ], + "name": null + } + ], + } + }) + ); } -} -/// Maximum duration that we are willing to wait for cleanup tasks to finish -const SHUTDOWN_TIME: std::time::Duration = std::time::Duration::from_secs(5); + #[test] + fn test_message() { + let m1 = PeerMessage::offer_message_v1("hello from rust"); + assert_eq!( + serde_json::json!(m1).to_string(), + "{\"offer\":{\"message\":\"hello from rust\"}}" + ); + } -/** Handle the post-{transfer, failure, cancellation} logic */ -async fn handle_run_result( - mut wormhole: Wormhole, - result: Result<(Result<(), TransferError>, impl Future), crate::util::Cancelled>, -) -> Result<(), TransferError> { - async fn wrap_timeout(run: impl Future, cancel: impl Future) { - let run = util::timeout(SHUTDOWN_TIME, run); - futures::pin_mut!(run); - match crate::util::cancellable(run, cancel).await { - Ok(Ok(())) => {}, - Ok(Err(_timeout)) => log::debug!("Post-transfer timed out"), - Err(_cancelled) => log::debug!("Post-transfer got cancelled by user"), - }; - } - - /// Ignore an error but at least debug print it - fn debug_err(result: Result<(), WormholeError>, operation: &str) { - if let Err(error) = result { - log::debug!("Failed to {} after transfer: {}", operation, error); - } + #[test] + fn test_offer_file() { + let f1 = PeerMessage::offer_file_v1("somefile.txt", 34556); + assert_eq!( + serde_json::json!(f1).to_string(), + "{\"offer\":{\"file\":{\"filename\":\"somefile.txt\",\"filesize\":34556}}}" + ); } - match result { - /* Happy case: everything went okay */ - Ok((Ok(()), cancel)) => { - log::debug!("Transfer done, doing cleanup logic"); - wrap_timeout( - async { - debug_err(wormhole.close().await, "close Wormhole"); - }, - cancel, - ) - .await; - Ok(()) - }, - /* Got peer error: stop everything immediately */ - Ok((Err(error @ TransferError::PeerError(_)), cancel)) => { - log::debug!( - "Transfer encountered an error ({}), doing cleanup logic", - error - ); - wrap_timeout( - async { - debug_err(wormhole.close().await, "close Wormhole"); - }, - cancel, - ) - .await; - Err(error) - }, - /* Got transit error: try receive peer error for better error message */ - Ok((Err(mut error @ TransferError::Transit(_)), cancel)) => { - log::debug!( - "Transfer encountered an error ({}), doing cleanup logic", - error - ); - wrap_timeout(async { - /* If transit failed, ask for a proper error and potentially use that instead */ - // TODO this should be replaced with some try_receive that only polls already available messages, - // and we should not only look for the next one but all have been received - // and we should not interrupt a receive operation without making sure it leaves the connection - // in a consistent state, otherwise the shutdown may cause protocol errors - if let Ok(Ok(Ok(PeerMessage::Error(e)))) = util::timeout(SHUTDOWN_TIME / 3, wormhole.receive_json()).await { - error = TransferError::PeerError(e); - } else { - log::debug!("Failed to retrieve more specific error message from peer. Maybe it crashed?"); - } - debug_err(wormhole.close().await, "close Wormhole"); - }, cancel).await; - Err(error) - }, - /* Other error: try to notify peer */ - Ok((Err(error), cancel)) => { - log::debug!( - "Transfer encountered an error ({}), doing cleanup logic", - error - ); - wrap_timeout( - async { - debug_err( - wormhole - .send_json(&PeerMessage::Error(format!("{}", error))) - .await, - "notify peer about the error", - ); - debug_err(wormhole.close().await, "close Wormhole"); - }, - cancel, - ) - .await; - Err(error) - }, - /* Cancelled: try to notify peer */ - Err(cancelled) => { - log::debug!("Transfer got cancelled, doing cleanup logic"); - /* Replace cancel with ever-pending future, as we have already been cancelled */ - wrap_timeout( - async { - debug_err( - wormhole - .send_json(&PeerMessage::Error(format!("{}", cancelled))) - .await, - "notify peer about our cancellation", - ); - debug_err(wormhole.close().await, "close Wormhole"); - }, - futures::future::pending(), - ) - .await; - Ok(()) - }, + #[test] + fn test_offer_directory() { + let d1 = PeerMessage::offer_directory_v1("somedirectory", "zipped", 45, 1234, 10); + assert_eq!( + serde_json::json!(d1).to_string(), + "{\"offer\":{\"directory\":{\"dirname\":\"somedirectory\",\"mode\":\"zipped\",\"numbytes\":1234,\"numfiles\":10,\"zipsize\":45}}}" + ); } -} -#[cfg(test)] -mod test { - use super::*; + #[test] + fn test_message_ack() { + let m1 = PeerMessage::message_ack_v1("ok"); + assert_eq!( + serde_json::json!(m1).to_string(), + "{\"answer\":{\"message_ack\":\"ok\"}}" + ); + } #[test] - fn test_transit_ack() { - let f1 = TransitAck::new("ok", "deadbeaf"); - assert_eq!(f1.serialize(), "{\"ack\":\"ok\",\"sha256\":\"deadbeaf\"}"); + fn test_file_ack() { + let f1 = PeerMessage::file_ack_v1("ok"); + assert_eq!( + serde_json::json!(f1).to_string(), + "{\"answer\":{\"file_ack\":\"ok\"}}" + ); } } diff --git a/src/transfer/cancel.rs b/src/transfer/cancel.rs new file mode 100644 index 00000000..cd53e65e --- /dev/null +++ b/src/transfer/cancel.rs @@ -0,0 +1,293 @@ +/// Various helpers to deal with closing connections and cancellation +use super::*; +use crate::util; +use futures::Future; + +/// A weird mixture of [`futures::future::Abortable`], [`async_std::sync::Condvar`] and [`futures::future::Select`] tailored to our Ctrl+C handling. +/// +/// At it's core, it is an `Abortable` but instead of having an `AbortHandle`, we use a future that resolves as trigger. +/// Under the hood, it is implementing the same functionality as a `select`, but mapping one of the outcomes to an error type. +pub async fn cancellable( + future: impl Future + Unpin, + cancel: impl Future, +) -> Result { + use futures::future::Either; + futures::pin_mut!(cancel); + match futures::future::select(cancel, future).await { + Either::Left(((), _)) => Err(Cancelled), + Either::Right((val, _)) => Ok(val), + } +} + +/** Like `cancellable`, but you'll get back the cancellation future in case the code terminates for future use */ +pub async fn cancellable_2 + Unpin>( + future: impl Future + Unpin, + cancel: C, +) -> Result<(T, C), Cancelled> { + use futures::future::Either; + match futures::future::select(cancel, future).await { + Either::Left(((), _)) => Err(Cancelled), + Either::Right((val, cancel)) => Ok((val, cancel)), + } +} + +/// Indicator that the [`Cancellable`] task was cancelled. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Cancelled; + +impl std::fmt::Display for Cancelled { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Task has been cancelled") + } +} + +/// Maximum duration that we are willing to wait for cleanup tasks to finish +const SHUTDOWN_TIME: std::time::Duration = std::time::Duration::from_secs(5); + +// TODO make function once possible (Rust language limitations etc.) +macro_rules! with_cancel_wormhole { + ($wormhole:ident, run = $run:expr, $cancel:expr, ret_cancel = $ret_cancel:expr $(,)?) => {{ + let run = Box::pin($run); + let result = cancel::cancellable_2(run, $cancel).await; + let Some((transit, wormhole, cancel)) = cancel::handle_run_result_noclose($wormhole, result).await? else { return Ok($ret_cancel); }; + (transit, wormhole, cancel) + }}; +} + +// Make macro public +pub(super) use with_cancel_wormhole; + +// Rustfmt has a bug where it will indent a few lines again and again and again and again and again anda +#[rustfmt::skip] +macro_rules! with_cancel_transit { + ($transit:ident, run = $run:expr, $cancel:expr, $make_error_message:expr, $parse_message:expr, ret_cancel = $ret_cancel:expr $(,)?) => {{ + let run = Box::pin($run); + let result = cancel::cancellable_2(run, $cancel).await; + let Some((value, transit)) = cancel::handle_run_result_transit( + $transit, + result, + $make_error_message, + $parse_message, + ).await? else { return Ok($ret_cancel); }; + (value, transit) + }}; +} + +// Make macro public +pub(super) use with_cancel_transit; + +/// Run a future with timeout and cancellation, ignore errors +async fn wrap_timeout(run: impl Future, cancel: impl Future) { + let run = util::timeout(SHUTDOWN_TIME, run); + futures::pin_mut!(run); + match cancellable(run, cancel).await { + Ok(Ok(())) => {}, + Ok(Err(_timeout)) => log::debug!("Post-transfer timed out"), + Err(_cancelled) => log::debug!("Post-transfer got cancelled by user"), + }; +} + +/// Ignore an error but at least debug print it +fn debug_err(result: Result<(), impl std::fmt::Display>, operation: &str) { + if let Err(error) = result { + log::debug!("Failed to {} after transfer: {}", operation, error); + } +} + +/** Handle the post-{transfer, failure, cancellation} logic, then close the Wormhole */ +pub async fn handle_run_result( + wormhole: Wormhole, + result: Result<(Result<(), TransferError>, impl Future), Cancelled>, +) -> Result<(), TransferError> { + match handle_run_result_noclose(wormhole, result).await { + Ok(Some(((), wormhole, cancel))) => { + /* Happy case: everything went okay. Now close the wormholhe */ + log::debug!("Transfer done, doing cleanup logic"); + wrap_timeout( + async { + debug_err(wormhole.close().await, "close Wormhole"); + }, + cancel, + ) + .await; + Ok(()) + }, + Ok(None) => Ok(()), + Err(e) => Err(e), + } +} + +/** Handle the post-{transfer, failure, cancellation} logic */ +pub async fn handle_run_result_noclose>( + mut wormhole: Wormhole, + result: Result<(Result, C), Cancelled>, +) -> Result, TransferError> { + match result { + /* Happy case: everything went okay */ + Ok((Ok(val), cancel)) => Ok(Some((val, wormhole, cancel))), + /* Got peer error: stop everything immediately */ + Ok((Err(error @ TransferError::PeerError(_)), cancel)) => { + log::debug!( + "Transfer encountered an error ({}), doing cleanup logic", + error + ); + wrap_timeout( + async { + debug_err(wormhole.close().await, "close Wormhole"); + }, + cancel, + ) + .await; + Err(error) + }, + /* Got transit error: try to receive peer error for better error message */ + Ok((Err(mut error @ TransferError::Transit(_)), cancel)) => { + log::debug!( + "Transfer encountered an error ({}), doing cleanup logic", + error + ); + wrap_timeout(async { + /* If transit failed, ask for a proper error and potentially use that instead */ + // TODO this should be replaced with some try_receive that only polls already available messages, + // and we should not only look for the next one but all have been received + // and we should not interrupt a receive operation without making sure it leaves the connection + // in a consistent state, otherwise the shutdown may cause protocol errors + if let Ok(Ok(Ok(PeerMessage::Error(e)))) = util::timeout(SHUTDOWN_TIME / 3, wormhole.receive_json()).await { + error = TransferError::PeerError(e); + } else { + log::debug!("Failed to retrieve more specific error message from peer. Maybe it crashed?"); + } + debug_err(wormhole.close().await, "close Wormhole"); + }, cancel).await; + Err(error) + }, + /* Other error: try to notify peer */ + Ok((Err(error), cancel)) => { + log::debug!( + "Transfer encountered an error ({}), doing cleanup logic", + error + ); + wrap_timeout( + async { + debug_err( + wormhole + .send_json(&PeerMessage::Error(format!("{}", error))) + .await, + "notify peer about the error", + ); + debug_err(wormhole.close().await, "close Wormhole"); + }, + cancel, + ) + .await; + Err(error) + }, + /* Cancelled: try to notify peer */ + Err(cancelled) => { + log::debug!("Transfer got cancelled, doing cleanup logic"); + /* Replace cancel with ever-pending future, as we have already been cancelled */ + wrap_timeout( + async { + debug_err( + wormhole + .send_json(&PeerMessage::Error(format!("{}", cancelled))) + .await, + "notify peer about our cancellation", + ); + debug_err(wormhole.close().await, "close Wormhole"); + }, + futures::future::pending(), + ) + .await; + Ok(None) + }, + } +} + +/** + * Handle the post-{transfer, failure, cancellation} logic where the error signaling is done over the transit channel + */ +pub async fn handle_run_result_transit( + mut transit: transit::Transit, + result: Result<(Result, impl Future), Cancelled>, + make_error_message: impl FnOnce(&(dyn std::string::ToString + Sync)) -> Vec, + parse_message: impl Fn(&[u8]) -> Result, TransferError>, +) -> Result, TransferError> { + match result { + /* Happy case: everything went okay */ + Ok((Ok(val), _cancel)) => Ok(Some((val, transit))), + /* Got peer error: stop everything immediately */ + Ok((Err(error @ TransferError::PeerError(_)), _cancel)) => { + log::debug!( + "Transfer encountered an error ({}), doing cleanup logic", + error + ); + Err(error) + }, + /* Got transit error: try to receive peer error for better error message */ + Ok((Err(mut error @ TransferError::Transit(_)), cancel)) => { + log::debug!( + "Transfer encountered an error ({}), doing cleanup logic", + error + ); + wrap_timeout( + async { + /* Receive one peer message to see if they sent some error prior to closing + * (Note that this will only happen if we noticed the closed connection while trying to send, + * otherwise receiving will already yield the error message). + */ + loop { + let Ok(msg) = transit.receive_record().await else { + break; + }; + match parse_message(&msg) { + Ok(None) => continue, + Ok(Some(err)) => { + error = TransferError::PeerError(err); + break; + }, + Err(_) => break, + } + } + }, + cancel, + ) + .await; + Err(error) + }, + /* Other error: try to notify peer */ + Ok((Err(error), cancel)) => { + log::debug!( + "Transfer encountered an error ({}), doing cleanup logic", + error + ); + wrap_timeout( + async { + debug_err( + transit.send_record(&make_error_message(&error)).await, + "notify peer about the error", + ); + }, + cancel, + ) + .await; + Err(error) + }, + /* Cancelled: try to notify peer */ + Err(cancelled) => { + log::debug!("Transfer got cancelled, doing cleanup logic"); + /* Replace cancel with ever-pending future, as we have already been cancelled */ + wrap_timeout( + async { + debug_err( + transit.send_record(&make_error_message(&cancelled)).await, + "notify peer about our cancellation", + ); + }, + futures::future::pending(), + ) + .await; + Ok(None) + }, + } +} diff --git a/src/transfer/messages.rs b/src/transfer/messages.rs deleted file mode 100644 index 33f79ecd..00000000 --- a/src/transfer/messages.rs +++ /dev/null @@ -1,252 +0,0 @@ -//! Over-the-wire messages for the file transfer (including transit) -//! -//! The transit protocol does not specify how to deliver the information to -//! the other side, so it is up to the file transfer to do that. hfoo - -use crate::transit::{self, Abilities as TransitAbilities}; -use serde_derive::{Deserialize, Serialize}; -use std::{collections::HashMap, path::PathBuf}; - -/** - * The type of message exchanged over the wormhole for this protocol - */ -#[derive(Deserialize, Serialize, Debug)] -#[serde(rename_all = "kebab-case")] -#[non_exhaustive] -pub enum PeerMessage { - Offer(Offer), - OfferV2(OfferV2), - Answer(Answer), - AnswerV2(AnswerV2), - /** Tell the other side you got an error */ - Error(String), - /** Used to set up a transit channel */ - Transit(TransitV1), - TransitV2(TransitV2), - #[serde(other)] - Unknown, -} - -impl PeerMessage { - pub fn offer_message(msg: impl Into) -> Self { - PeerMessage::Offer(Offer::Message(msg.into())) - } - - pub fn offer_file(name: impl Into, size: u64) -> Self { - PeerMessage::Offer(Offer::File { - filename: name.into(), - filesize: size, - }) - } - - #[allow(dead_code)] - pub fn offer_directory( - name: impl Into, - mode: impl Into, - compressed_size: u64, - numbytes: u64, - numfiles: u64, - ) -> Self { - PeerMessage::Offer(Offer::Directory { - dirname: name.into(), - mode: mode.into(), - zipsize: compressed_size, - numbytes, - numfiles, - }) - } - - #[allow(dead_code)] - pub fn message_ack(msg: impl Into) -> Self { - PeerMessage::Answer(Answer::MessageAck(msg.into())) - } - - pub fn file_ack(msg: impl Into) -> Self { - PeerMessage::Answer(Answer::FileAck(msg.into())) - } - - pub fn error_message(msg: impl Into) -> Self { - PeerMessage::Error(msg.into()) - } - - pub fn transit(abilities: TransitAbilities, hints: transit::Hints) -> Self { - PeerMessage::Transit(TransitV1 { - abilities_v1: abilities, - hints_v1: hints, - }) - } - - #[allow(dead_code)] - pub fn transit_v2(hints: transit::Hints) -> Self { - PeerMessage::TransitV2(TransitV2 { hints }) - } - - #[allow(dead_code)] - pub fn ser_json(&self) -> Vec { - serde_json::to_vec(self).unwrap() - } - - #[allow(dead_code)] - pub fn ser_msgpack(&self) -> Vec { - let mut writer = Vec::with_capacity(128); - let mut ser = rmp_serde::encode::Serializer::new(&mut writer) - .with_struct_map() - .with_human_readable(); - serde::Serialize::serialize(self, &mut ser).unwrap(); - writer - } - - #[allow(dead_code)] - pub fn de_msgpack(data: &[u8]) -> Result { - rmp_serde::from_read(&mut &*data) - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename_all = "kebab-case")] -pub enum Offer { - Message(String), - File { - filename: PathBuf, - filesize: u64, - }, - Directory { - dirname: PathBuf, - mode: String, - zipsize: u64, - numbytes: u64, - numfiles: u64, - }, - #[serde(other)] - Unknown, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename_all = "kebab-case")] -pub struct OfferV2 { - transfer_name: Option, - files: Vec, - format: String, // TODO use custom enum? -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename_all = "kebab-case")] -pub struct OfferV2Entry { - path: String, - size: u64, - mtime: u64, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum Answer { - MessageAck(String), - FileAck(String), -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename_all = "kebab-case")] -pub struct AnswerV2 { - files: HashMap, -} - -/** - * A set of hints for both sides to find each other - */ -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "kebab-case")] -pub struct TransitV1 { - pub abilities_v1: TransitAbilities, - pub hints_v1: transit::Hints, -} - -/** - * A set of hints for both sides to find each other - */ -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "kebab-case")] -pub struct TransitV2 { - pub hints: transit::Hints, -} - -#[cfg(test)] -mod test { - use super::*; - use transit::{Abilities, DirectHint, RelayHint}; - - #[test] - fn test_transit() { - let abilities = Abilities::ALL_ABILITIES; - let hints = transit::Hints::new( - [DirectHint::new("192.168.1.8", 46295)], - [RelayHint::new( - None, - [DirectHint::new("magic-wormhole-transit.debian.net", 4001)], - [], - )], - ); - assert_eq!( - serde_json::json!(crate::transfer::PeerMessage::transit(abilities, hints)), - serde_json::json!({ - "transit": { - "abilities-v1": [{"type":"direct-tcp-v1"},{"type":"relay-v1"}], - "hints-v1": [ - {"hostname":"192.168.1.8","port":46295,"type":"direct-tcp-v1"}, - { - "type": "relay-v1", - "hints": [ - {"type": "direct-tcp-v1", "hostname": "magic-wormhole-transit.debian.net", "port": 4001} - ], - "name": null - } - ], - } - }) - ); - } - - #[test] - fn test_message() { - let m1 = PeerMessage::offer_message("hello from rust"); - assert_eq!( - serde_json::json!(m1).to_string(), - "{\"offer\":{\"message\":\"hello from rust\"}}" - ); - } - - #[test] - fn test_offer_file() { - let f1 = PeerMessage::offer_file("somefile.txt", 34556); - assert_eq!( - serde_json::json!(f1).to_string(), - "{\"offer\":{\"file\":{\"filename\":\"somefile.txt\",\"filesize\":34556}}}" - ); - } - - #[test] - fn test_offer_directory() { - let d1 = PeerMessage::offer_directory("somedirectory", "zipped", 45, 1234, 10); - assert_eq!( - serde_json::json!(d1).to_string(), - "{\"offer\":{\"directory\":{\"dirname\":\"somedirectory\",\"mode\":\"zipped\",\"numbytes\":1234,\"numfiles\":10,\"zipsize\":45}}}" - ); - } - - #[test] - fn test_message_ack() { - let m1 = PeerMessage::message_ack("ok"); - assert_eq!( - serde_json::json!(m1).to_string(), - "{\"answer\":{\"message_ack\":\"ok\"}}" - ); - } - - #[test] - fn test_file_ack() { - let f1 = PeerMessage::file_ack("ok"); - assert_eq!( - serde_json::json!(f1).to_string(), - "{\"answer\":{\"file_ack\":\"ok\"}}" - ); - } -} diff --git a/src/transfer/v1.rs b/src/transfer/v1.rs index 15e31c17..2cfc0e7e 100644 --- a/src/transfer/v1.rs +++ b/src/transfer/v1.rs @@ -1,15 +1,141 @@ -use futures::{AsyncReadExt, AsyncWriteExt}; -use log::*; +use futures::{ + io::{AsyncReadExt, AsyncWriteExt}, + StreamExt, TryFutureExt, +}; use sha2::{digest::FixedOutput, Digest, Sha256}; -use std::path::PathBuf; use super::*; -pub async fn send_file( +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(rename_all = "kebab-case")] +pub enum OfferMessage { + Message(String), + File { + filename: String, + filesize: u64, + }, + Directory { + dirname: String, + mode: String, + zipsize: u64, + numbytes: u64, + numfiles: u64, + }, + #[serde(other)] + Unknown, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum AnswerMessage { + MessageAck(String), + FileAck(String), +} + +/** + * A set of hints for both sides to find each other + */ +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TransitV1 { + pub abilities_v1: TransitAbilities, + pub hints_v1: transit::Hints, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "kebab-case")] +struct TransitAck { + pub ack: String, + pub sha256: String, +} + +impl TransitAck { + pub fn new(msg: impl Into, sha256: impl Into) -> Self { + TransitAck { + ack: msg.into(), + sha256: sha256.into(), + } + } + + #[cfg(test)] + pub fn serialize(&self) -> String { + json!(self).to_string() + } + + pub fn serialize_vec(&self) -> Vec { + serde_json::to_vec(self).unwrap() + } +} + +pub async fn send( + wormhole: Wormhole, + relay_hints: Vec, + transit_abilities: transit::Abilities, + offer: OfferSend, + progress_handler: impl FnMut(u64, u64) + 'static, + transit_handler: impl FnOnce(transit::TransitInfo), + _peer_version: AppVersion, + cancel: impl Future, +) -> Result<(), TransferError> { + if offer.is_multiple() { + let folder = OfferSendEntry::Directory { + content: offer.content, + }; + send_folder( + wormhole, + relay_hints, + "".into(), + folder, + transit_abilities, + transit_handler, + progress_handler, + cancel, + ) + .await + } else if offer.is_directory() { + let (folder_name, folder) = offer.content.into_iter().next().unwrap(); + send_folder( + wormhole, + relay_hints, + folder_name, + folder, + transit_abilities, + transit_handler, + progress_handler, + cancel, + ) + .await + } else { + let (file_name, file) = offer.content.into_iter().next().unwrap(); + let (mut file, file_size) = match file { + OfferSendEntry::RegularFile { content, size } => { + /* This must be split into two statements to appease the borrow checker (unfortunate side effect of borrow-through) */ + let content = content(); + let content = content.await?; + (content, size) + }, + _ => unreachable!(), + }; + send_file( + wormhole, + relay_hints, + &mut file, + file_name, + file_size, + transit_abilities, + transit_handler, + progress_handler, + cancel, + ) + .await + } +} + +pub async fn send_file( mut wormhole: Wormhole, relay_hints: Vec, file: &mut F, - file_name: N, + file_name: impl Into, file_size: u64, transit_abilities: transit::Abilities, transit_handler: G, @@ -17,8 +143,7 @@ pub async fn send_file( cancel: impl Future, ) -> Result<(), TransferError> where - F: AsyncRead + Unpin, - N: Into, + F: AsyncRead + Unpin + Send, G: FnOnce(transit::TransitInfo), H: FnMut(u64, u64) + 'static, { @@ -28,7 +153,7 @@ where // We want to do some transit debug!("Sending transit message '{:?}", connector.our_hints()); wormhole - .send_json(&PeerMessage::transit( + .send_json(&PeerMessage::transit_v1( *connector.our_abilities(), (**connector.our_hints()).clone(), )) @@ -37,19 +162,16 @@ where // Send file offer message. debug!("Sending file offer"); wormhole - .send_json(&PeerMessage::offer_file(file_name, file_size)) + .send_json(&PeerMessage::offer_file_v1(file_name, file_size)) .await?; // Wait for their transit response let (their_abilities, their_hints): (transit::Abilities, transit::Hints) = - match wormhole.receive_json().await?? { + match wormhole.receive_json::().await??.check_err()? { PeerMessage::Transit(transit) => { debug!("Received transit message: {:?}", transit); (transit.abilities_v1, transit.hints_v1) }, - PeerMessage::Error(err) => { - bail!(TransferError::PeerError(err)); - }, other => { bail!(TransferError::unexpected_message("transit", other)) }, @@ -57,16 +179,13 @@ where { // Wait for file_ack - let fileack_msg = wormhole.receive_json().await??; + let fileack_msg = wormhole.receive_json::().await??; debug!("Received file ack message: {:?}", fileack_msg); - match fileack_msg { - PeerMessage::Answer(Answer::FileAck(msg)) => { + match fileack_msg.check_err()? { + PeerMessage::Answer(AnswerMessage::FileAck(msg)) => { ensure!(msg == "ok", TransferError::AckError); }, - PeerMessage::Error(err) => { - bail!(TransferError::PeerError(err)); - }, _ => { bail!(TransferError::unexpected_message( "answer/file_ack", @@ -88,6 +207,9 @@ where debug!("Beginning file transfer"); // 11. send the file as encrypted records. + let file = futures::stream::once(futures::future::ready(std::io::Result::Ok( + Box::new(file) as Box, + ))); let checksum = v1::send_records(&mut transit, file, file_size, progress_handler).await?; // 13. wait for the transit ack with sha256 sum from the peer. @@ -104,122 +226,138 @@ where }); futures::pin_mut!(cancel); - let result = crate::util::cancellable_2(run, cancel).await; - super::handle_run_result(wormhole, result).await + let result = cancel::cancellable_2(run, cancel).await; + cancel::handle_run_result(wormhole, result).await } -#[cfg(not(target_family = "wasm"))] -pub async fn send_folder( +pub async fn send_folder( mut wormhole: Wormhole, relay_hints: Vec, - folder_path: N, - folder_name: M, + mut folder_name: String, + folder: OfferSendEntry, transit_abilities: transit::Abilities, - transit_handler: G, - progress_handler: H, + transit_handler: impl FnOnce(transit::TransitInfo), + progress_handler: impl FnMut(u64, u64) + 'static, cancel: impl Future, -) -> Result<(), TransferError> -where - N: Into, - M: Into, - G: FnOnce(transit::TransitInfo), - H: FnMut(u64, u64) + 'static, -{ +) -> Result<(), TransferError> { let run = Box::pin(async { let connector = transit::init(transit_abilities, None, relay_hints).await?; - let folder_path = folder_path.into(); - - if !folder_path.is_dir() { - panic!( - "You should only call this method with directory paths, but '{}' is not", - folder_path.display() - ); - } // We want to do some transit debug!("Sending transit message '{:?}", connector.our_hints()); wormhole - .send_json(&PeerMessage::transit( + .send_json(&PeerMessage::transit_v1( *connector.our_abilities(), (**connector.our_hints()).clone(), )) .await?; - // use sha2::{digest::FixedOutput, Digest, Sha256}; + /* We need to know the length of what we are going to send in advance. So we already build + * all the headers of our file now but without the contents. We know that a file is + * header + contents + padding + */ + log::debug!("Estimating the file size"); - /* Helper struct stolen from https://docs.rs/count-write/0.1.0 */ - struct CountWrite { - inner: W, - count: u64, + // TODO try again but without pinning + use futures::{ + future::{ready, BoxFuture}, + io::Cursor, + }; + use std::io::Result as IoResult; + + /* Type tetris :) */ + fn wrap( + buffer: impl AsRef<[u8]> + Unpin + Send + 'static, + ) -> BoxFuture<'static, IoResult>> { + Box::pin(ready(IoResult::Ok( + Box::new(Cursor::new(buffer)) as Box + ))) as _ } - impl std::io::Write for CountWrite { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - let written = self.inner.write(buf)?; - self.count += written as u64; - Ok(written) - } - - fn flush(&mut self) -> std::io::Result<()> { - self.inner.flush() + /* Walk our offer recursively, concatenate all our readers into a stream that will build the tar file */ + fn create_offer( + mut total_content: Vec< + BoxFuture<'static, IoResult>>, + >, + total_size: &mut u64, + offer: OfferSendEntry, + path: &mut Vec, + ) -> IoResult>>>> + { + match offer { + OfferSendEntry::Directory { content } => { + log::debug!("Adding directory {path:?}"); + let header = tar_helper::create_header_directory(path)?; + *total_size += header.len() as u64; + total_content.push(wrap(header)); + + for (name, file) in content { + path.push(name); + total_content = create_offer(total_content, total_size, file, path)?; + path.pop(); + } + }, + OfferSendEntry::RegularFile { size, content } => { + log::debug!("Adding file {path:?}; {size} bytes"); + let header = tar_helper::create_header_file(&path, size)?; + let padding = tar_helper::padding(size); + *total_size += header.len() as u64; + *total_size += padding.len() as u64; + *total_size += size; + + total_content.push(wrap(header)); + let content = content().map_ok( + /* Re-box because we can't upcast trait objects */ + |read| Box::new(read) as Box, + ); + total_content.push(Box::pin(content) as _); + total_content.push(wrap(padding)); + }, + OfferSendEntry::Symlink { .. } => todo!(), } + Ok(total_content) } - /* We need to know the length of what we are going to send in advance. So we build the - * tar file once, stream it into the void, and the second time we stream it over the - * wire. Also hashing for future reference. - */ - log::info!("Calculating the size of '{}'", folder_path.display()); - let folder_path2 = folder_path.clone(); - let (length, sha256sum_initial) = { - let mut hasher = Sha256::new(); - let mut counter = CountWrite { - inner: &mut hasher, - count: 0, - }; - let mut builder = async_tar::Builder::new(futures::io::AllowStdIo::new(&mut counter)); - - builder.mode(async_tar::HeaderMode::Deterministic); - builder.follow_symlinks(false); - /* A hasher should never fail writing */ - builder.append_dir_all("", folder_path2).await.unwrap(); - builder.finish().await.unwrap(); - - std::mem::drop(builder); - let count = counter.count; - std::mem::drop(counter); - (count, hasher.finalize_fixed()) - }; + let mut total_size = 0; + let mut content = create_offer( + Vec::new(), + &mut total_size, + folder, + &mut vec![folder_name.clone()], + )?; + + /* Finish tar file */ + total_size += 1024; + content.push(wrap([0; 1024])); + + let content = futures::stream::iter(content).then(|content| async { content.await }); + + /* Convert to stream */ // Send file offer message. - debug!("Sending file offer"); + log::debug!("Sending file offer ({total_size} bytes)"); + folder_name.push_str(".tar"); wormhole - .send_json(&PeerMessage::offer_file(folder_name, length)) + .send_json(&PeerMessage::offer_file_v1(folder_name, total_size)) .await?; // Wait for their transit response let (their_abilities, their_hints): (transit::Abilities, transit::Hints) = - match wormhole.receive_json().await?? { + match wormhole.receive_json::().await??.check_err()? { PeerMessage::Transit(transit) => { debug!("received transit message: {:?}", transit); (transit.abilities_v1, transit.hints_v1) }, - PeerMessage::Error(err) => { - bail!(TransferError::PeerError(err)); - }, other => { bail!(TransferError::unexpected_message("transit", other)); }, }; // Wait for file_ack - match wormhole.receive_json().await?? { - PeerMessage::Answer(Answer::FileAck(msg)) => { + match wormhole.receive_json::().await??.check_err()? { + PeerMessage::Answer(AnswerMessage::FileAck(msg)) => { ensure!(msg == "ok", TransferError::AckError); }, - PeerMessage::Error(err) => { - bail!(TransferError::PeerError(err)); - }, other => { bail!(TransferError::unexpected_message("answer/file_ack", other)); }, @@ -236,91 +374,9 @@ where debug!("Beginning file transfer"); - /* Inspired by https://github.com/RustCrypto/traits/pull/1159/files */ - pub struct HashWriter { - writer: W, - hasher: D, - } - - use std::{ - pin::Pin, - task::{Context, Poll}, - }; - impl - futures::io::AsyncWrite for HashWriter - { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - // log::debug!("Poll write, {}", buf.len()); - match Pin::new(&mut self.writer).poll_write(cx, buf) { - Poll::Ready(Ok(n)) => { - self.hasher.update(&buf[..n]); - Poll::Ready(Ok(n)) - }, - res => res, - } - } - - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - // log::debug!("Poll flush"); - Pin::new(&mut self.writer).poll_flush(cx) - } - - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - // log::debug!("Poll close"); - Pin::new(&mut self.writer).poll_close(cx) - } - } - // 11. send the file as encrypted records. - let (mut reader, writer) = futures_ringbuf::RingBuffer::new(4096).split(); - - let file_sender = async_std::task::spawn(async move { - let mut hash_writer = HashWriter { - writer, - hasher: Sha256::new(), - }; - let mut builder = async_tar::Builder::new(&mut hash_writer); - - builder.mode(async_tar::HeaderMode::Deterministic); - builder.follow_symlinks(false); - builder.append_dir_all("", folder_path).await?; - builder.finish().await?; - std::mem::drop(builder); - - hash_writer.flush().await?; - hash_writer.close().await?; - let hasher = hash_writer.hasher; - - std::io::Result::Ok(hasher.finalize_fixed()) - }); - - let (checksum, sha256sum) = - match v1::send_records(&mut transit, &mut reader, length, progress_handler).await { - Ok(checksum) => (checksum, file_sender.await?), - Err(err) => { - log::debug!("Some more error {err}"); - if let Some(Err(err)) = file_sender.cancel().await { - log::warn!("Error in background task: {err}"); - } - return Err(err); - }, - }; - - /* Check if the hash sum still matches what we advertized. Otherwise, tell the other side and bail out */ - ensure!( - sha256sum == sha256sum_initial, - TransferError::FilesystemSkew - ); + let checksum = + v1::send_records(&mut transit, content, total_size, progress_handler).await?; // 13. wait for the transit ack with sha256 sum from the peer. debug!("sent file. Waiting for ack"); @@ -336,21 +392,184 @@ where }); futures::pin_mut!(cancel); - let result = crate::util::cancellable_2(run, cancel).await; - super::handle_run_result(wormhole, result).await + let result = cancel::cancellable_2(run, cancel).await; + cancel::handle_run_result(wormhole, result).await +} + +/** + * Wait for a file offer from the other side + * + * This method waits for an offer message and builds up a [`ReceiveRequest`](ReceiveRequest). + * It will also start building a TCP connection to the other side using the transit protocol. + * + * Returns `None` if the task got cancelled. + */ +pub async fn request( + mut wormhole: Wormhole, + relay_hints: Vec, + transit_abilities: transit::Abilities, + cancel: impl Future, +) -> Result, TransferError> { + // Error handling + let run = Box::pin(async { + let connector = transit::init(transit_abilities, None, relay_hints).await?; + + // send the transit message + debug!("Sending transit message '{:?}", connector.our_hints()); + wormhole + .send_json(&PeerMessage::transit_v1( + *connector.our_abilities(), + (**connector.our_hints()).clone(), + )) + .await?; + + // receive transit message + let (their_abilities, their_hints): (transit::Abilities, transit::Hints) = + match wormhole.receive_json::().await??.check_err()? { + PeerMessage::Transit(transit) => { + debug!("received transit message: {:?}", transit); + (transit.abilities_v1, transit.hints_v1) + }, + other => { + bail!(TransferError::unexpected_message("transit", other)); + }, + }; + + // 3. receive file offer message from peer + let (filename, filesize) = + match wormhole.receive_json::().await??.check_err()? { + PeerMessage::Offer(offer_type) => match offer_type { + v1::OfferMessage::File { filename, filesize } => (filename, filesize), + v1::OfferMessage::Directory { + mut dirname, + zipsize, + .. + } => { + dirname.push_str(".zip"); + (dirname, zipsize) + }, + _ => bail!(TransferError::UnsupportedOffer), + }, + other => { + bail!(TransferError::unexpected_message("offer", other)); + }, + }; + + Ok((filename, filesize, connector, their_abilities, their_hints)) + }); + + futures::pin_mut!(cancel); + let result = cancel::cancellable_2(run, cancel).await; + cancel::handle_run_result_noclose(wormhole, result) + .await + .map(|inner: Option<_>| { + inner.map( + |((filename, filesize, connector, their_abilities, their_hints), wormhole, _)| { + ReceiveRequest { + wormhole, + filename, + filesize, + connector, + their_abilities, + their_hints: Arc::new(their_hints), + } + }, + ) + }) +} + +/** + * A pending files send offer from the other side + * + * You *should* consume this object, either by calling [`accept`](ReceiveRequest::accept) or [`reject`](ReceiveRequest::reject). + */ +#[must_use] +pub struct ReceiveRequest { + wormhole: Wormhole, + connector: TransitConnector, + /// **Security warning:** this is untrusted and unverified input + pub filename: String, + pub filesize: u64, + their_abilities: transit::Abilities, + their_hints: Arc, +} + +impl ReceiveRequest { + /** + * Accept the file offer + * + * This will transfer the file and save it on disk. + */ + pub async fn accept( + mut self, + transit_handler: G, + content_handler: &mut W, + progress_handler: F, + cancel: impl Future, + ) -> Result<(), TransferError> + where + F: FnMut(u64, u64) + 'static, + G: FnOnce(transit::TransitInfo), + W: AsyncWrite + Unpin, + { + let run = Box::pin(async { + // send file ack. + debug!("Sending ack"); + self.wormhole + .send_json(&PeerMessage::file_ack_v1("ok")) + .await?; + + let (mut transit, info) = self + .connector + .follower_connect( + self.wormhole + .key() + .derive_transit_key(self.wormhole.appid()), + self.their_abilities, + self.their_hints.clone(), + ) + .await?; + transit_handler(info); + + debug!("Beginning file transfer"); + tcp_file_receive( + &mut transit, + self.filesize, + progress_handler, + content_handler, + ) + .await?; + Ok(()) + }); + + futures::pin_mut!(cancel); + let result = cancel::cancellable_2(run, cancel).await; + cancel::handle_run_result(self.wormhole, result).await + } + + /** + * Reject the file offer + * + * This will send an error message to the other side so that it knows the transfer failed. + */ + pub async fn reject(mut self) -> Result<(), TransferError> { + self.wormhole + .send_json(&PeerMessage::error_message("transfer rejected")) + .await?; + self.wormhole.close().await?; + + Ok(()) + } } // encrypt and send the file to tcp stream and return the sha256 sum // of the file before encryption. -pub async fn send_records( +pub async fn send_records<'a>( transit: &mut Transit, - file: &mut (impl AsyncRead + Unpin), + files: impl futures::Stream>>, file_size: u64, - mut progress_handler: F, -) -> Result, TransferError> -where - F: FnMut(u64, u64) + 'static, -{ + mut progress_handler: impl FnMut(u64, u64) + 'static, +) -> Result, TransferError> { // rough plan: // 1. Open the file // 2. read a block of N bytes @@ -368,28 +587,30 @@ where // Yeah, maybe don't allocate 4kiB on the stack… let mut plaintext = Box::new([0u8; 4096]); let mut sent_size = 0; - loop { - // read a block of up to 4096 bytes - let n = file.read(&mut plaintext[..]).await?; - log::debug!("Read {n}"); - - if n == 0 { - // EOF - break; - } + futures::pin_mut!(files); + while let Some(mut file) = files.next().await.transpose()? { + loop { + // read a block of up to 4096 bytes + let n = file.read(&mut plaintext[..]).await?; + + if n == 0 { + // EOF + break; + } - // send the encrypted record - transit.send_record(&plaintext[0..n]).await?; - sent_size += n as u64; - progress_handler(sent_size, file_size); + // send the encrypted record + transit.send_record(&plaintext[0..n]).await?; + sent_size += n as u64; + progress_handler(sent_size, file_size); - // sha256 of the input - hasher.update(&plaintext[..n]); + // sha256 of the input + hasher.update(&plaintext[..n]); - /* Don't do this. The EOF check above is sufficient */ - // if n < 4096 { - // break; - // } + /* Don't do this. The EOF check above is sufficient */ + // if n < 4096 { + // break; + // } + } } transit.flush().await?; @@ -408,7 +629,7 @@ pub async fn receive_records( filesize: u64, transit: &mut Transit, mut progress_handler: F, - content_handler: &mut W, + mut content_handler: W, ) -> Result, TransferError> where F: FnMut(u64, u64) + 'static, @@ -437,6 +658,7 @@ where let remaining = remaining_size as u64; progress_handler(total - remaining, total); } + content_handler.close().await?; debug!("done"); // TODO: 5. write the buffer into a file. @@ -471,3 +693,142 @@ where debug!("Transfer complete"); Ok(()) } + +/// Custom functions from the `tar` crate to access internals +mod tar_helper { + /* Imports may depend on target platform */ + #[allow(unused_imports)] + use std::{ + borrow::Cow, + io::{self, Read, Write}, + path::Path, + str, + }; + + pub fn create_header_file(path: &[String], size: u64) -> std::io::Result> { + let mut header = tar::Header::new_gnu(); + header.set_size(size); + let mut data = Vec::with_capacity(1024); + prepare_header_path(&mut data, &mut header, path.join("/").as_ref())?; + header.set_mode(0o644); + header.set_cksum(); + data.write_all(header.as_bytes())?; + Ok(data) + } + + pub fn create_header_directory(path: &[String]) -> std::io::Result> { + let mut header = tar::Header::new_gnu(); + header.set_entry_type(tar::EntryType::Directory); + let mut data = Vec::with_capacity(1024); + prepare_header_path(&mut data, &mut header, path.join("/").as_ref())?; + header.set_mode(0o755); + header.set_cksum(); + data.write_all(header.as_bytes())?; + // append(&mut data, header, data)?; + Ok(data) + } + + pub fn padding(size: u64) -> &'static [u8] { + const BLOCK: [u8; 512] = [0; 512]; + if size % 512 != 0 { + &BLOCK[size as usize % 512..] + } else { + &[] + } + } + + fn append( + mut dst: &mut dyn std::io::Write, + header: &tar::Header, + mut data: &mut dyn std::io::Read, + ) -> std::io::Result<()> { + dst.write_all(header.as_bytes())?; + let len = std::io::copy(&mut data, &mut dst)?; + dst.write_all(padding(len))?; + Ok(()) + } + + fn prepare_header(size: u64, entry_type: u8) -> tar::Header { + let mut header = tar::Header::new_gnu(); + let name = b"././@LongLink"; + header.as_gnu_mut().unwrap().name[..name.len()].clone_from_slice(&name[..]); + header.set_mode(0o644); + header.set_uid(0); + header.set_gid(0); + header.set_mtime(0); + // + 1 to be compliant with GNU tar + header.set_size(size + 1); + header.set_entry_type(tar::EntryType::new(entry_type)); + header.set_cksum(); + header + } + + fn prepare_header_path( + dst: &mut dyn std::io::Write, + header: &mut tar::Header, + path: &str, + ) -> std::io::Result<()> { + // Try to encode the path directly in the header, but if it ends up not + // working (probably because it's too long) then try to use the GNU-specific + // long name extension by emitting an entry which indicates that it's the + // filename. + if let Err(e) = header.set_path(path) { + let data = path2bytes(&path); + let max = header.as_old().name.len(); + // Since `e` isn't specific enough to let us know the path is indeed too + // long, verify it first before using the extension. + if data.len() < max { + return Err(e); + } + let header2 = prepare_header(data.len() as u64, b'L'); + // null-terminated string + let mut data2 = data.chain(io::repeat(0).take(1)); + append(dst, &header2, &mut data2)?; + + // Truncate the path to store in the header we're about to emit to + // ensure we've got something at least mentioned. Note that we use + // `str`-encoding to be compatible with Windows, but in general the + // entry in the header itself shouldn't matter too much since extraction + // doesn't look at it. + let truncated = match std::str::from_utf8(&data[..max]) { + Ok(s) => s, + Err(e) => std::str::from_utf8(&data[..e.valid_up_to()]).unwrap(), + }; + header.set_path(truncated)?; + } + Ok(()) + } + + #[cfg(any(windows, target_arch = "wasm32"))] + pub fn path2bytes(p: &str) -> Cow<[u8]> { + let bytes = p.as_bytes(); + if bytes.contains(&b'\\') { + // Normalize to Unix-style path separators + let mut bytes = bytes.to_owned(); + for b in &mut bytes { + if *b == b'\\' { + *b = b'/'; + } + } + Cow::Owned(bytes) + } else { + Cow::Borrowed(bytes) + } + } + + #[cfg(unix)] + pub fn path2bytes(p: &str) -> Cow<[u8]> { + Cow::Borrowed(p.as_bytes()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_transit_ack() { + let f1 = TransitAck::new("ok", "deadbeaf"); + assert_eq!(f1.serialize(), "{\"ack\":\"ok\",\"sha256\":\"deadbeaf\"}"); + } +} diff --git a/src/transfer/v2.rs b/src/transfer/v2.rs index 1906c5ef..3de6b1d2 100644 --- a/src/transfer/v2.rs +++ b/src/transfer/v2.rs @@ -1,115 +1,604 @@ -#![allow(dead_code)] +use futures::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; +use serde_derive::{Deserialize, Serialize}; +use sha2::{digest::FixedOutput, Sha256}; use super::*; -#[allow(unused_variables, unused_mut)] -pub async fn send_file( +/** + * A set of hints for both sides to find each other + */ +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TransitV2 { + pub hints_v2: transit::Hints, +} + +/** + * The type of message exchanged over the transit connection, serialized with msgpack + */ +#[derive(Deserialize, Serialize, derive_more::Display, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +#[non_exhaustive] +pub enum PeerMessageV2 { + #[display(fmt = "offer")] + Offer(Offer), + #[display(fmt = "answer")] + Answer(AnswerMessage), + #[display(fmt = "file-start")] + FileStart(FileStart), + #[display(fmt = "payload")] + Payload(Payload), + #[display(fmt = "file-end")] + FileEnd(FileEnd), + #[display(fmt = "transfer-ack")] + TransferAck(TransferAck), + #[display(fmt = "error")] + Error(String), + #[display(fmt = "unknown")] + #[serde(other)] + Unknown, +} + +impl PeerMessageV2 { + pub fn ser_msgpack(&self) -> Vec { + let mut writer = Vec::with_capacity(128); + let mut ser = rmp_serde::encode::Serializer::new(&mut writer) + .with_struct_map() + .with_human_readable(); + serde::Serialize::serialize(self, &mut ser).unwrap(); + writer + } + + pub fn de_msgpack(data: &[u8]) -> Result { + rmp_serde::from_read(&mut &*data) + } + + pub fn check_err(self) -> Result { + match self { + Self::Error(err) => Err(TransferError::PeerError(err)), + other => Ok(other), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "kebab-case")] +pub struct AnswerMessage { + pub(self) files: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "kebab-case")] +pub(self) struct AnswerMessageInner { + pub file: Vec, + pub offset: u64, + pub sha256: Option<[u8; 32]>, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct FileStart { + pub file: Vec, + pub start_at_offset: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct Payload { + payload: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct FileEnd {} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TransferAck {} + +/** The code to establish a transit connection is essentially the same on both sides. */ +async fn make_transit( + wormhole: &mut Wormhole, + is_leader: bool, + relay_hints: Vec, + transit_abilities: transit::Abilities, + peer_abilities: transit::Abilities, +) -> Result<(transit::Transit, transit::TransitInfo), TransferError> { + let connector = transit::init(transit_abilities, Some(peer_abilities), relay_hints).await?; + + /* Send our transit hints */ + wormhole + .send_json(&PeerMessage::transit_v2( + (**connector.our_hints()).clone().into(), + )) + .await?; + + /* Receive their transit hints */ + let their_hints: transit::Hints = + match wormhole.receive_json::().await??.check_err()? { + PeerMessage::TransitV2(transit) => { + debug!("received transit message: {:?}", transit); + transit.hints_v2.into() + }, + other => { + let error = TransferError::unexpected_message("transit-v2", other); + let _ = wormhole + .send_json(&PeerMessage::Error(format!("{}", error))) + .await; + bail!(error) + }, + }; + + /* Get a transit connection */ + let (transit, info) = match connector + .connect( + is_leader, + wormhole.key().derive_transit_key(wormhole.appid()), + peer_abilities, + Arc::new(their_hints), + ) + .await + { + Ok(transit) => transit, + Err(error) => { + let error = TransferError::TransitConnect(error); + let _ = wormhole + .send_json(&PeerMessage::Error(format!("{}", error))) + .await; + return Err(error); + }, + }; + + Ok((transit, info)) +} + +pub async fn send( mut wormhole: Wormhole, relay_hints: Vec, - file: &mut F, - file_path: N, - file_name: String, - progress_handler: H, + transit_abilities: transit::Abilities, + offer: OfferSend, + progress_handler: impl FnMut(u64, u64) + 'static, peer_version: AppVersion, -) -> Result<(), TransferError> -where - F: AsyncRead + Unpin, - N: Into, - H: FnMut(u64, u64) + 'static, -{ - // let file_path = file_path.into(); - // let peer_abilities = peer_version.transfer_v2.unwrap(); - // let mut actual_transit_abilities = transit::Ability::all_abilities(); - // actual_transit_abilities.retain(|a| peer_abilities.transit_abilities.contains(a)); - // let connector = transit::init(actual_transit_abilities, Some(&peer_abilities.transit_abilities), relay_hints).await?; - - // /* Send our transit hints */ - // wormhole - // .send_json( - // &PeerMessage::transit_v2( - // (**connector.our_hints()).clone().into(), - // ), - // ) - // .await?; - - // /* Receive their transit hints */ - // let their_hints: transit::Hints = - // match wormhole.receive_json().await?? { - // PeerMessage::TransitV2(transit) => { - // debug!("received transit message: {:?}", transit); - // transit.hints_v2.into() - // }, - // PeerMessage::Error(err) => { - // bail!(TransferError::PeerError(err)); - // }, - // other => { - // let error = TransferError::unexpected_message("transit-v2", other); - // let _ = wormhole - // .send_json(&PeerMessage::Error(format!("{}", error))) - // .await; - // bail!(error) - // }, - // }; - - // /* Get a transit connection */ - // let mut transit = match connector - // .leader_connect( - // wormhole.key().derive_transit_key(wormhole.appid()), - // Arc::new(peer_abilities.transit_abilities), - // Arc::new(their_hints), - // ) - // .await - // { - // Ok(transit) => transit, - // Err(error) => { - // let error = TransferError::TransitConnect(error); - // let _ = wormhole - // .send_json(&PeerMessage::Error(format!("{}", error))) - // .await; - // return Err(error); - // }, - // }; - - // /* Close the Wormhole and switch to using the transit connection (msgpack instead of json) */ - // wormhole.close().await?; - - // transit.send_record(&PeerMessage::OfferV2(OfferV2 { - // transfer_name: None, - // files: vec![], - // format: "tar.zst".into(), - // }).ser_msgpack()).await?; - - // match PeerMessage::de_msgpack(&transit.receive_record().await?)? { - // PeerMessage::AnswerV2(answer) => { - // // let files = answer.files; - // }, - // PeerMessage::Error(err) => { - // bail!(TransferError::PeerError(err)); - // }, - // other => { - // let error = TransferError::unexpected_message("answer-v2", other); - // let _ = transit - // .send_record(&PeerMessage::Error(format!("{}", error)).ser_msgpack()) - // .await; - // bail!(error) - // }, - // } + cancel: impl Future, +) -> Result<(), TransferError> { + let peer_abilities = peer_version.transfer_v2.unwrap(); + futures::pin_mut!(cancel); + + /* Establish transit connection, close the Wormhole and switch to using the transit connection (msgpack instead of json) */ + let (mut transit, wormhole, cancel) = cancel::with_cancel_wormhole!( + wormhole, + run = async { + Ok(make_transit( + &mut wormhole, + true, + relay_hints, + transit_abilities, + peer_abilities.transit_abilities, + ) + .await? + .0) + }, + cancel, + ret_cancel = (), + ); + + cancel::with_cancel_transit!( + transit, + run = async { + /* Close the wormhole only here so that the operation may be cancelled */ + wormhole.close().await?; + + send_inner(&mut transit, offer, progress_handler).await + }, + cancel, + |err| PeerMessageV2::Error(err.to_string()).ser_msgpack(), + |msg| match PeerMessageV2::de_msgpack(msg)? { + PeerMessageV2::Error(err) => Ok(Some(err)), + _ => Ok(None), + }, + ret_cancel = (), + ); + + Ok(()) +} + +/** We've established the transit connection and closed the Wormhole */ +async fn send_inner( + transit: &mut transit::Transit, + offer: OfferSend, + mut progress_handler: impl FnMut(u64, u64) + 'static, +) -> Result<(), TransferError> { + transit.send_record(&{ + /* This must be split into two statements to appease the borrow checker (unfortunate side effect of borrow-through) */ + let message = PeerMessageV2::Offer((&offer).into()).ser_msgpack(); + message + }).await?; + + let files = match PeerMessageV2::de_msgpack(&transit.receive_record().await?)?.check_err()? { + PeerMessageV2::Answer(answer) => answer.files, + other => { + bail!(TransferError::unexpected_message("answer", other)) + }, + }; + + let mut total_size = 0; + for file in &files { + if let Some((_, size)) = offer.get_file(&file.file) { + total_size += size; + } else { + bail!(TransferError::Protocol( + format!("Invalid file request: {}", file.file.join("/")).into() + )); + } + } + let mut total_sent = 0; + + // use zstd::stream::raw::Encoder; + // let zstd = Encoder::new(zstd::DEFAULT_COMPRESSION_LEVEL); + const BUFFER_LEN: usize = 16 * 1024; + let mut buffer = Box::new([0u8; BUFFER_LEN]); + + for AnswerMessageInner { + file, + offset, + sha256, + } in &files + { + let mut offset = *offset; + /* This must be split into two statements to appease the borrow checker (unfortunate side effect of borrow-through) */ + let content = (offer.get_file(&file).unwrap().0)(); + let mut content = content.await?; + let file = file.clone(); + + /* If they specified a hash, check our local file's contents */ + if let Some(sha256) = sha256 { + content.seek(std::io::SeekFrom::Start(offset)).await?; + let mut hasher = Sha256::default(); + futures::io::copy( + (&mut content).take(offset), + &mut futures::io::AllowStdIo::new(&mut hasher), + ) + .await?; + let our_hash = hasher.finalize_fixed(); + + /* If it doesn't match, start at 0 instead of the originally requested offset */ + if &*our_hash == &sha256[..] { + transit + .send_record( + &PeerMessageV2::FileStart(FileStart { + file, + start_at_offset: true, + }) + .ser_msgpack(), + ) + .await?; + } else { + transit + .send_record( + &PeerMessageV2::FileStart(FileStart { + file, + start_at_offset: false, + }) + .ser_msgpack(), + ) + .await?; + content.seek(std::io::SeekFrom::Start(0)).await?; + // offset = 0; TODO + } + } else { + content.seek(std::io::SeekFrom::Start(offset)).await?; + transit + .send_record( + &PeerMessageV2::FileStart(FileStart { + file, + start_at_offset: true, + }) + .ser_msgpack(), + ) + .await?; + } + + progress_handler(total_sent, total_size); + loop { + let n = content.read(&mut buffer[..]).await?; + let buffer = &buffer[..n]; + + if n == 0 { + // EOF + break; + } + + transit + .send_record( + &PeerMessageV2::Payload(Payload { + payload: buffer.into(), + }) + .ser_msgpack(), + ) + .await?; + total_sent += n as u64; + progress_handler(total_sent, total_size); + + if n < BUFFER_LEN { + break; + } + } + + transit + .send_record(&PeerMessageV2::FileEnd(FileEnd {}).ser_msgpack()) + .await?; + } + transit + .send_record(&PeerMessageV2::TransferAck(TransferAck {}).ser_msgpack()) + .await?; Ok(()) } -#[allow(unused_variables)] -pub async fn send_folder( - wormhole: Wormhole, +pub async fn request( + mut wormhole: Wormhole, relay_hints: Vec, - folder_path: N, - folder_name: M, - progress_handler: H, -) -> Result<(), TransferError> -where - N: Into, - M: Into, - H: FnMut(u64, u64) + 'static, -{ - unimplemented!() + peer_version: AppVersion, + transit_abilities: transit::Abilities, + cancel: impl Future, +) -> Result, TransferError> { + let peer_abilities = peer_version.transfer_v2.unwrap(); + futures::pin_mut!(cancel); + + /* Establish transit connection, close the Wormhole and switch to using the transit connection (msgpack instead of json) */ + let ((mut transit, info), wormhole, cancel) = cancel::with_cancel_wormhole!( + wormhole, + run = async { + make_transit( + &mut wormhole, + false, + relay_hints, + transit_abilities, + peer_abilities.transit_abilities, + ) + .await + }, + cancel, + ret_cancel = None, + ); + + let (offer, transit) = cancel::with_cancel_transit!( + transit, + run = async { + /* Close the wormhole only here so that the `.await` is scoped within cancellation */ + wormhole.close().await?; + + let offer = + match PeerMessageV2::de_msgpack(&transit.receive_record().await?)?.check_err()? { + PeerMessageV2::Offer(offer) => offer, + other => { + bail!(TransferError::unexpected_message("offer", other)) + }, + }; + + Ok(offer) + }, + cancel, + |err| PeerMessageV2::Error(err.to_string()).ser_msgpack(), + |msg| match PeerMessageV2::de_msgpack(msg)? { + PeerMessageV2::Error(err) => Ok(Some(err)), + _ => Ok(None), + }, + ret_cancel = None, + ); + + Ok(Some(ReceiveRequest::new(transit, offer, info))) +} + +/** + * A pending files send offer from the other side + * + * You *should* consume this object, either by calling [`accept`](ReceiveRequest::accept) or [`reject`](ReceiveRequest::reject). + */ +#[must_use] +pub struct ReceiveRequest { + transit: Transit, + offer: Arc, + info: transit::TransitInfo, +} + +impl ReceiveRequest { + pub fn new(transit: Transit, offer: Offer, info: transit::TransitInfo) -> Self { + Self { + transit, + offer: Arc::new(offer), + info, + } + } + + /** The offer we got */ + pub fn offer(&self) -> Arc { + self.offer.clone() + } + + /** + * Accept the file offer + * + * This will transfer the file and save it on disk. + */ + pub async fn accept( + self, + transit_handler: impl FnOnce(transit::TransitInfo), + answer: OfferAccept, + progress_handler: impl FnMut(u64, u64) + 'static, + cancel: impl Future, + ) -> Result<(), TransferError> { + transit_handler(self.info); + futures::pin_mut!(cancel); + + let mut transit = self.transit; + cancel::with_cancel_transit!( + transit, + run = async { + transit.send_record(&{ + /* This must be split into two statements to appease the borrow checker (unfortunate side effect of borrow-through) */ + let msg = PeerMessageV2::Answer(AnswerMessage { + files: answer.iter_files() + .map(|(path, inner, _size)| AnswerMessageInner { + file: path, + offset: inner.offset, + sha256: inner.sha256, + }) + .collect(), + }).ser_msgpack(); + msg + }).await?; + + receive_inner(&mut transit, &self.offer, answer, progress_handler).await + }, + cancel, + |err| PeerMessageV2::Error(err.to_string()).ser_msgpack(), + |msg| match PeerMessageV2::de_msgpack(msg)? { + PeerMessageV2::Error(err) => Ok(Some(err)), + _ => Ok(None), + }, + ret_cancel = (), + ); + Ok(()) + } + + /** + * Reject the file offer + * + * This will send an error message to the other side so that it knows the transfer failed. + */ + pub async fn reject(mut self) -> Result<(), TransferError> { + self.transit + .send_record(&PeerMessageV2::Error("transfer rejected".into()).ser_msgpack()) + .await?; + self.transit.flush().await?; + + Ok(()) + } +} + +/** We've established the transit connection and closed the Wormhole */ +async fn receive_inner( + transit: &mut transit::Transit, + offer: &Arc, + our_answer: OfferAccept, + mut progress_handler: impl FnMut(u64, u64) + 'static, +) -> Result<(), TransferError> { + /* This does not check for file sizes, but should be good enough + * (failures will eventually lead to protocol errors later on anyways) + */ + assert!( + our_answer + .iter_file_paths() + .all(|path| offer.get_file(&path).is_some()), + "Mismatch between offer and accept: accept must be a true subset of offer" + ); + let n_accepted = our_answer.iter_file_paths().count(); + let total_size = our_answer + .iter_files() + .map(|(_path, _inner, size)| size) + .sum::(); + let mut total_received = 0; + + /* The receive loop */ + for (i, (file, answer, size)) in our_answer.into_iter_files().enumerate() { + let file_start = match PeerMessageV2::de_msgpack(&transit.receive_record().await?)? + .check_err()? + { + PeerMessageV2::FileStart(file_start) => file_start, + PeerMessageV2::TransferAck(_) => { + bail!(TransferError::Protocol(format!("Unexpected message: got 'transfer-ack' but expected {} more 'file-start' messages", n_accepted - i).into_boxed_str())) + }, + other => { + bail!(TransferError::unexpected_message("file-start", other)) + }, + }; + ensure!( + file_start.file == file, + TransferError::Protocol( + format!( + "Unexpected file: got file {} but expected {}", + file_start.file.join("/"), + file.join("/"), + ) + .into_boxed_str() + ) + ); + + let mut content; + let mut received_size = 0; + if file_start.start_at_offset { + content = (answer.content)(true).await?; + let offset = answer.offset; + received_size = offset; + } else { + content = (answer.content)(false).await?; + } + + progress_handler(total_received, total_size); + loop { + let payload = + match PeerMessageV2::de_msgpack(&transit.receive_record().await?)?.check_err()? { + PeerMessageV2::Payload(payload) => payload.payload, + PeerMessageV2::FileEnd(_) => { + bail!(TransferError::Protocol( + format!( + "Unexpected message: got 'file-end' but expected {} more payload bytes", + size - received_size, + ) + .into_boxed_str() + )) + }, + other => { + bail!(TransferError::unexpected_message("payload", other)) + }, + }; + + content.write_all(&payload).await?; + received_size += payload.len() as u64; + total_received += payload.len() as u64; + progress_handler(total_received, total_size); + + if received_size == size { + break; + } else if received_size >= size { + /* `received_size` must never become greater than `size` or we might panic on an integer underflow in the next iteration + * (only on an unhappy path, but still). Also, the progress bar might not appreciate. + */ + bail!(TransferError::Protocol( + format!( + "File too large: expected only {size} bytes, got at least {} more", + size - received_size + ) + .into_boxed_str() + )) + } + } + + content.close().await?; + + let _end = match PeerMessageV2::de_msgpack(&transit.receive_record().await?)?.check_err()? { + PeerMessageV2::FileEnd(end) => end, + other => { + bail!(TransferError::unexpected_message("file-end", other)) + }, + }; + } + + let _transfer_ack = match PeerMessageV2::de_msgpack(&transit.receive_record().await?)? + .check_err()? + { + PeerMessageV2::TransferAck(transfer_ack) => transfer_ack, + PeerMessageV2::FileStart(_) => { + bail!(TransferError::Protocol( + format!("Unexpected message: got 'file-start' but did not expect any more files") + .into_boxed_str() + )) + }, + other => { + bail!(TransferError::unexpected_message("transfer-ack", other)) + }, + }; + + Ok(()) } diff --git a/src/util.rs b/src/util.rs index 9ac5be1b..6f3fd2e8 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,3 @@ -use futures::Future; - macro_rules! ensure { ($cond:expr, $err:expr $(,)?) => { if !$cond { @@ -172,45 +170,6 @@ pub fn hashcash(resource: String, bits: u32) -> String { } } } - -/// A weird mixture of [`futures::future::Abortable`], [`async_std::sync::Condvar`] and [`futures::future::Select`] tailored to our Ctrl+C handling. -/// -/// At it's core, it is an `Abortable` but instead of having an `AbortHandle`, we use a future that resolves as trigger. -/// Under the hood, it is implementing the same functionality as a `select`, but mapping one of the outcomes to an error type. -pub async fn cancellable( - future: impl Future + Unpin, - cancel: impl Future, -) -> Result { - use futures::future::Either; - futures::pin_mut!(cancel); - match futures::future::select(cancel, future).await { - Either::Left(((), _)) => Err(Cancelled), - Either::Right((val, _)) => Ok(val), - } -} - -/** Like `cancellable`, but you'll get back the cancellation future in case the code terminates for future use */ -pub async fn cancellable_2 + Unpin>( - future: impl Future + Unpin, - cancel: C, -) -> Result<(T, C), Cancelled> { - use futures::future::Either; - match futures::future::select(cancel, future).await { - Either::Left(((), _)) => Err(Cancelled), - Either::Right((val, cancel)) => Ok((val, cancel)), - } -} - -/// Indicator that the [`Cancellable`] task was cancelled. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Cancelled; - -impl std::fmt::Display for Cancelled { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Task has been cancelled") - } -} - #[cfg(not(target_family = "wasm"))] pub async fn sleep(duration: std::time::Duration) { async_std::task::sleep(duration).await From 87a67d326ff9597c5af1665057d75b4a0ec14d5e Mon Sep 17 00:00:00 2001 From: piegames Date: Thu, 8 Jun 2023 16:08:35 +0200 Subject: [PATCH 24/26] Remove symlink handling for now I'll try to move it down to an experimental custom protocol extension later on --- cli/src/main.rs | 4 +- src/transfer.rs | 111 +++++++++++++++++++++++---------------------- src/transfer/v1.rs | 2 +- src/transfer/v2.rs | 2 +- 4 files changed, 60 insertions(+), 59 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index ca7d2625..71e9468e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1097,8 +1097,8 @@ async fn receive_inner_v2( .await .context("Receive process failed")?; - /* Put in all the symlinks last, this greatly reduces the attack surface */ - offer.create_symlinks(&tmp_dir).await?; + // /* Put in all the symlinks last, this greatly reduces the attack surface */ + // offer.create_symlinks(&tmp_dir).await?; /* TODO walk the output directory and delete things we did not accept; this will be important for resumption */ diff --git a/src/transfer.rs b/src/transfer.rs index 7a53107c..ec6ff307 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -418,14 +418,14 @@ impl Offer { Ok(()) } - #[cfg(not(target_family = "wasm"))] - pub async fn create_symlinks(&self, target_path: &Path) -> std::io::Result<()> { - // TODO this could be made more efficient by passing around just one buffer - for (name, file) in &self.content { - file.create_symlinks(&target_path.join(name)).await?; - } - Ok(()) - } + // #[cfg(not(target_family = "wasm"))] + // pub async fn create_symlinks(&self, target_path: &Path) -> std::io::Result<()> { + // // TODO this could be made more efficient by passing around just one buffer + // for (name, file) in &self.content { + // file.create_symlinks(&target_path.join(name)).await?; + // } + // Ok(()) + // } pub fn offer_name(&self) -> String { let (name, entry) = self.content.iter().next().unwrap(); @@ -529,9 +529,9 @@ pub enum OfferEntry { Directory { content: BTreeMap, }, - Symlink { - target: String, - }, + // Symlink { + // target: String, + // }, } impl OfferSendEntry { @@ -546,7 +546,8 @@ impl OfferSendEntry { } let path = path.as_ref(); - let metadata = async_std::fs::symlink_metadata(path).await?; + // let metadata = async_std::fs::symlink_metadata(path).await?; + let metadata = async_std::fs::metadata(path).await?; // let mtime = metadata.modified()? // .duration_since(std::time::SystemTime::UNIX_EPOCH) // .unwrap_or_default() @@ -561,20 +562,20 @@ impl OfferSendEntry { async_std::fs::File::open(path) }), }) - } else if metadata.is_symlink() { - log::trace!("OfferSendEntry::new {path:?} is symlink"); - let target = async_std::fs::read_link(path).await?; - Ok(Self::Symlink { - target: target - .to_str() - .ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("{} is not UTF-8 encoded", target.display()), - ) - })? - .to_string(), - }) + // } else if metadata.is_symlink() { + // log::trace!("OfferSendEntry::new {path:?} is symlink"); + // let target = async_std::fs::read_link(path).await?; + // Ok(Self::Symlink { + // target: target + // .to_str() + // .ok_or_else(|| { + // std::io::Error::new( + // std::io::ErrorKind::Other, + // format!("{} is not UTF-8 encoded", target.display()), + // ) + // })? + // .to_string(), + // }) } else if metadata.is_dir() { use futures::TryStreamExt; log::trace!("OfferSendEntry::new {path:?} is directory"); @@ -624,7 +625,7 @@ impl OfferEntry { Self::RegularFile { content, size } => { Box::new(std::iter::once((vec![], content, *size))) as Box> }, - Self::Symlink { .. } => Box::new(std::iter::empty()) as Box>, + // Self::Symlink { .. } => Box::new(std::iter::empty()) as Box>, } } @@ -676,28 +677,28 @@ impl OfferEntry { } } - #[cfg(not(target_family = "wasm"))] - async fn create_symlinks(&self, target_path: &Path) -> std::io::Result<()> { - #[inline(always)] - fn recurse<'a, T>( - this: &'a OfferEntry, - path: &'a Path, - ) -> futures::future::LocalBoxFuture<'a, std::io::Result<()>> { - Box::pin(OfferEntry::create_symlinks(this, path)) - } - match self { - Self::Symlink { target } => { - todo!() - }, - Self::Directory { content, .. } => { - for (name, file) in content { - recurse(file, &target_path.join(name)).await?; - } - Ok(()) - }, - _ => Ok(()), - } - } + // #[cfg(not(target_family = "wasm"))] + // async fn create_symlinks(&self, target_path: &Path) -> std::io::Result<()> { + // #[inline(always)] + // fn recurse<'a, T>( + // this: &'a OfferEntry, + // path: &'a Path, + // ) -> futures::future::LocalBoxFuture<'a, std::io::Result<()>> { + // Box::pin(OfferEntry::create_symlinks(this, path)) + // } + // match self { + // Self::Symlink { target } => { + // todo!() + // }, + // Self::Directory { content, .. } => { + // for (name, file) in content { + // recurse(file, &target_path.join(name)).await?; + // } + // Ok(()) + // }, + // _ => Ok(()), + // } + // } fn set_content( &self, @@ -720,9 +721,9 @@ impl OfferEntry { }) .collect(), }, - OfferEntry::Symlink { target } => OfferEntry::Symlink { - target: target.clone(), - }, + // OfferEntry::Symlink { target } => OfferEntry::Symlink { + // target: target.clone(), + // }, } } } @@ -745,9 +746,9 @@ impl OfferEntry { Box::new(std::iter::once((vec![], content, size))) as Box + Send> }, - Self::Symlink { .. } => { - Box::new(std::iter::empty()) as Box + Send> - }, + // Self::Symlink { .. } => { + // Box::new(std::iter::empty()) as Box + Send> + // }, } } } diff --git a/src/transfer/v1.rs b/src/transfer/v1.rs index 2cfc0e7e..e7035811 100644 --- a/src/transfer/v1.rs +++ b/src/transfer/v1.rs @@ -313,7 +313,7 @@ pub async fn send_folder( total_content.push(Box::pin(content) as _); total_content.push(wrap(padding)); }, - OfferSendEntry::Symlink { .. } => todo!(), + // OfferSendEntry::Symlink { .. } => todo!(), } Ok(total_content) } diff --git a/src/transfer/v2.rs b/src/transfer/v2.rs index 3de6b1d2..adc1d256 100644 --- a/src/transfer/v2.rs +++ b/src/transfer/v2.rs @@ -244,7 +244,7 @@ async fn send_inner( sha256, } in &files { - let mut offset = *offset; + let offset = *offset; /* This must be split into two statements to appease the borrow checker (unfortunate side effect of borrow-through) */ let content = (offer.get_file(&file).unwrap().0)(); let mut content = content.await?; From 60c616949ff51b5b2d66e2190d5c35c80ab57d97 Mon Sep 17 00:00:00 2001 From: piegames Date: Tue, 13 Jun 2023 14:01:47 +0200 Subject: [PATCH 25/26] Update dependencies --- Cargo.lock | 909 +++++++++++++++++++++++++++-------------------------- Cargo.toml | 15 +- 2 files changed, 465 insertions(+), 459 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f8687fe..23abcd42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,18 +55,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "8868f09ff8cea88b079da74ae569d9b8c62a23c68c746240b704ee6f7525c89c" [[package]] name = "async-attributes" @@ -75,7 +75,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -91,9 +91,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ "async-lock", "async-task", @@ -120,39 +120,38 @@ dependencies = [ [[package]] name = "async-io" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg", + "cfg-if", "concurrent-queue", "futures-lite", - "libc", "log", "parking", "polling", + "rustix", "slab", - "socket2", + "socket2 0.4.9", "waker-fn", - "windows-sys", ] [[package]] name = "async-lock" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" dependencies = [ "event-listener", - "futures-lite", ] [[package]] name = "async-process" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io", "async-lock", @@ -161,9 +160,9 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "libc", + "rustix", "signal-hook", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -204,45 +203,46 @@ dependencies = [ "filetime", "libc", "pin-project", - "redox_syscall", + "redox_syscall 0.2.16", "xattr", ] [[package]] name = "async-task" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-tls" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f23d769dbf1838d5df5156e7b1ad404f4c463d1ac2c6aeb6cd943630f8a8400" +checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795" dependencies = [ "futures-core", "futures-io", "rustls", + "rustls-pemfile", "webpki", "webpki-roots", ] [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "async-tungstenite" -version = "0.17.2" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" +checksum = "ce01ac37fdc85f10a43c43bc582cbd566720357011578a935761075f898baf58" dependencies = [ "async-std", "async-tls", @@ -266,9 +266,9 @@ dependencies = [ [[package]] name = "atomic-waker" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atty" @@ -304,15 +304,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.20.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -326,7 +320,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -337,18 +331,18 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "blocking" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", "async-lock", @@ -356,13 +350,14 @@ dependencies = [ "atomic-waker", "fastrand", "futures-lite", + "log", ] [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytecodec" @@ -392,12 +387,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - [[package]] name = "cc" version = "1.0.79" @@ -452,9 +441,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags", @@ -462,7 +451,7 @@ dependencies = [ "clap_lex", "indexmap", "once_cell", - "strsim 0.10.0", + "strsim", "termcolor", "terminal_size", "textwrap", @@ -479,15 +468,15 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.18" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -553,55 +542,55 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.5" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] [[package]] name = "crc" -version = "2.1.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "1.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -624,9 +613,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] @@ -641,16 +630,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "ctr" version = "0.8.0" @@ -662,12 +641,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.4" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" +checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" dependencies = [ "nix 0.26.2", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -684,39 +663,10 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.10.2" +name = "data-encoding" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.9.3", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" -dependencies = [ - "darling_core", - "quote", - "syn", -] +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "derive-new" @@ -726,7 +676,7 @@ checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -737,14 +687,14 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "dialoguer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3c796f3b0b408d9fd581611b47fa850821fcb84aa640b83a3c1a5be2d691f2" +checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" dependencies = [ "console", "shell-words", @@ -763,9 +713,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -799,13 +749,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -846,23 +796,23 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "filetime" -version = "0.2.19" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", ] [[package]] @@ -879,18 +829,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -903,9 +853,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -913,15 +863,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -930,15 +880,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -951,32 +901,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -992,22 +942,21 @@ dependencies = [ [[package]] name = "futures_ringbuf" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b905098b5519bd63b2a1f9f4615198b0e38a473ce201ffdbd4dea6eb63087ddc" +checksum = "6628abb6eb1fc74beaeb20cd0670c43d158b0150f7689b38c3eaf663f99bdec7" dependencies = [ "futures", "log", - "log-derive", "ringbuf", "rustc_version", ] [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1030,16 +979,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", @@ -1060,9 +1008,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "gloo-timers" @@ -1084,9 +1032,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -1099,12 +1047,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "hex" @@ -1130,7 +1075,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -1139,14 +1084,14 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1333fad8d94b82cab989da428b0b36a3435db3870d85e971a1d6dc0a8576722" dependencies = [ - "sha1", + "sha1 0.2.0", ] [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -1165,17 +1110,11 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1183,12 +1122,12 @@ dependencies = [ [[package]] name = "if-addrs" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b24dd0826eee92c56edcda7ff190f2cf52115c49eadb2c2da8063e2673a8c2" +checksum = "2cfc4a06638d2fd0dda83b01126fefd38ef9f04f54d2fc717a938df68b83a68d" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1199,9 +1138,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -1209,11 +1148,12 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.3" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" +checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" dependencies = [ "console", + "instant", "number_prefix", "portable-atomic", "unicode-width", @@ -1233,37 +1173,38 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.4" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1285,21 +1226,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -1307,26 +1248,13 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ - "cfg-if", "value-bag", ] -[[package]] -name = "log-derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a42526bb432bcd1b43571d5f163984effa25409a29f1a3242a54d0577d55bcf" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "magic-wormhole" version = "0.6.0" @@ -1336,15 +1264,14 @@ dependencies = [ "async-tar", "async-trait", "async-tungstenite", - "base64 0.20.0", + "base64", "bytecodec", "derive_more", "env_logger", "eyre", "futures", "futures_ringbuf", - "getrandom 0.1.16", - "getrandom 0.2.8", + "getrandom 0.2.10", "hex", "hkdf", "if-addrs", @@ -1361,7 +1288,7 @@ dependencies = [ "serde_json", "sha-1", "sha2", - "socket2", + "socket2 0.5.3", "spake2", "stun_codec", "thiserror", @@ -1419,14 +1346,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1471,7 +1398,7 @@ dependencies = [ "aes-gcm", "blake2", "chacha20poly1305", - "getrandom 0.2.8", + "getrandom 0.2.10", "noise-protocol", "sha2", "x25519-dalek", @@ -1534,18 +1461,18 @@ dependencies = [ [[package]] name = "object" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -1555,19 +1482,19 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "os_pipe" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a252f1f8c11e84b3ab59d7a488e48e4478a93937e027076638c49536204639" +checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "os_str_bytes" -version = "6.4.1" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "owo-colors" @@ -1577,9 +1504,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -1599,7 +1526,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.6", + "parking_lot_core 0.9.8", ] [[package]] @@ -1611,41 +1538,41 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "windows-sys", + "windows-targets 0.48.0", ] [[package]] name = "paste" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", "indexmap", @@ -1663,22 +1590,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -1695,22 +1622,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "polling" -version = "2.5.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", + "bitflags", "cfg-if", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "windows-sys", + "pin-project-lite", + "windows-sys 0.48.0", ] [[package]] @@ -1738,9 +1667,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "0.3.19" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" +checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" [[package]] name = "ppv-lite86" @@ -1757,7 +1686,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -1774,9 +1703,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -1802,9 +1731,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1845,7 +1774,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -1857,11 +1786,20 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" -version = "1.7.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", @@ -1870,18 +1808,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "ring" @@ -1900,11 +1829,11 @@ dependencies = [ [[package]] name = "ringbuf" -version = "0.2.8" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65af18d50f789e74aaf23bbb3f65dcd22a3cb6e029b5bced149f6bd57c5c2a2" +checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a" dependencies = [ - "cache-padded", + "crossbeam-utils", ] [[package]] @@ -1931,9 +1860,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc_version" @@ -1946,36 +1875,44 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.7" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "rustls" -version = "0.19.1" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ - "base64 0.13.1", "log", "ring", "sct", "webpki", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64", +] + [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "salsa20" @@ -1995,9 +1932,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sct" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", @@ -2005,9 +1942,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "send_wrapper" @@ -2017,29 +1954,29 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -2054,7 +1991,7 @@ checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2063,6 +2000,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.10.6" @@ -2071,7 +2019,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2091,9 +2039,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", @@ -2112,18 +2060,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -2136,14 +2084,24 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "spake2" version = "0.3.1" @@ -2174,12 +2132,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - [[package]] name = "strsim" version = "0.10.0" @@ -2188,16 +2140,16 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "stun_codec" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1df6c4e592afc1ffed8f4bc50ab9f4d70cfccb42829662b7d276247cbef3b1" +checksum = "4089f66744a63bc909eed6ece965b493030ca896f21c24d9f26c659926c7e05b" dependencies = [ "bytecodec", "byteorder", "crc", "hmac-sha1", "md5", - "trackable 1.2.0", + "trackable 1.3.0", ] [[package]] @@ -2208,9 +2160,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -2218,29 +2170,28 @@ dependencies = [ ] [[package]] -name = "synstructure" -version = "0.12.6" +name = "syn" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", - "syn", - "unicode-xid", + "unicode-ident", ] [[package]] name = "tempfile" -version = "3.3.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -2254,12 +2205,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2273,38 +2224,39 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] [[package]] name = "time" -version = "0.3.17" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ "itoa", "serde", @@ -2314,15 +2266,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -2338,9 +2290,9 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tracing" @@ -2355,9 +2307,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -2375,9 +2327,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "sharded-slab", "thread_local", @@ -2390,15 +2342,15 @@ version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98abb9e7300b9ac902cc04920945a874c1973e08c310627cc4458c04b70dd32" dependencies = [ - "trackable 1.2.0", + "trackable 1.3.0", "trackable_derive", ] [[package]] name = "trackable" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017e2a1a93718e4e8386d037cfb8add78f1d690467f4350fb582f55af1203167" +checksum = "b15bd114abb99ef8cee977e517c8f37aee63f184f2d08e3e6ceca092373369ae" dependencies = [ "trackable_derive", ] @@ -2410,7 +2362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2429,18 +2381,18 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" dependencies = [ - "base64 0.13.1", "byteorder", "bytes", + "data-encoding", "http", "httparse", "log", "rand", - "sha-1", + "sha1 0.10.5", "thiserror", "url", "utf-8", @@ -2454,15 +2406,15 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -2479,12 +2431,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "universal-hash" version = "0.4.1" @@ -2503,9 +2449,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -2527,13 +2473,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.9" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] +checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" [[package]] name = "version_check" @@ -2561,9 +2503,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2571,24 +2513,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -2598,9 +2540,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2608,22 +2550,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-timer" @@ -2701,9 +2643,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -2711,9 +2653,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.4" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ "ring", "untrusted", @@ -2721,22 +2663,13 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.21.1" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2779,60 +2712,135 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "wl-clipboard-rs" @@ -2898,9 +2906,9 @@ dependencies = [ [[package]] name = "x11-clipboard" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0827f86aa910c4e73329a4f619deabe88ebb4b042370bf023c2d5d8b4eb54695" +checksum = "980b9aa9226c3b7de8e2adb11bf20124327c054e0e5812d2aac0b5b5a87e7464" dependencies = [ "x11rb", ] @@ -2949,9 +2957,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.4" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +checksum = "52839dc911083a8ef63efa4d039d1f58b5e409f923e44c80828f206f66e5541c" [[package]] name = "xsalsa20poly1305" @@ -2978,12 +2986,11 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.18", ] diff --git a/Cargo.toml b/Cargo.toml index 0e987745..47f5739b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,11 +26,11 @@ sha-1 = "0.10.0" sha2 = "0.10.0" hkdf = "0.12.2" hex = { version = "0.4.2", features = ["serde"] } -rand = "0.8.3" +rand = "0.8.0" log = "0.4.13" # zeroize = { version = "1.2.0", features = ["zeroize_derive"] } -base64 = "0.20.0" -futures_ringbuf = "0.3.1" +base64 = "0.21.0" +futures_ringbuf = "0.4.0" time = { version = "0.3.7", features = ["formatting"] } instant = { version = "0.1.12", features = ["wasm-bindgen"] } @@ -43,7 +43,7 @@ percent-encoding = { version = "2.1.0" } # Transit dependencies -stun_codec = { version = "0.2.0", optional = true } +stun_codec = { version = "0.3.0", optional = true } bytecodec = { version = "0.4.15", optional = true } async-trait = { version = "0.1.57", optional = true } noise-protocol = { version = "0.1.4", optional = true } @@ -60,12 +60,12 @@ rmp-serde = { version = "1.0.0", optional = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] libc = "0.2.101" async-std = { version = "1.12.0", features = ["attributes", "unstable"] } -async-tungstenite = { version = "0.17.1", features = ["async-std-runtime", "async-tls"] } +async-tungstenite = { version = "0.22.2", features = ["async-std-runtime", "async-tls"] } async-io = "1.6.0" # Transit -socket2 = { version = "0.4.1", optional = true } -if-addrs = { version = "0.8.0", optional = true } +socket2 = { version = "0.5.0", optional = true, features = ["all"] } +if-addrs = { version = "0.10.0", optional = true } # Transfer @@ -75,7 +75,6 @@ async-tar = { version = "0.4.2", optional = true } wasm-timer = "0.2.5" ws_stream_wasm = "0.7.3" getrandom = { version = "0.2.5", features = ["js"] } -getrandom_2 = { package = "getrandom", version = "0.1.16", features = ["js-sys"] } # for some tests [dev-dependencies] From 745e95d4d718a2f9c8a26a98f60a04724843c495 Mon Sep 17 00:00:00 2001 From: piegames Date: Tue, 13 Jun 2023 14:01:47 +0200 Subject: [PATCH 26/26] Update dependencies --- Cargo.lock | 909 +++++++++++++++++++++++++++-------------------------- Cargo.toml | 15 +- 2 files changed, 465 insertions(+), 459 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80824276..583b58c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,18 +55,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "8868f09ff8cea88b079da74ae569d9b8c62a23c68c746240b704ee6f7525c89c" [[package]] name = "async-attributes" @@ -75,7 +75,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -91,9 +91,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ "async-lock", "async-task", @@ -120,39 +120,38 @@ dependencies = [ [[package]] name = "async-io" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg", + "cfg-if", "concurrent-queue", "futures-lite", - "libc", "log", "parking", "polling", + "rustix", "slab", - "socket2", + "socket2 0.4.9", "waker-fn", - "windows-sys", ] [[package]] name = "async-lock" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" dependencies = [ "event-listener", - "futures-lite", ] [[package]] name = "async-process" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io", "async-lock", @@ -161,9 +160,9 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "libc", + "rustix", "signal-hook", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -204,45 +203,46 @@ dependencies = [ "filetime", "libc", "pin-project", - "redox_syscall", + "redox_syscall 0.2.16", "xattr", ] [[package]] name = "async-task" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-tls" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f23d769dbf1838d5df5156e7b1ad404f4c463d1ac2c6aeb6cd943630f8a8400" +checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795" dependencies = [ "futures-core", "futures-io", "rustls", + "rustls-pemfile", "webpki", "webpki-roots", ] [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "async-tungstenite" -version = "0.17.2" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" +checksum = "ce01ac37fdc85f10a43c43bc582cbd566720357011578a935761075f898baf58" dependencies = [ "async-std", "async-tls", @@ -266,9 +266,9 @@ dependencies = [ [[package]] name = "atomic-waker" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atty" @@ -304,15 +304,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.20.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -326,7 +320,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -337,18 +331,18 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "blocking" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", "async-lock", @@ -356,13 +350,14 @@ dependencies = [ "atomic-waker", "fastrand", "futures-lite", + "log", ] [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytecodec" @@ -392,12 +387,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - [[package]] name = "cc" version = "1.0.79" @@ -455,9 +444,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags", @@ -465,7 +454,7 @@ dependencies = [ "clap_lex", "indexmap", "once_cell", - "strsim 0.10.0", + "strsim", "termcolor", "terminal_size", "textwrap", @@ -482,15 +471,15 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.18" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -556,55 +545,55 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.5" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] [[package]] name = "crc" -version = "2.1.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "1.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -627,9 +616,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] @@ -644,16 +633,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "ctr" version = "0.8.0" @@ -665,12 +644,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.4" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" +checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" dependencies = [ "nix 0.26.2", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -687,39 +666,10 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.10.2" +name = "data-encoding" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.9.3", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" -dependencies = [ - "darling_core", - "quote", - "syn", -] +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "derive-new" @@ -729,7 +679,7 @@ checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -740,14 +690,14 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "dialoguer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3c796f3b0b408d9fd581611b47fa850821fcb84aa640b83a3c1a5be2d691f2" +checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" dependencies = [ "console", "shell-words", @@ -766,9 +716,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -802,13 +752,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -849,23 +799,23 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "filetime" -version = "0.2.19" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", ] [[package]] @@ -882,18 +832,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -906,9 +856,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -916,15 +866,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -933,15 +883,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -954,32 +904,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -995,22 +945,21 @@ dependencies = [ [[package]] name = "futures_ringbuf" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b905098b5519bd63b2a1f9f4615198b0e38a473ce201ffdbd4dea6eb63087ddc" +checksum = "6628abb6eb1fc74beaeb20cd0670c43d158b0150f7689b38c3eaf663f99bdec7" dependencies = [ "futures", "log", - "log-derive", "ringbuf", "rustc_version", ] [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1033,16 +982,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", @@ -1063,9 +1011,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "gloo-timers" @@ -1087,9 +1035,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -1102,12 +1050,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "hex" @@ -1133,7 +1078,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -1142,14 +1087,14 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1333fad8d94b82cab989da428b0b36a3435db3870d85e971a1d6dc0a8576722" dependencies = [ - "sha1", + "sha1 0.2.0", ] [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -1168,17 +1113,11 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1186,12 +1125,12 @@ dependencies = [ [[package]] name = "if-addrs" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b24dd0826eee92c56edcda7ff190f2cf52115c49eadb2c2da8063e2673a8c2" +checksum = "2cfc4a06638d2fd0dda83b01126fefd38ef9f04f54d2fc717a938df68b83a68d" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1202,9 +1141,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -1212,11 +1151,12 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.3" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" +checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" dependencies = [ "console", + "instant", "number_prefix", "portable-atomic", "unicode-width", @@ -1236,31 +1176,32 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.4" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jobserver" @@ -1273,9 +1214,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1297,21 +1238,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -1319,26 +1260,13 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ - "cfg-if", "value-bag", ] -[[package]] -name = "log-derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a42526bb432bcd1b43571d5f163984effa25409a29f1a3242a54d0577d55bcf" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "magic-wormhole" version = "0.6.0" @@ -1348,15 +1276,14 @@ dependencies = [ "async-tar", "async-trait", "async-tungstenite", - "base64 0.20.0", + "base64", "bytecodec", "derive_more", "env_logger", "eyre", "futures", "futures_ringbuf", - "getrandom 0.1.16", - "getrandom 0.2.8", + "getrandom 0.2.10", "hex", "hkdf", "if-addrs", @@ -1373,7 +1300,7 @@ dependencies = [ "serde_json", "sha-1", "sha2", - "socket2", + "socket2 0.5.3", "spake2", "stun_codec", "tar", @@ -1433,14 +1360,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1485,7 +1412,7 @@ dependencies = [ "aes-gcm", "blake2", "chacha20poly1305", - "getrandom 0.2.8", + "getrandom 0.2.10", "noise-protocol", "sha2", "x25519-dalek", @@ -1548,18 +1475,18 @@ dependencies = [ [[package]] name = "object" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -1569,19 +1496,19 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "os_pipe" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a252f1f8c11e84b3ab59d7a488e48e4478a93937e027076638c49536204639" +checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "os_str_bytes" -version = "6.4.1" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "owo-colors" @@ -1591,9 +1518,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -1613,7 +1540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.6", + "parking_lot_core 0.9.8", ] [[package]] @@ -1625,41 +1552,41 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "windows-sys", + "windows-targets 0.48.0", ] [[package]] name = "paste" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", "indexmap", @@ -1677,22 +1604,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -1709,22 +1636,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "polling" -version = "2.5.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", + "bitflags", "cfg-if", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "windows-sys", + "pin-project-lite", + "windows-sys 0.48.0", ] [[package]] @@ -1752,9 +1681,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "0.3.19" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" +checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" [[package]] name = "ppv-lite86" @@ -1771,7 +1700,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -1788,9 +1717,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -1816,9 +1745,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1859,7 +1788,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -1871,11 +1800,20 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" -version = "1.7.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", @@ -1884,18 +1822,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "ring" @@ -1914,11 +1843,11 @@ dependencies = [ [[package]] name = "ringbuf" -version = "0.2.8" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65af18d50f789e74aaf23bbb3f65dcd22a3cb6e029b5bced149f6bd57c5c2a2" +checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a" dependencies = [ - "cache-padded", + "crossbeam-utils", ] [[package]] @@ -1945,9 +1874,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc_version" @@ -1960,36 +1889,44 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.7" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "rustls" -version = "0.19.1" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ - "base64 0.13.1", "log", "ring", "sct", "webpki", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64", +] + [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "salsa20" @@ -2009,9 +1946,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sct" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", @@ -2019,9 +1956,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "send_wrapper" @@ -2031,29 +1968,29 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -2068,7 +2005,7 @@ checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2077,6 +2014,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.10.6" @@ -2085,7 +2033,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2105,9 +2053,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", @@ -2126,18 +2074,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -2150,14 +2098,24 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "spake2" version = "0.3.1" @@ -2188,12 +2146,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - [[package]] name = "strsim" version = "0.10.0" @@ -2202,16 +2154,16 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "stun_codec" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1df6c4e592afc1ffed8f4bc50ab9f4d70cfccb42829662b7d276247cbef3b1" +checksum = "4089f66744a63bc909eed6ece965b493030ca896f21c24d9f26c659926c7e05b" dependencies = [ "bytecodec", "byteorder", "crc", "hmac-sha1", "md5", - "trackable 1.2.0", + "trackable 1.3.0", ] [[package]] @@ -2222,9 +2174,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -2232,15 +2184,14 @@ dependencies = [ ] [[package]] -name = "synstructure" -version = "0.12.6" +name = "syn" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", - "syn", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2256,16 +2207,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -2279,12 +2230,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2298,38 +2249,39 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] [[package]] name = "time" -version = "0.3.17" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ "itoa", "serde", @@ -2339,15 +2291,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -2363,9 +2315,9 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tracing" @@ -2380,9 +2332,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -2400,9 +2352,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "sharded-slab", "thread_local", @@ -2415,15 +2367,15 @@ version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98abb9e7300b9ac902cc04920945a874c1973e08c310627cc4458c04b70dd32" dependencies = [ - "trackable 1.2.0", + "trackable 1.3.0", "trackable_derive", ] [[package]] name = "trackable" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017e2a1a93718e4e8386d037cfb8add78f1d690467f4350fb582f55af1203167" +checksum = "b15bd114abb99ef8cee977e517c8f37aee63f184f2d08e3e6ceca092373369ae" dependencies = [ "trackable_derive", ] @@ -2435,7 +2387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2454,18 +2406,18 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" dependencies = [ - "base64 0.13.1", "byteorder", "bytes", + "data-encoding", "http", "httparse", "log", "rand", - "sha-1", + "sha1 0.10.5", "thiserror", "url", "utf-8", @@ -2479,15 +2431,15 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -2504,12 +2456,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "universal-hash" version = "0.4.1" @@ -2528,9 +2474,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -2552,13 +2498,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.9" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] +checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" [[package]] name = "version_check" @@ -2586,9 +2528,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2596,24 +2538,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -2623,9 +2565,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2633,22 +2575,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-timer" @@ -2726,9 +2668,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -2736,9 +2678,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.4" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ "ring", "untrusted", @@ -2746,22 +2688,13 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.21.1" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2804,60 +2737,135 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "wl-clipboard-rs" @@ -2924,9 +2932,9 @@ dependencies = [ [[package]] name = "x11-clipboard" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0827f86aa910c4e73329a4f619deabe88ebb4b042370bf023c2d5d8b4eb54695" +checksum = "980b9aa9226c3b7de8e2adb11bf20124327c054e0e5812d2aac0b5b5a87e7464" dependencies = [ "x11rb", ] @@ -2975,9 +2983,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.4" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +checksum = "52839dc911083a8ef63efa4d039d1f58b5e409f923e44c80828f206f66e5541c" [[package]] name = "xsalsa20poly1305" @@ -3004,14 +3012,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.18", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b345ab3c..20382a63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,11 +26,11 @@ sha-1 = "0.10.0" sha2 = "0.10.0" hkdf = "0.12.2" hex = { version = "0.4.2", features = ["serde"] } -rand = "0.8.3" +rand = "0.8.0" log = "0.4.13" # zeroize = { version = "1.2.0", features = ["zeroize_derive"] } -base64 = "0.20.0" -futures_ringbuf = "0.3.1" +base64 = "0.21.0" +futures_ringbuf = "0.4.0" time = { version = "0.3.7", features = ["formatting"] } instant = { version = "0.1.12", features = ["wasm-bindgen"] } @@ -43,7 +43,7 @@ percent-encoding = { version = "2.1.0" } # Transit dependencies -stun_codec = { version = "0.2.0", optional = true } +stun_codec = { version = "0.3.0", optional = true } bytecodec = { version = "0.4.15", optional = true } async-trait = { version = "0.1.57", optional = true } noise-protocol = { version = "0.1.4", optional = true } @@ -61,12 +61,12 @@ tar = { version = "0.4.33", optional = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] libc = "0.2.101" async-std = { version = "1.12.0", features = ["attributes", "unstable"] } -async-tungstenite = { version = "0.17.1", features = ["async-std-runtime", "async-tls"] } +async-tungstenite = { version = "0.22.2", features = ["async-std-runtime", "async-tls"] } async-io = "1.6.0" # Transit -socket2 = { version = "0.4.1", optional = true } -if-addrs = { version = "0.8.0", optional = true } +socket2 = { version = "0.5.0", optional = true, features = ["all"] } +if-addrs = { version = "0.10.0", optional = true } # Transfer @@ -77,7 +77,6 @@ zstd = { version = "0.11.1", optional = true } wasm-timer = "0.2.5" ws_stream_wasm = "0.7.3" getrandom = { version = "0.2.5", features = ["js"] } -getrandom_2 = { package = "getrandom", version = "0.1.16", features = ["js-sys"] } # for some tests [dev-dependencies]