From 649bd1e35d81c314397a31cec469c25079958b54 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Fri, 9 Jun 2023 10:53:19 +0300 Subject: [PATCH 01/21] feat(noise): add WebTransport certhashes extension --- Cargo.lock | 2 + transports/noise/CHANGELOG.md | 4 + transports/noise/Cargo.toml | 6 +- transports/noise/src/generated/payload.proto | 8 +- .../noise/src/generated/payload/proto.rs | 44 ++++- transports/noise/src/io/framed.rs | 8 + transports/noise/src/io/handshake.rs | 67 ++++++++ transports/noise/src/lib.rs | 44 ++++- transports/noise/tests/smoke.rs | 4 +- .../noise/tests/webtransport_certhashes.rs | 152 ++++++++++++++++++ 10 files changed, 327 insertions(+), 12 deletions(-) create mode 100644 transports/noise/tests/webtransport_certhashes.rs diff --git a/Cargo.lock b/Cargo.lock index 39ee5d84cb5..10e298050bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2710,6 +2710,8 @@ dependencies = [ "libp2p-core", "libp2p-identity", "log", + "multiaddr", + "multihash", "once_cell", "quick-protobuf", "quickcheck-ext", diff --git a/transports/noise/CHANGELOG.md b/transports/noise/CHANGELOG.md index 27b37a81aef..33ca5036c77 100644 --- a/transports/noise/CHANGELOG.md +++ b/transports/noise/CHANGELOG.md @@ -5,8 +5,12 @@ - Remove deprecated APIs. See [PR 3511]. +- Add `Config::with_webtransport_certhashes`. See [PR 3991]. + This can be used by WebTransport implementers to send (responder) or verify (initiator) certhashes. + [PR 3511]: https://github.com/libp2p/rust-libp2p/pull/3511 [PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715 +[PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3991 ## 0.42.2 diff --git a/transports/noise/Cargo.toml b/transports/noise/Cargo.toml index 35648f376a2..397d87714ba 100644 --- a/transports/noise/Cargo.toml +++ b/transports/noise/Cargo.toml @@ -15,8 +15,10 @@ futures = "0.3.28" libp2p-core = { workspace = true } libp2p-identity = { workspace = true, features = ["ed25519"] } log = "0.4" -quick-protobuf = "0.8" +multiaddr = { workspace = true } +multihash = { workspace = true } once_cell = "1.18.0" +quick-protobuf = "0.8" rand = "0.8.3" sha2 = "0.10.0" static_assertions = "1" @@ -35,7 +37,7 @@ env_logger = "0.10.0" futures_ringbuf = "0.4.0" quickcheck = { workspace = true } -# Passing arguments to the docsrs builder in order to properly document cfg's. +# Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true diff --git a/transports/noise/src/generated/payload.proto b/transports/noise/src/generated/payload.proto index 1893dc55037..68bd01edd75 100644 --- a/transports/noise/src/generated/payload.proto +++ b/transports/noise/src/generated/payload.proto @@ -1,11 +1,15 @@ syntax = "proto3"; - package payload.proto; // Payloads for Noise handshake messages. +message NoiseExtensions { + repeated bytes webtransport_certhashes = 1; + repeated string stream_muxers = 2; +} + message NoiseHandshakePayload { bytes identity_key = 1; bytes identity_sig = 2; - bytes data = 3; + optional NoiseExtensions extensions = 4; } diff --git a/transports/noise/src/generated/payload/proto.rs b/transports/noise/src/generated/payload/proto.rs index 7b17a58ef37..98808ed466a 100644 --- a/transports/noise/src/generated/payload/proto.rs +++ b/transports/noise/src/generated/payload/proto.rs @@ -13,12 +13,48 @@ use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer use quick_protobuf::sizeofs::*; use super::super::*; +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct NoiseExtensions { + pub webtransport_certhashes: Vec>, + pub stream_muxers: Vec, +} + +impl<'a> MessageRead<'a> for NoiseExtensions { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.webtransport_certhashes.push(r.read_bytes(bytes)?.to_owned()), + Ok(18) => msg.stream_muxers.push(r.read_string(bytes)?.to_owned()), + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for NoiseExtensions { + fn get_size(&self) -> usize { + 0 + + self.webtransport_certhashes.iter().map(|s| 1 + sizeof_len((s).len())).sum::() + + self.stream_muxers.iter().map(|s| 1 + sizeof_len((s).len())).sum::() + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + for s in &self.webtransport_certhashes { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } + for s in &self.stream_muxers { w.write_with_tag(18, |w| w.write_string(&**s))?; } + Ok(()) + } +} + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Debug, Default, PartialEq, Clone)] pub struct NoiseHandshakePayload { pub identity_key: Vec, pub identity_sig: Vec, - pub data: Vec, + pub extensions: Option, } impl<'a> MessageRead<'a> for NoiseHandshakePayload { @@ -28,7 +64,7 @@ impl<'a> MessageRead<'a> for NoiseHandshakePayload { match r.next_tag(bytes) { Ok(10) => msg.identity_key = r.read_bytes(bytes)?.to_owned(), Ok(18) => msg.identity_sig = r.read_bytes(bytes)?.to_owned(), - Ok(26) => msg.data = r.read_bytes(bytes)?.to_owned(), + Ok(34) => msg.extensions = Some(r.read_message::(bytes)?), Ok(t) => { r.read_unknown(bytes, t)?; } Err(e) => return Err(e), } @@ -42,13 +78,13 @@ impl MessageWrite for NoiseHandshakePayload { 0 + if self.identity_key.is_empty() { 0 } else { 1 + sizeof_len((&self.identity_key).len()) } + if self.identity_sig.is_empty() { 0 } else { 1 + sizeof_len((&self.identity_sig).len()) } - + if self.data.is_empty() { 0 } else { 1 + sizeof_len((&self.data).len()) } + + self.extensions.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size())) } fn write_message(&self, w: &mut Writer) -> Result<()> { if !self.identity_key.is_empty() { w.write_with_tag(10, |w| w.write_bytes(&**&self.identity_key))?; } if !self.identity_sig.is_empty() { w.write_with_tag(18, |w| w.write_bytes(&**&self.identity_sig))?; } - if !self.data.is_empty() { w.write_with_tag(26, |w| w.write_bytes(&**&self.data))?; } + if let Some(ref s) = self.extensions { w.write_with_tag(34, |w| w.write_message(s))?; } Ok(()) } } diff --git a/transports/noise/src/io/framed.rs b/transports/noise/src/io/framed.rs index 8c1a49fc7b6..d7fa79fc815 100644 --- a/transports/noise/src/io/framed.rs +++ b/transports/noise/src/io/framed.rs @@ -81,6 +81,14 @@ impl NoiseFramed { } } + pub(crate) fn is_initiator(&self) -> bool { + self.session.is_initiator() + } + + pub(crate) fn is_responder(&self) -> bool { + !self.session.is_initiator() + } + /// Converts the `NoiseFramed` into a `NoiseOutput` encrypted data stream /// once the handshake is complete, including the static DH [`PublicKey`] /// of the remote, if received. diff --git a/transports/noise/src/io/handshake.rs b/transports/noise/src/io/handshake.rs index 3027fbfbd19..c853af7b189 100644 --- a/transports/noise/src/io/handshake.rs +++ b/transports/noise/src/io/handshake.rs @@ -23,6 +23,7 @@ mod proto { #![allow(unreachable_pub)] include!("../generated/mod.rs"); + pub use self::payload::proto::NoiseExtensions; pub use self::payload::proto::NoiseHandshakePayload; } @@ -32,7 +33,9 @@ use crate::{DecodeError, Error}; use bytes::Bytes; use futures::prelude::*; use libp2p_identity as identity; +use multihash::Multihash; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; +use std::collections::HashSet; use std::io; ////////////////////////////////////////////////////////////////////////////// @@ -49,6 +52,15 @@ pub(crate) struct State { dh_remote_pubkey_sig: Option>, /// The known or received public identity key of the remote, if any. id_remote_pubkey: Option, + /// The WebTransport certhashes of the responder, if any. + responder_webtransport_certhashes: Option>>, + /// The received extensions of the remote, if any. + remote_extensions: Option, +} + +/// Extensions +struct Extensions { + webtransport_certhashes: HashSet>, } impl State { @@ -63,12 +75,15 @@ impl State { session: snow::HandshakeState, identity: KeypairIdentity, expected_remote_key: Option, + responder_webtransport_certhashes: Option>>, ) -> Self { Self { identity, io: NoiseFramed::new(io, session), dh_remote_pubkey_sig: None, id_remote_pubkey: expected_remote_key, + responder_webtransport_certhashes, + remote_extensions: None, } } } @@ -77,6 +92,7 @@ impl State { /// Finish a handshake, yielding the established remote identity and the /// [`Output`] for communicating on the encrypted channel. pub(crate) fn finish(self) -> Result<(identity::PublicKey, Output), Error> { + let is_initiator = self.io.is_initiator(); let (pubkey, io) = self.io.into_transport()?; let id_pk = self @@ -91,10 +107,46 @@ impl State { return Err(Error::BadSignature); } + // Check WebTransport certhashes that responder reported back to us. + if is_initiator { + // We check only if we care (i.e. Config::with_webtransport_certhashes was used). + if let Some(expected_certhashes) = self.responder_webtransport_certhashes { + let ext = self.remote_extensions.ok_or_else(|| { + Error::UnknownWebTransportCerthashes( + expected_certhashes.to_owned(), + HashSet::new(), + ) + })?; + + let received_certhashes = ext.webtransport_certhashes; + + // Expected WebTransport certhashes must be a strict subset + // of the reported ones. + if !expected_certhashes.is_subset(&received_certhashes) { + return Err(Error::UnknownWebTransportCerthashes( + expected_certhashes, + received_certhashes, + )); + } + } + } + Ok((id_pk, io)) } } +impl From for Extensions { + fn from(value: proto::NoiseExtensions) -> Self { + Extensions { + webtransport_certhashes: value + .webtransport_certhashes + .into_iter() + .filter_map(|bytes| Multihash::read(&bytes[..]).ok()) + .collect(), + } + } +} + ////////////////////////////////////////////////////////////////////////////// // Handshake Message Futures @@ -149,6 +201,10 @@ where state.dh_remote_pubkey_sig = Some(pb.identity_sig); } + if let Some(extensions) = pb.extensions { + state.remote_extensions = Some(extensions.into()); + } + Ok(()) } @@ -164,6 +220,17 @@ where pb.identity_sig = state.identity.signature.clone(); + // If this is the responder then send WebTransport certhashes to initiator, if any. + if state.io.is_responder() { + if let Some(ref certhashes) = state.responder_webtransport_certhashes { + let ext = pb + .extensions + .get_or_insert_with(proto::NoiseExtensions::default); + + ext.webtransport_certhashes = certhashes.iter().map(|hash| hash.to_bytes()).collect(); + } + } + let mut msg = Vec::with_capacity(pb.get_size()); let mut writer = Writer::new(&mut msg); diff --git a/transports/noise/src/lib.rs b/transports/noise/src/lib.rs index b7360c0c799..be73ea3f7f9 100644 --- a/transports/noise/src/lib.rs +++ b/transports/noise/src/lib.rs @@ -68,7 +68,11 @@ use futures::prelude::*; use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use libp2p_identity as identity; use libp2p_identity::PeerId; +use multiaddr::Protocol; +use multihash::Multihash; use snow::params::NoiseParams; +use std::collections::HashSet; +use std::fmt::Write; use std::pin::Pin; /// The configuration for the noise handshake. @@ -76,6 +80,7 @@ use std::pin::Pin; pub struct Config { dh_keys: AuthenticKeypair, params: NoiseParams, + webtransport_certhashes: Option>>, /// Prologue to use in the noise handshake. /// @@ -94,6 +99,7 @@ impl Config { Ok(Self { dh_keys: noise_keys, params: PARAMS_XX.clone(), + webtransport_certhashes: None, prologue: vec![], }) } @@ -101,7 +107,17 @@ impl Config { /// Set the noise prologue. pub fn with_prologue(mut self, prologue: Vec) -> Self { self.prologue = prologue; + self + } + /// Set WebTransport certhashes extension. + /// + /// In case of initiator, these certhashes will be used to validate the ones reported by + /// responder. + /// + /// In case of responder, these certhashes will be reported to initiator. + pub fn with_webtransport_certhashes(mut self, certhashes: HashSet>) -> Self { + self.webtransport_certhashes = Some(certhashes).filter(|h| !h.is_empty()); self } @@ -114,7 +130,13 @@ impl Config { ) .build_responder()?; - let state = State::new(socket, session, self.dh_keys.identity, None); + let state = State::new( + socket, + session, + self.dh_keys.identity, + None, + self.webtransport_certhashes, + ); Ok(state) } @@ -128,7 +150,13 @@ impl Config { ) .build_initiator()?; - let state = State::new(socket, session, self.dh_keys.identity, None); + let state = State::new( + socket, + session, + self.dh_keys.identity, + None, + self.webtransport_certhashes, + ); Ok(state) } @@ -213,8 +241,20 @@ pub enum Error { InvalidPayload(#[from] DecodeError), #[error(transparent)] SigningError(#[from] libp2p_identity::SigningError), + #[error("Expected WebTransport certhashes ({}) are not a subset of received ones ({})", certhashes_to_string(.0), certhashes_to_string(.1))] + UnknownWebTransportCerthashes(HashSet>, HashSet>), } #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct DecodeError(quick_protobuf::Error); + +fn certhashes_to_string(certhashes: &HashSet>) -> String { + let mut s = String::new(); + + for hash in certhashes { + write!(&mut s, "{}", Protocol::Certhash(*hash)).unwrap(); + } + + s +} diff --git a/transports/noise/tests/smoke.rs b/transports/noise/tests/smoke.rs index b862a944dfd..6d1723ec7d6 100644 --- a/transports/noise/tests/smoke.rs +++ b/transports/noise/tests/smoke.rs @@ -50,8 +50,8 @@ fn xx() { futures::executor::block_on(async move { let ( - (reported_client_id, mut client_session), - (reported_server_id, mut server_session), + (reported_client_id, mut server_session), + (reported_server_id, mut client_session), ) = futures::future::try_join( noise::Config::new(&server_id) .unwrap() diff --git a/transports/noise/tests/webtransport_certhashes.rs b/transports/noise/tests/webtransport_certhashes.rs new file mode 100644 index 00000000000..41341fe8655 --- /dev/null +++ b/transports/noise/tests/webtransport_certhashes.rs @@ -0,0 +1,152 @@ +use libp2p_core::{InboundUpgrade, OutboundUpgrade}; +use libp2p_identity as identity; +use libp2p_noise as noise; +use multihash::Multihash; +use std::collections::HashSet; + +const SHA_256_MH: u64 = 0x12; + +#[test] +fn webtransport_same_set_of_certhashes() { + let (certhash1, certhash2, _) = certhashes(); + + handshake_with_certhashes(vec![certhash1, certhash2], vec![certhash1, certhash2]).unwrap(); +} + +#[test] +fn webtransport_subset_of_certhashes() { + let (certhash1, certhash2, _) = certhashes(); + + handshake_with_certhashes(vec![certhash1], vec![certhash1, certhash2]).unwrap(); +} + +#[test] +fn webtransport_client_without_certhashes() { + let (certhash1, certhash2, _) = certhashes(); + + // Valid when server uses CA-signed TLS certificate. + handshake_with_certhashes(vec![], vec![certhash1, certhash2]).unwrap(); +} + +#[test] +fn webtransport_client_and_server_without_certhashes() { + // Valid when server uses CA-signed TLS certificate. + handshake_with_certhashes(vec![], vec![]).unwrap(); +} + +#[test] +fn webtransport_server_empty_certhashes() { + let (certhash1, certhash2, _) = certhashes(); + + // Invalid case, because a MITM attacker may strip certificates of the server. + let Err(noise::Error::UnknownWebTransportCerthashes(expected, received)) = + handshake_with_certhashes(vec![certhash1, certhash2], vec![]) else { + panic!("unexpected result"); + }; + + assert_eq!(expected, HashSet::from([certhash1, certhash2])); + assert_eq!(received, HashSet::new()); +} + +#[test] +fn webtransport_client_uninit_certhashes() { + let (certhash1, certhash2, _) = certhashes(); + + // Valid when server uses CA-signed TLS certificate. + handshake_with_certhashes(None, vec![certhash1, certhash2]).unwrap(); +} + +#[test] +fn webtransport_client_and_server_uninit_certhashes() { + // Valid when server uses CA-signed TLS certificate. + handshake_with_certhashes(None, None).unwrap(); +} + +#[test] +fn webtransport_server_uninit_certhashes() { + let (certhash1, certhash2, _) = certhashes(); + + // Invalid case, because a MITM attacker may strip certificates of the server. + let Err(noise::Error::UnknownWebTransportCerthashes(expected, received)) = + handshake_with_certhashes(vec![certhash1, certhash2], None) else { + panic!("unexpected result"); + }; + + assert_eq!(expected, HashSet::from([certhash1, certhash2])); + assert_eq!(received, HashSet::new()); +} + +#[test] +fn webtransport_different_server_certhashes() { + let (certhash1, certhash2, certhash3) = certhashes(); + + let Err(noise::Error::UnknownWebTransportCerthashes(expected, received)) = + handshake_with_certhashes(vec![certhash1, certhash3], vec![certhash1, certhash2]) else { + panic!("unexpected result"); + }; + + assert_eq!(expected, HashSet::from([certhash1, certhash3])); + assert_eq!(received, HashSet::from([certhash1, certhash2])); +} + +#[test] +fn webtransport_superset_of_certhashes() { + let (certhash1, certhash2, _) = certhashes(); + + let Err(noise::Error::UnknownWebTransportCerthashes(expected, received)) = + handshake_with_certhashes(vec![certhash1, certhash2], vec![certhash1]) else { + panic!("unexpected result"); + }; + + assert_eq!(expected, HashSet::from([certhash1, certhash2])); + assert_eq!(received, HashSet::from([certhash1])); +} + +fn certhashes() -> (Multihash<64>, Multihash<64>, Multihash<64>) { + ( + Multihash::wrap(SHA_256_MH, b"1").unwrap(), + Multihash::wrap(SHA_256_MH, b"2").unwrap(), + Multihash::wrap(SHA_256_MH, b"3").unwrap(), + ) +} + +// `valid_certhases` must be a strict subset of `server_certhashes`. +fn handshake_with_certhashes( + valid_certhases: impl Into>>>, + server_certhashes: impl Into>>>, +) -> Result<(), noise::Error> { + let valid_certhases = valid_certhases.into(); + let server_certhashes = server_certhashes.into(); + + let client_id = identity::Keypair::generate_ed25519(); + let server_id = identity::Keypair::generate_ed25519(); + + let (client, server) = futures_ringbuf::Endpoint::pair(100, 100); + + futures::executor::block_on(async move { + let mut client_config = noise::Config::new(&client_id)?; + let mut server_config = noise::Config::new(&server_id)?; + + if let Some(valid_certhases) = valid_certhases { + client_config = + client_config.with_webtransport_certhashes(valid_certhases.into_iter().collect()); + } + + if let Some(server_certhashes) = server_certhashes { + server_config = + server_config.with_webtransport_certhashes(server_certhashes.into_iter().collect()); + } + + let ((reported_client_id, mut _server_session), (reported_server_id, mut _client_session)) = + futures::future::try_join( + server_config.upgrade_inbound(server, ""), + client_config.upgrade_outbound(client, ""), + ) + .await?; + + assert_eq!(reported_client_id, client_id.public().to_peer_id()); + assert_eq!(reported_server_id, server_id.public().to_peer_id()); + + Ok(()) + }) +} From b46517d9a7458b857d652951cd12550ea84ba21b Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 1 Jun 2023 18:05:43 +0300 Subject: [PATCH 02/21] chore(wasm-ext): Replace `parity-send-wrapper` with `send-wrapper` --- Cargo.lock | 16 ++++++++-------- transports/wasm-ext/Cargo.toml | 4 ++-- transports/wasm-ext/src/lib.rs | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10e298050bf..c3d7e340e38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1675,7 +1675,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" dependencies = [ "gloo-timers", - "send_wrapper", + "send_wrapper 0.4.0", ] [[package]] @@ -3041,7 +3041,7 @@ dependencies = [ "futures", "js-sys", "libp2p-core", - "parity-send-wrapper", + "send_wrapper 0.6.0", "wasm-bindgen", "wasm-bindgen-futures", ] @@ -3583,12 +3583,6 @@ dependencies = [ "libm", ] -[[package]] -name = "parity-send-wrapper" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" - [[package]] name = "parking" version = "2.0.0" @@ -4468,6 +4462,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" +[[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.163" diff --git a/transports/wasm-ext/Cargo.toml b/transports/wasm-ext/Cargo.toml index 54ada77b68b..68b344e5cb1 100644 --- a/transports/wasm-ext/Cargo.toml +++ b/transports/wasm-ext/Cargo.toml @@ -14,14 +14,14 @@ categories = ["network-programming", "asynchronous"] futures = "0.3.28" js-sys = "0.3.63" libp2p-core = { workspace = true } -parity-send-wrapper = "0.1.0" +send_wrapper = "0.6.0" wasm-bindgen = "0.2.86" wasm-bindgen-futures = "0.4.36" [features] websocket = [] -# Passing arguments to the docsrs builder in order to properly document cfg's. +# Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true diff --git a/transports/wasm-ext/src/lib.rs b/transports/wasm-ext/src/lib.rs index 91236ca8758..94259cb1ee6 100644 --- a/transports/wasm-ext/src/lib.rs +++ b/transports/wasm-ext/src/lib.rs @@ -40,7 +40,7 @@ use libp2p_core::{ transport::{ListenerId, TransportError, TransportEvent}, Multiaddr, Transport, }; -use parity_send_wrapper::SendWrapper; +use send_wrapper::SendWrapper; use std::{collections::VecDeque, error, fmt, io, mem, pin::Pin, task::Context, task::Poll}; use wasm_bindgen::{prelude::*, JsCast}; use wasm_bindgen_futures::JsFuture; From 1cc665629eef4e5352c5dead526b808fa0f54d8f Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Fri, 9 Jun 2023 13:29:26 +0300 Subject: [PATCH 03/21] feat(transport): add WebTransport for web-sys environments Uses `web-sys::WebTransport` and provides a `Transport` implementation. --- .github/workflows/ci.yml | 43 +- Cargo.lock | 70 +- Cargo.toml | 2 + libp2p/Cargo.toml | 3 + libp2p/src/lib.rs | 4 + transports/webtransport-websys/CHANGELOG.md | 5 + transports/webtransport-websys/Cargo.toml | 51 ++ transports/webtransport-websys/README.md | 27 + .../echo-server/.gitignore | 1 + .../echo-server/Dockerfile | 9 + .../webtransport-websys/echo-server/go.mod | 64 ++ .../webtransport-websys/echo-server/go.sum | 344 ++++++++++ .../webtransport-websys/echo-server/main.go | 99 +++ .../webtransport-websys/src/bindings.rs | 141 ++++ .../webtransport-websys/src/connection.rs | 647 ++++++++++++++++++ .../webtransport-websys/src/endpoint.rs | 228 ++++++ transports/webtransport-websys/src/error.rs | 36 + .../src/fused_js_promise.rs | 59 ++ transports/webtransport-websys/src/lib.rs | 18 + transports/webtransport-websys/src/stream.rs | 245 +++++++ .../webtransport-websys/src/transport.rs | 191 ++++++ transports/webtransport-websys/src/utils.rs | 108 +++ 22 files changed, 2392 insertions(+), 3 deletions(-) create mode 100644 transports/webtransport-websys/CHANGELOG.md create mode 100644 transports/webtransport-websys/Cargo.toml create mode 100644 transports/webtransport-websys/README.md create mode 100644 transports/webtransport-websys/echo-server/.gitignore create mode 100644 transports/webtransport-websys/echo-server/Dockerfile create mode 100644 transports/webtransport-websys/echo-server/go.mod create mode 100644 transports/webtransport-websys/echo-server/go.sum create mode 100644 transports/webtransport-websys/echo-server/main.go create mode 100644 transports/webtransport-websys/src/bindings.rs create mode 100644 transports/webtransport-websys/src/connection.rs create mode 100644 transports/webtransport-websys/src/endpoint.rs create mode 100644 transports/webtransport-websys/src/error.rs create mode 100644 transports/webtransport-websys/src/fused_js_promise.rs create mode 100644 transports/webtransport-websys/src/lib.rs create mode 100644 transports/webtransport-websys/src/stream.rs create mode 100644 transports/webtransport-websys/src/transport.rs create mode 100644 transports/webtransport-websys/src/utils.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd3737a49f4..822fe50782a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,12 +76,51 @@ jobs: run: | PACKAGE_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -e -r '.packages[] | select(.name == "'"$CRATE"'") | .version') SPECIFIED_VERSION=$(tomlq "workspace.dependencies.$CRATE.version" --file ./Cargo.toml) - + echo "Package version: $PACKAGE_VERSION"; echo "Specified version: $SPECIFIED_VERSION"; test "$PACKAGE_VERSION" = "$SPECIFIED_VERSION" + webtransport_tests: + name: Test WebTransport + runs-on: ubuntu-latest + defaults: + run: + working-directory: transports/webtransport-websys + steps: + - uses: actions/checkout@v3 + + - uses: dtolnay/rust-toolchain@stable + with: + target: wasm32-unknown-unknown + + - uses: docker/setup-buildx-action@v2 + + - uses: taiki-e/cache-cargo-install-action@v1 + with: + tool: wasm-pack@0.11.1 + + - name: Install Google Chrome + run: | + CHROME_VERSION=114.0.5735.106 + curl -O https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_$CHROME_VERSION-1_amd64.deb + sudo dpkg -i google-chrome-stable_$CHROME_VERSION-1_amd64.deb + + - name: Install chromedriver + run: | + CHROMEDRIVER_VERSION=114.0.5735.90 + curl -O https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip + unzip chromedriver_linux64.zip + + - name: Build echo-server + run: docker buildx build -o type=local,dest=echo-server -t echo-server echo-server + + - name: Run wasm-pack test (Chrome) + run: | + ./echo-server/echo-server & + wasm-pack test --chrome --chromedriver=./chromedriver --headless + cross: name: Compile on ${{ matrix.target }} strategy: @@ -284,6 +323,8 @@ jobs: steps: - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - id: cargo-metadata run: | WORKSPACE_MEMBERS=$(cargo metadata --format-version=1 --no-deps | jq -c '.packages | map(select(.publish == null) | .name)') diff --git a/Cargo.lock b/Cargo.lock index c3d7e340e38..d66f01d8ba0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -839,6 +839,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.9.2" @@ -2327,6 +2337,7 @@ dependencies = [ "libp2p-uds", "libp2p-wasm-ext", "libp2p-websocket", + "libp2p-webtransport-websys", "libp2p-yamux", "multiaddr", "pin-project", @@ -3106,6 +3117,28 @@ dependencies = [ "webpki-roots 0.23.1", ] +[[package]] +name = "libp2p-webtransport-websys" +version = "0.1.0" +dependencies = [ + "futures", + "getrandom 0.2.9", + "js-sys", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "log", + "multiaddr", + "multibase", + "multihash", + "send_wrapper 0.6.0", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + [[package]] name = "libp2p-yamux" version = "0.44.0" @@ -4384,6 +4417,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -4467,6 +4506,9 @@ name = "send_wrapper" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] [[package]] name = "serde" @@ -5320,11 +5362,35 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e636f3a428ff62b3742ebc3c70e254dfe12b8c2b469d688ea59cdd4abcf502" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18c1fad2f7c4958e7bcce014fa212f59a65d5e3721d0f77e6c0b27ede936ba3" +dependencies = [ + "proc-macro2", + "quote", +] + [[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", diff --git a/Cargo.toml b/Cargo.toml index 547dbabbeb9..e2a3962f782 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ members = [ "transports/wasm-ext", "transports/webrtc", "transports/websocket", + "transports/webtransport-websys", ] resolver = "2" @@ -94,6 +95,7 @@ libp2p-uds = { version = "0.39.0", path = "transports/uds" } libp2p-wasm-ext = { version = "0.40.0", path = "transports/wasm-ext" } libp2p-webrtc = { version = "0.5.0-alpha", path = "transports/webrtc" } libp2p-websocket = { version = "0.42.0", path = "transports/websocket" } +libp2p-webtransport-websys = { version = "0.1.0", path = "transports/webtransport-websys" } libp2p-yamux = { version = "0.44.0", path = "muxers/yamux" } multistream-select = { version = "0.13.0", path = "misc/multistream-select" } quick-protobuf-codec = { version = "0.2.0", path = "misc/quick-protobuf-codec" } diff --git a/libp2p/Cargo.toml b/libp2p/Cargo.toml index ddea2d3ea2d..c6f4e2a3e27 100644 --- a/libp2p/Cargo.toml +++ b/libp2p/Cargo.toml @@ -45,6 +45,7 @@ full = [ "wasm-ext", "wasm-ext-websocket", "websocket", + "webtransport-websys", "yamux", ] @@ -81,6 +82,7 @@ wasm-bindgen = ["futures-timer/wasm-bindgen", "instant/wasm-bindgen", "getrandom wasm-ext = ["dep:libp2p-wasm-ext"] wasm-ext-websocket = ["wasm-ext", "libp2p-wasm-ext?/websocket"] websocket = ["dep:libp2p-websocket"] +webtransport-websys = ["dep:libp2p-webtransport-websys"] yamux = ["dep:libp2p-yamux"] [dependencies] @@ -110,6 +112,7 @@ libp2p-rendezvous = { workspace = true, optional = true } libp2p-request-response = { workspace = true, optional = true } libp2p-swarm = { workspace = true } libp2p-wasm-ext = { workspace = true, optional = true } +libp2p-webtransport-websys = { workspace = true, optional = true } libp2p-yamux = { workspace = true, optional = true } multiaddr = { workspace = true } diff --git a/libp2p/src/lib.rs b/libp2p/src/lib.rs index 56b6adda490..059857d3b8a 100644 --- a/libp2p/src/lib.rs +++ b/libp2p/src/lib.rs @@ -127,6 +127,10 @@ pub use libp2p_wasm_ext as wasm_ext; #[cfg(not(target_arch = "wasm32"))] #[doc(inline)] pub use libp2p_websocket as websocket; +#[cfg(feature = "webtransport-websys")] +#[cfg_attr(docsrs, doc(cfg(feature = "webtransport-websys")))] +#[doc(inline)] +pub use libp2p_webtransport_websys as webtransport_websys; #[cfg(feature = "yamux")] #[doc(inline)] pub use libp2p_yamux as yamux; diff --git a/transports/webtransport-websys/CHANGELOG.md b/transports/webtransport-websys/CHANGELOG.md new file mode 100644 index 00000000000..dfc9e7d63f2 --- /dev/null +++ b/transports/webtransport-websys/CHANGELOG.md @@ -0,0 +1,5 @@ +## 0.1.0 - unreleased + +* Initial implementation of WebTranport trasnport that uses web-sys. [PR 4015] + +[PR 4015]: https://github.com/libp2p/rust-libp2p/pull/4015 diff --git a/transports/webtransport-websys/Cargo.toml b/transports/webtransport-websys/Cargo.toml new file mode 100644 index 00000000000..11fa8a89d3b --- /dev/null +++ b/transports/webtransport-websys/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "libp2p-webtransport-websys" +edition = "2021" +rust-version = { workspace = true } +description = "WebTransport for libp2p under WASM environment" +version = "0.1.0" +authors = [ + "Yiannis Marangos ", + "oblique ", +] +license = "MIT" +repository = "https://github.com/libp2p/rust-libp2p" +keywords = ["peer-to-peer", "libp2p", "networking"] +categories = ["network-programming", "asynchronous"] + +[dependencies] +futures = "0.3.28" +js-sys = "0.3.63" +libp2p-core = { workspace = true } +libp2p-identity = { workspace = true } +libp2p-noise = { workspace = true } +log = "0.4.18" +multiaddr = { workspace = true } +multihash = { workspace = true } +send_wrapper = { version = "0.6.0", features = ["futures"] } +thiserror = "1.0.4" +wasm-bindgen = "0.2.86" +wasm-bindgen-futures = "0.4.36" +web-sys = { version = "0.3.63", features = [ + "ReadableStreamDefaultReader", + "WebTransport", + "WebTransportBidirectionalStream", + "WebTransportHash", + "WebTransportOptions", + "WebTransportReceiveStream", + "WebTransportSendStream", + "WritableStreamDefaultWriter", +] } + +[dev-dependencies] +getrandom = { version = "0.2.9", features = ["js"] } +multibase = "0.9.1" +wasm-bindgen-test = "0.3.36" +web-sys = { version = "0.3.63", features = ["Response", "Window"] } + +# Passing arguments to the docsrs builder in order to properly document cfg's. +# More information: https://docs.rs/about/builds#cross-compiling +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] +rustc-args = ["--cfg", "docsrs"] diff --git a/transports/webtransport-websys/README.md b/transports/webtransport-websys/README.md new file mode 100644 index 00000000000..fcf07c26978 --- /dev/null +++ b/transports/webtransport-websys/README.md @@ -0,0 +1,27 @@ +# Run tests + +First you need to build and start the echo-server: + +``` +docker build -t echo-server echo-server +docker run -it --rm --network=host echo-server +``` + +On another terminal run: + +``` +wasm-pack test --chrome +``` + +Navigate with your browser at http://127.0.0.1:8000. + +You can also run the tests on a headless browser: + +``` +wasm-pack test --chrome --headless +``` + +> **Note:** For headless tests your Chrome browser needs to be compatible +> with chromedriver (i.e. they must have the same major version). +> +> You may need to define the path of chromedriver with `--chromedriver=/path/to/chromedriver`. diff --git a/transports/webtransport-websys/echo-server/.gitignore b/transports/webtransport-websys/echo-server/.gitignore new file mode 100644 index 00000000000..e831a850ae0 --- /dev/null +++ b/transports/webtransport-websys/echo-server/.gitignore @@ -0,0 +1 @@ +/echo-server diff --git a/transports/webtransport-websys/echo-server/Dockerfile b/transports/webtransport-websys/echo-server/Dockerfile new file mode 100644 index 00000000000..f498e2baa1b --- /dev/null +++ b/transports/webtransport-websys/echo-server/Dockerfile @@ -0,0 +1,9 @@ +# syntax=docker/dockerfile:1.5-labs +FROM docker.io/library/golang:1.20 AS builder +WORKDIR /workspace +ADD . . +RUN CGO_ENABLED=0 go build . + +FROM scratch +COPY --from=builder /workspace/echo-server / +ENTRYPOINT ["/echo-server"] diff --git a/transports/webtransport-websys/echo-server/go.mod b/transports/webtransport-websys/echo-server/go.mod new file mode 100644 index 00000000000..5856827d798 --- /dev/null +++ b/transports/webtransport-websys/echo-server/go.mod @@ -0,0 +1,64 @@ +module echo-server + +go 1.20 + +require ( + github.com/libp2p/go-libp2p v0.27.5 + github.com/multiformats/go-multiaddr v0.9.0 +) + +require ( + github.com/benbjohnson/clock v1.3.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/flynn/noise v1.0.0 // indirect + github.com/francoispqt/gojay v1.2.13 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect + github.com/ipfs/go-cid v0.4.1 // indirect + github.com/ipfs/go-log/v2 v2.5.1 // indirect + github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect + github.com/klauspost/compress v1.16.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/libp2p/go-netroute v0.2.1 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multicodec v0.8.1 // indirect + github.com/multiformats/go-multihash v0.2.1 // indirect + github.com/multiformats/go-multistream v0.4.1 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect + github.com/onsi/ginkgo/v2 v2.9.2 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qtls-go1-19 v0.3.2 // indirect + github.com/quic-go/qtls-go1-20 v0.2.2 // indirect + github.com/quic-go/quic-go v0.33.0 // indirect + github.com/quic-go/webtransport-go v0.5.2 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/tools v0.7.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + lukechampine.com/blake3 v1.1.7 // indirect +) diff --git a/transports/webtransport-websys/echo-server/go.sum b/transports/webtransport-websys/echo-server/go.sum new file mode 100644 index 00000000000..ab2832bce75 --- /dev/null +++ b/transports/webtransport-websys/echo-server/go.sum @@ -0,0 +1,344 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= +github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b h1:Qcx5LM0fSiks9uCyFZwDBUasd3lxd1RM0GYpL+Li5o4= +github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= +github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= +github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= +github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= +github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/libp2p/go-libp2p v0.27.5 h1:KwA7pXKXpz8hG6Cr1fMA7UkgleogcwQj0sxl5qquWRg= +github.com/libp2p/go-libp2p v0.27.5/go.mod h1:oMfQGTb9CHnrOuSM6yMmyK2lXz3qIhnkn2+oK3B1Y2g= +github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= +github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= +github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= +github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= +github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= +github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108= +github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= +github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= +github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= +github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= +github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= +github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= +github.com/quic-go/webtransport-go v0.5.2 h1:GA6Bl6oZY+g/flt00Pnu0XtivSD8vukOu3lYhJjnGEk= +github.com/quic-go/webtransport-go v0.5.2/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= +lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/transports/webtransport-websys/echo-server/main.go b/transports/webtransport-websys/echo-server/main.go new file mode 100644 index 00000000000..def4151bd1b --- /dev/null +++ b/transports/webtransport-websys/echo-server/main.go @@ -0,0 +1,99 @@ +package main + +import ( + "context" + "crypto/rand" + "fmt" + "io" + "net/http" + + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/transport" + "github.com/libp2p/go-libp2p/p2p/transport/quicreuse" + webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" + "github.com/multiformats/go-multiaddr" +) + +// This provides a way for test cases to discover the WebTransport address +func addrReporter(ma multiaddr.Multiaddr) { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + h := w.Header() + h.Add("Access-Control-Allow-Origin", "*") + h.Add("Cross-Origin-Resource-Policy", "cross-origin") + h.Add("Content-Type", "text/plain; charset=utf-8") + + fmt.Fprint(w, ma.String()) + }) + + http.ListenAndServe(":4455", nil) +} + +func serveConn(conn transport.CapableConn) { + go func() { + for { + stream, err := conn.OpenStream(context.Background()) + if err != nil { + break; + } + + // Stream is a local operation until data is send + // on the stream. We send a single byte to fully + // initiate the stream. + // + // Ref: https://github.com/libp2p/go-libp2p/issues/2343 + stream.Write([]byte("1")) + + go io.Copy(stream, stream) + } + }() + + for { + stream, err := conn.AcceptStream() + if err != nil { + break + } + + go io.Copy(stream, stream) + } +} + +func main() { + priv, pub, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + panic(err) + } + + peerId, err := peer.IDFromPublicKey(pub) + if err != nil { + panic(err) + } + + connManager, err := quicreuse.NewConnManager([32]byte{}) + if err != nil { + panic(err) + } + + transport, err := webtransport.New(priv, nil, connManager, nil, nil); + if err != nil { + panic(err) + } + + listener, err := transport.Listen(multiaddr.StringCast("/ip4/127.0.0.1/udp/0/quic-v1/webtransport")) + if err != nil { + panic(err) + } + + addr := listener.Multiaddr().Encapsulate(multiaddr.StringCast("/p2p/" + peerId.String())) + + go addrReporter(addr) + + for { + conn, err := listener.Accept() + if err != nil { + panic(nil) + } + + go serveConn(conn) + } +} diff --git a/transports/webtransport-websys/src/bindings.rs b/transports/webtransport-websys/src/bindings.rs new file mode 100644 index 00000000000..a8a1469f8ad --- /dev/null +++ b/transports/webtransport-websys/src/bindings.rs @@ -0,0 +1,141 @@ +//! This file is an extract from `web-sys` crate. It is a temporary +//! solution until `web_sys::WebTransport` and related structs get stabilized. +//! +//! Only the methods that are used by this crate are extracted. + +#![allow(clippy::all)] +use js_sys::{Object, Promise, Reflect}; +use wasm_bindgen::prelude::*; +use web_sys::{ReadableStream, WritableStream}; + +// WebTransport bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Object, js_name = WebTransport, typescript_type = "WebTransport")] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransport; + + #[wasm_bindgen(structural, method, getter, js_class = "WebTransport", js_name = ready)] + pub fn ready(this: &WebTransport) -> Promise; + + #[wasm_bindgen(structural, method, getter, js_class = "WebTransport", js_name = closed)] + pub fn closed(this: &WebTransport) -> Promise; + + #[wasm_bindgen(structural, method, getter, js_class = "WebTransport" , js_name = incomingBidirectionalStreams)] + pub fn incoming_bidirectional_streams(this: &WebTransport) -> ReadableStream; + + #[wasm_bindgen(catch, constructor, js_class = "WebTransport")] + pub fn new(url: &str) -> Result; + + #[wasm_bindgen(catch, constructor, js_class = "WebTransport")] + pub fn new_with_options( + url: &str, + options: &WebTransportOptions, + ) -> Result; + + #[wasm_bindgen(method, structural, js_class = "WebTransport", js_name = close)] + pub fn close(this: &WebTransport); + + #[wasm_bindgen (method, structural, js_class = "WebTransport", js_name = createBidirectionalStream)] + pub fn create_bidirectional_stream(this: &WebTransport) -> Promise; +} + +// WebTransportBidirectionalStream bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Object, js_name = WebTransportBidirectionalStream, typescript_type = "WebTransportBidirectionalStream")] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransportBidirectionalStream; + + #[wasm_bindgen(structural, method, getter, js_class = "WebTransportBidirectionalStream", js_name = readable)] + pub fn readable(this: &WebTransportBidirectionalStream) -> WebTransportReceiveStream; + + #[wasm_bindgen(structural, method, getter, js_class = "WebTransportBidirectionalStream", js_name = writable)] + pub fn writable(this: &WebTransportBidirectionalStream) -> WebTransportSendStream; +} + +// WebTransportReceiveStream bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = ReadableStream, extends = Object, js_name = WebTransportReceiveStream, typescript_type = "WebTransportReceiveStream")] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransportReceiveStream; +} + +// WebTransportSendStream bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = WritableStream, extends = Object, js_name = WebTransportSendStream, typescript_type = "WebTransportSendStream")] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransportSendStream; +} + +// WebTransportOptions bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Object, js_name = WebTransportOptions)] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransportOptions; +} + +impl WebTransportOptions { + pub fn new() -> Self { + #[allow(unused_mut)] + let mut ret: Self = JsCast::unchecked_into(Object::new()); + ret + } + + pub fn server_certificate_hashes(&mut self, val: &JsValue) -> &mut Self { + let r = ::js_sys::Reflect::set( + self.as_ref(), + &JsValue::from("serverCertificateHashes"), + &JsValue::from(val), + ); + debug_assert!( + r.is_ok(), + "setting properties should never fail on our dictionary objects" + ); + let _ = r; + self + } +} + +// WebTransportHash bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Object, js_name = WebTransportHash)] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransportHash; +} + +impl WebTransportHash { + pub fn new() -> Self { + #[allow(unused_mut)] + let mut ret: Self = JsCast::unchecked_into(Object::new()); + ret + } + + pub fn algorithm(&mut self, val: &str) -> &mut Self { + let r = Reflect::set( + self.as_ref(), + &JsValue::from("algorithm"), + &JsValue::from(val), + ); + debug_assert!( + r.is_ok(), + "setting properties should never fail on our dictionary objects" + ); + let _ = r; + self + } + + pub fn value(&mut self, val: &::js_sys::Object) -> &mut Self { + let r = Reflect::set(self.as_ref(), &JsValue::from("value"), &JsValue::from(val)); + debug_assert!( + r.is_ok(), + "setting properties should never fail on our dictionary objects" + ); + let _ = r; + self + } +} diff --git a/transports/webtransport-websys/src/connection.rs b/transports/webtransport-websys/src/connection.rs new file mode 100644 index 00000000000..e14b1e858a9 --- /dev/null +++ b/transports/webtransport-websys/src/connection.rs @@ -0,0 +1,647 @@ +use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent, StreamMuxerExt}; +use libp2p_core::{OutboundUpgrade, UpgradeInfo}; +use libp2p_identity::{Keypair, PeerId}; +use multihash::Multihash; +use send_wrapper::SendWrapper; +use std::collections::HashSet; +use std::future::poll_fn; +use std::pin::Pin; +use std::task::{ready, Context, Poll}; +use wasm_bindgen_futures::JsFuture; +use web_sys::ReadableStreamDefaultReader; + +use crate::bindings::{WebTransport, WebTransportBidirectionalStream}; +use crate::endpoint::Endpoint; +use crate::fused_js_promise::FusedJsPromise; +use crate::stream::StreamSend; +use crate::utils::{detach_promise, parse_reader_response, to_js_type}; +use crate::{Error, Stream}; + +/// An opened WebTransport connection. +#[derive(Debug)] +pub struct Connection { + session: WebTransport, + create_stream_promise: FusedJsPromise, + incoming_stream_promise: FusedJsPromise, + incoming_streams_reader: ReadableStreamDefaultReader, + closed: bool, +} + +/// Connection wrapped in [`SendWrapper`]. +/// +/// This is needed by Swarm. WASM is single-threaded and it is safe +/// to use [`SendWrapper`]. +#[derive(Debug)] +pub(crate) struct ConnectionSend { + inner: SendWrapper, +} + +impl Connection { + pub(crate) fn new(endpoint: &Endpoint) -> Result { + let url = endpoint.url(); + + let session = if endpoint.certhashes.is_empty() { + // Endpoint has CA-signed TLS certificate. + WebTransport::new(&url).map_err(Error::from_js_value)? + } else { + // Endpoint has self-signed TLS certificates. + let opts = endpoint.webtransport_opts(); + WebTransport::new_with_options(&url, &opts).map_err(Error::from_js_value)? + }; + + let incoming_streams = session.incoming_bidirectional_streams(); + let incoming_streams_reader = + to_js_type::(incoming_streams.get_reader())?; + + Ok(Connection { + session, + create_stream_promise: FusedJsPromise::new(), + incoming_stream_promise: FusedJsPromise::new(), + incoming_streams_reader, + closed: false, + }) + } + + /// Authenticates with the server + /// + /// This methods runs the security handshake as descripted + /// in the [spec][1]. It validates the certhashes and peer ID + /// of the server. + /// + /// [1]: https://github.com/libp2p/specs/tree/master/webtransport#security-handshake + pub(crate) async fn authenticate( + &mut self, + keypair: &Keypair, + remote_peer: Option, + certhashes: HashSet>, + ) -> Result { + JsFuture::from(self.session.ready()) + .await + .map_err(Error::from_js_value)?; + + let stream = StreamSend::new(self.create_stream().await?); + let mut noise = libp2p_noise::Config::new(keypair)?; + + if !certhashes.is_empty() { + noise = noise.with_webtransport_certhashes(certhashes); + } + + // We do not use `upgrade::apply_outbound` function because it uses + // `multistream_select` protocol, which is not used by WebTransport spec. + let info = noise.protocol_info().next().unwrap_or_default(); + let (peer_id, _io) = noise.upgrade_outbound(stream, info).await?; + + // TODO: This should be part libp2p-noise + if let Some(expected_peer_id) = remote_peer { + if peer_id != expected_peer_id { + return Err(Error::UnknownRemotePeerId); + } + } + + Ok(peer_id) + } + + /// Creates new outbound stream. + async fn create_stream(&mut self) -> Result { + poll_fn(|cx| self.poll_outbound_unpin(cx)).await + } + + /// Initiates and polls a promise from `create_bidirectional_stream`. + fn poll_create_bidirectional_stream( + &mut self, + cx: &mut Context, + ) -> Poll> { + // Create bidirectional stream + let val = ready!(self + .create_stream_promise + .maybe_init_and_poll(cx, || self.session.create_bidirectional_stream())) + .map_err(Error::from_js_value)?; + + let bidi_stream = to_js_type::(val)?; + let stream = Stream::new(bidi_stream)?; + + Poll::Ready(Ok(stream)) + } + + /// Polls for incoming stream from `incoming_bidirectional_streams` reader. + fn poll_incoming_bidirectional_streams( + &mut self, + cx: &mut Context, + ) -> Poll> { + // Read the next incoming stream from the JS channel + let val = ready!(self + .incoming_stream_promise + .maybe_init_and_poll(cx, || self.incoming_streams_reader.read())) + .map_err(Error::from_js_value)?; + + let val = parse_reader_response(&val) + .map_err(Error::from_js_value)? + .ok_or_else(|| Error::JsError("incoming_bidirectional_streams closed".to_string()))?; + + let bidi_stream = to_js_type::(val)?; + let stream = Stream::new(bidi_stream)?; + + Poll::Ready(Ok(stream)) + } + + /// Closes the session. + /// + /// This closes the streams also and they will return an error + /// when they will be used. + fn close_session(&mut self) { + if !self.closed { + detach_promise(self.incoming_streams_reader.cancel()); + self.session.close(); + self.closed = true; + } + } +} + +/// WebTransport native multiplexing +impl StreamMuxer for Connection { + type Substream = Stream; + type Error = Error; + + fn poll_inbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + self.poll_incoming_bidirectional_streams(cx) + } + + fn poll_outbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + self.poll_create_bidirectional_stream(cx) + } + + fn poll_close( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + self.close_session(); + Poll::Ready(Ok(())) + } + + fn poll( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Pending + } +} + +impl Drop for Connection { + fn drop(&mut self) { + self.close_session(); + } +} + +impl ConnectionSend { + pub(crate) fn new(conn: Connection) -> Self { + ConnectionSend { + inner: SendWrapper::new(conn), + } + } +} + +impl StreamMuxer for ConnectionSend { + type Substream = StreamSend; + type Error = Error; + + fn poll_inbound( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let stream = ready!(self.get_mut().inner.poll_inbound_unpin(cx))?; + Poll::Ready(Ok(StreamSend::new(stream))) + } + + fn poll_outbound( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let stream = ready!(self.get_mut().inner.poll_outbound_unpin(cx))?; + Poll::Ready(Ok(StreamSend::new(stream))) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.get_mut().inner.poll_close_unpin(cx) + } + + fn poll( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + self.get_mut().inner.poll_unpin(cx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::channel::oneshot; + use futures::{AsyncReadExt, AsyncWriteExt}; + use getrandom::getrandom; + use libp2p_core::Transport as _; + use libp2p_identity::Keypair; + use multiaddr::{Multiaddr, Protocol}; + use std::future::poll_fn; + use wasm_bindgen_futures::{spawn_local, JsFuture}; + use wasm_bindgen_test::wasm_bindgen_test; + use web_sys::{window, Response}; + + use crate::utils::to_js_type; + use crate::{Config, Stream, Transport}; + + #[wasm_bindgen_test] + async fn single_conn_single_stream() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + } + + #[wasm_bindgen_test] + async fn single_conn_single_stream_incoming() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); + let mut stream = incoming_stream(&mut conn).await; + + send_recv(&mut stream).await; + } + + #[wasm_bindgen_test] + async fn single_conn_multiple_streams() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let mut tasks = Vec::new(); + + let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); + let mut streams = Vec::new(); + + for i in 0..30 { + let stream = if i % 2 == 0 { + create_stream(&mut conn).await + } else { + incoming_stream(&mut conn).await + }; + + streams.push(stream); + } + + for stream in streams { + tasks.push(send_recv_task(stream)); + } + + futures::future::try_join_all(tasks).await.unwrap(); + } + + #[wasm_bindgen_test] + async fn multiple_conn_multiple_streams() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let mut tasks = Vec::new(); + let mut conns = Vec::new(); + + for _ in 0..10 { + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut streams = Vec::new(); + + for i in 0..10 { + let stream = if i % 2 == 0 { + create_stream(&mut conn).await + } else { + incoming_stream(&mut conn).await + }; + + streams.push(stream); + } + + // If `conn` gets drop then its streams will close. + // Keep it alive by moving it to the outer scope. + conns.push(conn); + + for stream in streams { + tasks.push(send_recv_task(stream)); + } + } + + futures::future::try_join_all(tasks).await.unwrap(); + } + + #[wasm_bindgen_test] + async fn multiple_conn_multiple_streams_sequential() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + for _ in 0..10 { + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + + for i in 0..10 { + let mut stream = if i % 2 == 0 { + create_stream(&mut conn).await + } else { + incoming_stream(&mut conn).await + }; + + send_recv(&mut stream).await; + } + } + } + + #[wasm_bindgen_test] + async fn allow_read_after_closing_writer() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + + let mut stream = create_stream(&mut conn).await; + + // Test that stream works + send_recv(&mut stream).await; + + // Write random data + let mut send_buf = [0u8; 1024]; + getrandom(&mut send_buf).unwrap(); + stream.write_all(&send_buf).await.unwrap(); + + // Close writer by calling AsyncWrite::poll_close + stream.close().await.unwrap(); + + // Make sure writer is closed + stream.write_all(b"1").await.unwrap_err(); + + // We should be able to read + let mut recv_buf = [0u8; 1024]; + stream.read_exact(&mut recv_buf).await.unwrap(); + + assert_eq!(send_buf, recv_buf); + } + + #[wasm_bindgen_test] + async fn poll_outbound_error_after_connection_close() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + + // Make sure that poll_outbound works well before closing the connection + let mut stream = create_stream(&mut conn).await; + send_recv(&mut stream).await; + drop(stream); + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + poll_fn(|cx| Pin::new(&mut conn).poll_outbound(cx)) + .await + .expect_err("poll_outbound error after conn closed"); + } + + #[wasm_bindgen_test] + async fn poll_inbound_error_after_connection_close() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + + // Make sure that poll_inbound works well before closing the connection + let mut stream = incoming_stream(&mut conn).await; + send_recv(&mut stream).await; + drop(stream); + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + poll_fn(|cx| Pin::new(&mut conn).poll_inbound(cx)) + .await + .expect_err("poll_inbound error after conn closed"); + } + + #[wasm_bindgen_test] + async fn read_error_after_connection_drop() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + + drop(conn); + + let mut buf = [0u8; 16]; + stream + .read(&mut buf) + .await + .expect_err("read error after conn drop"); + } + + #[wasm_bindgen_test] + async fn read_error_after_connection_close() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + let mut buf = [0u8; 16]; + stream + .read(&mut buf) + .await + .expect_err("read error after conn drop"); + } + + #[wasm_bindgen_test] + async fn write_error_after_connection_drop() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + + drop(conn); + + let buf = [0u8; 16]; + stream + .write(&buf) + .await + .expect_err("write error after conn drop"); + } + + #[wasm_bindgen_test] + async fn write_error_after_connection_close() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + let buf = [0u8; 16]; + stream + .write(&buf) + .await + .expect_err("write error after conn drop"); + } + + #[wasm_bindgen_test] + async fn connect_without_peer_id() { + let mut addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + // Remove peer id + addr.pop(); + + let mut transport = Transport::new(Config::new(&keypair)); + transport.dial(addr).unwrap().await.unwrap(); + } + + #[wasm_bindgen_test] + async fn error_on_unknown_peer_id() { + let mut addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + // Remove peer id + addr.pop(); + + // Add an unknown one + addr.push(Protocol::P2p(PeerId::random())); + + let mut transport = Transport::new(Config::new(&keypair)); + let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); + assert!(matches!(e, Error::UnknownRemotePeerId)); + } + + #[wasm_bindgen_test] + async fn error_on_unknown_certhash() { + let mut addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + // Remove peer id + let peer_id = addr.pop().unwrap(); + + // Add unknown certhash + addr.push(Protocol::Certhash(Multihash::wrap(1, b"1").unwrap())); + + // Add peer id back + addr.push(peer_id); + + let mut transport = Transport::new(Config::new(&keypair)); + let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); + assert!(matches!( + e, + Error::Noise(libp2p_noise::Error::UnknownWebTransportCerthashes(..)) + )); + } + + /// Helper that returns the multiaddress of echo-server + /// + /// It fetches the multiaddress via HTTP request to + /// 127.0.0.1:4455. + async fn fetch_server_addr() -> Multiaddr { + let url = "http://127.0.0.1:4455/"; + let window = window().expect("failed to get browser window"); + + let value = JsFuture::from(window.fetch_with_str(url)) + .await + .expect("fetch failed"); + let resp = to_js_type::(value).expect("cast failed"); + + let text = resp.text().expect("text failed"); + let text = JsFuture::from(text).await.expect("text promise failed"); + + text.as_string() + .filter(|s| !s.is_empty()) + .expect("response not a text") + .parse() + .unwrap() + } + + async fn create_stream(conn: &mut Connection) -> Stream { + poll_fn(|cx| Pin::new(&mut *conn).poll_outbound(cx)) + .await + .unwrap() + } + + async fn incoming_stream(conn: &mut Connection) -> Stream { + let mut stream = poll_fn(|cx| Pin::new(&mut *conn).poll_inbound(cx)) + .await + .unwrap(); + + // For the stream to be initiated `echo-server` sends a single byte + let mut buf = [0u8; 1]; + stream.read_exact(&mut buf).await.unwrap(); + + stream + } + + fn send_recv_task(mut steam: Stream) -> oneshot::Receiver<()> { + let (tx, rx) = oneshot::channel(); + + spawn_local(async move { + send_recv(&mut steam).await; + tx.send(()).unwrap(); + }); + + rx + } + + async fn send_recv(stream: &mut Stream) { + let mut send_buf = [0u8; 1024]; + let mut recv_buf = [0u8; 1024]; + + for _ in 0..30 { + getrandom(&mut send_buf).unwrap(); + + stream.write_all(&send_buf).await.unwrap(); + stream.read_exact(&mut recv_buf).await.unwrap(); + + assert_eq!(send_buf, recv_buf); + } + } +} diff --git a/transports/webtransport-websys/src/endpoint.rs b/transports/webtransport-websys/src/endpoint.rs new file mode 100644 index 00000000000..1766a9950f2 --- /dev/null +++ b/transports/webtransport-websys/src/endpoint.rs @@ -0,0 +1,228 @@ +use js_sys::{Array, Uint8Array}; +use libp2p_identity::PeerId; +use multiaddr::{Multiaddr, Protocol}; +use multihash::Multihash; +use std::collections::HashSet; + +use crate::bindings::{WebTransportHash, WebTransportOptions}; +use crate::Error; + +pub(crate) struct Endpoint { + pub(crate) host: String, + pub(crate) port: u16, + pub(crate) is_ipv6: bool, + pub(crate) certhashes: HashSet>, + pub(crate) remote_peer: Option, +} + +impl Endpoint { + pub(crate) fn from_multiaddr(addr: &Multiaddr) -> Result { + let mut host = None; + let mut port = None; + let mut found_quic = false; + let mut found_webtransport = false; + let mut certhashes = HashSet::new(); + let mut remote_peer = None; + let mut is_ipv6 = false; + + for proto in addr.iter() { + match proto { + Protocol::Ip4(addr) => { + if host.is_some() { + return Err(Error::InvalidMultiaddr("More than one host definitions")); + } + + host = Some(addr.to_string()); + } + Protocol::Ip6(addr) => { + if host.is_some() { + return Err(Error::InvalidMultiaddr("More than one host definitions")); + } + + is_ipv6 = true; + host = Some(addr.to_string()); + } + Protocol::Dns(domain) | Protocol::Dns4(domain) | Protocol::Dns6(domain) => { + if port.is_some() { + return Err(Error::InvalidMultiaddr("More than one host definitions")); + } + + host = Some(domain.to_string()) + } + Protocol::Dnsaddr(_) => { + return Err(Error::InvalidMultiaddr( + "/dnsaddr not supported from within a browser", + )); + } + Protocol::Udp(p) => { + if port.is_some() { + return Err(Error::InvalidMultiaddr("More than one port definitions")); + } + + port = Some(p); + } + Protocol::Quic | Protocol::QuicV1 => { + if host.is_none() || port.is_none() { + return Err(Error::InvalidMultiaddr( + "No host and port definition before /quic/webtransport", + )); + } + + found_quic = true; + } + Protocol::WebTransport => { + if !found_quic { + return Err(Error::InvalidMultiaddr( + "/quic is not found before /webtransport", + )); + } + + found_webtransport = true; + } + Protocol::Certhash(hash) => { + if !found_webtransport { + return Err(Error::InvalidMultiaddr( + "/certhashes must be after /quic/found_webtransport", + )); + } + + certhashes.insert(hash); + } + Protocol::P2p(peer) => { + if remote_peer.is_some() { + return Err(Error::InvalidMultiaddr("More than one peer definitions")); + } + + remote_peer = Some(peer); + } + _ => {} + } + } + + if !found_quic || !found_webtransport { + return Err(Error::InvalidMultiaddr( + "Not a /quic/webtransport multiaddr", + )); + } + + let host = host.ok_or_else(|| Error::InvalidMultiaddr("Host is not defined"))?; + let port = port.ok_or_else(|| Error::InvalidMultiaddr("Port is not defined"))?; + + Ok(Endpoint { + host, + port, + is_ipv6, + certhashes, + remote_peer, + }) + } + + pub(crate) fn url(&self) -> String { + let host = &self.host; + let port = self.port; + + if self.is_ipv6 { + format!("https://[{host}]:{port}/.well-known/libp2p-webtransport?type=noise") + } else { + format!("https://{host}:{port}/.well-known/libp2p-webtransport?type=noise") + } + } + + pub(crate) fn webtransport_opts(&self) -> WebTransportOptions { + let mut opts = WebTransportOptions::new(); + let hashes = Array::new(); + + for hash in &self.certhashes { + let digest = Uint8Array::from(hash.digest()); + + let mut jshash = WebTransportHash::new(); + jshash.algorithm("sha-256").value(&digest); + + hashes.push(&jshash); + } + + opts.server_certificate_hashes(&hashes); + + opts + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + use wasm_bindgen_test::wasm_bindgen_test; + + fn multihash_from_str(s: &str) -> Multihash<64> { + let (_base, bytes) = multibase::decode(s).unwrap(); + Multihash::from_bytes(&bytes).unwrap() + } + + #[wasm_bindgen_test] + fn valid_webtransport_multiaddr() { + let addr = Multiaddr::from_str("/ip4/127.0.0.1/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); + let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); + + assert_eq!(endpoint.host, "127.0.0.1"); + assert_eq!(endpoint.port, 44874); + assert_eq!(endpoint.certhashes.len(), 2); + + assert!(endpoint.certhashes.contains(&multihash_from_str( + "uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng" + ))); + + assert!(endpoint.certhashes.contains(&multihash_from_str( + "uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ" + ))); + + assert_eq!( + endpoint.remote_peer.unwrap(), + PeerId::from_str("12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap() + ); + + assert_eq!( + endpoint.url(), + "https://127.0.0.1:44874/.well-known/libp2p-webtransport?type=noise" + ); + } + + #[wasm_bindgen_test] + fn valid_webtransport_multiaddr_without_certhashes() { + let addr = Multiaddr::from_str("/ip4/127.0.0.1/udp/44874/quic-v1/webtransport/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); + let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); + + assert_eq!(endpoint.host, "127.0.0.1"); + assert_eq!(endpoint.port, 44874); + assert_eq!(endpoint.certhashes.len(), 0); + assert_eq!( + endpoint.remote_peer.unwrap(), + PeerId::from_str("12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap() + ); + } + + #[wasm_bindgen_test] + fn ipv6_webtransport() { + let addr = Multiaddr::from_str("/ip6/::1/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); + let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); + + assert_eq!(endpoint.host, "::1"); + assert_eq!(endpoint.port, 44874); + assert_eq!( + endpoint.url(), + "https://[::1]:44874/.well-known/libp2p-webtransport?type=noise" + ); + } + + #[wasm_bindgen_test] + fn dns_webtransport() { + let addr = Multiaddr::from_str("/dns/libp2p.io/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); + let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); + + assert_eq!(endpoint.host, "libp2p.io"); + assert_eq!(endpoint.port, 44874); + assert_eq!( + endpoint.url(), + "https://libp2p.io:44874/.well-known/libp2p-webtransport?type=noise" + ); + } +} diff --git a/transports/webtransport-websys/src/error.rs b/transports/webtransport-websys/src/error.rs new file mode 100644 index 00000000000..ad85cab7537 --- /dev/null +++ b/transports/webtransport-websys/src/error.rs @@ -0,0 +1,36 @@ +use wasm_bindgen::{JsCast, JsValue}; + +/// Errors that may happen on the [`Transport`](crate::Transport) or the +/// [`Connection`](crate::Connection). +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Invalid multiaddr: {0}")] + InvalidMultiaddr(&'static str), + + #[error("Noise authentication failed")] + Noise(#[from] libp2p_noise::Error), + + #[error("JavaScript error: {0}")] + JsError(String), + + #[error("JavaScript typecasting failed")] + JsCastFailed, + + #[error("Unknown remote peer ID")] + UnknownRemotePeerId, +} + +impl Error { + pub(crate) fn from_js_value(value: JsValue) -> Self { + let s = if value.is_instance_of::() { + js_sys::Error::from(value) + .to_string() + .as_string() + .unwrap_or_else(|| "Unknown error".to_string()) + } else { + "Unknown error".to_string() + }; + + Error::JsError(s) + } +} diff --git a/transports/webtransport-websys/src/fused_js_promise.rs b/transports/webtransport-websys/src/fused_js_promise.rs new file mode 100644 index 00000000000..759da4e74d5 --- /dev/null +++ b/transports/webtransport-websys/src/fused_js_promise.rs @@ -0,0 +1,59 @@ +use futures::FutureExt; +use js_sys::Promise; +use std::task::{ready, Context, Poll}; +use wasm_bindgen::JsValue; +use wasm_bindgen_futures::JsFuture; + +/// Convenient wrapper to poll a promise to completion. +#[derive(Debug)] +pub(crate) struct FusedJsPromise { + promise: Option, +} + +impl FusedJsPromise { + /// Creates new uninitialized promise. + pub(crate) fn new() -> Self { + FusedJsPromise { promise: None } + } + + /// Initialize promise if needed and then poll. + /// + /// If promise is not initialized then `init` is called to initialize it. + pub(crate) fn maybe_init_and_poll( + &mut self, + cx: &mut Context, + init: F, + ) -> Poll> + where + F: FnOnce() -> Promise, + { + if self.promise.is_none() { + self.promise = Some(JsFuture::from(init())); + } + + self.poll(cx) + } + + /// Poll an already initialized promise. + /// + /// # Panics + /// + /// Panics if promise is not initialized. Use `maybe_init_and_poll` if unsure. + pub(crate) fn poll(&mut self, cx: &mut Context) -> Poll> { + let val = ready!(self + .promise + .as_mut() + .expect("CachedJsPromise not initialized") + .poll_unpin(cx)); + + // Future finished, drop it + self.promise.take(); + + Poll::Ready(val) + } + + /// Checks if promise is already running + pub(crate) fn is_active(&self) -> bool { + self.promise.is_some() + } +} diff --git a/transports/webtransport-websys/src/lib.rs b/transports/webtransport-websys/src/lib.rs new file mode 100644 index 00000000000..1e8f08fdead --- /dev/null +++ b/transports/webtransport-websys/src/lib.rs @@ -0,0 +1,18 @@ +//! Libp2p WebTransport built on [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html) + +mod bindings; +mod connection; +mod endpoint; +mod error; +mod fused_js_promise; +mod stream; +mod transport; +mod utils; + +pub use self::connection::Connection; +pub use self::error::Error; +pub use self::stream::Stream; +pub use self::transport::{Config, Transport}; + +#[cfg(test)] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); diff --git a/transports/webtransport-websys/src/stream.rs b/transports/webtransport-websys/src/stream.rs new file mode 100644 index 00000000000..bbfed17d027 --- /dev/null +++ b/transports/webtransport-websys/src/stream.rs @@ -0,0 +1,245 @@ +use futures::{AsyncRead, AsyncWrite}; +use js_sys::Uint8Array; +use send_wrapper::SendWrapper; +use std::io; +use std::pin::Pin; +use std::task::ready; +use std::task::{Context, Poll}; +use web_sys::{ReadableStreamDefaultReader, WritableStreamDefaultWriter}; + +use crate::bindings::WebTransportBidirectionalStream; +use crate::fused_js_promise::FusedJsPromise; +use crate::utils::{detach_promise, parse_reader_response, to_io_error, to_js_type}; +use crate::Error; + +/// A stream on a connection. +#[derive(Debug)] +pub struct Stream { + reader: ReadableStreamDefaultReader, + reader_read_promise: FusedJsPromise, + read_leftovers: Option, + writer: WritableStreamDefaultWriter, + writer_state: StreamState, + writer_ready_promise: FusedJsPromise, + writer_closed_promise: FusedJsPromise, +} + +/// Stream wrapped in [`SendWrapper`]. +/// +/// This is needed by Swarm. WASM is single-threaded and it is safe +/// to use [`SendWrapper`]. +#[derive(Debug)] +pub(crate) struct StreamSend { + inner: SendWrapper, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum StreamState { + Open, + Closing, + Closed, +} + +impl Stream { + pub(crate) fn new(bidi_stream: WebTransportBidirectionalStream) -> Result { + let recv_stream = bidi_stream.readable(); + let send_stream = bidi_stream.writable(); + + let reader = to_js_type::(recv_stream.get_reader())?; + let writer = send_stream.get_writer().map_err(Error::from_js_value)?; + + Ok(Stream { + reader, + reader_read_promise: FusedJsPromise::new(), + read_leftovers: None, + writer, + writer_state: StreamState::Open, + writer_ready_promise: FusedJsPromise::new(), + writer_closed_promise: FusedJsPromise::new(), + }) + } + + fn poll_writer_ready(&mut self, cx: &mut Context) -> Poll> { + if self.writer_state != StreamState::Open { + return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())); + } + + let desired_size = self + .writer + .desired_size() + .map_err(to_io_error)? + .map(|n| n.trunc() as i64) + .unwrap_or(0); + + // We need to poll if the queue is full or if the promise was already activated. + // + // NOTE: `desired_size` can be negative if we overcommit messages to the queue. + if desired_size <= 0 || self.writer_ready_promise.is_active() { + ready!(self + .writer_ready_promise + .maybe_init_and_poll(cx, || self.writer.ready())) + .map_err(to_io_error)?; + } + + Poll::Ready(Ok(())) + } + + fn poll_writer_close(&mut self, cx: &mut Context) -> Poll> { + match self.writer_state { + StreamState::Open => { + self.writer_state = StreamState::Closing; + + // Initiate close + detach_promise(self.writer.close()); + + // Assume closed on error + let _ = ready!(self + .writer_closed_promise + .maybe_init_and_poll(cx, || self.writer.closed())); + + self.writer_state = StreamState::Closed; + } + StreamState::Closing => { + // Assume closed on error + let _ = ready!(self.writer_closed_promise.poll(cx)); + self.writer_state = StreamState::Closed; + } + StreamState::Closed => {} + } + + Poll::Ready(Ok(())) + } + + fn poll_reader_read(&mut self, cx: &mut Context) -> Poll>> { + let val = ready!(self + .reader_read_promise + .maybe_init_and_poll(cx, || self.reader.read())) + .map_err(to_io_error)?; + + let val = parse_reader_response(&val) + .map_err(to_io_error)? + .map(Uint8Array::from); + + Poll::Ready(Ok(val)) + } +} + +impl Drop for Stream { + fn drop(&mut self) { + // Close writer. + // + // We choose to use `close()` instead of `abort()`, because + // abort was causing some side effects on the WebTransport + // layer and connection was lost. + detach_promise(self.writer.close()); + + // Cancel any ongoing reads. + detach_promise(self.reader.cancel()); + } +} + +impl AsyncRead for Stream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let this = self.get_mut(); + + // If we have leftovers from a previous read, then use them. + // Otherwise read new data. + let data = match this.read_leftovers.take() { + Some(data) => data, + None => { + match ready!(this.poll_reader_read(cx))? { + Some(data) => data, + // EOF + None => return Poll::Ready(Ok(0)), + } + } + }; + + if data.byte_length() == 0 { + return Poll::Ready(Ok(0)); + } + + let out_len = data.byte_length().min(buf.len() as u32); + data.slice(0, out_len).copy_to(&mut buf[..out_len as usize]); + + let leftovers = data.slice(out_len, data.byte_length()); + + if leftovers.byte_length() > 0 { + this.read_leftovers = Some(leftovers); + } + + Poll::Ready(Ok(out_len as usize)) + } +} + +impl AsyncWrite for Stream { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { + let this = self.get_mut(); + + ready!(this.poll_writer_ready(cx))?; + + let len = buf.len() as u32; + let data = Uint8Array::new_with_length(len); + data.copy_from(buf); + + detach_promise(this.writer.write_with_chunk(&data)); + + Poll::Ready(Ok(len as usize)) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let this = self.get_mut(); + + if this.writer_state == StreamState::Open { + // Writer has queue size of 1, so as soon it is ready, this means the + // messages were flushed. + this.poll_writer_ready(cx) + } else { + debug_assert!( + false, + "libp2p_webtransport_websys::Stream: poll_flush called after poll_close" + ); + Poll::Ready(Ok(())) + } + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.get_mut().poll_writer_close(cx) + } +} + +impl StreamSend { + pub(crate) fn new(stream: Stream) -> Self { + StreamSend { + inner: SendWrapper::new(stream), + } + } +} + +impl AsyncRead for StreamSend { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Pin::new(&mut *self.get_mut().inner).poll_read(cx, buf) + } +} + +impl AsyncWrite for StreamSend { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { + Pin::new(&mut *self.get_mut().inner).poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + Pin::new(&mut *self.get_mut().inner).poll_flush(cx) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + Pin::new(&mut *self.get_mut().inner).poll_close(cx) + } +} diff --git a/transports/webtransport-websys/src/transport.rs b/transports/webtransport-websys/src/transport.rs new file mode 100644 index 00000000000..95e0a3562e3 --- /dev/null +++ b/transports/webtransport-websys/src/transport.rs @@ -0,0 +1,191 @@ +use futures::future::FutureExt; +use libp2p_core::muxing::StreamMuxerBox; +use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; +use libp2p_identity::{Keypair, PeerId}; +use multiaddr::Multiaddr; +use send_wrapper::SendWrapper; +use std::future::Future; +use std::pin::Pin; +use std::task::ready; +use std::task::{Context, Poll}; + +use crate::connection::ConnectionSend; +use crate::endpoint::Endpoint; +use crate::Connection; +use crate::Error; + +/// Config for the [`Transport`]. +pub struct Config { + keypair: Keypair, +} + +/// A WebTransport [`Transport`](libp2p_core::Transport) that works with `web-sys`. +pub struct Transport { + config: Config, +} + +/// Transport wrapped in [`SendWrapper`]. +/// +/// This is needed by Swarm. WASM is single-threaded and it is safe +/// to use [`SendWrapper`]. +pub(crate) struct TransportSend { + inner: SendWrapper, +} + +impl Config { + /// Constructs a new configuration for the [`Transport`]. + pub fn new(keypair: &Keypair) -> Self { + Config { + keypair: keypair.to_owned(), + } + } +} + +impl Transport { + /// Constructs a new `Transport` with the given [`Config`]. + pub fn new(config: Config) -> Transport { + Transport { config } + } + + /// Wraps `Transport` in [`Boxed`] and makes it ready to be consumed by + /// SwarmBuilder. + pub fn boxed(self) -> Boxed<(PeerId, StreamMuxerBox)> { + TransportSend::new(self) + .map(|(peer_id, muxer), _| (peer_id, StreamMuxerBox::new(muxer))) + .boxed() + } +} + +impl libp2p_core::Transport for Transport { + type Output = (PeerId, Connection); + type Error = Error; + type ListenerUpgrade = Pin>>>; + type Dial = Pin>>>; + + fn listen_on( + &mut self, + _id: ListenerId, + addr: Multiaddr, + ) -> Result<(), TransportError> { + Err(TransportError::MultiaddrNotSupported(addr)) + } + + fn remove_listener(&mut self, _id: ListenerId) -> bool { + false + } + + fn dial(&mut self, addr: Multiaddr) -> Result> { + let endpoint = Endpoint::from_multiaddr(&addr).map_err(|e| match e { + e @ Error::InvalidMultiaddr(_) => { + log::error!("{}", e); + TransportError::MultiaddrNotSupported(addr) + } + e => TransportError::Other(e), + })?; + + let mut session = Connection::new(&endpoint).map_err(TransportError::Other)?; + let keypair = self.config.keypair.clone(); + + Ok(async move { + let peer_id = session + .authenticate(&keypair, endpoint.remote_peer, endpoint.certhashes) + .await?; + Ok((peer_id, session)) + } + .boxed_local()) + } + + fn dial_as_listener( + &mut self, + addr: Multiaddr, + ) -> Result> { + Err(TransportError::MultiaddrNotSupported(addr)) + } + + fn poll( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Pending + } + + fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option { + None + } +} + +impl TransportSend { + pub(crate) fn new(transport: Transport) -> Self { + TransportSend { + inner: SendWrapper::new(transport), + } + } +} + +impl libp2p_core::Transport for TransportSend { + type Output = (PeerId, ConnectionSend); + type Error = Error; + type ListenerUpgrade = Pin> + Send>>; + type Dial = Pin> + Send>>; + + fn listen_on( + &mut self, + id: ListenerId, + addr: Multiaddr, + ) -> Result<(), TransportError> { + self.inner.listen_on(id, addr) + } + + fn remove_listener(&mut self, id: ListenerId) -> bool { + self.inner.remove_listener(id) + } + + fn dial(&mut self, addr: Multiaddr) -> Result> { + let fut = SendWrapper::new(self.inner.dial(addr)?); + + Ok(async move { + let (peer_id, conn) = fut.await?; + let conn = ConnectionSend::new(conn); + Ok((peer_id, conn)) + } + .boxed()) + } + + fn dial_as_listener( + &mut self, + addr: Multiaddr, + ) -> Result> { + let fut = SendWrapper::new(self.inner.dial_as_listener(addr)?); + + Ok(async move { + let (peer_id, conn) = fut.await?; + let conn = ConnectionSend::new(conn); + Ok((peer_id, conn)) + } + .boxed()) + } + + fn poll( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let inner = Pin::new(&mut *self.get_mut().inner); + + let event = ready!(inner.poll(cx)).map_upgrade(|fut| { + let fut = SendWrapper::new(fut); + + async move { + let (peer_id, conn) = fut.await?; + let conn = ConnectionSend::new(conn); + Ok((peer_id, conn)) + } + .boxed() + }); + + Poll::Ready(event) + } + + fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option { + self.inner.address_translation(listen, observed) + } +} diff --git a/transports/webtransport-websys/src/utils.rs b/transports/webtransport-websys/src/utils.rs new file mode 100644 index 00000000000..2c0bdcc092c --- /dev/null +++ b/transports/webtransport-websys/src/utils.rs @@ -0,0 +1,108 @@ +use js_sys::{Promise, Reflect}; +use send_wrapper::SendWrapper; +use std::io; +use wasm_bindgen::{JsCast, JsValue}; + +use crate::Error; + +/// Properly detach a promise. +/// +/// A promise always runs in the background, however if you don't await it, +/// or specify a `catch` handler before you drop it, it might cause some side +/// effects. This function avoids any side effects. +// +// Ref: https://github.com/typescript-eslint/typescript-eslint/blob/391a6702c0a9b5b3874a7a27047f2a721f090fb6/packages/eslint-plugin/docs/rules/no-floating-promises.md +pub(crate) fn detach_promise(promise: Promise) { + type Closure = wasm_bindgen::closure::Closure; + static mut DO_NOTHING: Option> = None; + + // Allocate Closure only once and reuse it + let do_nothing = unsafe { + if DO_NOTHING.is_none() { + let cb = Closure::new(|_| {}); + DO_NOTHING = Some(SendWrapper::new(cb)); + } + + DO_NOTHING.as_deref().unwrap() + }; + + // Avoid having "floating" promise and ignore any errors. + // After `catch` promise is allowed to be dropped. + let _ = promise.catch(do_nothing); +} + +/// Typecasts a JavaScript type. +/// +/// Returns a `Ok(value)` casted to the requested type. +/// +/// If the underlying value is an error and the requested +/// type is not, then `Err(Error::JsError)` is returned. +/// +/// If the underlying value can not be casted to the requested type and +/// is not an error, then `Err(Error::JsCastFailed)` is returned. +pub(crate) fn to_js_type(value: impl Into) -> Result +where + T: JsCast + From, +{ + let value = value.into(); + + if value.has_type::() { + Ok(value.unchecked_into()) + } else if value.has_type::() { + Err(Error::from_js_value(value)) + } else { + Err(Error::JsCastFailed) + } +} + +/// Parse reponse from `ReadableStreamDefaultReader::read`. +// +// Ref: https://streams.spec.whatwg.org/#default-reader-prototype +pub(crate) fn parse_reader_response(resp: &JsValue) -> Result, JsValue> { + let value = Reflect::get(resp, &JsValue::from_str("value"))?; + let done = Reflect::get(resp, &JsValue::from_str("done"))? + .as_bool() + .unwrap_or_default(); + + if value.is_undefined() || done { + Ok(None) + } else { + Ok(Some(value)) + } +} + +pub(crate) fn to_io_error(value: JsValue) -> io::Error { + io::Error::new(io::ErrorKind::Other, Error::from_js_value(value)) +} + +#[cfg(test)] +mod tests { + use super::*; + use js_sys::{Promise, TypeError, Uint8Array}; + use wasm_bindgen_test::wasm_bindgen_test; + + #[wasm_bindgen_test] + fn check_js_typecasting() { + // Successful typecast. + let value = JsValue::from(Uint8Array::new_with_length(0)); + assert!(to_js_type::(value).is_ok()); + + // Type can not be typecasted. + let value = JsValue::from(Uint8Array::new_with_length(0)); + assert!(matches!( + to_js_type::(value), + Err(Error::JsCastFailed) + )); + + // Request typecasting, however the underlying value is an error. + let value = JsValue::from(TypeError::new("abc")); + assert!(matches!( + to_js_type::(value), + Err(Error::JsError(_)) + )); + + // Explicitly request js_sys::Error typecasting. + let value = JsValue::from(TypeError::new("abc")); + assert!(to_js_type::(value).is_ok()); + } +} From d92590d61ca6ae04f447dc9c619975fd84f3efd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Zwoli=C5=84ski?= Date: Wed, 14 Jun 2023 18:57:11 +0200 Subject: [PATCH 04/21] chore(webtransport): add interop-tests support with wasm Co-authored-by: Thomas Eizinger Co-authored-by: Yiannis Marangos --- .github/workflows/interop-test.yml | 14 +- Cargo.lock | 1475 ++++++++++++----- examples/dcutr/Cargo.toml | 1 + examples/dcutr/src/main.rs | 50 +- examples/metrics/Cargo.toml | 2 +- examples/relay-server/Cargo.toml | 1 + examples/relay-server/src/main.rs | 38 +- identity/src/keypair.rs | 19 + interop-tests/Cargo.toml | 30 +- interop-tests/Dockerfile.chromium | 28 + .../{Dockerfile => Dockerfile.native} | 4 +- interop-tests/README.md | 18 +- interop-tests/chromium-ping-version.json | 7 + ...-version.json => native-ping-version.json} | 4 +- interop-tests/pkg/readme.md | 6 + interop-tests/src/arch.rs | 231 +++ interop-tests/src/bin/config/mod.rs | 37 + interop-tests/src/bin/native_ping.rs | 21 + interop-tests/src/bin/ping.rs | 267 --- interop-tests/src/bin/wasm_ping.rs | 219 +++ interop-tests/src/lib.rs | 251 +++ misc/keygen/Cargo.toml | 2 +- protocols/gossipsub/Cargo.toml | 2 +- protocols/identify/CHANGELOG.md | 7 + protocols/identify/Cargo.toml | 2 +- protocols/kad/CHANGELOG.md | 4 +- protocols/mdns/Cargo.toml | 2 +- protocols/ping/Cargo.toml | 2 +- protocols/request-response/Cargo.toml | 2 +- swarm-derive/src/lib.rs | 6 + swarm-test/Cargo.toml | 2 +- swarm/CHANGELOG.md | 7 + swarm/src/behaviour.rs | 11 + swarm/src/lib.rs | 48 +- swarm/src/listen_opts.rs | 33 + swarm/tests/listener.rs | 143 ++ transports/dns/Cargo.toml | 2 +- transports/plaintext/Cargo.toml | 2 +- transports/pnet/Cargo.toml | 2 +- transports/quic/CHANGELOG.md | 3 + transports/quic/Cargo.toml | 2 +- transports/quic/src/endpoint.rs | 7 + transports/quic/src/hole_punching.rs | 47 + transports/quic/src/lib.rs | 11 + transports/quic/src/provider.rs | 6 +- transports/quic/src/provider/async_std.rs | 5 + transports/quic/src/provider/tokio.rs | 7 +- transports/quic/src/transport.rs | 200 ++- transports/tcp/Cargo.toml | 2 +- transports/uds/Cargo.toml | 2 +- transports/websocket/Cargo.toml | 2 +- 51 files changed, 2522 insertions(+), 774 deletions(-) create mode 100644 interop-tests/Dockerfile.chromium rename interop-tests/{Dockerfile => Dockerfile.native} (77%) create mode 100644 interop-tests/chromium-ping-version.json rename interop-tests/{ping-version.json => native-ping-version.json} (67%) create mode 100644 interop-tests/pkg/readme.md create mode 100644 interop-tests/src/arch.rs create mode 100644 interop-tests/src/bin/config/mod.rs create mode 100644 interop-tests/src/bin/native_ping.rs delete mode 100644 interop-tests/src/bin/ping.rs create mode 100644 interop-tests/src/bin/wasm_ping.rs create mode 100644 interop-tests/src/lib.rs create mode 100644 swarm/src/listen_opts.rs create mode 100644 swarm/tests/listener.rs create mode 100644 transports/quic/src/hole_punching.rs diff --git a/.github/workflows/interop-test.yml b/.github/workflows/interop-test.yml index e6527709aa3..5e9889c4067 100644 --- a/.github/workflows/interop-test.yml +++ b/.github/workflows/interop-test.yml @@ -13,15 +13,19 @@ jobs: run-multidim-interop: name: Run multidimensional interoperability tests runs-on: ${{ fromJSON(github.repository == 'libp2p/rust-libp2p' && '["self-hosted", "linux", "x64", "xlarge"]' || '"ubuntu-latest"') }} + strategy: + matrix: + flavour: [chromium, native] steps: - uses: actions/checkout@v3 - uses: docker/setup-buildx-action@v2 - - name: Build image - run: docker buildx build --load -t rust-libp2p-head . -f interop-tests/Dockerfile - - uses: libp2p/test-plans/.github/actions/run-interop-ping-test@master + - name: Build ${{ matrix.flavour }} image + run: docker buildx build --load -t ${{ matrix.flavour }}-rust-libp2p-head . -f interop-tests/Dockerfile.${{ matrix.flavour }} + - name: Run ${{ matrix.flavour }} tests + uses: libp2p/test-plans/.github/actions/run-interop-ping-test@master with: - test-filter: rust-libp2p-head - extra-versions: ${{ github.workspace }}/interop-tests/ping-version.json + test-filter: ${{ matrix.flavour }}-rust-libp2p-head + extra-versions: ${{ github.workspace }}/interop-tests/${{ matrix.flavour }}-ping-version.json s3-cache-bucket: libp2p-by-tf-aws-bootstrap s3-access-key-id: ${{ vars.TEST_PLANS_BUILD_CACHE_KEY_ID }} s3-secret-access-key: ${{ secrets.TEST_PLANS_BUILD_CACHE_KEY }} diff --git a/Cargo.lock b/Cargo.lock index d66f01d8ba0..45a5aaee6bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aes" version = "0.6.0" @@ -51,17 +61,14 @@ dependencies = [ ] [[package]] -name = "aes-gcm" -version = "0.8.0" +name = "aes" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ - "aead 0.3.2", - "aes 0.6.0", - "cipher 0.2.5", - "ctr 0.6.0", - "ghash 0.3.1", - "subtle", + "cfg-if", + "cipher 0.4.4", + "cpufeatures", ] [[package]] @@ -78,6 +85,20 @@ dependencies = [ "subtle", ] +[[package]] +name = "aes-gcm" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" +dependencies = [ + "aead 0.5.2", + "aes 0.8.2", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.0", + "subtle", +] + [[package]] name = "aes-soft" version = "0.6.4" @@ -111,9 +132,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -126,9 +147,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ "anstyle", "anstyle-parse", @@ -165,9 +186,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -181,9 +202,9 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "arbitrary" -version = "1.2.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e90af4de65aa7b293ef2d09daff88501eb254f58edde2e1ac02c82d873eadad" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" [[package]] name = "arc-swap" @@ -193,9 +214,9 @@ checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "arrayref" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" @@ -221,9 +242,9 @@ dependencies = [ [[package]] name = "asn1-rs" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf6690c370453db30743b373a60ba498fc0d6d83b11f4abfd87a84a075db5dd4" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" dependencies = [ "asn1-rs-derive 0.4.0", "asn1-rs-impl", @@ -299,9 +320,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", @@ -360,12 +381,11 @@ dependencies = [ [[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]] @@ -382,9 +402,9 @@ dependencies = [ [[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", @@ -393,9 +413,9 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "libc", + "rustix", "signal-hook", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -443,9 +463,9 @@ dependencies = [ [[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-trait" @@ -473,9 +493,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 = "autocfg" @@ -494,6 +514,56 @@ dependencies = [ "libp2p", ] +[[package]] +name = "axum" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite 0.2.9", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http 0.3.5", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "base-x" version = "0.2.11" @@ -526,9 +596,9 @@ checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-toml" @@ -566,7 +636,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]] @@ -580,9 +650,9 @@ dependencies = [ [[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", ] @@ -605,9 +675,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[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", @@ -615,6 +685,7 @@ dependencies = [ "atomic-waker", "fastrand", "futures-lite", + "log", ] [[package]] @@ -628,9 +699,9 @@ dependencies = [ [[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 = "byteorder" @@ -715,9 +786,9 @@ dependencies = [ [[package]] name = "ciborium" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", @@ -726,15 +797,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half", @@ -760,9 +831,9 @@ dependencies = [ [[package]] name = "cipher" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", @@ -770,9 +841,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ed2379f8603fa2b7509891660e802b88c70a79a6427a70abb5968054de2c28" +checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" dependencies = [ "clap_builder", "clap_derive", @@ -794,9 +865,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e9ef9a08ee1c0e1f2e162121665ac45ac3783b0f897db7244ae75ad9a8f65b" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck", "proc-macro2", @@ -832,9 +903,9 @@ 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", ] @@ -855,6 +926,17 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -867,9 +949,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core2" @@ -882,19 +964,13 @@ dependencies = [ [[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 = "cpuid-bool" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" - [[package]] name = "crc" version = "3.0.1" @@ -957,9 +1033,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -967,9 +1043,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -978,22 +1054,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.7.1", + "memoffset 0.8.0", "scopeguard", ] [[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", ] @@ -1035,6 +1111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -1048,16 +1125,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "crypto-mac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "crypto-mac" version = "0.11.1" @@ -1070,20 +1137,20 @@ dependencies = [ [[package]] name = "ctr" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" dependencies = [ - "cipher 0.2.5", + "cipher 0.3.0", ] [[package]] name = "ctr" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.3.0", + "cipher 0.4.4", ] [[package]] @@ -1126,9 +1193,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ "darling_core", "darling_macro", @@ -1136,9 +1203,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", @@ -1150,9 +1217,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", "quote", @@ -1167,9 +1234,9 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "data-encoding-macro" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86927b7cd2fe88fa698b87404b287ab98d1a0063a34071d92e575b72d3029aca" +checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1177,9 +1244,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db" +checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" dependencies = [ "data-encoding", "syn 1.0.109", @@ -1194,6 +1261,7 @@ dependencies = [ "futures", "futures-timer", "libp2p", + "libp2p-quic", "log", ] @@ -1210,9 +1278,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e58dffcdcc8ee7b22f0c1f71a69243d7c2d9ad87b5a14361f2424a1565c219" +checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" dependencies = [ "const-oid", "pem-rfc7468 0.7.0", @@ -1235,11 +1303,11 @@ dependencies = [ [[package]] name = "der-parser" -version = "8.1.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d4bc9b0db0a0df9ae64634ac5bdefb7afcb534e182275ca0beadbe486701c1" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" dependencies = [ - "asn1-rs 0.5.1", + "asn1-rs 0.5.2", "displaydoc", "nom", "num-bigint", @@ -1289,11 +1357,11 @@ 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 0.10.3", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -1301,13 +1369,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -1323,9 +1391,9 @@ dependencies = [ [[package]] name = "dtoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00704156a7de8df8da0911424e30c2049957b0a714542a44e05fe693dd85313" +checksum = "65d09067bfacaa79114679b279d7f5885b53295b1e2cfb4e79c8e4bd3d633169" [[package]] name = "ecdsa" @@ -1341,15 +1409,16 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.16.6" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a48e5d537b8a30c0b023116d981b16334be1485af7ca68db3a2b7024cbc957fd" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" dependencies = [ - "der 0.7.5", - "digest 0.10.6", - "elliptic-curve 0.13.4", + "der 0.7.6", + "digest 0.10.7", + "elliptic-curve 0.13.5", "rfc6979 0.4.0", "signature 2.0.0", + "spki 0.7.2", ] [[package]] @@ -1390,7 +1459,7 @@ dependencies = [ "base16ct 0.1.1", "crypto-bigint 0.4.9", "der 0.6.1", - "digest 0.10.6", + "digest 0.10.7", "ff 0.12.1", "generic-array", "group 0.12.1", @@ -1405,24 +1474,33 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" dependencies = [ "base16ct 0.2.0", "crypto-bigint 0.5.2", - "digest 0.10.6", + "digest 0.10.7", "ff 0.13.0", "generic-array", "group 0.13.0", "pem-rfc7468 0.7.0", "pkcs8 0.10.2", "rand_core 0.6.4", - "sec1 0.7.1", + "sec1 0.7.2", "subtle", "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum-as-inner" version = "0.5.1" @@ -1460,13 +1538,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1485,11 +1563,33 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "fantoccini" +version = "0.20.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5eb32b0001134a1d3b9e16010eb4b119451edf68446963a30a8130a0d056e98" +dependencies = [ + "base64 0.13.1", + "cookie", + "futures-core", + "futures-util", + "http", + "hyper", + "hyper-rustls", + "mime", + "serde", + "serde_json", + "time", + "tokio", + "url", + "webdriver", +] + [[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", ] @@ -1516,9 +1616,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a214f5bb88731d436478f3ae1f8a277b62124089ba9fb67f4f93fb100ef73c90" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "file-sharing" @@ -1550,11 +1650,26 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[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", ] @@ -1610,9 +1725,9 @@ 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", @@ -1720,9 +1835,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", @@ -1742,9 +1857,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", @@ -1755,22 +1870,22 @@ dependencies = [ [[package]] name = "ghash" -version = "0.3.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug", - "polyval 0.4.5", + "polyval 0.5.3", ] [[package]] name = "ghash" -version = "0.4.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ "opaque-debug", - "polyval 0.5.3", + "polyval 0.6.0", ] [[package]] @@ -1815,9 +1930,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -1911,16 +2026,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "hmac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" -dependencies = [ - "crypto-mac 0.10.1", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.11.0" @@ -1937,7 +2042,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]] @@ -1964,9 +2069,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", @@ -1984,6 +2089,12 @@ dependencies = [ "pin-project-lite 0.2.9", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -2012,6 +2123,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "httparse", @@ -2025,6 +2137,34 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +dependencies = [ + "http", + "hyper", + "log", + "rustls 0.20.8", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2054,9 +2194,9 @@ dependencies = [ [[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", @@ -2094,9 +2234,9 @@ dependencies = [ [[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 0.12.3", @@ -2147,39 +2287,56 @@ name = "interop-tests" version = "0.1.0" dependencies = [ "anyhow", + "axum", + "console_error_panic_hook", "either", "env_logger 0.10.0", "futures", + "futures-timer", + "instant", "libp2p", "libp2p-mplex", "libp2p-quic", "libp2p-webrtc", "log", + "mime_guess", "rand 0.8.5", "redis", + "reqwest", + "rust-embed", + "serde", + "serde_json", + "thirtyfour", "tokio", + "tower-http 0.4.0", + "tracing", + "tracing-subscriber", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-logger", ] [[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 0.42.0", + "windows-sys 0.48.0", ] [[package]] name = "ipconfig" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd302af1b90f2463a98fa5ad469fc212c8e3175a41c3068601bfa2727591c5be" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.4.9", + "socket2 0.5.3", "widestring", - "winapi", - "winreg", + "windows-sys 0.48.0", + "winreg 0.50.0", ] [[package]] @@ -2207,20 +2364,20 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2234,9 +2391,9 @@ 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" @@ -2286,9 +2443,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.145" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "libm" @@ -2308,7 +2465,7 @@ dependencies = [ "env_logger 0.10.0", "futures", "futures-timer", - "getrandom 0.2.9", + "getrandom 0.2.10", "instant", "libp2p-allow-block-list", "libp2p-autonat", @@ -2521,7 +2678,7 @@ dependencies = [ "fnv", "futures", "futures-ticker", - "getrandom 0.2.9", + "getrandom 0.2.10", "hex", "hex_fmt", "instant", @@ -2587,7 +2744,7 @@ dependencies = [ "rand 0.8.5", "ring", "rmp-serde", - "sec1 0.7.1", + "sec1 0.7.2", "serde", "serde_json", "sha2 0.10.6", @@ -2942,7 +3099,7 @@ dependencies = [ "fnv", "futures", "futures-timer", - "getrandom 0.2.9", + "getrandom 0.2.10", "instant", "libp2p-core", "libp2p-identify", @@ -3114,7 +3271,7 @@ dependencies = [ "rw-stream-sink", "soketto", "url", - "webpki-roots 0.23.1", + "webpki-roots 0.23.0", ] [[package]] @@ -3122,7 +3279,7 @@ name = "libp2p-webtransport-websys" version = "0.1.0" dependencies = [ "futures", - "getrandom 0.2.9", + "getrandom 0.2.10", "js-sys", "libp2p-core", "libp2p-identity", @@ -3208,15 +3365,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.3.0" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d" +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", @@ -3224,9 +3381,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ "value-bag", ] @@ -3256,18 +3413,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] -name = "matches" -version = "0.1.10" +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "md-5" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -3287,9 +3459,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg", ] @@ -3307,6 +3479,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3324,14 +3512,13 @@ 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 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -3401,6 +3588,24 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "netlink-packet-core" version = "0.4.2" @@ -3456,9 +3661,9 @@ dependencies = [ [[package]] name = "netlink-sys" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "260e21fbb6f3d253a14df90eb0000a6066780a15dd901a7519ce02d77a94985b" +checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" dependencies = [ "async-io", "bytes", @@ -3496,6 +3701,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -3551,7 +3766,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" dependencies = [ - "asn1-rs 0.5.1", + "asn1-rs 0.5.2", ] [[package]] @@ -3572,6 +3787,56 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "p256" version = "0.11.1" @@ -3589,8 +3854,8 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "ecdsa 0.16.6", - "elliptic-curve 0.13.4", + "ecdsa 0.16.7", + "elliptic-curve 0.13.5", "primeorder", "sha2 0.10.6", ] @@ -3618,9 +3883,9 @@ dependencies = [ [[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" @@ -3634,22 +3899,22 @@ dependencies = [ [[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 0.2.16", + "redox_syscall", "smallvec", - "windows-sys 0.42.0", + "windows-targets", ] [[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 = "pem" @@ -3680,9 +3945,9 @@ dependencies = [ [[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 = "pin-project" @@ -3748,10 +4013,16 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.5", - "spki 0.7.1", + "der 0.7.6", + "spki 0.7.2", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "platforms" version = "3.0.2" @@ -3788,16 +4059,18 @@ dependencies = [ [[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 0.42.0", + "pin-project-lite 0.2.9", + "windows-sys 0.48.0", ] [[package]] @@ -3808,30 +4081,31 @@ checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", "opaque-debug", - "universal-hash", + "universal-hash 0.4.1", ] [[package]] name = "polyval" -version = "0.4.5" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ - "cpuid-bool", + "cfg-if", + "cpufeatures", "opaque-debug", - "universal-hash", + "universal-hash 0.4.1", ] [[package]] name = "polyval" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" dependencies = [ "cfg-if", "cpufeatures", "opaque-debug", - "universal-hash", + "universal-hash 0.5.1", ] [[package]] @@ -3842,11 +4116,35 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "primeorder" -version = "0.13.1" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve 0.13.5", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8d3875361e28f7753baefef104386e7aa47642c93023356d97fdef4003bfb5" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "elliptic-curve 0.13.4", + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", ] [[package]] @@ -3862,9 +4160,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -4033,7 +4331,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", ] [[package]] @@ -4047,9 +4345,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", @@ -4057,9 +4355,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.2" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -4111,15 +4409,6 @@ dependencies = [ "url", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -4131,15 +4420,30 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.3" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.2" @@ -4156,6 +4460,7 @@ dependencies = [ "env_logger 0.10.0", "futures", "libp2p", + "libp2p-quic", ] [[package]] @@ -4171,6 +4476,43 @@ dependencies = [ "tokio", ] +[[package]] +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "base64 0.21.2", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite 0.2.9", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.10.1", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -4289,6 +4631,40 @@ dependencies = [ "webrtc-util", ] +[[package]] +name = "rust-embed" +version = "6.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73e721f488c353141288f223b599b4ae9303ecf3e62923f40a492f0634a4dc3" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22ce362f5561923889196595504317a4372b84210e6e335da529a65ea5452b5" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.18", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +dependencies = [ + "sha2 0.10.6", + "walkdir", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -4315,16 +4691,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.3" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -4364,6 +4740,18 @@ dependencies = [ "sct 0.7.0", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.2" @@ -4383,6 +4771,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + [[package]] name = "rw-stream-sink" version = "0.4.0" @@ -4395,9 +4789,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" @@ -4405,7 +4799,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" dependencies = [ - "cipher 0.4.3", + "cipher 0.4.4", ] [[package]] @@ -4417,6 +4811,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -4477,23 +4880,46 @@ dependencies = [ [[package]] name = "sec1" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e" +checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" dependencies = [ "base16ct 0.2.0", - "der 0.7.5", + "der 0.7.6", "generic-array", "pkcs8 0.10.2", "subtle", "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[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" @@ -4512,9 +4938,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] @@ -4531,9 +4957,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", @@ -4546,6 +4972,39 @@ version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", "itoa", "ryu", "serde", @@ -4564,6 +5023,17 @@ dependencies = [ "opaque-debug", ] +[[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.9.9" @@ -4585,7 +5055,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -4594,15 +5064,24 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[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", @@ -4610,9 +5089,9 @@ 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", ] @@ -4623,7 +5102,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -4633,15 +5112,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "rand_core 0.6.4", ] [[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", ] @@ -4739,12 +5218,12 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der 0.7.5", + "der 0.7.6", ] [[package]] @@ -4753,6 +5232,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stringmatch" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aadc0801d92f0cdc26127c67c4b8766284f52a5ba22894f285e3101fa57d05d" +dependencies = [ + "regex", +] + [[package]] name = "strsim" version = "0.10.0" @@ -4815,6 +5303,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -4829,9 +5323,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75182f12f490e953596550b65ee31bda7c8e043d9386174b353bda50838c3fd" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags", "core-foundation", @@ -4850,15 +5344,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", - "redox_syscall 0.3.5", + "redox_syscall", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -4870,6 +5365,44 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thirtyfour" +version = "0.32.0-rc.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0fe180d5f1f7dd32bb5f1a8d19231bb63dc9bbb1985e1dbb6f07163b6a8578" +dependencies = [ + "async-trait", + "base64 0.21.2", + "cookie", + "fantoccini", + "futures", + "http", + "indexmap", + "log", + "parking_lot", + "paste", + "serde", + "serde_json", + "serde_repr", + "stringmatch", + "thirtyfour-macros", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "thirtyfour-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cae91d1c7c61ec65817f1064954640ee350a50ae6548ff9a1bdd2489d6ffbb0" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "thiserror" version = "1.0.40" @@ -4890,11 +5423,21 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +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", @@ -4904,15 +5447,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", ] @@ -4938,9 +5481,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 = "tokio" @@ -4972,6 +5515,16 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -4998,6 +5551,72 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite 0.2.9", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite 0.2.9", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite 0.2.9", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -5011,6 +5630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite 0.2.9", "tracing-attributes", "tracing-core", @@ -5018,22 +5638,52 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ + "matchers", + "nu-ansi-term", "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -5151,17 +5801,26 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[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" @@ -5172,6 +5831,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -5188,6 +5853,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "unsigned-varint" version = "0.7.1" @@ -5206,12 +5881,12 @@ 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 0.3.0", + "idna 0.4.0", "percent-encoding", ] @@ -5223,19 +5898,31 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.2.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "value-bag" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -5265,12 +5952,11 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -5386,6 +6072,17 @@ dependencies = [ "quote", ] +[[package]] +name = "wasm-logger" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.63" @@ -5396,6 +6093,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webdriver" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9973cb72c8587d5ad5efdb91e663d36177dc37725e6c90ca86c626b0cc45c93f" +dependencies = [ + "base64 0.13.1", + "bytes", + "cookie", + "http", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "unicode-segmentation", + "url", +] + [[package]] name = "webpki" version = "0.21.4" @@ -5427,9 +6143,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.23.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" +checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125" dependencies = [ "rustls-webpki", ] @@ -5493,22 +6209,22 @@ dependencies = [ [[package]] name = "webrtc-dtls" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7021987ae0a2ed6c8cd33f68e98e49bb6e74ffe9543310267b48a1bbe3900e5f" +checksum = "942be5bd85f072c3128396f6e5a9bfb93ca8c1939ded735d177b7bcba9a13d05" dependencies = [ "aes 0.6.0", - "aes-gcm 0.8.0", + "aes-gcm 0.10.2", "async-trait", "bincode", "block-modes", "byteorder", "ccm", "curve25519-dalek 3.2.0", - "der-parser 8.1.0", + "der-parser 8.2.0", "elliptic-curve 0.12.3", "hkdf", - "hmac 0.10.1", + "hmac 0.12.1", "log", "oid-registry 0.6.1", "p256 0.11.1", @@ -5521,8 +6237,8 @@ dependencies = [ "rustls 0.19.1", "sec1 0.3.0", "serde", - "sha-1", - "sha2 0.9.9", + "sha1", + "sha2 0.10.6", "signature 1.6.4", "subtle", "thiserror", @@ -5535,9 +6251,9 @@ dependencies = [ [[package]] name = "webrtc-ice" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494483fbb2f5492620871fdc78b084aed8807377f6e3fe88b2e49f0a9c9c41d7" +checksum = "465a03cc11e9a7d7b4f9f99870558fe37a102b65b93f8045392fef7c67b39e80" dependencies = [ "arc-swap", "async-trait", @@ -5572,18 +6288,15 @@ dependencies = [ [[package]] name = "webrtc-media" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a3c157a040324e5049bcbd644ffc9079e6738fa2cfab2bcff64e5cc4c00d7" +checksum = "f72e1650a8ae006017d1a5280efb49e2610c19ccc3c0905b03b648aee9554991" dependencies = [ "byteorder", "bytes", - "derive_builder", - "displaydoc", "rand 0.8.5", "rtp", "thiserror", - "webrtc-util", ] [[package]] @@ -5648,20 +6361,11 @@ dependencies = [ "winapi", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "widestring" -version = "0.5.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[package]] name = "winapi" @@ -5713,22 +6417,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", -] - -[[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.1", + "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]] @@ -5737,22 +6432,7 @@ 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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", + "windows-targets", ] [[package]] @@ -5772,9 +6452,9 @@ dependencies = [ [[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" @@ -5790,9 +6470,9 @@ checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" [[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" @@ -5808,9 +6488,9 @@ checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" [[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" @@ -5826,9 +6506,9 @@ checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -5844,9 +6524,9 @@ checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" [[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" @@ -5856,9 +6536,9 @@ checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -5874,9 +6554,9 @@ checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" [[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" @@ -5893,6 +6573,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "x25519-dalek" version = "1.1.1" @@ -5940,9 +6630,9 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bab0c2f54ae1d92f4fcb99c0b7ccf0b1e3451cbd395e5f115ccbdbcb18d4f634" dependencies = [ - "asn1-rs 0.5.1", + "asn1-rs 0.5.2", "data-encoding", - "der-parser 8.1.0", + "der-parser 8.2.0", "lazy_static", "nom", "oid-registry 0.6.1", @@ -5985,12 +6675,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 1.0.109", - "synstructure", + "syn 2.0.18", ] diff --git a/examples/dcutr/Cargo.toml b/examples/dcutr/Cargo.toml index 40198aaeadd..1b1230796f9 100644 --- a/examples/dcutr/Cargo.toml +++ b/examples/dcutr/Cargo.toml @@ -11,4 +11,5 @@ env_logger = "0.10.0" futures = "0.3.28" futures-timer = "3.0" libp2p = { path = "../../libp2p", features = ["async-std", "dns", "dcutr", "identify", "macros", "noise", "ping", "relay", "rendezvous", "tcp", "tokio", "yamux"] } +libp2p-quic = { path = "../../transports/quic", features = ["async-std"] } log = "0.4" diff --git a/examples/dcutr/src/main.rs b/examples/dcutr/src/main.rs index df14e79828b..8359bb1902a 100644 --- a/examples/dcutr/src/main.rs +++ b/examples/dcutr/src/main.rs @@ -23,13 +23,14 @@ use clap::Parser; use futures::{ executor::{block_on, ThreadPool}, - future::FutureExt, + future::{Either, FutureExt}, stream::StreamExt, }; use libp2p::{ core::{ multiaddr::{Multiaddr, Protocol}, - transport::{OrTransport, Transport}, + muxing::StreamMuxerBox, + transport::Transport, upgrade, }, dcutr, @@ -38,9 +39,9 @@ use libp2p::{ swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, tcp, yamux, PeerId, }; +use libp2p_quic as quic; use log::info; use std::error::Error; -use std::net::Ipv4Addr; use std::str::FromStr; #[derive(Debug, Parser)] @@ -91,19 +92,26 @@ fn main() -> Result<(), Box> { let (relay_transport, client) = relay::client::new(local_peer_id); - let transport = OrTransport::new( - relay_transport, - block_on(DnsConfig::system(tcp::async_io::Transport::new( - tcp::Config::default().port_reuse(true), - ))) - .unwrap(), - ) - .upgrade(upgrade::Version::V1Lazy) - .authenticate( - noise::Config::new(&local_key).expect("Signing libp2p-noise static DH keypair failed."), - ) - .multiplex(yamux::Config::default()) - .boxed(); + let transport = { + let relay_tcp_quic_transport = relay_transport + .or_transport(tcp::async_io::Transport::new( + tcp::Config::default().port_reuse(true), + )) + .upgrade(upgrade::Version::V1) + .authenticate(noise::Config::new(&local_key).unwrap()) + .multiplex(yamux::Config::default()) + .or_transport(quic::async_std::Transport::new(quic::Config::new( + &local_key, + ))); + + block_on(DnsConfig::system(relay_tcp_quic_transport)) + .unwrap() + .map(|either_output, _| match either_output { + Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + }) + .boxed() + }; #[derive(NetworkBehaviour)] #[behaviour(to_swarm = "Event")] @@ -164,11 +172,10 @@ fn main() -> Result<(), Box> { .build(); swarm - .listen_on( - Multiaddr::empty() - .with("0.0.0.0".parse::().unwrap().into()) - .with(Protocol::Tcp(0)), - ) + .listen_on("/ip4/0.0.0.0/udp/0/quic-v1".parse().unwrap()) + .unwrap(); + swarm + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) .unwrap(); // Wait to listen on all interfaces. @@ -214,6 +221,7 @@ fn main() -> Result<(), Box> { .. })) => { info!("Relay told us our public address: {:?}", observed_addr); + swarm.add_external_address(observed_addr); learned_observed_addr = true; } event => panic!("{event:?}"), diff --git a/examples/metrics/Cargo.toml b/examples/metrics/Cargo.toml index 09ecf431f0d..463b98a2383 100644 --- a/examples/metrics/Cargo.toml +++ b/examples/metrics/Cargo.toml @@ -10,6 +10,6 @@ env_logger = "0.10.0" futures = "0.3.27" hyper = { version = "0.14", features = ["server", "tcp", "http1"] } libp2p = { path = "../../libp2p", features = ["async-std", "metrics", "ping", "noise", "identify", "tcp", "yamux", "macros"] } -log = "0.4.18" +log = "0.4.19" tokio = { version = "1", features = ["rt-multi-thread"] } prometheus-client = "0.21.0" diff --git a/examples/relay-server/Cargo.toml b/examples/relay-server/Cargo.toml index 2b204fe3bf0..ebe0da7c66a 100644 --- a/examples/relay-server/Cargo.toml +++ b/examples/relay-server/Cargo.toml @@ -12,3 +12,4 @@ async-trait = "0.1" env_logger = "0.10.0" futures = "0.3.28" libp2p = { path = "../../libp2p", features = ["async-std", "noise", "macros", "ping", "tcp", "identify", "yamux", "relay"] } +libp2p-quic = { path = "../../transports/quic", features = ["async-std"] } diff --git a/examples/relay-server/src/main.rs b/examples/relay-server/src/main.rs index cf265e78f05..6a1d956b5a5 100644 --- a/examples/relay-server/src/main.rs +++ b/examples/relay-server/src/main.rs @@ -22,10 +22,11 @@ #![doc = include_str!("../README.md")] use clap::Parser; -use futures::executor::block_on; use futures::stream::StreamExt; +use futures::{executor::block_on, future::Either}; use libp2p::{ core::multiaddr::Protocol, + core::muxing::StreamMuxerBox, core::upgrade, core::{Multiaddr, Transport}, identify, identity, @@ -34,6 +35,7 @@ use libp2p::{ swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, tcp, }; +use libp2p_quic as quic; use std::error::Error; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -50,12 +52,21 @@ fn main() -> Result<(), Box> { let tcp_transport = tcp::async_io::Transport::default(); - let transport = tcp_transport + let tcp_transport = tcp_transport .upgrade(upgrade::Version::V1Lazy) .authenticate( noise::Config::new(&local_key).expect("Signing libp2p-noise static DH keypair failed."), ) - .multiplex(libp2p::yamux::Config::default()) + .multiplex(libp2p::yamux::Config::default()); + + let quic_transport = quic::async_std::Transport::new(quic::Config::new(&local_key)); + + let transport = quic_transport + .or_transport(tcp_transport) + .map(|either_output, _| match either_output { + Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + }) .boxed(); let behaviour = Behaviour { @@ -70,18 +81,35 @@ fn main() -> Result<(), Box> { let mut swarm = SwarmBuilder::without_executor(transport, behaviour, local_peer_id).build(); // Listen on all interfaces - let listen_addr = Multiaddr::empty() + let listen_addr_tcp = Multiaddr::empty() .with(match opt.use_ipv6 { Some(true) => Protocol::from(Ipv6Addr::UNSPECIFIED), _ => Protocol::from(Ipv4Addr::UNSPECIFIED), }) .with(Protocol::Tcp(opt.port)); - swarm.listen_on(listen_addr)?; + swarm.listen_on(listen_addr_tcp)?; + + let listen_addr_quic = Multiaddr::empty() + .with(match opt.use_ipv6 { + Some(true) => Protocol::from(Ipv6Addr::UNSPECIFIED), + _ => Protocol::from(Ipv4Addr::UNSPECIFIED), + }) + .with(Protocol::Udp(opt.port)) + .with(Protocol::QuicV1); + swarm.listen_on(listen_addr_quic)?; block_on(async { loop { match swarm.next().await.expect("Infinite Stream.") { SwarmEvent::Behaviour(event) => { + if let BehaviourEvent::Identify(identify::Event::Received { + info: identify::Info { observed_addr, .. }, + .. + }) = &event + { + swarm.add_external_address(observed_addr.clone()); + } + println!("{event:?}") } SwarmEvent::NewListenAddr { address, .. } => { diff --git a/identity/src/keypair.rs b/identity/src/keypair.rs index b31949cf081..ef1cd7f7179 100644 --- a/identity/src/keypair.rs +++ b/identity/src/keypair.rs @@ -18,6 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#[cfg(any( + feature = "ecdsa", + feature = "secp256k1", + feature = "ed25519", + feature = "rsa" +))] use crate::error::OtherVariantError; use crate::error::{DecodingError, SigningError}; #[cfg(any( @@ -27,7 +33,19 @@ use crate::error::{DecodingError, SigningError}; feature = "rsa" ))] use crate::proto; +#[cfg(any( + feature = "ecdsa", + feature = "secp256k1", + feature = "ed25519", + feature = "rsa" +))] use quick_protobuf::{BytesReader, Writer}; +#[cfg(any( + feature = "ecdsa", + feature = "secp256k1", + feature = "ed25519", + feature = "rsa" +))] use std::convert::TryFrom; #[cfg(feature = "ed25519")] @@ -159,6 +177,7 @@ impl Keypair { /// Sign a message using the private key of this keypair, producing /// a signature that can be verified using the corresponding public key. + #[allow(unused_variables)] pub fn sign(&self, msg: &[u8]) -> Result, SigningError> { match self.keypair { #[cfg(feature = "ed25519")] diff --git a/interop-tests/Cargo.toml b/interop-tests/Cargo.toml index 6d65861c3c2..87bef038074 100644 --- a/interop-tests/Cargo.toml +++ b/interop-tests/Cargo.toml @@ -5,16 +5,40 @@ version = "0.1.0" publish = false license = "MIT" +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] anyhow = "1" either = "1.8.0" env_logger = "0.10.0" futures = "0.3.28" -libp2p = { path = "../libp2p", features = ["websocket", "yamux", "tcp", "tokio", "ping", "noise", "tls", "dns", "rsa", "macros"] } +log = "0.4" +serde = { version = "1", features = ["derive"] } +rand = "0.8.5" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +axum = "0.6" +libp2p = { path = "../libp2p", features = ["ping", "noise", "tls", "rsa", "macros", "websocket", "tokio", "yamux", "tcp", "dns"] } libp2p-quic = { workspace = true, features = ["tokio"] } libp2p-webrtc = { workspace = true, features = ["tokio"] } libp2p-mplex = { path = "../muxers/mplex" } -log = "0.4" -rand = "0.8.5" +mime_guess = "2.0" redis = { version = "0.23.0", default-features = false, features = ["tokio-comp"] } +rust-embed = "6.7" +serde_json = "1" +thirtyfour = "=0.32.0-rc.8" # https://github.com/stevepryde/thirtyfour/issues/169 tokio = { version = "1.28.2", features = ["full"] } +tower-http = { version = "0.4", features = ["cors", "fs", "trace"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +libp2p = { path = "../libp2p", features = ["ping", "macros", "webtransport-websys", "wasm-bindgen"] } +wasm-bindgen = { version = "0.2" } +wasm-bindgen-futures = { version = "0.4" } +wasm-logger = { version = "0.2.0" } +instant = "0.1.12" +reqwest = { version = "0.11", features = ["json"] } +console_error_panic_hook = { version = "0.1.7" } +futures-timer = "3.0.2" diff --git a/interop-tests/Dockerfile.chromium b/interop-tests/Dockerfile.chromium new file mode 100644 index 00000000000..37d730af91a --- /dev/null +++ b/interop-tests/Dockerfile.chromium @@ -0,0 +1,28 @@ +FROM rust:1.67.0 as builder + +# Run with access to the target cache to speed up builds +WORKDIR /workspace +ADD . . + +RUN rustup target add wasm32-unknown-unknown + +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + cargo install wasm-pack@0.11.1 --locked + +RUN --mount=type=cache,target=./target \ + --mount=type=cache,target=/usr/local/cargo/registry \ + wasm-pack build --target web interop-tests + +RUN --mount=type=cache,target=./target \ + --mount=type=cache,target=/usr/local/cargo/registry \ + cargo build --release --package interop-tests --bin wasm_ping + +RUN --mount=type=cache,target=./target \ + mv ./target/release/wasm_ping /usr/local/bin/testplan + +FROM selenium/standalone-chrome:112.0 +COPY --from=builder /usr/local/bin/testplan /usr/local/bin/testplan + +ENV RUST_BACKTRACE=1 + +ENTRYPOINT ["testplan"] diff --git a/interop-tests/Dockerfile b/interop-tests/Dockerfile.native similarity index 77% rename from interop-tests/Dockerfile rename to interop-tests/Dockerfile.native index 0d80a872f5c..f78b85e424c 100644 --- a/interop-tests/Dockerfile +++ b/interop-tests/Dockerfile.native @@ -6,10 +6,10 @@ WORKDIR /workspace ADD . . RUN --mount=type=cache,target=./target \ --mount=type=cache,target=/usr/local/cargo/registry \ - cargo build --release --package interop-tests + cargo build --release --package interop-tests --bin native_ping RUN --mount=type=cache,target=./target \ - mv ./target/release/ping /usr/local/bin/testplan + mv ./target/release/native_ping /usr/local/bin/testplan FROM gcr.io/distroless/cc COPY --from=builder /usr/local/bin/testplan /usr/local/bin/testplan diff --git a/interop-tests/README.md b/interop-tests/README.md index 6a933edf6c0..4e198e60de1 100644 --- a/interop-tests/README.md +++ b/interop-tests/README.md @@ -10,15 +10,27 @@ can dial/listen for ourselves we can do the following: 1. Start redis (needed by the tests): `docker run --rm -it -p 6379:6379 redis/redis-stack`. -2. In one terminal run the dialer: `REDIS_ADDR=localhost:6379 ip="0.0.0.0" +2. In one terminal run the dialer: `redis_addr=localhost:6379 ip="0.0.0.0" transport=quic-v1 security=quic muxer=quic is_dialer="true" cargo run --bin ping` -3. In another terminal, run the listener: `REDIS_ADDR=localhost:6379 - ip="0.0.0.0" transport=quic-v1 security=quic muxer=quic is_dialer="false" cargo run --bin ping` +3. In another terminal, run the listener: `redis_addr=localhost:6379 + ip="0.0.0.0" transport=quic-v1 security=quic muxer=quic is_dialer="false" cargo run --bin native_ping` To test the interop with other versions do something similar, except replace one of these nodes with the other version's interop test. +# Running this test with webtransport dialer in browser + +To run the webtransport test from within the browser, you'll need the +`chromedriver` in your `$PATH`, compatible with your Chrome browser. +Firefox is not yet supported as it doesn't support all required features yet +(in v114 there is no support for certhashes). + +2. + - Build the wasm package: `wasm-pack build --target web` + - Run the dialer: `redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webtransport + is_dialer=true cargo run --bin wasm_ping` + # Running all interop tests locally with Compose To run this test against all released libp2p versions you'll need to have the diff --git a/interop-tests/chromium-ping-version.json b/interop-tests/chromium-ping-version.json new file mode 100644 index 00000000000..9fb2cd2252c --- /dev/null +++ b/interop-tests/chromium-ping-version.json @@ -0,0 +1,7 @@ +{ + "id": "chromium-rust-libp2p-head", + "containerImageID": "chromium-rust-libp2p-head", + "transports": [{ "name": "webtransport", "onlyDial": true }], + "secureChannels": [], + "muxers": [] +} diff --git a/interop-tests/ping-version.json b/interop-tests/native-ping-version.json similarity index 67% rename from interop-tests/ping-version.json rename to interop-tests/native-ping-version.json index fbf858e85e8..c509f72bfd0 100644 --- a/interop-tests/ping-version.json +++ b/interop-tests/native-ping-version.json @@ -1,6 +1,6 @@ { - "id": "rust-libp2p-head", - "containerImageID": "rust-libp2p-head", + "id": "native-rust-libp2p-head", + "containerImageID": "native-rust-libp2p-head", "transports": [ "ws", "tcp", diff --git a/interop-tests/pkg/readme.md b/interop-tests/pkg/readme.md new file mode 100644 index 00000000000..28a771b5ac5 --- /dev/null +++ b/interop-tests/pkg/readme.md @@ -0,0 +1,6 @@ +# Wasm package directory + +Content of this directory should be generated with +``` +wasm pack build --target web +``` diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs new file mode 100644 index 00000000000..2f4a7495a04 --- /dev/null +++ b/interop-tests/src/arch.rs @@ -0,0 +1,231 @@ +use libp2p::core::muxing::StreamMuxerBox; +use libp2p::core::transport::Boxed; +use libp2p::PeerId; + +// Native re-exports +#[cfg(not(target_arch = "wasm32"))] +pub(crate) use native::{build_transport, init_logger, sleep, swarm_builder, Instant, RedisClient}; + +// Wasm re-exports +#[cfg(target_arch = "wasm32")] +pub(crate) use wasm::{build_transport, init_logger, sleep, swarm_builder, Instant, RedisClient}; + +type BoxedTransport = Boxed<(PeerId, StreamMuxerBox)>; + +#[cfg(not(target_arch = "wasm32"))] +pub(crate) mod native { + use std::time::Duration; + + use anyhow::{bail, Context, Result}; + use either::Either; + use env_logger::{Env, Target}; + use futures::future::BoxFuture; + use futures::FutureExt; + use libp2p::core::muxing::StreamMuxerBox; + use libp2p::core::upgrade::Version; + use libp2p::identity::Keypair; + use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; + use libp2p::websocket::WsConfig; + use libp2p::{noise, tcp, tls, yamux, PeerId, Transport as _}; + use libp2p_mplex as mplex; + use libp2p_quic as quic; + use libp2p_webrtc as webrtc; + use redis::AsyncCommands; + + use crate::{from_env, Muxer, SecProtocol, Transport}; + + use super::BoxedTransport; + + pub(crate) type Instant = std::time::Instant; + + pub(crate) fn init_logger() { + env_logger::Builder::from_env(Env::default().default_filter_or("info")) + .target(Target::Stdout) + .init(); + } + + pub(crate) fn sleep(duration: Duration) -> BoxFuture<'static, ()> { + tokio::time::sleep(duration).boxed() + } + + fn muxer_protocol_from_env() -> Result> { + Ok(match from_env("muxer")? { + Muxer::Yamux => Either::Left(yamux::Config::default()), + Muxer::Mplex => Either::Right(mplex::MplexConfig::new()), + }) + } + + pub(crate) fn build_transport( + local_key: Keypair, + ip: &str, + transport: Transport, + ) -> Result<(BoxedTransport, String)> { + let (transport, addr) = match (transport, from_env::("security")) { + (Transport::QuicV1, _) => ( + quic::tokio::Transport::new(quic::Config::new(&local_key)) + .map(|(p, c), _| (p, StreamMuxerBox::new(c))) + .boxed(), + format!("/ip4/{ip}/udp/0/quic-v1"), + ), + (Transport::Tcp, Ok(SecProtocol::Tls)) => ( + tcp::tokio::Transport::new(tcp::Config::new()) + .upgrade(Version::V1Lazy) + .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) + .multiplex(muxer_protocol_from_env()?) + .timeout(Duration::from_secs(5)) + .boxed(), + format!("/ip4/{ip}/tcp/0"), + ), + (Transport::Tcp, Ok(SecProtocol::Noise)) => ( + tcp::tokio::Transport::new(tcp::Config::new()) + .upgrade(Version::V1Lazy) + .authenticate( + noise::Config::new(&local_key).context("failed to intialise noise")?, + ) + .multiplex(muxer_protocol_from_env()?) + .timeout(Duration::from_secs(5)) + .boxed(), + format!("/ip4/{ip}/tcp/0"), + ), + (Transport::Ws, Ok(SecProtocol::Tls)) => ( + WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) + .upgrade(Version::V1Lazy) + .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) + .multiplex(muxer_protocol_from_env()?) + .timeout(Duration::from_secs(5)) + .boxed(), + format!("/ip4/{ip}/tcp/0/ws"), + ), + (Transport::Ws, Ok(SecProtocol::Noise)) => ( + WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) + .upgrade(Version::V1Lazy) + .authenticate( + noise::Config::new(&local_key).context("failed to intialise noise")?, + ) + .multiplex(muxer_protocol_from_env()?) + .timeout(Duration::from_secs(5)) + .boxed(), + format!("/ip4/{ip}/tcp/0/ws"), + ), + (Transport::WebRtcDirect, _) => ( + webrtc::tokio::Transport::new( + local_key, + webrtc::tokio::Certificate::generate(&mut rand::thread_rng())?, + ) + .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) + .boxed(), + format!("/ip4/{ip}/udp/0/webrtc-direct"), + ), + (Transport::Tcp, Err(_)) => bail!("Missing security protocol for TCP transport"), + (Transport::Ws, Err(_)) => bail!("Missing security protocol for Websocket transport"), + (Transport::Webtransport, _) => bail!("Webtransport can only be used with wasm"), + }; + Ok((transport, addr)) + } + + pub(crate) fn swarm_builder( + transport: BoxedTransport, + behaviour: TBehaviour, + peer_id: PeerId, + ) -> SwarmBuilder { + SwarmBuilder::with_tokio_executor(transport, behaviour, peer_id) + } + + pub(crate) struct RedisClient(redis::Client); + + impl RedisClient { + pub(crate) fn new(redis_addr: &str) -> Result { + Ok(Self( + redis::Client::open(redis_addr).context("Could not connect to redis")?, + )) + } + + pub(crate) async fn blpop(&self, key: &str, timeout: u64) -> Result> { + let mut conn = self.0.get_async_connection().await?; + Ok(conn.blpop(key, timeout as usize).await?) + } + + pub(crate) async fn rpush(&self, key: &str, value: String) -> Result<()> { + let mut conn = self.0.get_async_connection().await?; + conn.rpush(key, value).await?; + Ok(()) + } + } +} + +#[cfg(target_arch = "wasm32")] +pub(crate) mod wasm { + use anyhow::{bail, Result}; + use futures::future::{BoxFuture, FutureExt}; + use libp2p::identity::Keypair; + use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; + use libp2p::PeerId; + use std::time::Duration; + + use crate::{BlpopRequest, Transport}; + + use super::BoxedTransport; + + pub(crate) type Instant = instant::Instant; + + pub(crate) fn init_logger() { + console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::default()); + } + + pub(crate) fn sleep(duration: Duration) -> BoxFuture<'static, ()> { + futures_timer::Delay::new(duration).boxed() + } + + pub(crate) fn build_transport( + local_key: Keypair, + ip: &str, + transport: Transport, + ) -> Result<(BoxedTransport, String)> { + if let Transport::Webtransport = transport { + Ok(( + libp2p::webtransport_websys::Transport::new( + libp2p::webtransport_websys::Config::new(&local_key), + ) + .boxed(), + format!("/ip4/{ip}/udp/0/quic/webtransport"), + )) + } else { + bail!("Only webtransport supported with wasm") + } + } + + pub(crate) fn swarm_builder( + transport: BoxedTransport, + behaviour: TBehaviour, + peer_id: PeerId, + ) -> SwarmBuilder { + SwarmBuilder::with_wasm_executor(transport, behaviour, peer_id) + } + + pub(crate) struct RedisClient(String); + + impl RedisClient { + pub(crate) fn new(base_url: &str) -> Result { + Ok(Self(base_url.to_owned())) + } + + pub(crate) async fn blpop(&self, key: &str, timeout: u64) -> Result> { + let res = reqwest::Client::new() + .post(&format!("http://{}/blpop", self.0)) + .json(&BlpopRequest { + key: key.to_owned(), + timeout, + }) + .send() + .await? + .json() + .await?; + Ok(res) + } + + pub(crate) async fn rpush(&self, _: &str, _: String) -> Result<()> { + bail!("unimplemented") + } + } +} diff --git a/interop-tests/src/bin/config/mod.rs b/interop-tests/src/bin/config/mod.rs new file mode 100644 index 00000000000..82747e82802 --- /dev/null +++ b/interop-tests/src/bin/config/mod.rs @@ -0,0 +1,37 @@ +use std::env; + +use anyhow::{Context, Result}; + +#[derive(Debug, Clone)] +pub(crate) struct Config { + pub(crate) transport: String, + pub(crate) ip: String, + pub(crate) is_dialer: bool, + pub(crate) test_timeout: u64, + pub(crate) redis_addr: String, +} + +impl Config { + pub(crate) fn from_env() -> Result { + let transport = + env::var("transport").context("transport environment variable is not set")?; + let ip = env::var("ip").context("ip environment variable is not set")?; + let is_dialer = env::var("is_dialer") + .unwrap_or_else(|_| "true".into()) + .parse::()?; + let test_timeout = env::var("test_timeout_seconds") + .unwrap_or_else(|_| "180".into()) + .parse::()?; + let redis_addr = env::var("redis_addr") + .map(|addr| format!("redis://{addr}")) + .unwrap_or_else(|_| "redis://redis:6379".into()); + + Ok(Self { + transport, + ip, + is_dialer, + test_timeout, + redis_addr, + }) + } +} diff --git a/interop-tests/src/bin/native_ping.rs b/interop-tests/src/bin/native_ping.rs new file mode 100644 index 00000000000..88905803d26 --- /dev/null +++ b/interop-tests/src/bin/native_ping.rs @@ -0,0 +1,21 @@ +use anyhow::Result; + +mod config; + +#[tokio::main] +async fn main() -> Result<()> { + let config = config::Config::from_env()?; + + let report = interop_tests::run_test( + &config.transport, + &config.ip, + config.is_dialer, + config.test_timeout, + &config.redis_addr, + ) + .await?; + + println!("{}", serde_json::to_string(&report)?); + + Ok(()) +} diff --git a/interop-tests/src/bin/ping.rs b/interop-tests/src/bin/ping.rs deleted file mode 100644 index 23cc86f4571..00000000000 --- a/interop-tests/src/bin/ping.rs +++ /dev/null @@ -1,267 +0,0 @@ -use std::env; -use std::str::FromStr; -use std::time::{Duration, Instant}; - -use anyhow::{bail, Context, Result}; -use either::Either; -use env_logger::{Env, Target}; -use futures::StreamExt; -use libp2p::core::muxing::StreamMuxerBox; -use libp2p::core::upgrade::Version; -use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmEvent}; -use libp2p::websocket::WsConfig; -use libp2p::{ - identity, noise, ping, swarm::SwarmBuilder, tcp, tls, yamux, Multiaddr, PeerId, Transport as _, -}; -use libp2p_mplex as mplex; -use libp2p_quic as quic; -use libp2p_webrtc as webrtc; -use redis::AsyncCommands; - -#[tokio::main] -async fn main() -> Result<()> { - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); - - let transport_param: Transport = from_env("transport")?; - - let ip = env::var("ip").context("ip environment variable is not set")?; - - let is_dialer = env::var("is_dialer") - .unwrap_or_else(|_| "true".into()) - .parse::()?; - - let test_timeout = env::var("test_timeout_seconds") - .unwrap_or_else(|_| "180".into()) - .parse::()?; - - let redis_addr = env::var("redis_addr") - .map(|addr| format!("redis://{addr}")) - .unwrap_or_else(|_| "redis://redis:6379".into()); - - let client = redis::Client::open(redis_addr).context("Could not connect to redis")?; - - // Build the transport from the passed ENV var. - let (boxed_transport, local_addr) = match (transport_param, from_env("security")) { - (Transport::QuicV1, _) => ( - quic::tokio::Transport::new(quic::Config::new(&local_key)) - .map(|(p, c), _| (p, StreamMuxerBox::new(c))) - .boxed(), - format!("/ip4/{ip}/udp/0/quic-v1"), - ), - (Transport::Tcp, Ok(SecProtocol::Tls)) => ( - tcp::tokio::Transport::new(tcp::Config::new()) - .upgrade(Version::V1Lazy) - .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) - .multiplex(muxer_protocol_from_env()?) - .timeout(Duration::from_secs(5)) - .boxed(), - format!("/ip4/{ip}/tcp/0"), - ), - (Transport::Tcp, Ok(SecProtocol::Noise)) => ( - tcp::tokio::Transport::new(tcp::Config::new()) - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key).context("failed to intialise noise")?) - .multiplex(muxer_protocol_from_env()?) - .timeout(Duration::from_secs(5)) - .boxed(), - format!("/ip4/{ip}/tcp/0"), - ), - (Transport::Ws, Ok(SecProtocol::Tls)) => ( - WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) - .upgrade(Version::V1Lazy) - .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) - .multiplex(muxer_protocol_from_env()?) - .timeout(Duration::from_secs(5)) - .boxed(), - format!("/ip4/{ip}/tcp/0/ws"), - ), - (Transport::Ws, Ok(SecProtocol::Noise)) => ( - WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key).context("failed to intialise noise")?) - .multiplex(muxer_protocol_from_env()?) - .timeout(Duration::from_secs(5)) - .boxed(), - format!("/ip4/{ip}/tcp/0/ws"), - ), - (Transport::WebRtcDirect, _) => ( - webrtc::tokio::Transport::new( - local_key, - webrtc::tokio::Certificate::generate(&mut rand::thread_rng())?, - ) - .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) - .boxed(), - format!("/ip4/{ip}/udp/0/webrtc-direct"), - ), - (Transport::Tcp, Err(_)) => bail!("Missing security protocol for TCP transport"), - (Transport::Ws, Err(_)) => bail!("Missing security protocol for Websocket transport"), - }; - - let mut swarm = SwarmBuilder::with_tokio_executor( - boxed_transport, - Behaviour { - ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), - keep_alive: keep_alive::Behaviour, - }, - local_peer_id, - ) - .build(); - - let mut conn = client.get_async_connection().await?; - - log::info!("Running ping test: {}", swarm.local_peer_id()); - env_logger::Builder::from_env(Env::default().default_filter_or("info")) - .target(Target::Stdout) - .init(); - - log::info!( - "Test instance, listening for incoming connections on: {:?}.", - local_addr - ); - let id = swarm.listen_on(local_addr.parse()?)?; - - // Run a ping interop test. Based on `is_dialer`, either dial the address - // retrieved via `listenAddr` key over the redis connection. Or wait to be pinged and have - // `dialerDone` key ready on the redis connection. - if is_dialer { - let result: Vec = conn.blpop("listenerAddr", test_timeout as usize).await?; - let other = result - .get(1) - .context("Failed to wait for listener to be ready")?; - - let handshake_start = Instant::now(); - - swarm.dial(other.parse::()?)?; - log::info!("Test instance, dialing multiaddress on: {}.", other); - - let rtt = loop { - if let Some(SwarmEvent::Behaviour(BehaviourEvent::Ping(ping::Event { - result: Ok(rtt), - .. - }))) = swarm.next().await - { - log::info!("Ping successful: {rtt:?}"); - break rtt.as_millis() as f32; - } - }; - - let handshake_plus_ping = handshake_start.elapsed().as_millis() as f32; - println!( - r#"{{"handshakePlusOneRTTMillis": {handshake_plus_ping:.1}, "pingRTTMilllis": {rtt:.1}}}"# - ); - } else { - loop { - if let Some(SwarmEvent::NewListenAddr { - listener_id, - address, - }) = swarm.next().await - { - if address.to_string().contains("127.0.0.1") { - continue; - } - if listener_id == id { - let ma = format!("{address}/p2p/{local_peer_id}"); - conn.rpush("listenerAddr", ma).await?; - break; - } - } - } - - // Drive Swarm in the background while we await for `dialerDone` to be ready. - tokio::spawn(async move { - loop { - swarm.next().await; - } - }); - tokio::time::sleep(Duration::from_secs(test_timeout)).await; - bail!("Test should have been killed by the test runner!"); - } - - Ok(()) -} - -fn muxer_protocol_from_env() -> Result> { - Ok(match from_env("muxer")? { - Muxer::Yamux => Either::Left(yamux::Config::default()), - Muxer::Mplex => Either::Right(mplex::MplexConfig::new()), - }) -} - -/// Supported transports by rust-libp2p. -#[derive(Clone, Debug)] -pub enum Transport { - Tcp, - QuicV1, - WebRtcDirect, - Ws, -} - -impl FromStr for Transport { - type Err = anyhow::Error; - - fn from_str(s: &str) -> std::result::Result { - Ok(match s { - "tcp" => Self::Tcp, - "quic-v1" => Self::QuicV1, - "webrtc-direct" => Self::WebRtcDirect, - "ws" => Self::Ws, - other => bail!("unknown transport {other}"), - }) - } -} - -/// Supported stream multiplexers by rust-libp2p. -#[derive(Clone, Debug)] -pub enum Muxer { - Mplex, - Yamux, -} - -impl FromStr for Muxer { - type Err = anyhow::Error; - - fn from_str(s: &str) -> std::result::Result { - Ok(match s { - "mplex" => Self::Mplex, - "yamux" => Self::Yamux, - other => bail!("unknown muxer {other}"), - }) - } -} - -/// Supported security protocols by rust-libp2p. -#[derive(Clone, Debug)] -pub enum SecProtocol { - Noise, - Tls, -} - -impl FromStr for SecProtocol { - type Err = anyhow::Error; - - fn from_str(s: &str) -> std::result::Result { - Ok(match s { - "noise" => Self::Noise, - "tls" => Self::Tls, - other => bail!("unknown security protocol {other}"), - }) - } -} - -#[derive(NetworkBehaviour)] -struct Behaviour { - ping: ping::Behaviour, - keep_alive: keep_alive::Behaviour, -} - -/// Helper function to get a ENV variable into an test parameter like `Transport`. -pub fn from_env(env_var: &str) -> Result -where - T: FromStr, -{ - env::var(env_var) - .with_context(|| format!("{env_var} environment variable is not set"))? - .parse() - .map_err(Into::into) -} diff --git a/interop-tests/src/bin/wasm_ping.rs b/interop-tests/src/bin/wasm_ping.rs new file mode 100644 index 00000000000..20350170d59 --- /dev/null +++ b/interop-tests/src/bin/wasm_ping.rs @@ -0,0 +1,219 @@ +use std::process::Stdio; +use std::time::Duration; + +use anyhow::{bail, Context, Result}; +use axum::body; +use axum::http::{header, Uri}; +use axum::response::{Html, IntoResponse, Response}; +use axum::routing::get; +use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; +use redis::{AsyncCommands, Client}; +use thirtyfour::prelude::*; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::Child; +use tokio::sync::mpsc; +use tower_http::cors::CorsLayer; +use tower_http::trace::TraceLayer; +use tracing::{error, warn}; +use tracing_subscriber::{fmt, prelude::*, EnvFilter}; + +use interop_tests::{BlpopRequest, Report}; + +mod config; + +const BIND_ADDR: &str = "127.0.0.1:8080"; + +/// Embedded Wasm package +/// +/// Make sure to build the wasm with `wasm-pack build --target web` +#[derive(rust_embed::RustEmbed)] +#[folder = "pkg"] +struct WasmPackage; + +#[derive(Clone)] +struct TestState { + redis_client: Client, + config: config::Config, + results_tx: mpsc::Sender>, +} + +#[tokio::main] +async fn main() -> Result<()> { + // start logging + tracing_subscriber::registry() + .with(fmt::layer()) + .with(EnvFilter::from_default_env()) + .init(); + + // read env variables + let config = config::Config::from_env()?; + let test_timeout = Duration::from_secs(config.test_timeout); + + // create a redis client + let redis_client = + Client::open(config.redis_addr.as_str()).context("Could not connect to redis")?; + let (results_tx, mut results_rx) = mpsc::channel(1); + + let state = TestState { + redis_client, + config, + results_tx, + }; + + // create a wasm-app service + let app = Router::new() + // Redis proxy + .route("/blpop", post(redis_blpop)) + // Report tests status + .route("/results", post(post_results)) + // Wasm ping test trigger + .route("/", get(serve_index_html)) + // Wasm app static files + .fallback(serve_wasm_pkg) + // Middleware + .layer(CorsLayer::very_permissive()) + .layer(TraceLayer::new_for_http()) + .with_state(state); + + // Run the service in background + tokio::spawn(axum::Server::bind(&BIND_ADDR.parse()?).serve(app.into_make_service())); + + // Start executing the test in a browser + let (mut chrome, driver) = open_in_browser().await?; + + // Wait for the outcome to be reported + let test_result = match tokio::time::timeout(test_timeout, results_rx.recv()).await { + Ok(received) => received.unwrap_or(Err("Results channel closed".to_owned())), + Err(_) => Err("Test timed out".to_owned()), + }; + + // Close the browser after we got the results + driver.quit().await?; + chrome.kill().await?; + + match test_result { + Ok(report) => println!("{}", serde_json::to_string(&report)?), + Err(error) => bail!("Tests failed: {error}"), + } + + Ok(()) +} + +async fn open_in_browser() -> Result<(Child, WebDriver)> { + // start a webdriver process + // currently only the chromedriver is supported as firefox doesn't + // have support yet for the certhashes + let mut chrome = tokio::process::Command::new("chromedriver") + .arg("--port=45782") + .stdout(Stdio::piped()) + .spawn()?; + // read driver's stdout + let driver_out = chrome + .stdout + .take() + .context("No stdout found for webdriver")?; + // wait for the 'ready' message + let mut reader = BufReader::new(driver_out).lines(); + while let Some(line) = reader.next_line().await? { + if line.contains("ChromeDriver was started successfully.") { + break; + } + } + + // run a webdriver client + let mut caps = DesiredCapabilities::chrome(); + caps.set_headless()?; + let driver = WebDriver::new("http://localhost:45782", caps).await?; + // go to the wasm test service + driver.goto(format!("http://{BIND_ADDR}")).await?; + + Ok((chrome, driver)) +} + +/// Redis proxy handler. +/// `blpop` is currently the only redis client method used in a ping dialer. +async fn redis_blpop( + state: State, + request: Json, +) -> Result>, StatusCode> { + let client = state.0.redis_client; + let mut conn = client.get_async_connection().await.map_err(|e| { + warn!("Failed to connect to redis: {e}"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + let res = conn + .blpop(&request.key, request.timeout as usize) + .await + .map_err(|e| { + warn!( + "Failed to get list elem {} within timeout {}: {e}", + request.key, request.timeout + ); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + Ok(Json(res)) +} + +/// Receive test results +async fn post_results( + state: State, + request: Json>, +) -> Result<(), StatusCode> { + state.0.results_tx.send(request.0).await.map_err(|_| { + error!("Failed to send results"); + StatusCode::INTERNAL_SERVER_ERROR + }) +} + +/// Serve the main page which loads our javascript +async fn serve_index_html(state: State) -> Result { + let config::Config { + transport, + ip, + is_dialer, + test_timeout, + .. + } = state.0.config; + Ok(Html(format!( + r#" + + + + + libp2p ping test + + + + + + "# + ))) +} + +async fn serve_wasm_pkg(uri: Uri) -> Result { + let path = uri.path().trim_start_matches('/').to_string(); + if let Some(content) = WasmPackage::get(&path) { + let mime = mime_guess::from_path(&path).first_or_octet_stream(); + Ok(Response::builder() + .header(header::CONTENT_TYPE, mime.as_ref()) + .body(body::boxed(body::Full::from(content.data))) + .unwrap()) + } else { + Err(StatusCode::NOT_FOUND) + } +} diff --git a/interop-tests/src/lib.rs b/interop-tests/src/lib.rs new file mode 100644 index 00000000000..beb7c91c63d --- /dev/null +++ b/interop-tests/src/lib.rs @@ -0,0 +1,251 @@ +use std::str::FromStr; +use std::time::Duration; + +use anyhow::{bail, Context, Result}; +use futures::{FutureExt, StreamExt}; +use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmEvent}; +use libp2p::{identity, ping, Multiaddr, PeerId}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +mod arch; + +use arch::{build_transport, init_logger, swarm_builder, Instant, RedisClient}; + +pub async fn run_test( + transport: &str, + ip: &str, + is_dialer: bool, + test_timeout_seconds: u64, + redis_addr: &str, +) -> Result { + init_logger(); + + let test_timeout = Duration::from_secs(test_timeout_seconds); + let transport = transport.parse().context("Couldn't parse transport")?; + + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + let redis_client = RedisClient::new(redis_addr).context("Could not connect to redis")?; + + // Build the transport from the passed ENV var. + let (boxed_transport, local_addr) = build_transport(local_key, ip, transport)?; + let mut swarm = swarm_builder( + boxed_transport, + Behaviour { + ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), + keep_alive: keep_alive::Behaviour, + }, + local_peer_id, + ) + .build(); + + log::info!("Running ping test: {}", swarm.local_peer_id()); + + let mut maybe_id = None; + + // See https://github.com/libp2p/rust-libp2p/issues/4071. + if transport == Transport::WebRtcDirect { + maybe_id = Some(swarm.listen_on(local_addr.parse()?)?); + } + + // Run a ping interop test. Based on `is_dialer`, either dial the address + // retrieved via `listenAddr` key over the redis connection. Or wait to be pinged and have + // `dialerDone` key ready on the redis connection. + match is_dialer { + true => { + let result: Vec = redis_client + .blpop("listenerAddr", test_timeout.as_secs()) + .await?; + let other = result + .get(1) + .context("Failed to wait for listener to be ready")?; + + let handshake_start = Instant::now(); + + swarm.dial(other.parse::()?)?; + log::info!("Test instance, dialing multiaddress on: {}.", other); + + let rtt = loop { + if let Some(SwarmEvent::Behaviour(BehaviourEvent::Ping(ping::Event { + result: Ok(rtt), + .. + }))) = swarm.next().await + { + log::info!("Ping successful: {rtt:?}"); + break rtt.as_micros() as f32 / 1000.; + } + }; + + let handshake_plus_ping = handshake_start.elapsed().as_micros() as f32 / 1000.; + Ok(Report { + handshake_plus_one_rtt_millis: handshake_plus_ping, + ping_rtt_millis: rtt, + }) + } + false => { + // Listen if we haven't done so already. + // This is a hack until https://github.com/libp2p/rust-libp2p/issues/4071 is fixed at which point we can do this unconditionally here. + let id = match maybe_id { + None => swarm.listen_on(local_addr.parse()?)?, + Some(id) => id, + }; + + log::info!( + "Test instance, listening for incoming connections on: {:?}.", + local_addr + ); + + loop { + if let Some(SwarmEvent::NewListenAddr { + listener_id, + address, + }) = swarm.next().await + { + if address.to_string().contains("127.0.0.1") { + continue; + } + if listener_id == id { + let ma = format!("{address}/p2p/{local_peer_id}"); + redis_client.rpush("listenerAddr", ma).await?; + break; + } + } + } + + // Drive Swarm while we await for `dialerDone` to be ready. + futures::future::select( + async move { + loop { + swarm.next().await; + } + } + .boxed(), + arch::sleep(test_timeout), + ) + .await; + + // The loop never ends so if we get here, we hit the timeout. + bail!("Test should have been killed by the test runner!"); + } + } +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub async fn run_test_wasm( + transport: &str, + ip: &str, + is_dialer: bool, + test_timeout_secs: u64, + base_url: &str, +) -> Result<(), JsValue> { + let result = run_test(transport, ip, is_dialer, test_timeout_secs, base_url).await; + log::info!("Sending test result: {result:?}"); + reqwest::Client::new() + .post(&format!("http://{}/results", base_url)) + .json(&result.map_err(|e| e.to_string())) + .send() + .await? + .error_for_status() + .map_err(|e| format!("Sending test result failed: {e}"))?; + + Ok(()) +} + +/// A request to redis proxy that will pop the value from the list +/// and will wait for it being inserted until a timeout is reached. +#[derive(serde::Deserialize, serde::Serialize)] +pub struct BlpopRequest { + pub key: String, + pub timeout: u64, +} + +/// A report generated by the test +#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct Report { + #[serde(rename = "handshakePlusOneRTTMillis")] + handshake_plus_one_rtt_millis: f32, + #[serde(rename = "pingRTTMilllis")] + ping_rtt_millis: f32, +} + +/// Supported transports by rust-libp2p. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Transport { + Tcp, + QuicV1, + WebRtcDirect, + Ws, + Webtransport, +} + +impl FromStr for Transport { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + Ok(match s { + "tcp" => Self::Tcp, + "quic-v1" => Self::QuicV1, + "webrtc-direct" => Self::WebRtcDirect, + "ws" => Self::Ws, + "webtransport" => Self::Webtransport, + other => bail!("unknown transport {other}"), + }) + } +} + +/// Supported stream multiplexers by rust-libp2p. +#[derive(Clone, Debug)] +pub enum Muxer { + Mplex, + Yamux, +} + +impl FromStr for Muxer { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + Ok(match s { + "mplex" => Self::Mplex, + "yamux" => Self::Yamux, + other => bail!("unknown muxer {other}"), + }) + } +} + +/// Supported security protocols by rust-libp2p. +#[derive(Clone, Debug)] +pub enum SecProtocol { + Noise, + Tls, +} + +impl FromStr for SecProtocol { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + Ok(match s { + "noise" => Self::Noise, + "tls" => Self::Tls, + other => bail!("unknown security protocol {other}"), + }) + } +} + +#[derive(NetworkBehaviour)] +struct Behaviour { + ping: ping::Behaviour, + keep_alive: keep_alive::Behaviour, +} + +/// Helper function to get a ENV variable into an test parameter like `Transport`. +pub fn from_env(env_var: &str) -> Result +where + T: FromStr, +{ + std::env::var(env_var) + .with_context(|| format!("{env_var} environment variable is not set"))? + .parse() + .map_err(Into::into) +} diff --git a/misc/keygen/Cargo.toml b/misc/keygen/Cargo.toml index 004ea21cc65..c394d6e3478 100644 --- a/misc/keygen/Cargo.toml +++ b/misc/keygen/Cargo.toml @@ -12,7 +12,7 @@ publish = false [dependencies] clap = { version = "4.3.1", features = ["derive"] } zeroize = "1" -serde = { version = "1.0.163", features = ["derive"] } +serde = { version = "1.0.164", features = ["derive"] } serde_json = "1.0.96" libp2p-core = { workspace = true } base64 = "0.21.2" diff --git a/protocols/gossipsub/Cargo.toml b/protocols/gossipsub/Cargo.toml index 1ee5f402541..9b7c82532ee 100644 --- a/protocols/gossipsub/Cargo.toml +++ b/protocols/gossipsub/Cargo.toml @@ -28,7 +28,7 @@ instant = "0.1.12" libp2p-core = { workspace = true } libp2p-identity = { workspace = true } libp2p-swarm = { workspace = true } -log = "0.4.18" +log = "0.4.19" quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } rand = "0.8" diff --git a/protocols/identify/CHANGELOG.md b/protocols/identify/CHANGELOG.md index f90d8ce3da4..6e305c81d7d 100644 --- a/protocols/identify/CHANGELOG.md +++ b/protocols/identify/CHANGELOG.md @@ -1,5 +1,10 @@ ## 0.43.0 - unreleased +- Observed addresses (aka. external address candidates) of the local node, reported by a remote node via `libp2p-identify`, are no longer automatically considered confirmed external addresses, in other words they are no longer trusted by default. + Instead users need to confirm the reported observed address either manually, or by using `libp2p-autonat`. + In trusted environments users can simply extract observed addresses from a `libp2p-identify::Event::Received { info: libp2p_identify::Info { observed_addr }}` and confirm them via `Swarm::add_external_address`. + See [PR 3954] and [PR 4052]. + - Remove deprecated `Identify` prefixed symbols. See [PR 3698]. - Raise MSRV to 1.65. See [PR 3715]. @@ -17,7 +22,9 @@ [PR 3698]: https://github.com/libp2p/rust-libp2p/pull/3698 [PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715 [PR 3876]: https://github.com/libp2p/rust-libp2p/pull/3876 +[PR 3954]: https://github.com/libp2p/rust-libp2p/pull/3954 [PR 3980]: https://github.com/libp2p/rust-libp2p/pull/3980 +[PR 4052]: https://github.com/libp2p/rust-libp2p/pull/4052 ## 0.42.2 diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index 25684896f32..f323d1db5d6 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -17,7 +17,7 @@ futures-timer = "3.0.2" libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } -log = "0.4.18" +log = "0.4.19" lru = "0.10.0" quick-protobuf-codec = { workspace = true } quick-protobuf = "0.8" diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index 061420b676b..32e61776faa 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -7,8 +7,10 @@ See [PR 3896]. - Automatically configure client/server mode based on external addresses. - If we have or learn about an external address of our node, we operate in server-mode and thus allow inbound requests. + If we have or learn about an external address of our node, e.g. through `Swarm::add_external_address` or automated through `libp2p-autonat`, we operate in server-mode and thus allow inbound requests. By default, a node is in client-mode and only allows outbound requests. + If you want to maintain the status quo, i.e. always operate in server mode, make sure to add at least one external address through `Swarm::add_external_address`. + See also [Kademlia specification](https://github.com/libp2p/specs/tree/master/kad-dht#client-and-server-mode) for an introduction to Kademlia client/server mode. See [PR 3877]. [PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715 diff --git a/protocols/mdns/Cargo.toml b/protocols/mdns/Cargo.toml index e3fa04eb93c..5206fe9effe 100644 --- a/protocols/mdns/Cargo.toml +++ b/protocols/mdns/Cargo.toml @@ -18,7 +18,7 @@ if-watch = "3.0.1" libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } -log = "0.4.18" +log = "0.4.19" rand = "0.8.3" smallvec = "1.6.1" socket2 = { version = "0.5.3", features = ["all"] } diff --git a/protocols/ping/Cargo.toml b/protocols/ping/Cargo.toml index a8b71c31c53..8a2dd90f428 100644 --- a/protocols/ping/Cargo.toml +++ b/protocols/ping/Cargo.toml @@ -18,7 +18,7 @@ instant = "0.1.12" libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } -log = "0.4.18" +log = "0.4.19" rand = "0.8" void = "1.0" diff --git a/protocols/request-response/Cargo.toml b/protocols/request-response/Cargo.toml index 9de652c36df..2e19efcd864 100644 --- a/protocols/request-response/Cargo.toml +++ b/protocols/request-response/Cargo.toml @@ -23,7 +23,7 @@ serde_json = { version = "1.0.96", optional = true } serde_cbor = { version = "0.11.2", optional = true } smallvec = "1.6.1" void = "1.0.2" -log = "0.4.18" +log = "0.4.19" [features] json = ["dep:serde", "dep:serde_json", "libp2p-swarm/macros"] diff --git a/swarm-derive/src/lib.rs b/swarm-derive/src/lib.rs index dafd1076218..e54cd058daf 100644 --- a/swarm-derive/src/lib.rs +++ b/swarm-derive/src/lib.rs @@ -732,6 +732,12 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> syn::Result { return std::task::Poll::Ready(#network_behaviour_action::Dial { opts }); } + std::task::Poll::Ready(#network_behaviour_action::ListenOn { opts }) => { + return std::task::Poll::Ready(#network_behaviour_action::ListenOn { opts }); + } + std::task::Poll::Ready(#network_behaviour_action::RemoveListener { id }) => { + return std::task::Poll::Ready(#network_behaviour_action::RemoveListener { id }); + } std::task::Poll::Ready(#network_behaviour_action::NotifyHandler { peer_id, handler, event }) => { return std::task::Poll::Ready(#network_behaviour_action::NotifyHandler { peer_id, diff --git a/swarm-test/Cargo.toml b/swarm-test/Cargo.toml index 853bb2240e0..617e6552999 100644 --- a/swarm-test/Cargo.toml +++ b/swarm-test/Cargo.toml @@ -20,6 +20,6 @@ libp2p-swarm = { workspace = true } libp2p-tcp = { workspace = true, features = ["async-io"] } libp2p-yamux = { workspace = true } futures = "0.3.28" -log = "0.4.18" +log = "0.4.19" rand = "0.8.5" futures-timer = "3.0.2" diff --git a/swarm/CHANGELOG.md b/swarm/CHANGELOG.md index d18629a0eb0..13147908721 100644 --- a/swarm/CHANGELOG.md +++ b/swarm/CHANGELOG.md @@ -1,5 +1,8 @@ ## 0.43.0 - unreleased +- Allow `NetworkBehaviours` to create and remove listeners. + See [PR 3292]. + - Raise MSRV to 1.65. See [PR 3715]. @@ -61,6 +64,9 @@ - Remove deprecated items. See [PR 3956]. +- Add ability to `downcast_ref` ConnectionDenied errors. See [PR 4020]. + +[PR 3292]: https://github.com/libp2p/rust-libp2p/pull/3292 [PR 3605]: https://github.com/libp2p/rust-libp2p/pull/3605 [PR 3651]: https://github.com/libp2p/rust-libp2p/pull/3651 [PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715 @@ -76,6 +82,7 @@ [PR 3927]: https://github.com/libp2p/rust-libp2p/pull/3927 [PR 3955]: https://github.com/libp2p/rust-libp2p/pull/3955 [PR 3956]: https://github.com/libp2p/rust-libp2p/pull/3956 +[PR 4020]: https://github.com/libp2p/rust-libp2p/pull/4020 [PR 4037]: https://github.com/libp2p/rust-libp2p/pull/4037 ## 0.42.2 diff --git a/swarm/src/behaviour.rs b/swarm/src/behaviour.rs index 1ddec51dfe4..0615457291a 100644 --- a/swarm/src/behaviour.rs +++ b/swarm/src/behaviour.rs @@ -28,6 +28,7 @@ pub use listen_addresses::ListenAddresses; use crate::connection::ConnectionId; use crate::dial_opts::DialOpts; +use crate::listen_opts::ListenOpts; use crate::{ ConnectionDenied, ConnectionHandler, DialError, ListenError, THandler, THandlerInEvent, THandlerOutEvent, @@ -250,6 +251,12 @@ pub enum ToSwarm { /// This allows a [`NetworkBehaviour`] to identify a connection that resulted out of its own dial request. Dial { opts: DialOpts }, + /// Instructs the [`Swarm`](crate::Swarm) to listen on the provided address. + ListenOn { opts: ListenOpts }, + + /// Instructs the [`Swarm`](crate::Swarm) to remove the listener. + RemoveListener { id: ListenerId }, + /// Instructs the `Swarm` to send an event to the handler dedicated to a /// connection with a peer. /// @@ -324,6 +331,8 @@ impl ToSwarm { match self { ToSwarm::GenerateEvent(e) => ToSwarm::GenerateEvent(e), ToSwarm::Dial { opts } => ToSwarm::Dial { opts }, + ToSwarm::ListenOn { opts } => ToSwarm::ListenOn { opts }, + ToSwarm::RemoveListener { id } => ToSwarm::RemoveListener { id }, ToSwarm::NotifyHandler { peer_id, handler, @@ -353,6 +362,8 @@ impl ToSwarm { match self { ToSwarm::GenerateEvent(e) => ToSwarm::GenerateEvent(f(e)), ToSwarm::Dial { opts } => ToSwarm::Dial { opts }, + ToSwarm::ListenOn { opts } => ToSwarm::ListenOn { opts }, + ToSwarm::RemoveListener { id } => ToSwarm::RemoveListener { id }, ToSwarm::NotifyHandler { peer_id, handler, diff --git a/swarm/src/lib.rs b/swarm/src/lib.rs index 03e6a1efe57..9b739f33780 100644 --- a/swarm/src/lib.rs +++ b/swarm/src/lib.rs @@ -68,6 +68,7 @@ pub mod dial_opts; pub mod dummy; pub mod handler; pub mod keep_alive; +mod listen_opts; /// Bundles all symbols required for the [`libp2p_swarm_derive::NetworkBehaviour`] macro. #[doc(hidden)] @@ -121,6 +122,7 @@ pub use handler::{ }; #[cfg(feature = "macros")] pub use libp2p_swarm_derive::NetworkBehaviour; +pub use listen_opts::ListenOpts; pub use stream::Stream; pub use stream_protocol::{InvalidProtocol, StreamProtocol}; @@ -370,12 +372,9 @@ where /// Listeners report their new listening addresses as [`SwarmEvent::NewListenAddr`]. /// Depending on the underlying transport, one listener may have multiple listening addresses. pub fn listen_on(&mut self, addr: Multiaddr) -> Result> { - let id = ListenerId::next(); - self.transport.listen_on(id, addr)?; - self.behaviour - .on_swarm_event(FromSwarm::NewListener(behaviour::NewListener { - listener_id: id, - })); + let opts = ListenOpts::new(addr); + let id = opts.listener_id(); + self.add_listener(opts)?; Ok(id) } @@ -542,6 +541,28 @@ where self.confirmed_external_addr.iter() } + fn add_listener(&mut self, opts: ListenOpts) -> Result<(), TransportError> { + let addr = opts.address(); + let listener_id = opts.listener_id(); + + if let Err(e) = self.transport.listen_on(listener_id, addr.clone()) { + self.behaviour + .on_swarm_event(FromSwarm::ListenerError(behaviour::ListenerError { + listener_id, + err: &e, + })); + + return Err(e); + } + + self.behaviour + .on_swarm_event(FromSwarm::NewListener(behaviour::NewListener { + listener_id, + })); + + Ok(()) + } + /// Add a **confirmed** external address for the local node. /// /// This function should only be called with addresses that are guaranteed to be reachable. @@ -1014,6 +1035,13 @@ where }); } } + ToSwarm::ListenOn { opts } => { + // Error is dispatched internally, safe to ignore. + let _ = self.add_listener(opts); + } + ToSwarm::RemoveListener { id } => { + self.remove_listener(id); + } ToSwarm::NotifyHandler { peer_id, handler, @@ -1702,6 +1730,14 @@ impl ConnectionDenied { Ok(*inner) } + + /// Attempt to downcast to a particular reason for why the connection was denied. + pub fn downcast_ref(&self) -> Option<&E> + where + E: error::Error + Send + Sync + 'static, + { + self.inner.downcast_ref::() + } } impl fmt::Display for ConnectionDenied { diff --git a/swarm/src/listen_opts.rs b/swarm/src/listen_opts.rs new file mode 100644 index 00000000000..9c4d69a6fa0 --- /dev/null +++ b/swarm/src/listen_opts.rs @@ -0,0 +1,33 @@ +use crate::ListenerId; +use libp2p_core::Multiaddr; + +#[derive(Debug)] +pub struct ListenOpts { + id: ListenerId, + address: Multiaddr, +} + +impl ListenOpts { + pub fn new(address: Multiaddr) -> ListenOpts { + ListenOpts { + id: ListenerId::next(), + address, + } + } + + /// Get the [`ListenerId`] of this listen attempt + pub fn listener_id(&self) -> ListenerId { + self.id + } + + /// Get the [`Multiaddr`] that is being listened on + pub fn address(&self) -> &Multiaddr { + &self.address + } +} + +impl From for ListenOpts { + fn from(addr: Multiaddr) -> Self { + ListenOpts::new(addr) + } +} diff --git a/swarm/tests/listener.rs b/swarm/tests/listener.rs new file mode 100644 index 00000000000..71d92cb0e1f --- /dev/null +++ b/swarm/tests/listener.rs @@ -0,0 +1,143 @@ +use std::{ + collections::{HashSet, VecDeque}, + task::{Context, Poll}, +}; + +use libp2p_core::{multiaddr::Protocol, transport::ListenerId, Endpoint, Multiaddr}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + derive_prelude::NewListener, dummy, ConnectionDenied, ConnectionId, FromSwarm, ListenOpts, + ListenerClosed, ListenerError, NetworkBehaviour, NewListenAddr, PollParameters, Swarm, + SwarmEvent, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, +}; + +use libp2p_swarm_test::SwarmExt; + +#[async_std::test] +async fn behaviour_listener() { + let mut swarm = Swarm::new_ephemeral(|_| Behaviour::default()); + let addr: Multiaddr = Protocol::Memory(0).into(); + let id = swarm.behaviour_mut().listen(addr.clone()); + + let address = swarm + .wait(|e| match e { + SwarmEvent::NewListenAddr { + listener_id, + address, + } => { + assert_eq!(listener_id, id); + Some(address) + } + _ => None, + }) + .await; + + swarm.behaviour_mut().stop_listening(id); + + swarm + .wait(|e| match e { + SwarmEvent::ListenerClosed { + listener_id, + addresses, + reason, + } => { + assert_eq!(listener_id, id); + assert!(addresses.contains(&address)); + assert!(reason.is_ok()); + Some(()) + } + _ => None, + }) + .await; +} + +#[derive(Default)] +struct Behaviour { + events: VecDeque::ToSwarm, THandlerInEvent>>, + listeners: HashSet, +} + +impl Behaviour { + pub(crate) fn listen(&mut self, addr: Multiaddr) -> ListenerId { + let opts = ListenOpts::new(addr); + let listener_id = opts.listener_id(); + assert!(!self.listeners.contains(&listener_id)); + self.events.push_back(ToSwarm::ListenOn { opts }); + self.listeners.insert(listener_id); + + listener_id + } + + pub(crate) fn stop_listening(&mut self, id: ListenerId) { + self.events.push_back(ToSwarm::RemoveListener { id }); + } +} + +impl NetworkBehaviour for Behaviour { + type ConnectionHandler = dummy::ConnectionHandler; + type ToSwarm = void::Void; + + fn handle_established_inbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: &Multiaddr, + ) -> Result, ConnectionDenied> { + Ok(dummy::ConnectionHandler) + } + + fn handle_established_outbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: Endpoint, + ) -> Result, ConnectionDenied> { + Ok(dummy::ConnectionHandler) + } + + fn on_connection_handler_event( + &mut self, + _: PeerId, + _: ConnectionId, + _: THandlerOutEvent, + ) { + } + + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::NewListener(NewListener { listener_id }) => { + assert!(self.listeners.contains(&listener_id)); + } + FromSwarm::NewListenAddr(NewListenAddr { listener_id, .. }) => { + assert!(self.listeners.contains(&listener_id)); + } + FromSwarm::ListenerError(ListenerError { listener_id, err }) => { + panic!("Error for listener {listener_id:?}: {err}"); + } + FromSwarm::ListenerClosed(ListenerClosed { + listener_id, + reason, + }) => { + assert!(self.listeners.contains(&listener_id)); + assert!(reason.is_ok()); + self.listeners.remove(&listener_id); + assert!(!self.listeners.contains(&listener_id)); + } + _ => {} + } + } + + fn poll( + &mut self, + _: &mut Context<'_>, + _: &mut impl PollParameters, + ) -> Poll>> { + if let Some(event) = self.events.pop_front() { + return Poll::Ready(event); + } + + Poll::Pending + } +} diff --git a/transports/dns/Cargo.toml b/transports/dns/Cargo.toml index 157248ef7c9..b8956d20e96 100644 --- a/transports/dns/Cargo.toml +++ b/transports/dns/Cargo.toml @@ -13,7 +13,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] libp2p-core = { workspace = true } libp2p-identity = { workspace = true } -log = "0.4.18" +log = "0.4.19" futures = "0.3.28" async-std-resolver = { version = "0.22", optional = true } parking_lot = "0.12.0" diff --git a/transports/plaintext/Cargo.toml b/transports/plaintext/Cargo.toml index 6d6f8df1571..6990a846c27 100644 --- a/transports/plaintext/Cargo.toml +++ b/transports/plaintext/Cargo.toml @@ -16,7 +16,7 @@ bytes = "1" futures = "0.3.28" libp2p-core = { workspace = true } libp2p-identity = { workspace = true } -log = "0.4.18" +log = "0.4.19" quick-protobuf = "0.8" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } diff --git a/transports/pnet/Cargo.toml b/transports/pnet/Cargo.toml index a2b5acaf887..986ff9dd3f3 100644 --- a/transports/pnet/Cargo.toml +++ b/transports/pnet/Cargo.toml @@ -12,7 +12,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] futures = "0.3.28" -log = "0.4.18" +log = "0.4.19" salsa20 = "0.10" sha3 = "0.10" rand = "0.8" diff --git a/transports/quic/CHANGELOG.md b/transports/quic/CHANGELOG.md index db50b7b8a31..7002a9c2f73 100644 --- a/transports/quic/CHANGELOG.md +++ b/transports/quic/CHANGELOG.md @@ -3,7 +3,10 @@ - Raise MSRV to 1.65. See [PR 3715]. +- Add hole punching support by implementing `Transport::dial_as_listener`. See [PR 3964]. + [PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715 +[PR 3964]: https://github.com/libp2p/rust-libp2p/pull/3964 ## 0.7.0-alpha.3 diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 79bd83f59f7..2005208c734 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -23,7 +23,7 @@ quinn-proto = { version = "0.10.1", default-features = false, features = ["tls-r rand = "0.8.5" rustls = { version = "0.21.1", default-features = false } thiserror = "1.0.40" -tokio = { version = "1.28.2", default-features = false, features = ["net", "rt"], optional = true } +tokio = { version = "1.28.2", default-features = false, features = ["net", "rt", "time"], optional = true } [features] tokio = ["dep:tokio", "if-watch/tokio"] diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index cef062a0d7e..bf69df50b62 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -279,6 +279,13 @@ impl Channel { Ok(Ok(())) } + pub(crate) async fn send(&mut self, to_endpoint: ToEndpoint) -> Result<(), Disconnected> { + self.to_endpoint + .send(to_endpoint) + .await + .map_err(|_| Disconnected {}) + } + /// Send a message to inform the [`Driver`] about an /// event caused by the owner of this [`Channel`] dropping. /// This clones the sender to the endpoint to guarantee delivery. diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs new file mode 100644 index 00000000000..b9589dd17a0 --- /dev/null +++ b/transports/quic/src/hole_punching.rs @@ -0,0 +1,47 @@ +use std::{net::SocketAddr, time::Duration}; + +use futures::future::Either; +use rand::{distributions, Rng}; + +use crate::{ + endpoint::{self, ToEndpoint}, + Error, Provider, +}; + +pub(crate) async fn hole_puncher( + endpoint_channel: endpoint::Channel, + remote_addr: SocketAddr, + timeout_duration: Duration, +) -> Error { + let punch_holes_future = punch_holes::

(endpoint_channel, remote_addr); + futures::pin_mut!(punch_holes_future); + match futures::future::select(P::sleep(timeout_duration), punch_holes_future).await { + Either::Left(_) => Error::HandshakeTimedOut, + Either::Right((hole_punch_err, _)) => hole_punch_err, + } +} + +async fn punch_holes( + mut endpoint_channel: endpoint::Channel, + remote_addr: SocketAddr, +) -> Error { + loop { + let sleep_duration = Duration::from_millis(rand::thread_rng().gen_range(10..=200)); + P::sleep(sleep_duration).await; + + let random_udp_packet = ToEndpoint::SendUdpPacket(quinn_proto::Transmit { + destination: remote_addr, + ecn: None, + contents: rand::thread_rng() + .sample_iter(distributions::Standard) + .take(64) + .collect(), + segment_size: None, + src_ip: None, + }); + + if endpoint_channel.send(random_udp_packet).await.is_err() { + return Error::EndpointDriverCrashed; + } + } +} diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 594ba0b6108..945f5119c6e 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -59,9 +59,12 @@ mod connection; mod endpoint; +mod hole_punching; mod provider; mod transport; +use std::net::SocketAddr; + pub use connection::{Connecting, Connection, Substream}; pub use endpoint::Config; #[cfg(feature = "async-std")] @@ -94,6 +97,14 @@ pub enum Error { /// The [`Connecting`] future timed out. #[error("Handshake with the remote timed out.")] HandshakeTimedOut, + + /// Error when `Transport::dial_as_listener` is called without an active listener. + #[error("Tried to dial as listener without an active listener.")] + NoActiveListenerForDialAsListener, + + /// Error when holepunching for a remote is already in progress + #[error("Already punching hole for {0}).")] + HolePunchInProgress(SocketAddr), } /// Dialing a remote peer failed. diff --git a/transports/quic/src/provider.rs b/transports/quic/src/provider.rs index c38f77fd1b9..c9401e9b99f 100644 --- a/transports/quic/src/provider.rs +++ b/transports/quic/src/provider.rs @@ -18,12 +18,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::Future; +use futures::{future::BoxFuture, Future}; use if_watch::IfEvent; use std::{ io, net::SocketAddr, task::{Context, Poll}, + time::Duration, }; #[cfg(feature = "async-std")] @@ -74,4 +75,7 @@ pub trait Provider: Unpin + Send + Sized + 'static { watcher: &mut Self::IfWatcher, cx: &mut Context<'_>, ) -> Poll>; + + /// Sleep for specified amount of time. + fn sleep(duration: Duration) -> BoxFuture<'static, ()>; } diff --git a/transports/quic/src/provider/async_std.rs b/transports/quic/src/provider/async_std.rs index 222c8e55e90..e593b2ed4f4 100644 --- a/transports/quic/src/provider/async_std.rs +++ b/transports/quic/src/provider/async_std.rs @@ -26,6 +26,7 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, + time::Duration, }; use crate::GenTransport; @@ -104,6 +105,10 @@ impl super::Provider for Provider { ) -> Poll> { watcher.poll_if_event(cx) } + + fn sleep(duration: Duration) -> BoxFuture<'static, ()> { + async_std::task::sleep(duration).boxed() + } } type ReceiveStreamItem = ( diff --git a/transports/quic/src/provider/tokio.rs b/transports/quic/src/provider/tokio.rs index 07e23f8813c..77c9060e3c1 100644 --- a/transports/quic/src/provider/tokio.rs +++ b/transports/quic/src/provider/tokio.rs @@ -18,11 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::{ready, Future}; +use futures::{future::BoxFuture, ready, Future, FutureExt}; use std::{ io, net::SocketAddr, task::{Context, Poll}, + time::Duration, }; use tokio::{io::ReadBuf, net::UdpSocket}; @@ -95,4 +96,8 @@ impl super::Provider for Provider { ) -> Poll> { watcher.poll_if_event(cx) } + + fn sleep(duration: Duration) -> BoxFuture<'static, ()> { + tokio::time::sleep(duration).boxed() + } } diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 668034ed147..afdaf86cdf4 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -19,11 +19,12 @@ // DEALINGS IN THE SOFTWARE. use crate::endpoint::{Config, QuinnConfig, ToEndpoint}; +use crate::hole_punching::hole_puncher; use crate::provider::Provider; use crate::{endpoint, Connecting, Connection, Error}; use futures::channel::{mpsc, oneshot}; -use futures::future::BoxFuture; +use futures::future::{BoxFuture, Either}; use futures::ready; use futures::stream::StreamExt; use futures::{prelude::*, stream::SelectAll}; @@ -73,6 +74,8 @@ pub struct GenTransport { dialer: HashMap, /// Waker to poll the transport again when a new dialer or listener is added. waker: Option, + /// Holepunching attempts + hole_punch_attempts: HashMap>, } impl GenTransport

{ @@ -88,6 +91,49 @@ impl GenTransport

{ dialer: HashMap::new(), waker: None, support_draft_29, + hole_punch_attempts: Default::default(), + } + } + + fn remote_multiaddr_to_socketaddr( + &self, + addr: Multiaddr, + ) -> Result< + (SocketAddr, ProtocolVersion, Option), + TransportError<::Error>, + > { + let (socket_addr, version, peer_id) = multiaddr_to_socketaddr(&addr, self.support_draft_29) + .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; + if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + Ok((socket_addr, version, peer_id)) + } + + fn eligible_listener(&mut self, socket_addr: &SocketAddr) -> Option<&mut Listener

> { + let mut listeners: Vec<_> = self + .listeners + .iter_mut() + .filter(|l| { + if l.is_closed { + return false; + } + let listen_addr = l.endpoint_channel.socket_addr(); + SocketFamily::is_same(&listen_addr.ip(), &socket_addr.ip()) + && listen_addr.ip().is_loopback() == socket_addr.ip().is_loopback() + }) + .collect(); + match listeners.len() { + 0 => None, + 1 => listeners.pop(), + _ => { + // Pick any listener to use for dialing. + // We hash the socket address to achieve determinism. + let mut hasher = DefaultHasher::new(); + socket_addr.hash(&mut hasher); + let index = hasher.finish() as usize % listeners.len(); + Some(listeners.swap_remove(index)) + } } } } @@ -103,8 +149,9 @@ impl Transport for GenTransport

{ listener_id: ListenerId, addr: Multiaddr, ) -> Result<(), TransportError> { - let (socket_addr, version) = multiaddr_to_socketaddr(&addr, self.support_draft_29) - .ok_or(TransportError::MultiaddrNotSupported(addr))?; + let (socket_addr, version, _peer_id) = + multiaddr_to_socketaddr(&addr, self.support_draft_29) + .ok_or(TransportError::MultiaddrNotSupported(addr))?; let listener = Listener::new( listener_id, socket_addr, @@ -147,27 +194,12 @@ impl Transport for GenTransport

{ } fn dial(&mut self, addr: Multiaddr) -> Result> { - let (socket_addr, version) = multiaddr_to_socketaddr(&addr, self.support_draft_29) - .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; - if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { - return Err(TransportError::MultiaddrNotSupported(addr)); - } + let (socket_addr, version, _peer_id) = self.remote_multiaddr_to_socketaddr(addr)?; - let mut listeners = self - .listeners - .iter_mut() - .filter(|l| { - if l.is_closed { - return false; - } - let listen_addr = l.endpoint_channel.socket_addr(); - SocketFamily::is_same(&listen_addr.ip(), &socket_addr.ip()) - && listen_addr.ip().is_loopback() == socket_addr.ip().is_loopback() - }) - .collect::>(); + let handshake_timeout = self.handshake_timeout; - let dialer_state = match listeners.len() { - 0 => { + let dialer_state = match self.eligible_listener(&socket_addr) { + None => { // No listener. Get or create an explicit dialer. let socket_family = socket_addr.ip().into(); let dialer = match self.dialer.entry(socket_family) { @@ -181,28 +213,61 @@ impl Transport for GenTransport

{ }; &mut dialer.state } - 1 => &mut listeners[0].dialer_state, - _ => { - // Pick any listener to use for dialing. - // We hash the socket address to achieve determinism. - let mut hasher = DefaultHasher::new(); - socket_addr.hash(&mut hasher); - let index = hasher.finish() as usize % listeners.len(); - &mut listeners[index].dialer_state - } + Some(listener) => &mut listener.dialer_state, }; - Ok(dialer_state.new_dial(socket_addr, self.handshake_timeout, version)) + Ok(dialer_state.new_dial(socket_addr, handshake_timeout, version)) } fn dial_as_listener( &mut self, addr: Multiaddr, ) -> Result> { - // TODO: As the listener of a QUIC hole punch, we need to send a random UDP packet to the - // `addr`. See DCUtR specification below. - // - // https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#the-protocol - Err(TransportError::MultiaddrNotSupported(addr)) + let (socket_addr, _version, peer_id) = self.remote_multiaddr_to_socketaddr(addr.clone())?; + let peer_id = peer_id.ok_or(TransportError::MultiaddrNotSupported(addr))?; + + let endpoint_channel = self + .eligible_listener(&socket_addr) + .ok_or(TransportError::Other( + Error::NoActiveListenerForDialAsListener, + ))? + .endpoint_channel + .clone(); + + let hole_puncher = hole_puncher::

(endpoint_channel, socket_addr, self.handshake_timeout); + + let (sender, receiver) = oneshot::channel(); + + match self.hole_punch_attempts.entry(socket_addr) { + Entry::Occupied(mut sender_entry) => { + // Stale senders, i.e. from failed hole punches are not removed. + // Thus, we can just overwrite a stale sender. + if !sender_entry.get().is_canceled() { + return Err(TransportError::Other(Error::HolePunchInProgress( + socket_addr, + ))); + } + sender_entry.insert(sender); + } + Entry::Vacant(entry) => { + entry.insert(sender); + } + }; + + Ok(Box::pin(async move { + futures::pin_mut!(hole_puncher); + match futures::future::select(receiver, hole_puncher).await { + Either::Left((message, _)) => { + let (inbound_peer_id, connection) = message + .expect("hole punch connection sender is never dropped before receiver") + .await?; + if inbound_peer_id != peer_id { + log::warn!("expected inbound connection from {socket_addr} to resolve to {peer_id} but got {inbound_peer_id}"); + } + Ok((inbound_peer_id, connection)) + } + Either::Right((hole_punch_err, _)) => Err(hole_punch_err), + } + })) } fn poll( @@ -222,8 +287,37 @@ impl Transport for GenTransport

{ self.dialer.remove(&key); } - if let Poll::Ready(Some(ev)) = self.listeners.poll_next_unpin(cx) { - return Poll::Ready(ev); + while let Poll::Ready(Some(ev)) = self.listeners.poll_next_unpin(cx) { + match ev { + TransportEvent::Incoming { + listener_id, + mut upgrade, + local_addr, + send_back_addr, + } => { + let socket_addr = + multiaddr_to_socketaddr(&send_back_addr, self.support_draft_29) + .unwrap() + .0; + + if let Some(sender) = self.hole_punch_attempts.remove(&socket_addr) { + match sender.send(upgrade) { + Ok(()) => continue, + Err(timed_out_holepunch) => { + upgrade = timed_out_holepunch; + } + } + } + + return Poll::Ready(TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + }); + } + _ => return Poll::Ready(ev), + } } self.waker = Some(cx.waker().clone()); @@ -594,15 +688,18 @@ fn ip_to_listenaddr( fn multiaddr_to_socketaddr( addr: &Multiaddr, support_draft_29: bool, -) -> Option<(SocketAddr, ProtocolVersion)> { +) -> Option<(SocketAddr, ProtocolVersion, Option)> { let mut iter = addr.iter(); let proto1 = iter.next()?; let proto2 = iter.next()?; let proto3 = iter.next()?; + let mut peer_id = None; for proto in iter { match proto { - Protocol::P2p(_) => {} // Ignore a `/p2p/...` prefix of possibly outer protocols, if present. + Protocol::P2p(id) => { + peer_id = Some(id); + } _ => return None, } } @@ -614,10 +711,10 @@ fn multiaddr_to_socketaddr( match (proto1, proto2) { (Protocol::Ip4(ip), Protocol::Udp(port)) => { - Some((SocketAddr::new(ip.into(), port), version)) + Some((SocketAddr::new(ip.into(), port), version, peer_id)) } (Protocol::Ip6(ip), Protocol::Udp(port)) => { - Some((SocketAddr::new(ip.into(), port), version)) + Some((SocketAddr::new(ip.into(), port), version, peer_id)) } _ => None, } @@ -691,7 +788,8 @@ mod test { ), Some(( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12345,), - ProtocolVersion::V1 + ProtocolVersion::V1, + None )) ); assert_eq!( @@ -703,7 +801,8 @@ mod test { ), Some(( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), 8080,), - ProtocolVersion::V1 + ProtocolVersion::V1, + None )) ); assert_eq!( @@ -715,7 +814,7 @@ mod test { Some((SocketAddr::new( IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 55148, - ), ProtocolVersion::V1)) + ), ProtocolVersion::V1, Some("12D3KooW9xk7Zp1gejwfwNpfm6L9zH5NL4Bx5rm94LRYJJHJuARZ".parse().unwrap()))) ); assert_eq!( multiaddr_to_socketaddr( @@ -724,7 +823,8 @@ mod test { ), Some(( SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 12345,), - ProtocolVersion::V1 + ProtocolVersion::V1, + None )) ); assert_eq!( @@ -741,7 +841,8 @@ mod test { )), 8080, ), - ProtocolVersion::V1 + ProtocolVersion::V1, + None )) ); @@ -757,7 +858,8 @@ mod test { ), Some(( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 1234,), - ProtocolVersion::Draft29 + ProtocolVersion::Draft29, + None )) ); } diff --git a/transports/tcp/Cargo.toml b/transports/tcp/Cargo.toml index 2a275736f8b..6207460311b 100644 --- a/transports/tcp/Cargo.toml +++ b/transports/tcp/Cargo.toml @@ -18,7 +18,7 @@ if-watch = "3.0.1" libc = "0.2.145" libp2p-core = { workspace = true } libp2p-identity = { workspace = true } -log = "0.4.18" +log = "0.4.19" socket2 = { version = "0.5.3", features = ["all"] } tokio = { version = "1.28.2", default-features = false, features = ["net"], optional = true } diff --git a/transports/uds/Cargo.toml b/transports/uds/Cargo.toml index 0cb3744d65a..1b33ba65aff 100644 --- a/transports/uds/Cargo.toml +++ b/transports/uds/Cargo.toml @@ -13,7 +13,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-std = { version = "1.6.2", optional = true } libp2p-core = { workspace = true } -log = "0.4.18" +log = "0.4.19" futures = "0.3.28" tokio = { version = "1.28", default-features = false, features = ["net"], optional = true } diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index a05d11f4592..469e19edac5 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -16,7 +16,7 @@ either = "1.5.3" futures = "0.3.28" libp2p-core = { workspace = true } libp2p-identity = { workspace = true } -log = "0.4.18" +log = "0.4.19" parking_lot = "0.12.0" quicksink = "0.1" rw-stream-sink = { workspace = true } From c8a83c341f715c86de128dd60ed9fcc7c6970e92 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Wed, 14 Jun 2023 21:05:18 +0300 Subject: [PATCH 05/21] Update transports/webtransport-websys/CHANGELOG.md Co-authored-by: Thomas Eizinger --- transports/webtransport-websys/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webtransport-websys/CHANGELOG.md b/transports/webtransport-websys/CHANGELOG.md index dfc9e7d63f2..509e9be5994 100644 --- a/transports/webtransport-websys/CHANGELOG.md +++ b/transports/webtransport-websys/CHANGELOG.md @@ -1,5 +1,5 @@ ## 0.1.0 - unreleased -* Initial implementation of WebTranport trasnport that uses web-sys. [PR 4015] +* Initial implementation of WebTranport transport using web-sys bindings. See [PR 4015]. [PR 4015]: https://github.com/libp2p/rust-libp2p/pull/4015 From 147fa323710922947014679f88ba5f9beb777264 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Wed, 14 Jun 2023 21:05:36 +0300 Subject: [PATCH 06/21] Update interop-tests/README.md Co-authored-by: Thomas Eizinger --- interop-tests/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/interop-tests/README.md b/interop-tests/README.md index 4e198e60de1..88cd7518833 100644 --- a/interop-tests/README.md +++ b/interop-tests/README.md @@ -26,10 +26,8 @@ To run the webtransport test from within the browser, you'll need the Firefox is not yet supported as it doesn't support all required features yet (in v114 there is no support for certhashes). -2. - - Build the wasm package: `wasm-pack build --target web` - - Run the dialer: `redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webtransport - is_dialer=true cargo run --bin wasm_ping` +1. Build the wasm package: `wasm-pack build --target web` +2. Run the dialer: `redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webtransport is_dialer=true cargo run --bin wasm_ping` # Running all interop tests locally with Compose From de728e80c3666ef74bdfbc11e691160168bb04ce Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Wed, 14 Jun 2023 23:14:27 +0300 Subject: [PATCH 07/21] Split maybe_init_and_poll in two different methods --- .../webtransport-websys/src/connection.rs | 7 +++- .../src/fused_js_promise.rs | 39 +++++++++---------- transports/webtransport-websys/src/stream.rs | 13 ++++--- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/transports/webtransport-websys/src/connection.rs b/transports/webtransport-websys/src/connection.rs index e14b1e858a9..9f6c8be88ad 100644 --- a/transports/webtransport-websys/src/connection.rs +++ b/transports/webtransport-websys/src/connection.rs @@ -1,3 +1,4 @@ +use futures::FutureExt; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent, StreamMuxerExt}; use libp2p_core::{OutboundUpgrade, UpgradeInfo}; use libp2p_identity::{Keypair, PeerId}; @@ -114,7 +115,8 @@ impl Connection { // Create bidirectional stream let val = ready!(self .create_stream_promise - .maybe_init_and_poll(cx, || self.session.create_bidirectional_stream())) + .maybe_init(|| self.session.create_bidirectional_stream()) + .poll_unpin(cx)) .map_err(Error::from_js_value)?; let bidi_stream = to_js_type::(val)?; @@ -131,7 +133,8 @@ impl Connection { // Read the next incoming stream from the JS channel let val = ready!(self .incoming_stream_promise - .maybe_init_and_poll(cx, || self.incoming_streams_reader.read())) + .maybe_init(|| self.incoming_streams_reader.read()) + .poll_unpin(cx)) .map_err(Error::from_js_value)?; let val = parse_reader_response(&val) diff --git a/transports/webtransport-websys/src/fused_js_promise.rs b/transports/webtransport-websys/src/fused_js_promise.rs index 759da4e74d5..42b4122022a 100644 --- a/transports/webtransport-websys/src/fused_js_promise.rs +++ b/transports/webtransport-websys/src/fused_js_promise.rs @@ -1,10 +1,16 @@ use futures::FutureExt; use js_sys::Promise; +use std::future::Future; +use std::pin::Pin; use std::task::{ready, Context, Poll}; use wasm_bindgen::JsValue; use wasm_bindgen_futures::JsFuture; /// Convenient wrapper to poll a promise to completion. +/// +/// # Panics +/// +/// Panics if polled and promise is not initialized. Use `maybe_init` if unsure. #[derive(Debug)] pub(crate) struct FusedJsPromise { promise: Option, @@ -16,14 +22,8 @@ impl FusedJsPromise { FusedJsPromise { promise: None } } - /// Initialize promise if needed and then poll. - /// - /// If promise is not initialized then `init` is called to initialize it. - pub(crate) fn maybe_init_and_poll( - &mut self, - cx: &mut Context, - init: F, - ) -> Poll> + /// Initialize promise if needed + pub(crate) fn maybe_init(&mut self, init: F) -> &mut Self where F: FnOnce() -> Promise, { @@ -31,15 +31,19 @@ impl FusedJsPromise { self.promise = Some(JsFuture::from(init())); } - self.poll(cx) + self } - /// Poll an already initialized promise. - /// - /// # Panics - /// - /// Panics if promise is not initialized. Use `maybe_init_and_poll` if unsure. - pub(crate) fn poll(&mut self, cx: &mut Context) -> Poll> { + /// Checks if promise is already running + pub(crate) fn is_active(&self) -> bool { + self.promise.is_some() + } +} + +impl Future for FusedJsPromise { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { let val = ready!(self .promise .as_mut() @@ -51,9 +55,4 @@ impl FusedJsPromise { Poll::Ready(val) } - - /// Checks if promise is already running - pub(crate) fn is_active(&self) -> bool { - self.promise.is_some() - } } diff --git a/transports/webtransport-websys/src/stream.rs b/transports/webtransport-websys/src/stream.rs index bbfed17d027..8e0bd1018f1 100644 --- a/transports/webtransport-websys/src/stream.rs +++ b/transports/webtransport-websys/src/stream.rs @@ -1,4 +1,4 @@ -use futures::{AsyncRead, AsyncWrite}; +use futures::{AsyncRead, AsyncWrite, FutureExt}; use js_sys::Uint8Array; use send_wrapper::SendWrapper; use std::io; @@ -77,7 +77,8 @@ impl Stream { if desired_size <= 0 || self.writer_ready_promise.is_active() { ready!(self .writer_ready_promise - .maybe_init_and_poll(cx, || self.writer.ready())) + .maybe_init(|| self.writer.ready()) + .poll_unpin(cx)) .map_err(to_io_error)?; } @@ -95,13 +96,14 @@ impl Stream { // Assume closed on error let _ = ready!(self .writer_closed_promise - .maybe_init_and_poll(cx, || self.writer.closed())); + .maybe_init(|| self.writer.closed()) + .poll_unpin(cx)); self.writer_state = StreamState::Closed; } StreamState::Closing => { // Assume closed on error - let _ = ready!(self.writer_closed_promise.poll(cx)); + let _ = ready!(self.writer_closed_promise.poll_unpin(cx)); self.writer_state = StreamState::Closed; } StreamState::Closed => {} @@ -113,7 +115,8 @@ impl Stream { fn poll_reader_read(&mut self, cx: &mut Context) -> Poll>> { let val = ready!(self .reader_read_promise - .maybe_init_and_poll(cx, || self.reader.read())) + .maybe_init(|| self.reader.read()) + .poll_unpin(cx)) .map_err(to_io_error)?; let val = parse_reader_response(&val) From 5bb685c4546da1592f5c5c3b73e9d9355bf8b175 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Wed, 14 Jun 2023 23:25:37 +0300 Subject: [PATCH 08/21] Log multaddr parse failure as a warning --- transports/webtransport-websys/src/transport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webtransport-websys/src/transport.rs b/transports/webtransport-websys/src/transport.rs index 95e0a3562e3..f814842b093 100644 --- a/transports/webtransport-websys/src/transport.rs +++ b/transports/webtransport-websys/src/transport.rs @@ -77,7 +77,7 @@ impl libp2p_core::Transport for Transport { fn dial(&mut self, addr: Multiaddr) -> Result> { let endpoint = Endpoint::from_multiaddr(&addr).map_err(|e| match e { e @ Error::InvalidMultiaddr(_) => { - log::error!("{}", e); + log::warn!("{}", e); TransportError::MultiaddrNotSupported(addr) } e => TransportError::Other(e), From 75ff1af0b88439ac72709e8820c88f7f1728f24c Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Sat, 17 Jun 2023 07:35:15 +0300 Subject: [PATCH 09/21] Fix typo --- transports/webtransport-websys/src/fused_js_promise.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webtransport-websys/src/fused_js_promise.rs b/transports/webtransport-websys/src/fused_js_promise.rs index 42b4122022a..0ba846501c2 100644 --- a/transports/webtransport-websys/src/fused_js_promise.rs +++ b/transports/webtransport-websys/src/fused_js_promise.rs @@ -47,7 +47,7 @@ impl Future for FusedJsPromise { let val = ready!(self .promise .as_mut() - .expect("CachedJsPromise not initialized") + .expect("FusedJsPromise not initialized") .poll_unpin(cx)); // Future finished, drop it From 2ccfd67099742969d89996e62f3339e035c00663 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 00:17:25 +0300 Subject: [PATCH 10/21] Move webtransport tests under wasm-tests --- Cargo.lock | 20 +- Cargo.toml | 3 +- transports/webtransport-websys/Cargo.toml | 3 - .../webtransport-websys/src/connection.rs | 408 ------------------ .../webtransport-websys/src/endpoint.rs | 9 +- transports/webtransport-websys/src/lib.rs | 3 - transports/webtransport-websys/src/utils.rs | 32 -- wasm-tests/run-all.sh | 4 + wasm-tests/webtransport-tests/Cargo.toml | 18 + .../webtransport-tests}/README.md | 4 +- .../echo-server/.gitignore | 0 .../echo-server/Dockerfile | 0 .../webtransport-tests}/echo-server/go.mod | 0 .../webtransport-tests}/echo-server/go.sum | 0 .../webtransport-tests}/echo-server/main.go | 0 wasm-tests/webtransport-tests/run.sh | 28 ++ wasm-tests/webtransport-tests/src/lib.rs | 407 +++++++++++++++++ 17 files changed, 483 insertions(+), 456 deletions(-) create mode 100755 wasm-tests/run-all.sh create mode 100644 wasm-tests/webtransport-tests/Cargo.toml rename {transports/webtransport-websys => wasm-tests/webtransport-tests}/README.md (82%) rename {transports/webtransport-websys => wasm-tests/webtransport-tests}/echo-server/.gitignore (100%) rename {transports/webtransport-websys => wasm-tests/webtransport-tests}/echo-server/Dockerfile (100%) rename {transports/webtransport-websys => wasm-tests/webtransport-tests}/echo-server/go.mod (100%) rename {transports/webtransport-websys => wasm-tests/webtransport-tests}/echo-server/go.sum (100%) rename {transports/webtransport-websys => wasm-tests/webtransport-tests}/echo-server/main.go (100%) create mode 100755 wasm-tests/webtransport-tests/run.sh create mode 100644 wasm-tests/webtransport-tests/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 45a5aaee6bb..d2b973a446e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3279,7 +3279,6 @@ name = "libp2p-webtransport-websys" version = "0.1.0" dependencies = [ "futures", - "getrandom 0.2.10", "js-sys", "libp2p-core", "libp2p-identity", @@ -3292,7 +3291,6 @@ dependencies = [ "thiserror", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-bindgen-test", "web-sys", ] @@ -6361,6 +6359,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "webtransport-tests" +version = "0.1.0" +dependencies = [ + "futures", + "getrandom 0.2.10", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "libp2p-webtransport-websys", + "multiaddr", + "multihash", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + [[package]] name = "widestring" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index e2a3962f782..18047cd031f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] members = [ "core", - "examples/chat-example", "examples/autonat", + "examples/chat-example", "examples/dcutr", "examples/distributed-key-value-store", "examples/file-sharing", @@ -54,6 +54,7 @@ members = [ "transports/webrtc", "transports/websocket", "transports/webtransport-websys", + "wasm-tests/webtransport-tests", ] resolver = "2" diff --git a/transports/webtransport-websys/Cargo.toml b/transports/webtransport-websys/Cargo.toml index 11fa8a89d3b..1e1ee97ddfa 100644 --- a/transports/webtransport-websys/Cargo.toml +++ b/transports/webtransport-websys/Cargo.toml @@ -38,10 +38,7 @@ web-sys = { version = "0.3.63", features = [ ] } [dev-dependencies] -getrandom = { version = "0.2.9", features = ["js"] } multibase = "0.9.1" -wasm-bindgen-test = "0.3.36" -web-sys = { version = "0.3.63", features = ["Response", "Window"] } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling diff --git a/transports/webtransport-websys/src/connection.rs b/transports/webtransport-websys/src/connection.rs index 9f6c8be88ad..54e5f32c591 100644 --- a/transports/webtransport-websys/src/connection.rs +++ b/transports/webtransport-websys/src/connection.rs @@ -240,411 +240,3 @@ impl StreamMuxer for ConnectionSend { self.get_mut().inner.poll_unpin(cx) } } - -#[cfg(test)] -mod tests { - use super::*; - use futures::channel::oneshot; - use futures::{AsyncReadExt, AsyncWriteExt}; - use getrandom::getrandom; - use libp2p_core::Transport as _; - use libp2p_identity::Keypair; - use multiaddr::{Multiaddr, Protocol}; - use std::future::poll_fn; - use wasm_bindgen_futures::{spawn_local, JsFuture}; - use wasm_bindgen_test::wasm_bindgen_test; - use web_sys::{window, Response}; - - use crate::utils::to_js_type; - use crate::{Config, Stream, Transport}; - - #[wasm_bindgen_test] - async fn single_conn_single_stream() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); - let mut stream = create_stream(&mut conn).await; - - send_recv(&mut stream).await; - } - - #[wasm_bindgen_test] - async fn single_conn_single_stream_incoming() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); - let mut stream = incoming_stream(&mut conn).await; - - send_recv(&mut stream).await; - } - - #[wasm_bindgen_test] - async fn single_conn_multiple_streams() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - let mut tasks = Vec::new(); - - let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); - let mut streams = Vec::new(); - - for i in 0..30 { - let stream = if i % 2 == 0 { - create_stream(&mut conn).await - } else { - incoming_stream(&mut conn).await - }; - - streams.push(stream); - } - - for stream in streams { - tasks.push(send_recv_task(stream)); - } - - futures::future::try_join_all(tasks).await.unwrap(); - } - - #[wasm_bindgen_test] - async fn multiple_conn_multiple_streams() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - let mut tasks = Vec::new(); - let mut conns = Vec::new(); - - for _ in 0..10 { - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - let mut streams = Vec::new(); - - for i in 0..10 { - let stream = if i % 2 == 0 { - create_stream(&mut conn).await - } else { - incoming_stream(&mut conn).await - }; - - streams.push(stream); - } - - // If `conn` gets drop then its streams will close. - // Keep it alive by moving it to the outer scope. - conns.push(conn); - - for stream in streams { - tasks.push(send_recv_task(stream)); - } - } - - futures::future::try_join_all(tasks).await.unwrap(); - } - - #[wasm_bindgen_test] - async fn multiple_conn_multiple_streams_sequential() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - for _ in 0..10 { - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - - for i in 0..10 { - let mut stream = if i % 2 == 0 { - create_stream(&mut conn).await - } else { - incoming_stream(&mut conn).await - }; - - send_recv(&mut stream).await; - } - } - } - - #[wasm_bindgen_test] - async fn allow_read_after_closing_writer() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - - let mut stream = create_stream(&mut conn).await; - - // Test that stream works - send_recv(&mut stream).await; - - // Write random data - let mut send_buf = [0u8; 1024]; - getrandom(&mut send_buf).unwrap(); - stream.write_all(&send_buf).await.unwrap(); - - // Close writer by calling AsyncWrite::poll_close - stream.close().await.unwrap(); - - // Make sure writer is closed - stream.write_all(b"1").await.unwrap_err(); - - // We should be able to read - let mut recv_buf = [0u8; 1024]; - stream.read_exact(&mut recv_buf).await.unwrap(); - - assert_eq!(send_buf, recv_buf); - } - - #[wasm_bindgen_test] - async fn poll_outbound_error_after_connection_close() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - - // Make sure that poll_outbound works well before closing the connection - let mut stream = create_stream(&mut conn).await; - send_recv(&mut stream).await; - drop(stream); - - poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) - .await - .unwrap(); - - poll_fn(|cx| Pin::new(&mut conn).poll_outbound(cx)) - .await - .expect_err("poll_outbound error after conn closed"); - } - - #[wasm_bindgen_test] - async fn poll_inbound_error_after_connection_close() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - - // Make sure that poll_inbound works well before closing the connection - let mut stream = incoming_stream(&mut conn).await; - send_recv(&mut stream).await; - drop(stream); - - poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) - .await - .unwrap(); - - poll_fn(|cx| Pin::new(&mut conn).poll_inbound(cx)) - .await - .expect_err("poll_inbound error after conn closed"); - } - - #[wasm_bindgen_test] - async fn read_error_after_connection_drop() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - let mut stream = create_stream(&mut conn).await; - - send_recv(&mut stream).await; - - drop(conn); - - let mut buf = [0u8; 16]; - stream - .read(&mut buf) - .await - .expect_err("read error after conn drop"); - } - - #[wasm_bindgen_test] - async fn read_error_after_connection_close() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - let mut stream = create_stream(&mut conn).await; - - send_recv(&mut stream).await; - - poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) - .await - .unwrap(); - - let mut buf = [0u8; 16]; - stream - .read(&mut buf) - .await - .expect_err("read error after conn drop"); - } - - #[wasm_bindgen_test] - async fn write_error_after_connection_drop() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - let mut stream = create_stream(&mut conn).await; - - send_recv(&mut stream).await; - - drop(conn); - - let buf = [0u8; 16]; - stream - .write(&buf) - .await - .expect_err("write error after conn drop"); - } - - #[wasm_bindgen_test] - async fn write_error_after_connection_close() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - let mut stream = create_stream(&mut conn).await; - - send_recv(&mut stream).await; - - poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) - .await - .unwrap(); - - let buf = [0u8; 16]; - stream - .write(&buf) - .await - .expect_err("write error after conn drop"); - } - - #[wasm_bindgen_test] - async fn connect_without_peer_id() { - let mut addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - // Remove peer id - addr.pop(); - - let mut transport = Transport::new(Config::new(&keypair)); - transport.dial(addr).unwrap().await.unwrap(); - } - - #[wasm_bindgen_test] - async fn error_on_unknown_peer_id() { - let mut addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - // Remove peer id - addr.pop(); - - // Add an unknown one - addr.push(Protocol::P2p(PeerId::random())); - - let mut transport = Transport::new(Config::new(&keypair)); - let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); - assert!(matches!(e, Error::UnknownRemotePeerId)); - } - - #[wasm_bindgen_test] - async fn error_on_unknown_certhash() { - let mut addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - // Remove peer id - let peer_id = addr.pop().unwrap(); - - // Add unknown certhash - addr.push(Protocol::Certhash(Multihash::wrap(1, b"1").unwrap())); - - // Add peer id back - addr.push(peer_id); - - let mut transport = Transport::new(Config::new(&keypair)); - let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); - assert!(matches!( - e, - Error::Noise(libp2p_noise::Error::UnknownWebTransportCerthashes(..)) - )); - } - - /// Helper that returns the multiaddress of echo-server - /// - /// It fetches the multiaddress via HTTP request to - /// 127.0.0.1:4455. - async fn fetch_server_addr() -> Multiaddr { - let url = "http://127.0.0.1:4455/"; - let window = window().expect("failed to get browser window"); - - let value = JsFuture::from(window.fetch_with_str(url)) - .await - .expect("fetch failed"); - let resp = to_js_type::(value).expect("cast failed"); - - let text = resp.text().expect("text failed"); - let text = JsFuture::from(text).await.expect("text promise failed"); - - text.as_string() - .filter(|s| !s.is_empty()) - .expect("response not a text") - .parse() - .unwrap() - } - - async fn create_stream(conn: &mut Connection) -> Stream { - poll_fn(|cx| Pin::new(&mut *conn).poll_outbound(cx)) - .await - .unwrap() - } - - async fn incoming_stream(conn: &mut Connection) -> Stream { - let mut stream = poll_fn(|cx| Pin::new(&mut *conn).poll_inbound(cx)) - .await - .unwrap(); - - // For the stream to be initiated `echo-server` sends a single byte - let mut buf = [0u8; 1]; - stream.read_exact(&mut buf).await.unwrap(); - - stream - } - - fn send_recv_task(mut steam: Stream) -> oneshot::Receiver<()> { - let (tx, rx) = oneshot::channel(); - - spawn_local(async move { - send_recv(&mut steam).await; - tx.send(()).unwrap(); - }); - - rx - } - - async fn send_recv(stream: &mut Stream) { - let mut send_buf = [0u8; 1024]; - let mut recv_buf = [0u8; 1024]; - - for _ in 0..30 { - getrandom(&mut send_buf).unwrap(); - - stream.write_all(&send_buf).await.unwrap(); - stream.read_exact(&mut recv_buf).await.unwrap(); - - assert_eq!(send_buf, recv_buf); - } - } -} diff --git a/transports/webtransport-websys/src/endpoint.rs b/transports/webtransport-websys/src/endpoint.rs index 1766a9950f2..0bff1ed6186 100644 --- a/transports/webtransport-websys/src/endpoint.rs +++ b/transports/webtransport-websys/src/endpoint.rs @@ -151,14 +151,13 @@ impl Endpoint { mod tests { use super::*; use std::str::FromStr; - use wasm_bindgen_test::wasm_bindgen_test; fn multihash_from_str(s: &str) -> Multihash<64> { let (_base, bytes) = multibase::decode(s).unwrap(); Multihash::from_bytes(&bytes).unwrap() } - #[wasm_bindgen_test] + #[test] fn valid_webtransport_multiaddr() { let addr = Multiaddr::from_str("/ip4/127.0.0.1/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); @@ -186,7 +185,7 @@ mod tests { ); } - #[wasm_bindgen_test] + #[test] fn valid_webtransport_multiaddr_without_certhashes() { let addr = Multiaddr::from_str("/ip4/127.0.0.1/udp/44874/quic-v1/webtransport/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); @@ -200,7 +199,7 @@ mod tests { ); } - #[wasm_bindgen_test] + #[test] fn ipv6_webtransport() { let addr = Multiaddr::from_str("/ip6/::1/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); @@ -213,7 +212,7 @@ mod tests { ); } - #[wasm_bindgen_test] + #[test] fn dns_webtransport() { let addr = Multiaddr::from_str("/dns/libp2p.io/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); diff --git a/transports/webtransport-websys/src/lib.rs b/transports/webtransport-websys/src/lib.rs index 1e8f08fdead..f9c59694fa3 100644 --- a/transports/webtransport-websys/src/lib.rs +++ b/transports/webtransport-websys/src/lib.rs @@ -13,6 +13,3 @@ pub use self::connection::Connection; pub use self::error::Error; pub use self::stream::Stream; pub use self::transport::{Config, Transport}; - -#[cfg(test)] -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); diff --git a/transports/webtransport-websys/src/utils.rs b/transports/webtransport-websys/src/utils.rs index 2c0bdcc092c..fcde226be87 100644 --- a/transports/webtransport-websys/src/utils.rs +++ b/transports/webtransport-websys/src/utils.rs @@ -74,35 +74,3 @@ pub(crate) fn parse_reader_response(resp: &JsValue) -> Result, J pub(crate) fn to_io_error(value: JsValue) -> io::Error { io::Error::new(io::ErrorKind::Other, Error::from_js_value(value)) } - -#[cfg(test)] -mod tests { - use super::*; - use js_sys::{Promise, TypeError, Uint8Array}; - use wasm_bindgen_test::wasm_bindgen_test; - - #[wasm_bindgen_test] - fn check_js_typecasting() { - // Successful typecast. - let value = JsValue::from(Uint8Array::new_with_length(0)); - assert!(to_js_type::(value).is_ok()); - - // Type can not be typecasted. - let value = JsValue::from(Uint8Array::new_with_length(0)); - assert!(matches!( - to_js_type::(value), - Err(Error::JsCastFailed) - )); - - // Request typecasting, however the underlying value is an error. - let value = JsValue::from(TypeError::new("abc")); - assert!(matches!( - to_js_type::(value), - Err(Error::JsError(_)) - )); - - // Explicitly request js_sys::Error typecasting. - let value = JsValue::from(TypeError::new("abc")); - assert!(to_js_type::(value).is_ok()); - } -} diff --git a/wasm-tests/run-all.sh b/wasm-tests/run-all.sh new file mode 100755 index 00000000000..532fc9dc4d5 --- /dev/null +++ b/wasm-tests/run-all.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +./webtransport-tests/run.sh diff --git a/wasm-tests/webtransport-tests/Cargo.toml b/wasm-tests/webtransport-tests/Cargo.toml new file mode 100644 index 00000000000..d06191b2a60 --- /dev/null +++ b/wasm-tests/webtransport-tests/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "webtransport-tests" +version = "0.1.0" +edition = "2021" + +[dependencies] +futures = "0.3.28" +getrandom = { version = "0.2.9", features = ["js"] } +libp2p-core = { workspace = true } +libp2p-identity = { workspace = true } +libp2p-noise = { workspace = true } +libp2p-webtransport-websys = { workspace = true } +multiaddr = { workspace = true } +multihash = { workspace = true } +wasm-bindgen = "0.2.86" +wasm-bindgen-futures = "0.4.36" +wasm-bindgen-test = "0.3.36" +web-sys = { version = "0.3.63", features = ["Response", "Window"] } diff --git a/transports/webtransport-websys/README.md b/wasm-tests/webtransport-tests/README.md similarity index 82% rename from transports/webtransport-websys/README.md rename to wasm-tests/webtransport-tests/README.md index fcf07c26978..bc3c84326e8 100644 --- a/transports/webtransport-websys/README.md +++ b/wasm-tests/webtransport-tests/README.md @@ -3,8 +3,8 @@ First you need to build and start the echo-server: ``` -docker build -t echo-server echo-server -docker run -it --rm --network=host echo-server +docker build -t webtransport-echo-server echo-server +docker run -it --rm --network=host webtransport-echo-server ``` On another terminal run: diff --git a/transports/webtransport-websys/echo-server/.gitignore b/wasm-tests/webtransport-tests/echo-server/.gitignore similarity index 100% rename from transports/webtransport-websys/echo-server/.gitignore rename to wasm-tests/webtransport-tests/echo-server/.gitignore diff --git a/transports/webtransport-websys/echo-server/Dockerfile b/wasm-tests/webtransport-tests/echo-server/Dockerfile similarity index 100% rename from transports/webtransport-websys/echo-server/Dockerfile rename to wasm-tests/webtransport-tests/echo-server/Dockerfile diff --git a/transports/webtransport-websys/echo-server/go.mod b/wasm-tests/webtransport-tests/echo-server/go.mod similarity index 100% rename from transports/webtransport-websys/echo-server/go.mod rename to wasm-tests/webtransport-tests/echo-server/go.mod diff --git a/transports/webtransport-websys/echo-server/go.sum b/wasm-tests/webtransport-tests/echo-server/go.sum similarity index 100% rename from transports/webtransport-websys/echo-server/go.sum rename to wasm-tests/webtransport-tests/echo-server/go.sum diff --git a/transports/webtransport-websys/echo-server/main.go b/wasm-tests/webtransport-tests/echo-server/main.go similarity index 100% rename from transports/webtransport-websys/echo-server/main.go rename to wasm-tests/webtransport-tests/echo-server/main.go diff --git a/wasm-tests/webtransport-tests/run.sh b/wasm-tests/webtransport-tests/run.sh new file mode 100755 index 00000000000..8e189f06ac0 --- /dev/null +++ b/wasm-tests/webtransport-tests/run.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Prefer podman over docker since it doesn't require root privileges +if command -v podman > /dev/null; then + docker=podman +else + docker=docker +fi + +# cd to this script directory +cd "$(dirname "${BASH_SOURCE[0]}")" + +# Print the directory for debugging +echo "Tests: $PWD" + +# Build and run echo-server +$docker build -t webtransport-echo-server echo-server || exit 1 +id="$($docker run -d --network=host webtransport-echo-server)" || exit 1 + +# Run tests +wasm-pack test --chrome --headless +EXIT_CODE=$? + +# Remove echo-server container +$docker rm -f "$id" + +# Propagate wasm-pack's exit code +exit $EXIT_CODE diff --git a/wasm-tests/webtransport-tests/src/lib.rs b/wasm-tests/webtransport-tests/src/lib.rs new file mode 100644 index 00000000000..b1207a82161 --- /dev/null +++ b/wasm-tests/webtransport-tests/src/lib.rs @@ -0,0 +1,407 @@ +use futures::channel::oneshot; +use futures::{AsyncReadExt, AsyncWriteExt}; +use getrandom::getrandom; +use libp2p_core::{StreamMuxer, Transport as _}; +use libp2p_identity::{Keypair, PeerId}; +use libp2p_noise as noise; +use libp2p_webtransport_websys::{Config, Connection, Error, Stream, Transport}; +use multiaddr::{Multiaddr, Protocol}; +use multihash::Multihash; +use std::future::poll_fn; +use std::pin::Pin; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::{spawn_local, JsFuture}; +use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; +use web_sys::{window, Response}; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +async fn single_conn_single_stream() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; +} + +#[wasm_bindgen_test] +async fn single_conn_single_stream_incoming() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); + let mut stream = incoming_stream(&mut conn).await; + + send_recv(&mut stream).await; +} + +#[wasm_bindgen_test] +async fn single_conn_multiple_streams() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let mut tasks = Vec::new(); + + let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); + let mut streams = Vec::new(); + + for i in 0..30 { + let stream = if i % 2 == 0 { + create_stream(&mut conn).await + } else { + incoming_stream(&mut conn).await + }; + + streams.push(stream); + } + + for stream in streams { + tasks.push(send_recv_task(stream)); + } + + futures::future::try_join_all(tasks).await.unwrap(); +} + +#[wasm_bindgen_test] +async fn multiple_conn_multiple_streams() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let mut tasks = Vec::new(); + let mut conns = Vec::new(); + + for _ in 0..10 { + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut streams = Vec::new(); + + for i in 0..10 { + let stream = if i % 2 == 0 { + create_stream(&mut conn).await + } else { + incoming_stream(&mut conn).await + }; + + streams.push(stream); + } + + // If `conn` gets drop then its streams will close. + // Keep it alive by moving it to the outer scope. + conns.push(conn); + + for stream in streams { + tasks.push(send_recv_task(stream)); + } + } + + futures::future::try_join_all(tasks).await.unwrap(); +} + +#[wasm_bindgen_test] +async fn multiple_conn_multiple_streams_sequential() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + for _ in 0..10 { + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + + for i in 0..10 { + let mut stream = if i % 2 == 0 { + create_stream(&mut conn).await + } else { + incoming_stream(&mut conn).await + }; + + send_recv(&mut stream).await; + } + } +} + +#[wasm_bindgen_test] +async fn allow_read_after_closing_writer() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + + let mut stream = create_stream(&mut conn).await; + + // Test that stream works + send_recv(&mut stream).await; + + // Write random data + let mut send_buf = [0u8; 1024]; + getrandom(&mut send_buf).unwrap(); + stream.write_all(&send_buf).await.unwrap(); + + // Close writer by calling AsyncWrite::poll_close + stream.close().await.unwrap(); + + // Make sure writer is closed + stream.write_all(b"1").await.unwrap_err(); + + // We should be able to read + let mut recv_buf = [0u8; 1024]; + stream.read_exact(&mut recv_buf).await.unwrap(); + + assert_eq!(send_buf, recv_buf); +} + +#[wasm_bindgen_test] +async fn poll_outbound_error_after_connection_close() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + + // Make sure that poll_outbound works well before closing the connection + let mut stream = create_stream(&mut conn).await; + send_recv(&mut stream).await; + drop(stream); + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + poll_fn(|cx| Pin::new(&mut conn).poll_outbound(cx)) + .await + .expect_err("poll_outbound error after conn closed"); +} + +#[wasm_bindgen_test] +async fn poll_inbound_error_after_connection_close() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + + // Make sure that poll_inbound works well before closing the connection + let mut stream = incoming_stream(&mut conn).await; + send_recv(&mut stream).await; + drop(stream); + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + poll_fn(|cx| Pin::new(&mut conn).poll_inbound(cx)) + .await + .expect_err("poll_inbound error after conn closed"); +} + +#[wasm_bindgen_test] +async fn read_error_after_connection_drop() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + + drop(conn); + + let mut buf = [0u8; 16]; + stream + .read(&mut buf) + .await + .expect_err("read error after conn drop"); +} + +#[wasm_bindgen_test] +async fn read_error_after_connection_close() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + let mut buf = [0u8; 16]; + stream + .read(&mut buf) + .await + .expect_err("read error after conn drop"); +} + +#[wasm_bindgen_test] +async fn write_error_after_connection_drop() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + + drop(conn); + + let buf = [0u8; 16]; + stream + .write(&buf) + .await + .expect_err("write error after conn drop"); +} + +#[wasm_bindgen_test] +async fn write_error_after_connection_close() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + let buf = [0u8; 16]; + stream + .write(&buf) + .await + .expect_err("write error after conn drop"); +} + +#[wasm_bindgen_test] +async fn connect_without_peer_id() { + let mut addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + // Remove peer id + addr.pop(); + + let mut transport = Transport::new(Config::new(&keypair)); + transport.dial(addr).unwrap().await.unwrap(); +} + +#[wasm_bindgen_test] +async fn error_on_unknown_peer_id() { + let mut addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + // Remove peer id + addr.pop(); + + // Add an unknown one + addr.push(Protocol::P2p(PeerId::random())); + + let mut transport = Transport::new(Config::new(&keypair)); + let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); + assert!(matches!(e, Error::UnknownRemotePeerId)); +} + +#[wasm_bindgen_test] +async fn error_on_unknown_certhash() { + let mut addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + // Remove peer id + let peer_id = addr.pop().unwrap(); + + // Add unknown certhash + addr.push(Protocol::Certhash(Multihash::wrap(1, b"1").unwrap())); + + // Add peer id back + addr.push(peer_id); + + let mut transport = Transport::new(Config::new(&keypair)); + let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); + assert!(matches!( + e, + Error::Noise(noise::Error::UnknownWebTransportCerthashes(..)) + )); +} + +/// Helper that returns the multiaddress of echo-server +/// +/// It fetches the multiaddress via HTTP request to +/// 127.0.0.1:4455. +async fn fetch_server_addr() -> Multiaddr { + let url = "http://127.0.0.1:4455/"; + let window = window().expect("failed to get browser window"); + + let value = JsFuture::from(window.fetch_with_str(url)) + .await + .expect("fetch failed"); + let resp = value.dyn_into::().expect("cast failed"); + + let text = resp.text().expect("text failed"); + let text = JsFuture::from(text).await.expect("text promise failed"); + + text.as_string() + .filter(|s| !s.is_empty()) + .expect("response not a text") + .parse() + .unwrap() +} + +async fn create_stream(conn: &mut Connection) -> Stream { + poll_fn(|cx| Pin::new(&mut *conn).poll_outbound(cx)) + .await + .unwrap() +} + +async fn incoming_stream(conn: &mut Connection) -> Stream { + let mut stream = poll_fn(|cx| Pin::new(&mut *conn).poll_inbound(cx)) + .await + .unwrap(); + + // For the stream to be initiated `echo-server` sends a single byte + let mut buf = [0u8; 1]; + stream.read_exact(&mut buf).await.unwrap(); + + stream +} + +fn send_recv_task(mut steam: Stream) -> oneshot::Receiver<()> { + let (tx, rx) = oneshot::channel(); + + spawn_local(async move { + send_recv(&mut steam).await; + tx.send(()).unwrap(); + }); + + rx +} + +async fn send_recv(stream: &mut Stream) { + let mut send_buf = [0u8; 1024]; + let mut recv_buf = [0u8; 1024]; + + for _ in 0..30 { + getrandom(&mut send_buf).unwrap(); + + stream.write_all(&send_buf).await.unwrap(); + stream.read_exact(&mut recv_buf).await.unwrap(); + + assert_eq!(send_buf, recv_buf); + } +} From 51335e47b73aa998583c1f1183dbfc9dfb809737 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 01:21:09 +0300 Subject: [PATCH 11/21] Make Transport and related types Send --- .../webtransport-websys/src/connection.rs | 110 +++------ transports/webtransport-websys/src/stream.rs | 220 ++++++++---------- .../webtransport-websys/src/transport.rs | 96 +------- 3 files changed, 142 insertions(+), 284 deletions(-) diff --git a/transports/webtransport-websys/src/connection.rs b/transports/webtransport-websys/src/connection.rs index 54e5f32c591..9ea1dbefd1c 100644 --- a/transports/webtransport-websys/src/connection.rs +++ b/transports/webtransport-websys/src/connection.rs @@ -1,5 +1,5 @@ use futures::FutureExt; -use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent, StreamMuxerExt}; +use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; use libp2p_core::{OutboundUpgrade, UpgradeInfo}; use libp2p_identity::{Keypair, PeerId}; use multihash::Multihash; @@ -14,13 +14,19 @@ use web_sys::ReadableStreamDefaultReader; use crate::bindings::{WebTransport, WebTransportBidirectionalStream}; use crate::endpoint::Endpoint; use crate::fused_js_promise::FusedJsPromise; -use crate::stream::StreamSend; use crate::utils::{detach_promise, parse_reader_response, to_js_type}; use crate::{Error, Stream}; /// An opened WebTransport connection. #[derive(Debug)] pub struct Connection { + // Swarm needs all types to be Send. WASM is single-threaded + // and it is safe to use SendWrapper. + inner: SendWrapper, +} + +#[derive(Debug)] +struct ConnectionInner { session: WebTransport, create_stream_promise: FusedJsPromise, incoming_stream_promise: FusedJsPromise, @@ -28,15 +34,6 @@ pub struct Connection { closed: bool, } -/// Connection wrapped in [`SendWrapper`]. -/// -/// This is needed by Swarm. WASM is single-threaded and it is safe -/// to use [`SendWrapper`]. -#[derive(Debug)] -pub(crate) struct ConnectionSend { - inner: SendWrapper, -} - impl Connection { pub(crate) fn new(endpoint: &Endpoint) -> Result { let url = endpoint.url(); @@ -55,14 +52,28 @@ impl Connection { to_js_type::(incoming_streams.get_reader())?; Ok(Connection { - session, - create_stream_promise: FusedJsPromise::new(), - incoming_stream_promise: FusedJsPromise::new(), - incoming_streams_reader, - closed: false, + inner: SendWrapper::new(ConnectionInner { + session, + create_stream_promise: FusedJsPromise::new(), + incoming_stream_promise: FusedJsPromise::new(), + incoming_streams_reader, + closed: false, + }), }) } + pub(crate) async fn authenticate( + &mut self, + keypair: &Keypair, + remote_peer: Option, + certhashes: HashSet>, + ) -> Result { + let fut = SendWrapper::new(self.inner.authenticate(keypair, remote_peer, certhashes)); + fut.await + } +} + +impl ConnectionInner { /// Authenticates with the server /// /// This methods runs the security handshake as descripted @@ -70,7 +81,7 @@ impl Connection { /// of the server. /// /// [1]: https://github.com/libp2p/specs/tree/master/webtransport#security-handshake - pub(crate) async fn authenticate( + async fn authenticate( &mut self, keypair: &Keypair, remote_peer: Option, @@ -80,7 +91,7 @@ impl Connection { .await .map_err(Error::from_js_value)?; - let stream = StreamSend::new(self.create_stream().await?); + let stream = poll_fn(|cx| self.poll_create_bidirectional_stream(cx)).await?; let mut noise = libp2p_noise::Config::new(keypair)?; if !certhashes.is_empty() { @@ -102,11 +113,6 @@ impl Connection { Ok(peer_id) } - /// Creates new outbound stream. - async fn create_stream(&mut self) -> Result { - poll_fn(|cx| self.poll_outbound_unpin(cx)).await - } - /// Initiates and polls a promise from `create_bidirectional_stream`. fn poll_create_bidirectional_stream( &mut self, @@ -160,6 +166,12 @@ impl Connection { } } +impl Drop for ConnectionInner { + fn drop(&mut self) { + self.close_session(); + } +} + /// WebTransport native multiplexing impl StreamMuxer for Connection { type Substream = Stream; @@ -169,21 +181,21 @@ impl StreamMuxer for Connection { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - self.poll_incoming_bidirectional_streams(cx) + self.inner.poll_incoming_bidirectional_streams(cx) } fn poll_outbound( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - self.poll_create_bidirectional_stream(cx) + self.inner.poll_create_bidirectional_stream(cx) } fn poll_close( mut self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { - self.close_session(); + self.inner.close_session(); Poll::Ready(Ok(())) } @@ -194,49 +206,3 @@ impl StreamMuxer for Connection { Poll::Pending } } - -impl Drop for Connection { - fn drop(&mut self) { - self.close_session(); - } -} - -impl ConnectionSend { - pub(crate) fn new(conn: Connection) -> Self { - ConnectionSend { - inner: SendWrapper::new(conn), - } - } -} - -impl StreamMuxer for ConnectionSend { - type Substream = StreamSend; - type Error = Error; - - fn poll_inbound( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - let stream = ready!(self.get_mut().inner.poll_inbound_unpin(cx))?; - Poll::Ready(Ok(StreamSend::new(stream))) - } - - fn poll_outbound( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - let stream = ready!(self.get_mut().inner.poll_outbound_unpin(cx))?; - Poll::Ready(Ok(StreamSend::new(stream))) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.get_mut().inner.poll_close_unpin(cx) - } - - fn poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - self.get_mut().inner.poll_unpin(cx) - } -} diff --git a/transports/webtransport-websys/src/stream.rs b/transports/webtransport-websys/src/stream.rs index 8e0bd1018f1..ba4238ac814 100644 --- a/transports/webtransport-websys/src/stream.rs +++ b/transports/webtransport-websys/src/stream.rs @@ -15,6 +15,13 @@ use crate::Error; /// A stream on a connection. #[derive(Debug)] pub struct Stream { + // Swarm needs all types to be Send. WASM is single-threaded + // and it is safe to use SendWrapper. + inner: SendWrapper, +} + +#[derive(Debug)] +struct StreamInner { reader: ReadableStreamDefaultReader, reader_read_promise: FusedJsPromise, read_leftovers: Option, @@ -24,15 +31,6 @@ pub struct Stream { writer_closed_promise: FusedJsPromise, } -/// Stream wrapped in [`SendWrapper`]. -/// -/// This is needed by Swarm. WASM is single-threaded and it is safe -/// to use [`SendWrapper`]. -#[derive(Debug)] -pub(crate) struct StreamSend { - inner: SendWrapper, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum StreamState { Open, @@ -49,15 +47,63 @@ impl Stream { let writer = send_stream.get_writer().map_err(Error::from_js_value)?; Ok(Stream { - reader, - reader_read_promise: FusedJsPromise::new(), - read_leftovers: None, - writer, - writer_state: StreamState::Open, - writer_ready_promise: FusedJsPromise::new(), - writer_closed_promise: FusedJsPromise::new(), + inner: SendWrapper::new(StreamInner { + reader, + reader_read_promise: FusedJsPromise::new(), + read_leftovers: None, + writer, + writer_state: StreamState::Open, + writer_ready_promise: FusedJsPromise::new(), + writer_closed_promise: FusedJsPromise::new(), + }), }) } +} + +impl StreamInner { + fn poll_reader_read(&mut self, cx: &mut Context) -> Poll>> { + let val = ready!(self + .reader_read_promise + .maybe_init(|| self.reader.read()) + .poll_unpin(cx)) + .map_err(to_io_error)?; + + let val = parse_reader_response(&val) + .map_err(to_io_error)? + .map(Uint8Array::from); + + Poll::Ready(Ok(val)) + } + + fn poll_read(&mut self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { + // If we have leftovers from a previous read, then use them. + // Otherwise read new data. + let data = match self.read_leftovers.take() { + Some(data) => data, + None => { + match ready!(self.poll_reader_read(cx))? { + Some(data) => data, + // EOF + None => return Poll::Ready(Ok(0)), + } + } + }; + + if data.byte_length() == 0 { + return Poll::Ready(Ok(0)); + } + + let out_len = data.byte_length().min(buf.len() as u32); + data.slice(0, out_len).copy_to(&mut buf[..out_len as usize]); + + let leftovers = data.slice(out_len, data.byte_length()); + + if leftovers.byte_length() > 0 { + self.read_leftovers = Some(leftovers); + } + + Poll::Ready(Ok(out_len as usize)) + } fn poll_writer_ready(&mut self, cx: &mut Context) -> Poll> { if self.writer_state != StreamState::Open { @@ -85,6 +131,32 @@ impl Stream { Poll::Ready(Ok(())) } + fn poll_write(&mut self, cx: &mut Context, buf: &[u8]) -> Poll> { + ready!(self.poll_writer_ready(cx))?; + + let len = buf.len() as u32; + let data = Uint8Array::new_with_length(len); + data.copy_from(buf); + + detach_promise(self.writer.write_with_chunk(&data)); + + Poll::Ready(Ok(len as usize)) + } + + fn poll_flush(&mut self, cx: &mut Context) -> Poll> { + if self.writer_state == StreamState::Open { + // Writer has queue size of 1, so as soon it is ready, self means the + // messages were flushed. + self.poll_writer_ready(cx) + } else { + debug_assert!( + false, + "libp2p_webtransport_websys::Stream: poll_flush called after poll_close" + ); + Poll::Ready(Ok(())) + } + } + fn poll_writer_close(&mut self, cx: &mut Context) -> Poll> { match self.writer_state { StreamState::Open => { @@ -111,23 +183,9 @@ impl Stream { Poll::Ready(Ok(())) } - - fn poll_reader_read(&mut self, cx: &mut Context) -> Poll>> { - let val = ready!(self - .reader_read_promise - .maybe_init(|| self.reader.read()) - .poll_unpin(cx)) - .map_err(to_io_error)?; - - let val = parse_reader_response(&val) - .map_err(to_io_error)? - .map(Uint8Array::from); - - Poll::Ready(Ok(val)) - } } -impl Drop for Stream { +impl Drop for StreamInner { fn drop(&mut self) { // Close writer. // @@ -143,106 +201,28 @@ impl Drop for Stream { impl AsyncRead for Stream { fn poll_read( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - let this = self.get_mut(); - - // If we have leftovers from a previous read, then use them. - // Otherwise read new data. - let data = match this.read_leftovers.take() { - Some(data) => data, - None => { - match ready!(this.poll_reader_read(cx))? { - Some(data) => data, - // EOF - None => return Poll::Ready(Ok(0)), - } - } - }; - - if data.byte_length() == 0 { - return Poll::Ready(Ok(0)); - } - - let out_len = data.byte_length().min(buf.len() as u32); - data.slice(0, out_len).copy_to(&mut buf[..out_len as usize]); - - let leftovers = data.slice(out_len, data.byte_length()); - - if leftovers.byte_length() > 0 { - this.read_leftovers = Some(leftovers); - } - - Poll::Ready(Ok(out_len as usize)) + self.inner.poll_read(cx, buf) } } impl AsyncWrite for Stream { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { - let this = self.get_mut(); - - ready!(this.poll_writer_ready(cx))?; - - let len = buf.len() as u32; - let data = Uint8Array::new_with_length(len); - data.copy_from(buf); - - detach_promise(this.writer.write_with_chunk(&data)); - - Poll::Ready(Ok(len as usize)) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let this = self.get_mut(); - - if this.writer_state == StreamState::Open { - // Writer has queue size of 1, so as soon it is ready, this means the - // messages were flushed. - this.poll_writer_ready(cx) - } else { - debug_assert!( - false, - "libp2p_webtransport_websys::Stream: poll_flush called after poll_close" - ); - Poll::Ready(Ok(())) - } - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - self.get_mut().poll_writer_close(cx) - } -} - -impl StreamSend { - pub(crate) fn new(stream: Stream) -> Self { - StreamSend { - inner: SendWrapper::new(stream), - } - } -} - -impl AsyncRead for StreamSend { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], ) -> Poll> { - Pin::new(&mut *self.get_mut().inner).poll_read(cx, buf) - } -} - -impl AsyncWrite for StreamSend { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { - Pin::new(&mut *self.get_mut().inner).poll_write(cx, buf) + self.inner.poll_write(cx, buf) } - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - Pin::new(&mut *self.get_mut().inner).poll_flush(cx) + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.inner.poll_flush(cx) } - fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - Pin::new(&mut *self.get_mut().inner).poll_close(cx) + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.inner.poll_writer_close(cx) } } diff --git a/transports/webtransport-websys/src/transport.rs b/transports/webtransport-websys/src/transport.rs index f814842b093..dcb3639a194 100644 --- a/transports/webtransport-websys/src/transport.rs +++ b/transports/webtransport-websys/src/transport.rs @@ -3,13 +3,10 @@ use libp2p_core::muxing::StreamMuxerBox; use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; use libp2p_identity::{Keypair, PeerId}; use multiaddr::Multiaddr; -use send_wrapper::SendWrapper; use std::future::Future; use std::pin::Pin; -use std::task::ready; use std::task::{Context, Poll}; -use crate::connection::ConnectionSend; use crate::endpoint::Endpoint; use crate::Connection; use crate::Error; @@ -24,14 +21,6 @@ pub struct Transport { config: Config, } -/// Transport wrapped in [`SendWrapper`]. -/// -/// This is needed by Swarm. WASM is single-threaded and it is safe -/// to use [`SendWrapper`]. -pub(crate) struct TransportSend { - inner: SendWrapper, -} - impl Config { /// Constructs a new configuration for the [`Transport`]. pub fn new(keypair: &Keypair) -> Self { @@ -50,8 +39,7 @@ impl Transport { /// Wraps `Transport` in [`Boxed`] and makes it ready to be consumed by /// SwarmBuilder. pub fn boxed(self) -> Boxed<(PeerId, StreamMuxerBox)> { - TransportSend::new(self) - .map(|(peer_id, muxer), _| (peer_id, StreamMuxerBox::new(muxer))) + self.map(|(peer_id, muxer), _| (peer_id, StreamMuxerBox::new(muxer))) .boxed() } } @@ -59,8 +47,8 @@ impl Transport { impl libp2p_core::Transport for Transport { type Output = (PeerId, Connection); type Error = Error; - type ListenerUpgrade = Pin>>>; - type Dial = Pin>>>; + type ListenerUpgrade = Pin> + Send>>; + type Dial = Pin> + Send>>; fn listen_on( &mut self, @@ -92,7 +80,7 @@ impl libp2p_core::Transport for Transport { .await?; Ok((peer_id, session)) } - .boxed_local()) + .boxed()) } fn dial_as_listener( @@ -113,79 +101,3 @@ impl libp2p_core::Transport for Transport { None } } - -impl TransportSend { - pub(crate) fn new(transport: Transport) -> Self { - TransportSend { - inner: SendWrapper::new(transport), - } - } -} - -impl libp2p_core::Transport for TransportSend { - type Output = (PeerId, ConnectionSend); - type Error = Error; - type ListenerUpgrade = Pin> + Send>>; - type Dial = Pin> + Send>>; - - fn listen_on( - &mut self, - id: ListenerId, - addr: Multiaddr, - ) -> Result<(), TransportError> { - self.inner.listen_on(id, addr) - } - - fn remove_listener(&mut self, id: ListenerId) -> bool { - self.inner.remove_listener(id) - } - - fn dial(&mut self, addr: Multiaddr) -> Result> { - let fut = SendWrapper::new(self.inner.dial(addr)?); - - Ok(async move { - let (peer_id, conn) = fut.await?; - let conn = ConnectionSend::new(conn); - Ok((peer_id, conn)) - } - .boxed()) - } - - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - let fut = SendWrapper::new(self.inner.dial_as_listener(addr)?); - - Ok(async move { - let (peer_id, conn) = fut.await?; - let conn = ConnectionSend::new(conn); - Ok((peer_id, conn)) - } - .boxed()) - } - - fn poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - let inner = Pin::new(&mut *self.get_mut().inner); - - let event = ready!(inner.poll(cx)).map_upgrade(|fut| { - let fut = SendWrapper::new(fut); - - async move { - let (peer_id, conn) = fut.await?; - let conn = ConnectionSend::new(conn); - Ok((peer_id, conn)) - } - .boxed() - }); - - Poll::Ready(event) - } - - fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.address_translation(listen, observed) - } -} From 1ab2856b33953b561c38963141e3fa03ddca9383 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 01:26:30 +0300 Subject: [PATCH 12/21] Update wasm-tests CI --- .github/workflows/ci.yml | 34 ++++++++++++---------------------- wasm-tests/run-all.sh | 3 +++ 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 822fe50782a..5e9d71d8700 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,12 +82,9 @@ jobs: test "$PACKAGE_VERSION" = "$SPECIFIED_VERSION" - webtransport_tests: - name: Test WebTransport + wasm_tests: + name: Run all WASM tests runs-on: ubuntu-latest - defaults: - run: - working-directory: transports/webtransport-websys steps: - uses: actions/checkout@v3 @@ -95,31 +92,24 @@ jobs: with: target: wasm32-unknown-unknown - - uses: docker/setup-buildx-action@v2 - - uses: taiki-e/cache-cargo-install-action@v1 with: - tool: wasm-pack@0.11.1 + tool: wasm-pack@0.12.0 - name: Install Google Chrome + env: + CHROME_VERSION: "114.0.5735.106" run: | - CHROME_VERSION=114.0.5735.106 - curl -O https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_$CHROME_VERSION-1_amd64.deb - sudo dpkg -i google-chrome-stable_$CHROME_VERSION-1_amd64.deb + curl -o /tmp/google-chrome-stable_amd64.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}-1_amd64.deb + sudo dpkg -i /tmp/google-chrome-stable_amd64.deb - name: Install chromedriver - run: | - CHROMEDRIVER_VERSION=114.0.5735.90 - curl -O https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip - unzip chromedriver_linux64.zip - - - name: Build echo-server - run: docker buildx build -o type=local,dest=echo-server -t echo-server echo-server + uses: nanasess/setup-chromedriver@v2 + with: + chromedriver-version: '114.0.5735.90' - - name: Run wasm-pack test (Chrome) - run: | - ./echo-server/echo-server & - wasm-pack test --chrome --chromedriver=./chromedriver --headless + - name: Run all tests + run: ./wasm-tests/run-all.sh cross: name: Compile on ${{ matrix.target }} diff --git a/wasm-tests/run-all.sh b/wasm-tests/run-all.sh index 532fc9dc4d5..9381ac7f535 100755 --- a/wasm-tests/run-all.sh +++ b/wasm-tests/run-all.sh @@ -1,4 +1,7 @@ #!/bin/bash set -e +# cd to this script directory +cd "$(dirname "${BASH_SOURCE[0]}")" + ./webtransport-tests/run.sh From 8f25ad7fa14c411fb64767d97ed6a0c823177149 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 01:33:51 +0300 Subject: [PATCH 13/21] Add readme --- wasm-tests/README.md | 11 +++++++++++ wasm-tests/webtransport-tests/README.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 wasm-tests/README.md diff --git a/wasm-tests/README.md b/wasm-tests/README.md new file mode 100644 index 00000000000..1d0902b106c --- /dev/null +++ b/wasm-tests/README.md @@ -0,0 +1,11 @@ +# Dependencies + +Before you run the tests you need to install the following: + +* Chrome or Chromium +* chromedriver (major version must be the same as Chrome's) +* wasm-pack + +# Run tests + +Just call `run-all.sh` or `run.sh` in the test directory you are interested. diff --git a/wasm-tests/webtransport-tests/README.md b/wasm-tests/webtransport-tests/README.md index bc3c84326e8..b57a159176b 100644 --- a/wasm-tests/webtransport-tests/README.md +++ b/wasm-tests/webtransport-tests/README.md @@ -1,4 +1,4 @@ -# Run tests +# Manually run tests First you need to build and start the echo-server: From ceff6a3c7937a7b9404db55a14326e098650b5cd Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 01:43:47 +0300 Subject: [PATCH 14/21] Upgrade dependencies --- Cargo.lock | 12 ++++++------ transports/webtransport-websys/Cargo.toml | 12 ++++++------ wasm-tests/webtransport-tests/Cargo.toml | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f20807f6cc2..20409598b03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6038,9 +6038,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-bindgen-test" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e636f3a428ff62b3742ebc3c70e254dfe12b8c2b469d688ea59cdd4abcf502" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" dependencies = [ "console_error_panic_hook", "js-sys", @@ -6052,9 +6052,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18c1fad2f7c4958e7bcce014fa212f59a65d5e3721d0f77e6c0b27ede936ba3" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" dependencies = [ "proc-macro2", "quote", @@ -6073,9 +6073,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/transports/webtransport-websys/Cargo.toml b/transports/webtransport-websys/Cargo.toml index 1e1ee97ddfa..db554bc1982 100644 --- a/transports/webtransport-websys/Cargo.toml +++ b/transports/webtransport-websys/Cargo.toml @@ -15,18 +15,18 @@ categories = ["network-programming", "asynchronous"] [dependencies] futures = "0.3.28" -js-sys = "0.3.63" +js-sys = "0.3.64" libp2p-core = { workspace = true } libp2p-identity = { workspace = true } libp2p-noise = { workspace = true } -log = "0.4.18" +log = "0.4.19" multiaddr = { workspace = true } multihash = { workspace = true } send_wrapper = { version = "0.6.0", features = ["futures"] } -thiserror = "1.0.4" -wasm-bindgen = "0.2.86" -wasm-bindgen-futures = "0.4.36" -web-sys = { version = "0.3.63", features = [ +thiserror = "1.0.40" +wasm-bindgen = "0.2.87" +wasm-bindgen-futures = "0.4.37" +web-sys = { version = "0.3.64", features = [ "ReadableStreamDefaultReader", "WebTransport", "WebTransportBidirectionalStream", diff --git a/wasm-tests/webtransport-tests/Cargo.toml b/wasm-tests/webtransport-tests/Cargo.toml index d06191b2a60..65b4530ced2 100644 --- a/wasm-tests/webtransport-tests/Cargo.toml +++ b/wasm-tests/webtransport-tests/Cargo.toml @@ -12,7 +12,7 @@ libp2p-noise = { workspace = true } libp2p-webtransport-websys = { workspace = true } multiaddr = { workspace = true } multihash = { workspace = true } -wasm-bindgen = "0.2.86" -wasm-bindgen-futures = "0.4.36" -wasm-bindgen-test = "0.3.36" -web-sys = { version = "0.3.63", features = ["Response", "Window"] } +wasm-bindgen = "0.2.87" +wasm-bindgen-futures = "0.4.37" +wasm-bindgen-test = "0.3.37" +web-sys = { version = "0.3.64", features = ["Response", "Window"] } From 1ad26dcdc35581a50719f9da7cb5c877e484a74f Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 01:48:03 +0300 Subject: [PATCH 15/21] disable publish on webtransport-tests --- wasm-tests/webtransport-tests/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/wasm-tests/webtransport-tests/Cargo.toml b/wasm-tests/webtransport-tests/Cargo.toml index 65b4530ced2..bdff7533a9d 100644 --- a/wasm-tests/webtransport-tests/Cargo.toml +++ b/wasm-tests/webtransport-tests/Cargo.toml @@ -2,6 +2,7 @@ name = "webtransport-tests" version = "0.1.0" edition = "2021" +publish = false [dependencies] futures = "0.3.28" From 3a464b460fa5a48b824f44fec3a0b1af9809639c Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 01:58:11 +0300 Subject: [PATCH 16/21] webtransport: add test for leftovers --- wasm-tests/webtransport-tests/src/lib.rs | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/wasm-tests/webtransport-tests/src/lib.rs b/wasm-tests/webtransport-tests/src/lib.rs index b1207a82161..1e190d65c3e 100644 --- a/wasm-tests/webtransport-tests/src/lib.rs +++ b/wasm-tests/webtransport-tests/src/lib.rs @@ -127,6 +127,34 @@ async fn multiple_conn_multiple_streams_sequential() { } } +#[wasm_bindgen_test] +async fn read_leftovers() { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + + let mut stream = create_stream(&mut conn).await; + + // Test that stream works + send_recv(&mut stream).await; + + stream.write_all(b"hello").await.unwrap(); + + let mut buf = [0u8; 3]; + + // Read first half + let len = stream.read(&mut buf[..]).await.unwrap(); + assert_eq!(len, 3); + assert_eq!(&buf[..len], b"hel"); + + // Read second half + let len = stream.read(&mut buf[..]).await.unwrap(); + assert_eq!(len, 2); + assert_eq!(&buf[..len], b"lo"); +} + #[wasm_bindgen_test] async fn allow_read_after_closing_writer() { let addr = fetch_server_addr().await; From 0d7cc024784057dee53b4cfd363aea533547acd0 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 02:09:47 +0300 Subject: [PATCH 17/21] update openssl to make cargo-deny happy --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20409598b03..b3547bb78af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3769,9 +3769,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.54" +version = "0.10.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -3801,9 +3801,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.88" +version = "0.9.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" dependencies = [ "cc", "libc", From 4287d9f760e1642700709c453ebcd672693d0325 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 02:17:35 +0300 Subject: [PATCH 18/21] add license for webtransport-tests --- wasm-tests/webtransport-tests/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/wasm-tests/webtransport-tests/Cargo.toml b/wasm-tests/webtransport-tests/Cargo.toml index bdff7533a9d..6c564281274 100644 --- a/wasm-tests/webtransport-tests/Cargo.toml +++ b/wasm-tests/webtransport-tests/Cargo.toml @@ -2,6 +2,7 @@ name = "webtransport-tests" version = "0.1.0" edition = "2021" +license = "MIT" publish = false [dependencies] From 8bfa085162eaf6bd2277b61e03db4d1d36ffebad Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 21:26:09 +0300 Subject: [PATCH 19/21] Make tests cleaner --- wasm-tests/webtransport-tests/src/lib.rs | 101 ++++++----------------- 1 file changed, 24 insertions(+), 77 deletions(-) diff --git a/wasm-tests/webtransport-tests/src/lib.rs b/wasm-tests/webtransport-tests/src/lib.rs index 1e190d65c3e..0ec2f0bcb03 100644 --- a/wasm-tests/webtransport-tests/src/lib.rs +++ b/wasm-tests/webtransport-tests/src/lib.rs @@ -18,12 +18,7 @@ wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] async fn single_conn_single_stream() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); + let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; send_recv(&mut stream).await; @@ -31,12 +26,7 @@ async fn single_conn_single_stream() { #[wasm_bindgen_test] async fn single_conn_single_stream_incoming() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); + let mut conn = new_connection_to_echo_server().await; let mut stream = incoming_stream(&mut conn).await; send_recv(&mut stream).await; @@ -44,13 +34,8 @@ async fn single_conn_single_stream_incoming() { #[wasm_bindgen_test] async fn single_conn_multiple_streams() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); + let mut conn = new_connection_to_echo_server().await; let mut tasks = Vec::new(); - - let (_peer_id, mut conn) = transport.dial(addr).unwrap().await.unwrap(); let mut streams = Vec::new(); for i in 0..30 { @@ -72,15 +57,11 @@ async fn single_conn_multiple_streams() { #[wasm_bindgen_test] async fn multiple_conn_multiple_streams() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); let mut tasks = Vec::new(); let mut conns = Vec::new(); for _ in 0..10 { - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut conn = new_connection_to_echo_server().await; let mut streams = Vec::new(); for i in 0..10 { @@ -107,13 +88,8 @@ async fn multiple_conn_multiple_streams() { #[wasm_bindgen_test] async fn multiple_conn_multiple_streams_sequential() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - for _ in 0..10 { - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut conn = new_connection_to_echo_server().await; for i in 0..10 { let mut stream = if i % 2 == 0 { @@ -129,12 +105,7 @@ async fn multiple_conn_multiple_streams_sequential() { #[wasm_bindgen_test] async fn read_leftovers() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - + let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; // Test that stream works @@ -157,12 +128,7 @@ async fn read_leftovers() { #[wasm_bindgen_test] async fn allow_read_after_closing_writer() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); - + let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; // Test that stream works @@ -188,11 +154,7 @@ async fn allow_read_after_closing_writer() { #[wasm_bindgen_test] async fn poll_outbound_error_after_connection_close() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut conn = new_connection_to_echo_server().await; // Make sure that poll_outbound works well before closing the connection let mut stream = create_stream(&mut conn).await; @@ -210,11 +172,7 @@ async fn poll_outbound_error_after_connection_close() { #[wasm_bindgen_test] async fn poll_inbound_error_after_connection_close() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut conn = new_connection_to_echo_server().await; // Make sure that poll_inbound works well before closing the connection let mut stream = incoming_stream(&mut conn).await; @@ -232,16 +190,10 @@ async fn poll_inbound_error_after_connection_close() { #[wasm_bindgen_test] async fn read_error_after_connection_drop() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; send_recv(&mut stream).await; - drop(conn); let mut buf = [0u8; 16]; @@ -253,12 +205,7 @@ async fn read_error_after_connection_drop() { #[wasm_bindgen_test] async fn read_error_after_connection_close() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; send_recv(&mut stream).await; @@ -276,16 +223,10 @@ async fn read_error_after_connection_close() { #[wasm_bindgen_test] async fn write_error_after_connection_drop() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; send_recv(&mut stream).await; - drop(conn); let buf = [0u8; 16]; @@ -297,12 +238,7 @@ async fn write_error_after_connection_drop() { #[wasm_bindgen_test] async fn write_error_after_connection_close() { - let addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); - - let mut transport = Transport::new(Config::new(&keypair)); - - let (_peer_id, mut conn) = transport.dial(addr.clone()).unwrap().await.unwrap(); + let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; send_recv(&mut stream).await; @@ -368,6 +304,17 @@ async fn error_on_unknown_certhash() { )); } +async fn new_connection_to_echo_server() -> Connection { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, conn) = transport.dial(addr).unwrap().await.unwrap(); + + conn +} + /// Helper that returns the multiaddress of echo-server /// /// It fetches the multiaddress via HTTP request to From cfec286d5c1f7de3ebfb7da8a8999d609bd8016b Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 21:32:38 +0300 Subject: [PATCH 20/21] Align Chrome and chromedriver versions --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e9d71d8700..ad180ef5f2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,8 @@ jobs: wasm_tests: name: Run all WASM tests runs-on: ubuntu-latest + env: + CHROMEDRIVER_VERSION: '114.0.5735.90' steps: - uses: actions/checkout@v3 @@ -97,16 +99,14 @@ jobs: tool: wasm-pack@0.12.0 - name: Install Google Chrome - env: - CHROME_VERSION: "114.0.5735.106" run: | - curl -o /tmp/google-chrome-stable_amd64.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}-1_amd64.deb + curl -o /tmp/google-chrome-stable_amd64.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROMEDRIVER_VERSION}-1_amd64.deb sudo dpkg -i /tmp/google-chrome-stable_amd64.deb - name: Install chromedriver uses: nanasess/setup-chromedriver@v2 with: - chromedriver-version: '114.0.5735.90' + chromedriver-version: ${{ env.CHROMEDRIVER_VERSION }} - name: Run all tests run: ./wasm-tests/run-all.sh From c2cfa8abc04c2dae1a0a27eb573601c4d492af71 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 22 Jun 2023 22:20:40 +0300 Subject: [PATCH 21/21] fix minor lints in run.sh --- wasm-tests/run-all.sh | 2 +- wasm-tests/webtransport-tests/run.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wasm-tests/run-all.sh b/wasm-tests/run-all.sh index 9381ac7f535..77b896a167d 100755 --- a/wasm-tests/run-all.sh +++ b/wasm-tests/run-all.sh @@ -2,6 +2,6 @@ set -e # cd to this script directory -cd "$(dirname "${BASH_SOURCE[0]}")" +cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 ./webtransport-tests/run.sh diff --git a/wasm-tests/webtransport-tests/run.sh b/wasm-tests/webtransport-tests/run.sh index 8e189f06ac0..1819fc97770 100755 --- a/wasm-tests/webtransport-tests/run.sh +++ b/wasm-tests/webtransport-tests/run.sh @@ -8,7 +8,7 @@ else fi # cd to this script directory -cd "$(dirname "${BASH_SOURCE[0]}")" +cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 # Print the directory for debugging echo "Tests: $PWD" @@ -19,10 +19,10 @@ id="$($docker run -d --network=host webtransport-echo-server)" || exit 1 # Run tests wasm-pack test --chrome --headless -EXIT_CODE=$? +exit_code=$? # Remove echo-server container $docker rm -f "$id" # Propagate wasm-pack's exit code -exit $EXIT_CODE +exit $exit_code