From d5a79735a371dd72a0e8040ff8738634220542c1 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 10:27:47 -0300 Subject: [PATCH 001/235] add webrtc-websys workspace members --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 956816074e2..f39e7337c15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,9 +52,12 @@ members = [ "transports/uds", "transports/wasm-ext", "transports/webrtc", + "transports/webrtc-websys", "transports/websocket", "transports/webtransport-websys", "wasm-tests/webtransport-tests", + "wasm-tests/webrtc", + "wasm-tests/webrtc/server", ] resolver = "2" From 5ad9abaf8cb4bb17b8f69707de128ba427b71cb8 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 10:28:24 -0300 Subject: [PATCH 002/235] add webrtc test server (ping + relay) --- wasm-tests/webrtc/server/Cargo.toml | 25 ++++++ wasm-tests/webrtc/server/README.md | 8 ++ wasm-tests/webrtc/server/src/main.rs | 122 +++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 wasm-tests/webrtc/server/Cargo.toml create mode 100644 wasm-tests/webrtc/server/README.md create mode 100644 wasm-tests/webrtc/server/src/main.rs diff --git a/wasm-tests/webrtc/server/Cargo.toml b/wasm-tests/webrtc/server/Cargo.toml new file mode 100644 index 00000000000..a03ca5ba9ab --- /dev/null +++ b/wasm-tests/webrtc/server/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "webrtc-websys-test-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum = "0.6.19" +anyhow = "1.0.72" +futures = "0.3.28" +rand = "0.8" +void = "1" +libp2p-identity = { workspace = true } +tokio = { version = "1.29", features = ["rt", "macros", "signal", "net"] } +tokio-util = { version = "0.7", features = ["compat"] } +tokio-stream = "0.1" +libp2p-webrtc = { workspace = true, features = ["tokio"] } +webrtc-websys-tests = { path = "../" } # for PORT +tower-http = { version = "0.4.0", features = ["cors"] } # axum middleware + +[dependencies.libp2p] +path = "../../../libp2p" +version = "0.52.1" +features = ["tokio", "noise", "relay", "ping", "macros"] diff --git a/wasm-tests/webrtc/server/README.md b/wasm-tests/webrtc/server/README.md new file mode 100644 index 00000000000..ea1f4c2eee1 --- /dev/null +++ b/wasm-tests/webrtc/server/README.md @@ -0,0 +1,8 @@ +# Websys RTC Test Server + +- Runs a Rust-Libp2p WebRTC Node +- Serves the multiaddr for testing on http://localhost:3000/ + +# Run + +`cargo run` diff --git a/wasm-tests/webrtc/server/src/main.rs b/wasm-tests/webrtc/server/src/main.rs new file mode 100644 index 00000000000..40bc3056527 --- /dev/null +++ b/wasm-tests/webrtc/server/src/main.rs @@ -0,0 +1,122 @@ +use anyhow::Result; +use axum::{ + http::{HeaderValue, Method}, + routing::get, + Router, +}; +use futures::StreamExt; +use libp2p::core::muxing::StreamMuxerBox; +use libp2p::core::Transport; +use libp2p::multiaddr::{Multiaddr, Protocol}; +use libp2p::ping; +use libp2p::relay; +use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmBuilder, SwarmEvent}; +use libp2p_identity as identity; +use libp2p_webrtc as webrtc; +use rand::thread_rng; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use tower_http::cors::{Any, CorsLayer}; +use void::Void; + +/// An example WebRTC server that will accept connections and run the ping protocol on them. +#[tokio::main] +async fn main() -> Result<()> { + let id_keys = identity::Keypair::generate_ed25519(); + let local_peer_id = id_keys.public().to_peer_id(); + let transport = webrtc::tokio::Transport::new( + id_keys, + webrtc::tokio::Certificate::generate(&mut thread_rng())?, + ); + + let transport = transport + .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) + .boxed(); + + let behaviour = Behaviour { + relay: relay::Behaviour::new(local_peer_id, Default::default()), + ping: ping::Behaviour::new(ping::Config::new()), + keep_alive: keep_alive::Behaviour, + }; + + let mut swarm = SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build(); + + let address_webrtc = Multiaddr::from(Ipv6Addr::LOCALHOST) + .with(Protocol::Udp(0)) + .with(Protocol::WebRTCDirect); + + swarm.listen_on(address_webrtc.clone())?; + + loop { + tokio::select! { + evt = swarm.next() => { + match evt { + Some(SwarmEvent::NewListenAddr { address, .. }) => { + + let addr = address + .with(Protocol::P2p(*swarm.local_peer_id())) + .clone() + .to_string(); + + eprintln!("Listening on {}", addr); + + tokio::spawn(async move { + + let app = Router::new().route("/", get(|| async { addr })) + .layer( + // allow cors + CorsLayer::new() + .allow_origin(Any) + .allow_methods([Method::GET]), + ); + + axum::Server::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), webrtc_websys_tests::PORT)) + .serve(app.into_make_service()) + .await + .unwrap(); + }); + } + _ => { + // do nothing + }, + } + }, + _ = tokio::signal::ctrl_c() => { + break; + } + } + } + Ok(()) +} + +#[derive(NetworkBehaviour)] +#[behaviour(to_swarm = "Event", prelude = "libp2p::swarm::derive_prelude")] +struct Behaviour { + ping: ping::Behaviour, + keep_alive: keep_alive::Behaviour, + relay: relay::Behaviour, +} + +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +enum Event { + Ping(ping::Event), + Relay(relay::Event), +} + +impl From for Event { + fn from(event: ping::Event) -> Self { + Event::Ping(event) + } +} + +impl From for Event { + fn from(event: Void) -> Self { + void::unreachable(event) + } +} + +impl From for Event { + fn from(event: relay::Event) -> Self { + Event::Relay(event) + } +} From b6a27909d488548dfe88fdf1aab396324753d59f Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 10:28:51 -0300 Subject: [PATCH 003/235] strawman tests --- wasm-tests/webrtc/Cargo.toml | 22 +++++++++++++++ wasm-tests/webrtc/README.md | 14 ++++++++++ wasm-tests/webrtc/src/lib.rs | 52 ++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 wasm-tests/webrtc/Cargo.toml create mode 100644 wasm-tests/webrtc/README.md create mode 100644 wasm-tests/webrtc/src/lib.rs diff --git a/wasm-tests/webrtc/Cargo.toml b/wasm-tests/webrtc/Cargo.toml new file mode 100644 index 00000000000..5af38bcb32e --- /dev/null +++ b/wasm-tests/webrtc/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "webrtc-websys-tests" +version = "0.1.0" +edition = "2021" +license = "MIT" +publish = false + +[dependencies] +futures = "0.3.28" +getrandom = { version = "0.2.9", features = ["js"] } +multiaddr = { workspace = true } +multihash = { workspace = true } +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"] } +libp2p-identity = { workspace = true } + +[dependencies.libp2p] +path = "../../libp2p" +version = "0.52.1" +features = ["tokio", "noise"] diff --git a/wasm-tests/webrtc/README.md b/wasm-tests/webrtc/README.md new file mode 100644 index 00000000000..34edbf7bd92 --- /dev/null +++ b/wasm-tests/webrtc/README.md @@ -0,0 +1,14 @@ +# Manually run tests + +First you need to build and start the echo-server: + +```bash +cd server +cargo run +``` + +In another terminal run: + +```bash +wasm-pack test --chrome --headless +``` diff --git a/wasm-tests/webrtc/src/lib.rs b/wasm-tests/webrtc/src/lib.rs new file mode 100644 index 00000000000..e39030c3497 --- /dev/null +++ b/wasm-tests/webrtc/src/lib.rs @@ -0,0 +1,52 @@ +use futures::channel::oneshot; +use futures::{AsyncReadExt, AsyncWriteExt}; +use getrandom::getrandom; +use libp2p::core::{StreamMuxer, Transport as _}; +use libp2p::noise; +use libp2p_identity::{Keypair, PeerId}; +// 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); + +pub const PORT: u16 = 4455; + +#[wasm_bindgen_test] +async fn connect_without_peer_id() { + let mut addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + // eprintln + eprintln!("addr: {:?}", addr); + println!("addr: {:?}", addr); +} + +/// 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 = format!("http://127.0.0.1:{}/", PORT); + 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() +} From 56e0dceb2b81a343e1a6a821633f5a7731a41722 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 10:33:03 -0300 Subject: [PATCH 004/235] add library modules --- transports/webrtc-websys/src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 transports/webrtc-websys/src/lib.rs diff --git a/transports/webrtc-websys/src/lib.rs b/transports/webrtc-websys/src/lib.rs new file mode 100644 index 00000000000..5a97cd281b6 --- /dev/null +++ b/transports/webrtc-websys/src/lib.rs @@ -0,0 +1,15 @@ +mod cbfutures; +mod connection; +mod error; +mod fingerprint; +mod fused_js_promise; +mod sdp; +mod stream; +mod transport; +mod upgrade; +mod utils; + +pub use self::connection::Connection; +pub use self::error::Error; +pub use self::stream::WebRTCStream; +pub use self::transport::{Config, Transport}; From 135b0af1d38cc03f7359e55eb8568a4c35f31041 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 10:33:15 -0300 Subject: [PATCH 005/235] add transport.rs --- transports/webrtc-websys/src/transport.rs | 140 ++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 transports/webrtc-websys/src/transport.rs diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs new file mode 100644 index 00000000000..8d255291a7f --- /dev/null +++ b/transports/webrtc-websys/src/transport.rs @@ -0,0 +1,140 @@ +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, Protocol}; +use std::future::Future; +use std::net::{IpAddr, SocketAddr}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +// use crate::endpoint::Endpoint; +use crate::fingerprint::Fingerprint; +use crate::Connection; +use crate::Error; + +const HANDSHAKE_TIMEOUT_MS: u64 = 10_000; + +/// Config for the [`Transport`]. +#[derive(Clone)] +pub struct Config { + keypair: Keypair, +} + +/// A WebTransport [`Transport`](libp2p_core::Transport) that works with `web-sys`. +pub struct Transport { + config: Config, +} + +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)> { + 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> + Send>>; + type Dial = Pin> + Send>>; + + 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 (sock_addr, server_fingerprint) = parse_webrtc_dial_addr(&addr) + .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; + + if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + + let config = self.config.clone(); + let connection = Connection::new(sock_addr, server_fingerprint, config.keypair.clone()); + + Ok(async move { + let peer_id = connection.connect().await?; + + Ok((peer_id, connection)) + } + .boxed()) + } + + 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 + } +} + +/// Parse the given [`Multiaddr`] into a [`SocketAddr`] and a [`Fingerprint`] for dialing. +fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint)> { + let mut iter = addr.iter(); + + let ip = match iter.next()? { + Protocol::Ip4(ip) => IpAddr::from(ip), + Protocol::Ip6(ip) => IpAddr::from(ip), + _ => return None, + }; + + let port = iter.next()?; + let webrtc = iter.next()?; + let certhash = iter.next()?; + + let (port, fingerprint) = match (port, webrtc, certhash) { + (Protocol::Udp(port), Protocol::WebRTCDirect, Protocol::Certhash(cert_hash)) => { + let fingerprint = Fingerprint::try_from_multihash(cert_hash)?; + + (port, fingerprint) + } + _ => return None, + }; + + match iter.next() { + Some(Protocol::P2p(_)) => {} + // peer ID is optional + None => {} + // unexpected protocol + Some(_) => return None, + } + + Some((SocketAddr::new(ip, port), fingerprint)) +} From 8d0407d87996da424a0dd6d3246dc7854a4fccc1 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 10:33:42 -0300 Subject: [PATCH 006/235] sdp --- transports/webrtc-websys/src/sdp.rs | 280 ++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 transports/webrtc-websys/src/sdp.rs diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc-websys/src/sdp.rs new file mode 100644 index 00000000000..437c8896019 --- /dev/null +++ b/transports/webrtc-websys/src/sdp.rs @@ -0,0 +1,280 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use js_sys::Reflect; +use serde::Serialize; +use tinytemplate::TinyTemplate; +use wasm_bindgen::JsValue; +use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; + +use std::net::{IpAddr, SocketAddr}; + +use crate::fingerprint::Fingerprint; + +/// Creates the SDP answer used by the client. +pub(crate) fn answer( + addr: SocketAddr, + server_fingerprint: &Fingerprint, + client_ufrag: &str, +) -> RtcSessionDescriptionInit { + let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); + answer_obj.sdp(&render_description( + SERVER_SESSION_DESCRIPTION, + addr, + server_fingerprint, + client_ufrag, + )); + answer_obj +} + +/// Creates the SDP offer used by the server. +/// +/// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. +pub(crate) fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescriptionInit { + let offer_sdp = Reflect::get(&offer, &JsValue::from_str("sdp")) + .expect("valid offer object") + .as_string() + .unwrap(); + + let munged_offer_sdp = offer_sdp + .replace( + "\na=ice-ufrag:[^\n]*\n", + &format!("\na=ice-ufrag:{client_ufrag}\n"), + ) + .replace( + "\na=ice-pwd:[^\n]*\n", + &format!("\na=ice-pwd:{client_ufrag}\n"), + ); + + // setLocalDescription + let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); + offer_obj.sdp(&munged_offer_sdp); + + offer_obj +} + +// An SDP message that constitutes the offer. +// +// Main RFC: +// `sctp-port` and `max-message-size` attrs RFC: +// `group` and `mid` attrs RFC: +// `ice-ufrag`, `ice-pwd` and `ice-options` attrs RFC: +// `setup` attr RFC: +// +// Short description: +// +// v= -> always 0 +// o= +// +// identifies the creator of the SDP document. We are allowed to use dummy values +// (`-` and `0.0.0.0` as ) to remain anonymous, which we do. Note that "IN" means +// "Internet". +// +// s= +// +// We are allowed to pass a dummy `-`. +// +// c= +// +// Indicates the IP address of the remote. +// Note that "IN" means "Internet". +// +// t= +// +// Start and end of the validity of the session. `0 0` means that the session never expires. +// +// m= ... +// +// A `m=` line describes a request to establish a certain protocol. The protocol in this line +// (i.e. `TCP/DTLS/SCTP` or `UDP/DTLS/SCTP`) must always be the same as the one in the offer. +// We know that this is true because we tweak the offer to match the protocol. The `` +// component must always be `webrtc-datachannel` for WebRTC. +// RFCs: 8839, 8866, 8841 +// +// a=mid: +// +// Media ID - uniquely identifies this media stream (RFC9143). +// +// a=ice-options:ice2 +// +// Indicates that we are complying with RFC8839 (as oppposed to the legacy RFC5245). +// +// a=ice-ufrag: +// a=ice-pwd: +// +// ICE username and password, which are used for establishing and +// maintaining the ICE connection. (RFC8839) +// MUST match ones used by the answerer (server). +// +// a=fingerprint:sha-256 +// +// Fingerprint of the certificate that the remote will use during the TLS +// handshake. (RFC8122) +// +// a=setup:actpass +// +// The endpoint that is the offerer MUST use the setup attribute value of setup:actpass and be +// prepared to receive a client_hello before it receives the answer. +// +// a=sctp-port: +// +// The SCTP port (RFC8841) +// Note it's different from the "m=" line port value, which indicates the port of the +// underlying transport-layer protocol (UDP or TCP). +// +// a=max-message-size: +// +// The maximum SCTP user message size (in bytes). (RFC8841) +const CLIENT_SESSION_DESCRIPTION: &str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +c=IN {ip_version} {target_ip} +t=0 0 + +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +a=mid:0 +a=ice-options:ice2 +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} +a=setup:actpass +a=sctp-port:5000 +a=max-message-size:16384 +"; + +// See [`CLIENT_SESSION_DESCRIPTION`]. +// +// a=ice-lite +// +// A lite implementation is only appropriate for devices that will *always* be connected to +// the public Internet and have a public IP address at which it can receive packets from any +// correspondent. ICE will not function when a lite implementation is placed behind a NAT +// (RFC8445). +// +// a=tls-id: +// +// "TLS ID" uniquely identifies a TLS association. +// The ICE protocol uses a "TLS ID" system to indicate whether a fresh DTLS connection +// must be reopened in case of ICE renegotiation. Considering that ICE renegotiations +// never happen in our use case, we can simply put a random value and not care about +// it. Note however that the TLS ID in the answer must be present if and only if the +// offer contains one. (RFC8842) +// TODO: is it true that renegotiations never happen? what about a connection closing? +// "tls-id" attribute MUST be present in the initial offer and respective answer (RFC8839). +// XXX: but right now browsers don't send it. +// +// a=setup:passive +// +// "passive" indicates that the remote DTLS server will only listen for incoming +// connections. (RFC5763) +// The answerer (server) MUST not be located behind a NAT (RFC6135). +// +// The answerer MUST use either a setup attribute value of setup:active or setup:passive. +// Note that if the answerer uses setup:passive, then the DTLS handshake will not begin until +// the answerer is received, which adds additional latency. setup:active allows the answer and +// the DTLS handshake to occur in parallel. Thus, setup:active is RECOMMENDED. +// +// a=candidate: +// +// A transport address for a candidate that can be used for connectivity checks (RFC8839). +// +// a=end-of-candidates +// +// Indicate that no more candidates will ever be sent (RFC8838). +const SERVER_SESSION_DESCRIPTION: &str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +t=0 0 +a=ice-lite +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +c=IN {ip_version} {target_ip} +a=mid:0 +a=ice-options:ice2 +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} + +a=setup:passive +a=sctp-port:5000 +a=max-message-size:16384 +a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host +a=end-of-candidates +"; + +/// Indicates the IP version used in WebRTC: `IP4` or `IP6`. +#[derive(Serialize)] +enum IpVersion { + IP4, + IP6, +} + +/// Context passed to the templating engine, which replaces the above placeholders (e.g. +/// `{IP_VERSION}`) with real values. +#[derive(Serialize)] +struct DescriptionContext { + pub(crate) ip_version: IpVersion, + pub(crate) target_ip: IpAddr, + pub(crate) target_port: u16, + pub(crate) fingerprint_algorithm: String, + pub(crate) fingerprint_value: String, + pub(crate) ufrag: String, + pub(crate) pwd: String, +} + +/// Renders a [`TinyTemplate`] description using the provided arguments. +fn render_description( + description: &str, + addr: SocketAddr, + fingerprint: &Fingerprint, + ufrag: &str, +) -> String { + let mut tt = TinyTemplate::new(); + tt.add_template("description", description).unwrap(); + + let context = DescriptionContext { + ip_version: { + if addr.is_ipv4() { + IpVersion::IP4 + } else { + IpVersion::IP6 + } + }, + target_ip: addr.ip(), + target_port: addr.port(), + fingerprint_algorithm: fingerprint.algorithm(), + fingerprint_value: fingerprint.to_sdp_format(), + // NOTE: ufrag is equal to pwd. + ufrag: ufrag.to_owned(), + pwd: ufrag.to_owned(), + }; + tt.render("description", &context).unwrap() +} + +/// Fingerprint from SDP +pub fn fingerprint(sdp: &str) -> Result { + let fingerprint_regex = regex::Regex::new( + r"(?m)^a=fingerprint:(?:\w+-[0-9]+)\s(?P(:?[0-9a-fA-F]{2})+)$", + ) + .unwrap(); + let captures = fingerprint_regex.captures(sdp).unwrap(); + let fingerprint = captures.name("fingerprint").unwrap().as_str(); + let fingerprint = hex::decode(fingerprint).unwrap(); + Ok(Fingerprint::from_certificate(&fingerprint)) +} From e2158080e4243d1a4a2d6e9201f6281da8f0b4c2 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 10:34:07 -0300 Subject: [PATCH 007/235] utils --- transports/webrtc-websys/src/utils.rs | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 transports/webrtc-websys/src/utils.rs diff --git a/transports/webrtc-websys/src/utils.rs b/transports/webrtc-websys/src/utils.rs new file mode 100644 index 00000000000..62a4cb8ce7d --- /dev/null +++ b/transports/webrtc-websys/src/utils.rs @@ -0,0 +1,69 @@ +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) + } +} + +pub fn gen_ufrag(len: usize) -> String { + let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + let mut ufrag = String::new(); + let mut buf = vec![0; len]; + getrandom::getrandom(&mut buf).unwrap(); + for i in buf { + let idx = i as usize % charset.len(); + ufrag.push(charset.chars().nth(idx).unwrap()); + } + ufrag +} From 19d20f130949834817d0f17590222d7edd688159 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 10:34:42 -0300 Subject: [PATCH 008/235] fingerprint intial commit --- transports/webrtc-websys/src/fingerprint.rs | 113 ++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 transports/webrtc-websys/src/fingerprint.rs diff --git a/transports/webrtc-websys/src/fingerprint.rs b/transports/webrtc-websys/src/fingerprint.rs new file mode 100644 index 00000000000..251e3efa87e --- /dev/null +++ b/transports/webrtc-websys/src/fingerprint.rs @@ -0,0 +1,113 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use sha2::Digest as _; +use std::fmt; + +const SHA256: &str = "sha-256"; +const MULTIHASH_SHA256_CODE: u64 = 0x12; + +type Multihash = multihash::Multihash<64>; + +/// A certificate fingerprint that is assumed to be created using the SHA256 hash algorithm. +#[derive(Eq, PartialEq, Copy, Clone)] +pub struct Fingerprint([u8; 32]); + +impl Fingerprint { + pub(crate) const FF: Fingerprint = Fingerprint([0xFF; 32]); + + #[cfg(test)] + pub fn raw(bytes: [u8; 32]) -> Self { + Self(bytes) + } + + /// Creates a fingerprint from a raw certificate. + pub fn from_certificate(bytes: &[u8]) -> Self { + Fingerprint(sha2::Sha256::digest(bytes).into()) + } + + /// Converts [`Multihash`](multihash::Multihash) to [`Fingerprint`]. + pub fn try_from_multihash(hash: Multihash) -> Option { + if hash.code() != MULTIHASH_SHA256_CODE { + // Only support SHA256 for now. + return None; + } + + let bytes = hash.digest().try_into().ok()?; + + Some(Self(bytes)) + } + + /// Converts this fingerprint to [`Multihash`](multihash::Multihash). + pub fn to_multihash(self) -> Multihash { + Multihash::wrap(MULTIHASH_SHA256_CODE, &self.0).expect("fingerprint's len to be 32 bytes") + } + + /// Formats this fingerprint as uppercase hex, separated by colons (`:`). + /// + /// This is the format described in . + pub fn to_sdp_format(self) -> String { + self.0.map(|byte| format!("{byte:02X}")).join(":") + } + + /// From SDP format + /// fingerprintRegex = /^a=fingerprint:(?:\w+-[0-9]+)\s(?(:?[0-9a-fA-F]{2})+)$/m + pub fn from_sdp_format(sdp_format: &str) -> Option { + let mut bytes = [0u8; 32]; + let mut i = 0; + for byte in sdp_format.split(':') { + if byte.len() != 2 { + return None; + } + let byte = u8::from_str_radix(byte, 16).ok()?; + bytes[i] = byte; + i += 1; + } + Some(Self(bytes)) + } + + /// Returns the algorithm used (e.g. "sha-256"). + /// See + pub fn algorithm(&self) -> String { + SHA256.to_owned() + } +} + +impl fmt::Debug for Fingerprint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&hex::encode(self.0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sdp_format() { + let fp = Fingerprint::raw(hex_literal::hex!( + "7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC" + )); + + let sdp_format = fp.to_sdp_format(); + + assert_eq!(sdp_format, "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC") + } +} From 10ca24c1b8f49c9d4357fed39a99fa36a3dca68c Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 10:35:07 -0300 Subject: [PATCH 009/235] errors init commit --- transports/webrtc-websys/src/error.rs | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 transports/webrtc-websys/src/error.rs diff --git a/transports/webrtc-websys/src/error.rs b/transports/webrtc-websys/src/error.rs new file mode 100644 index 00000000000..fa7cfcf4cb9 --- /dev/null +++ b/transports/webrtc-websys/src/error.rs @@ -0,0 +1,47 @@ +use libp2p_core::transport::TransportError; +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, + + #[error("Connection error: {0}")] + Connection(String), +} + +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) + } +} + +// implement `std::convert::From` for `error::Error` +impl std::convert::From for Error { + fn from(value: JsValue) -> Self { + Error::from_js_value(value) + } +} From 1790f01ab894e946451bb6bc664221e54a9c6961 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 10:35:36 -0300 Subject: [PATCH 010/235] add FusedJsPromise --- .../webrtc-websys/src/fused_js_promise.rs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 transports/webrtc-websys/src/fused_js_promise.rs diff --git a/transports/webrtc-websys/src/fused_js_promise.rs b/transports/webrtc-websys/src/fused_js_promise.rs new file mode 100644 index 00000000000..0ba846501c2 --- /dev/null +++ b/transports/webrtc-websys/src/fused_js_promise.rs @@ -0,0 +1,58 @@ +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, +} + +impl FusedJsPromise { + /// Creates new uninitialized promise. + pub(crate) fn new() -> Self { + FusedJsPromise { promise: None } + } + + /// Initialize promise if needed + pub(crate) fn maybe_init(&mut self, init: F) -> &mut Self + where + F: FnOnce() -> Promise, + { + if self.promise.is_none() { + self.promise = Some(JsFuture::from(init())); + } + + self + } + + /// 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() + .expect("FusedJsPromise not initialized") + .poll_unpin(cx)); + + // Future finished, drop it + self.promise.take(); + + Poll::Ready(val) + } +} From 5d34fd3ad8821473c62f083068cb17c1ff0b69bb Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 17:14:29 -0300 Subject: [PATCH 011/235] It compiles (TM), more work to do --- transports/webrtc-websys/Cargo.toml | 50 +++ transports/webrtc-websys/src/cbfutures.rs | 56 ++++ transports/webrtc-websys/src/connection.rs | 218 +++++++++++++ transports/webrtc-websys/src/error.rs | 8 +- transports/webrtc-websys/src/stream.rs | 289 ++++++++++++++++++ transports/webrtc-websys/src/transport.rs | 2 +- transports/webrtc-websys/src/upgrade.rs | 43 +++ transports/webrtc-websys/src/upgrade/noise.rs | 104 +++++++ 8 files changed, 768 insertions(+), 2 deletions(-) create mode 100644 transports/webrtc-websys/Cargo.toml create mode 100644 transports/webrtc-websys/src/cbfutures.rs create mode 100644 transports/webrtc-websys/src/connection.rs create mode 100644 transports/webrtc-websys/src/stream.rs create mode 100644 transports/webrtc-websys/src/upgrade.rs create mode 100644 transports/webrtc-websys/src/upgrade/noise.rs diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml new file mode 100644 index 00000000000..70861491148 --- /dev/null +++ b/transports/webrtc-websys/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "libp2p-webrtc-websys" +version = "0.1.0" +edition = "2021" +rust-version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +futures = "0.3.28" +callback-future = "0.1.0" # because web_sys::Rtc is callback based, but we need futures +js-sys = "0.3.64" +libp2p-core = { workspace = true } +libp2p-identity = { workspace = true } +libp2p-noise = { workspace = true } +log = "0.4.19" +hex = "0.4" +getrandom = { version = "0.2.9", features = ["js"] } +multiaddr = { workspace = true } +multihash = { workspace = true } +regex = "1.9.1" +send_wrapper = { version = "0.6.0", features = ["futures"] } +thiserror = "1.0.43" +serde_json = "1.0.103" +sha2 = "0.10.7" +serde = { version = "1.0", features = ["derive"] } +tinytemplate = "1.2.1" # used for the SDP creation +wasm-bindgen = "0.2.87" +wasm-bindgen-futures = "0.4.37" +web-sys = { version = "0.3.64", features = [ + "MessageEvent", + "RtcPeerConnection", + "RtcSignalingState", + "RtcSdpType", + "RtcSessionDescription", + "RtcSessionDescriptionInit", + "RtcPeerConnectionIceEvent", + "RtcIceCandidate", + "RtcDataChannel", + "RtcDataChannelEvent", + "RtcCertificate", + "RtcConfiguration", + "RtcDataChannelInit", + "RtcDataChannelType", + "RtcDataChannelState", +] } + +[dev-dependencies] +anyhow = "1.0" +hex-literal = "0.4" diff --git a/transports/webrtc-websys/src/cbfutures.rs b/transports/webrtc-websys/src/cbfutures.rs new file mode 100644 index 00000000000..762251d8c48 --- /dev/null +++ b/transports/webrtc-websys/src/cbfutures.rs @@ -0,0 +1,56 @@ +use std::cell::Cell; +use std::future::Future; +use std::pin::Pin; +use std::rc::Rc; +use std::task::{Context, Poll, Waker}; + +#[derive(Clone, Debug)] +pub struct CbFuture(Rc>); + +struct CallbackFutureInner { + waker: Cell>, + result: Cell>, +} + +impl std::fmt::Debug for CallbackFutureInner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CallbackFutureInner").finish() + } +} + +impl Default for CallbackFutureInner { + fn default() -> Self { + Self { + waker: Cell::new(None), + result: Cell::new(None), + } + } +} + +impl CbFuture { + /// New Callback Future + pub fn new() -> Self { + Self(Rc::new(CallbackFutureInner::::default())) + } + + // call this from your callback + pub fn publish(&self, result: T) { + self.0.result.set(Some(result)); + if let Some(w) = self.0.waker.take() { + w.wake() + }; + } +} + +impl Future for CbFuture { + type Output = T; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.0.result.take() { + Some(x) => Poll::Ready(x), + None => { + self.0.waker.set(Some(cx.waker().clone())); + Poll::Pending + } + } + } +} diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs new file mode 100644 index 00000000000..33d922a3dfb --- /dev/null +++ b/transports/webrtc-websys/src/connection.rs @@ -0,0 +1,218 @@ +//! Websys WebRTC Connection +//! +use crate::cbfutures::CbFuture; +use crate::fingerprint::Fingerprint; +use crate::stream::DataChannelConfig; +use crate::upgrade::{self}; +use crate::utils; +use crate::{Error, WebRTCStream}; +use futures::FutureExt; +use js_sys::Object; +use js_sys::Reflect; +use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; +use libp2p_identity::{Keypair, PeerId}; +use send_wrapper::SendWrapper; +use std::net::SocketAddr; +use std::pin::Pin; +use std::task::{ready, Context, Poll}; +use wasm_bindgen_futures::JsFuture; +use web_sys::{RtcConfiguration, RtcDataChannel, RtcDataChannelInit, RtcPeerConnection}; + +pub const SHA2_256: u64 = 0x12; +pub const SHA2_512: u64 = 0x13; + +pub struct Connection { + // Swarm needs all types to be Send. WASM is single-threaded + // and it is safe to use SendWrapper. + inner: SendWrapper, +} + +struct ConnectionInner { + peer_connection: Option, + sock_addr: SocketAddr, + remote_fingerprint: Fingerprint, + id_keys: Keypair, + create_data_channel_cbfuture: CbFuture, + closed: bool, +} + +impl Connection { + /// Create a new Connection + pub fn new(sock_addr: SocketAddr, remote_fingerprint: Fingerprint, id_keys: Keypair) -> Self { + Self { + inner: SendWrapper::new(ConnectionInner::new(sock_addr, remote_fingerprint, id_keys)), + } + } + + /// Connect + pub async fn connect(&mut self) -> Result { + let fut = SendWrapper::new(self.inner.connect()); + fut.await + } + + /// Peer Connection Getter + pub fn peer_connection(&self) -> Option<&RtcPeerConnection> { + self.inner.peer_connection.as_ref() + } +} + +impl ConnectionInner { + pub fn new(sock_addr: SocketAddr, remote_fingerprint: Fingerprint, id_keys: Keypair) -> Self { + Self { + peer_connection: None, + sock_addr, + remote_fingerprint, + id_keys, + create_data_channel_cbfuture: CbFuture::new(), + closed: false, + } + } + + pub async fn connect(&mut self) -> Result { + let hash = match self.remote_fingerprint.to_multihash().code() { + SHA2_256 => "sha-256", + SHA2_512 => "sha2-512", + _ => return Err(Error::JsError("unsupported hash".to_string())), + }; + + // let keygen_algorithm = json!({ + // "name": "ECDSA", + // "namedCurve": "P-256", + // "hash": hash + // }); + + let algo: js_sys::Object = Object::new(); + Reflect::set(&algo, &"name".into(), &"ECDSA".into()).unwrap(); + Reflect::set(&algo, &"namedCurve".into(), &"P-256".into()).unwrap(); + Reflect::set(&algo, &"hash".into(), &hash.into()).unwrap(); + + let certificate_promise = RtcPeerConnection::generate_certificate_with_object(&algo) + .expect("certificate to be valid"); + + let certificate = JsFuture::from(certificate_promise).await?; // Needs to be Send + + let mut config = RtcConfiguration::new(); + config.certificates(&certificate); + + let peer_connection = web_sys::RtcPeerConnection::new_with_configuration(&config)?; + + let ufrag = format!("libp2p+webrtc+v1/{}", utils::gen_ufrag(32)); + /* + * OFFER + */ + let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send + let offer_obj = crate::sdp::offer(offer, &ufrag); + let sld_promise = peer_connection.set_local_description(&offer_obj); + JsFuture::from(sld_promise).await?; + + /* + * ANSWER + */ + let answer_obj = crate::sdp::answer(self.sock_addr, &self.remote_fingerprint, &ufrag); + let srd_promise = peer_connection.set_remote_description(&answer_obj); + JsFuture::from(srd_promise).await?; + + let peer_id = upgrade::outbound( + &peer_connection, + self.id_keys.clone(), + self.remote_fingerprint, + ) + .await?; + + self.peer_connection = Some(peer_connection); + Ok(peer_id) + } + + /// Initiates and polls a future from `create_data_channel`. + fn poll_create_data_channel( + &mut self, + cx: &mut Context, + config: DataChannelConfig, + ) -> Poll> { + // Create Data Channel + // take the peer_connection and DataChannelConfig and create a pollable future + let mut dc = + crate::stream::create_data_channel(&self.peer_connection.as_ref().unwrap(), config); + + let val = ready!(dc.poll_unpin(cx)); + + let channel = WebRTCStream::new(val); + + Poll::Ready(Ok(channel)) + } + + /// Polls Incoming Peer Connections? Or Data Channels? + pub fn poll_incoming(&mut self, cx: &mut Context) -> Poll> { + let mut dc = crate::stream::create_data_channel( + &self.peer_connection.as_ref().unwrap(), + DataChannelConfig::default(), + ); + + let val = ready!(dc.poll_unpin(cx)); + + let channel = WebRTCStream::new(val); + + Poll::Ready(Ok(channel)) + } + + /// Closes the Peer Connection. + /// + /// This closes the data channels also and they will return an error + /// if they are used. + fn close_connection(&mut self) { + match (&self.peer_connection, self.closed) { + (Some(conn), false) => { + conn.close(); + self.closed = true; + } + _ => (), + } + } +} + +impl Drop for ConnectionInner { + fn drop(&mut self) { + self.close_connection(); + } +} + +/// WebRTC native multiplexing +/// Allows users to open substreams +impl StreamMuxer for Connection { + type Substream = WebRTCStream; // A Substream of a WebRTC PeerConnection is a Data Channel + type Error = Error; + + fn poll_inbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + // Inbound substreams for Browser WebRTC? + // Can only be done through a relayed connection + self.inner.poll_incoming(cx) + } + + fn poll_outbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + // Since this is not a initial handshake outbound request (ie. Dialer) + // we need to create a new Data Channel without negotiated flag set to true + let config = DataChannelConfig::default(); + self.inner.poll_create_data_channel(cx, config) + } + + fn poll_close( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + self.inner.close_connection(); + Poll::Ready(Ok(())) + } + + fn poll( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Pending + } +} diff --git a/transports/webrtc-websys/src/error.rs b/transports/webrtc-websys/src/error.rs index fa7cfcf4cb9..887b040317a 100644 --- a/transports/webrtc-websys/src/error.rs +++ b/transports/webrtc-websys/src/error.rs @@ -39,9 +39,15 @@ impl Error { } } -// implement `std::convert::From` for `error::Error` impl std::convert::From for Error { fn from(value: JsValue) -> Self { Error::from_js_value(value) } } + +// impl From String +impl From for Error { + fn from(value: String) -> Self { + Error::JsError(value) + } +} diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs new file mode 100644 index 00000000000..3fb0dc1aa4c --- /dev/null +++ b/transports/webrtc-websys/src/stream.rs @@ -0,0 +1,289 @@ +//! The Substream over the Connection +use crate::cbfutures::CbFuture; +use futures::future::poll_fn; +use futures::{AsyncRead, AsyncWrite, FutureExt}; +use send_wrapper::SendWrapper; +use std::io; +use std::pin::Pin; +use std::task::{ready, Context, Poll}; +use wasm_bindgen::prelude::*; +use web_sys::{ + MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelInit, RtcDataChannelState, + RtcDataChannelType, RtcPeerConnection, +}; + +macro_rules! console_log { + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) +} +macro_rules! console_warn { + ($($t:tt)*) => (warn(&format_args!($($t)*).to_string())) +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); + #[wasm_bindgen(js_namespace = console)] + fn warn(s: &str); +} +// Max message size that can be sent to the DataChannel +const MAX_MESSAGE_SIZE: u32 = 16 * 1024; + +// How much can be buffered to the DataChannel at once +const MAX_BUFFERED_AMOUNT: u32 = 16 * 1024 * 1024; + +// How long time we wait for the 'bufferedamountlow' event to be emitted +const BUFFERED_AMOUNT_LOW_TIMEOUT: u32 = 30 * 1000; + +#[derive(Debug)] +pub struct DataChannelConfig { + negotiated: bool, + id: u16, + binary_type: RtcDataChannelType, +} + +impl Default for DataChannelConfig { + fn default() -> Self { + Self { + negotiated: false, + id: 0, + /// We set our default to Arraybuffer + binary_type: RtcDataChannelType::Arraybuffer, // Blob is the default in the Browser, + } + } +} + +/// Builds a Data Channel with selected options and given peer connection +/// +/// +impl DataChannelConfig { + pub fn new() -> Self { + Self::default() + } + + pub fn negotiated(&mut self, negotiated: bool) -> &mut Self { + self.negotiated = negotiated; + self + } + + /// Set a custom id for the Data Channel + pub fn id(&mut self, id: u16) -> &mut Self { + self.id = id; + self + } + + // Set the binary type for the Data Channel + // TODO: We'll likely never use this, all channels are created with Arraybuffer? + // pub fn binary_type(&mut self, binary_type: RtcDataChannelType) -> &mut Self { + // self.binary_type = binary_type; + // self + // } + + /// Opens a WebRTC DataChannel for [RtcPeerConnection] with selected [DataChannelConfig] + /// We can cretae `ondatachannel` future before building this + /// then await it after building this + pub fn open(&self, peer_connection: &RtcPeerConnection) -> RtcDataChannel { + const LABEL: &str = ""; + + let mut dc = match self.negotiated { + true => { + let mut data_channel_dict = RtcDataChannelInit::new(); + data_channel_dict.negotiated(true).id(self.id); + peer_connection + .create_data_channel_with_data_channel_dict(LABEL, &data_channel_dict) + } + false => peer_connection.create_data_channel(LABEL), + }; + dc.set_binary_type(self.binary_type); + dc + } +} + +/// Substream over the Connection +pub struct WebRTCStream { + inner: SendWrapper, +} + +impl WebRTCStream { + /// Create a new Substream + pub fn new(channel: RtcDataChannel) -> Self { + Self { + inner: SendWrapper::new(StreamInner::new(channel)), + } + } +} + +#[derive(Debug, Clone)] +struct StreamInner { + channel: RtcDataChannel, + onclose_fut: CbFuture<()>, + // incoming_data: FusedJsPromise, + // message_queue: Vec, + // ondatachannel_fut: CbFuture, +} + +impl StreamInner { + pub fn new(channel: RtcDataChannel) -> Self { + let onclose_fut = CbFuture::new(); + let cback_clone = onclose_fut.clone(); + + let onclose_callback = Closure::::new(move |ev: RtcDataChannelEvent| { + console_log!("Data Channel closed. onclose_callback"); + cback_clone.publish(()); + }); + + channel.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); + + Self { + channel, + onclose_fut, + // incoming_data: FusedJsPromise::new(), + // message_queue: Vec::new(), + // ondatachannel_fut: CbFuture::new(), + } + } +} + +pub fn create_data_channel( + conn: &RtcPeerConnection, + config: DataChannelConfig, +) -> CbFuture { + // peer_connection.set_ondatachannel is callback based + // but we need a Future we can poll + // so we convert this callback into a Future by using [CbFuture] + + // 1. create the ondatachannel callbackFuture + // 2. build the channel with the DataChannelConfig + // 3. await the ondatachannel callbackFutures + // 4. Now we have a ready DataChannel + let ondatachannel_fut = CbFuture::new(); + let cback_clone = ondatachannel_fut.clone(); + + // set up callback and futures + // set_onopen callback to wake the Rust Future + let ondatachannel_callback = Closure::::new(move |ev: RtcDataChannelEvent| { + let dc2 = ev.channel(); + console_log!("pc2.ondatachannel!: {:?}", dc2.label()); + + cback_clone.publish(dc2); + }); + + conn.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); + + let dc = config.open(&conn); + + ondatachannel_fut +} + +impl AsyncRead for WebRTCStream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + // self.inner.poll_read(cx, buf) + let mut onmessage_fut = CbFuture::new(); + let cback_clone = onmessage_fut.clone(); + + let onmessage_callback = Closure::::new(move |ev: MessageEvent| { + let data = ev.data(); + let data = js_sys::Uint8Array::from(data); + let data = data.to_vec(); + console_log!("onmessage: {:?}", data); + cback_clone.publish(data); + }); + + self.inner + .channel + .set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref())); + + // poll onmessage_fut + let data = ready!(onmessage_fut.poll_unpin(cx)); + let data_len = data.len(); + let buf_len = buf.len(); + let len = std::cmp::min(data_len, buf_len); + buf[..len].copy_from_slice(&data[..len]); + Poll::Ready(Ok(len)) + } +} + +impl AsyncWrite for WebRTCStream { + /// Attempt to write bytes from buf into the object. + /// On success, returns Poll::Ready(Ok(num_bytes_written)). + /// If the object is not ready for writing, + /// the method returns Poll::Pending and + /// arranges for the current task (via cx.waker().wake_by_ref()) + /// to receive a notification when the object becomes writable + /// or is closed. + /// + /// In WebRTC DataChannels, we can always write to the channel + /// so we don't need to poll the channel for writability + /// as long as the channel is open + /// + /// So we need access to the channel or peer_connection, + /// and the State of the Channel (DataChannel.readyState) + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], + ) -> Poll> { + match self.inner.channel.ready_state() { + RtcDataChannelState::Connecting => { + // TODO: Buffer message queue + console_log!("DataChannel is Connecting"); + Poll::Pending + } + RtcDataChannelState::Open => { + console_log!("DataChannel is Open"); + // let data = js_sys::Uint8Array::from(buf); + self.inner.channel.send_with_u8_array(&buf); + Poll::Ready(Ok(buf.len())) + } + RtcDataChannelState::Closing => { + console_log!("DataChannel is Closing"); + Poll::Pending + } + RtcDataChannelState::Closed => { + console_log!("DataChannel is Closed"); + Poll::Ready(Err(io::Error::new( + io::ErrorKind::BrokenPipe, + "DataChannel is Closed", + ))) + } + RtcDataChannelState::__Nonexhaustive => { + console_log!("DataChannel is __Nonexhaustive"); + Poll::Pending + } + } + // data_channel.send(...) + // self.inner.poll_write(cx, buf) + } + + /// Attempt to flush the object, ensuring that any buffered data reach their destination. + /// + /// On success, returns Poll::Ready(Ok(())). + /// + /// If flushing cannot immediately complete, this method returns Poll::Pending and arranges for the current task (via cx.waker().wake_by_ref()) to receive a notification when the object can make progress towards flushing. + // Flush means that we want to send all the data we have buffered to the + // remote. We don't buffer anything, so we can just return Ready. + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + // can only flush if the channel is open + match self.inner.channel.ready_state() { + RtcDataChannelState::Open => { + // TODO: send data + console_log!("DataChannel is Open"); + Poll::Ready(Ok(())) + } + _ => { + console_log!("DataChannel is not Open, cannot flush"); + Poll::Pending + } + } + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.inner.channel.close(); + ready!(self.inner.onclose_fut.poll_unpin(cx)); + Poll::Ready(Ok(())) + } +} diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs index 8d255291a7f..8a63825619a 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc-websys/src/transport.rs @@ -76,7 +76,7 @@ impl libp2p_core::Transport for Transport { } let config = self.config.clone(); - let connection = Connection::new(sock_addr, server_fingerprint, config.keypair.clone()); + let mut connection = Connection::new(sock_addr, server_fingerprint, config.keypair.clone()); Ok(async move { let peer_id = connection.connect().await?; diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs new file mode 100644 index 00000000000..a6881c64b08 --- /dev/null +++ b/transports/webrtc-websys/src/upgrade.rs @@ -0,0 +1,43 @@ +pub mod noise; + +use crate::fingerprint::Fingerprint; +use crate::sdp; +use crate::stream::{DataChannelConfig, WebRTCStream}; + +use crate::Error; +use libp2p_identity::{Keypair, PeerId}; +use web_sys::{RtcDataChannel, RtcDataChannelInit, RtcPeerConnection}; + +/// Creates a new outbound WebRTC connection. +pub(crate) async fn outbound( + peer_connection: &RtcPeerConnection, + id_keys: Keypair, + remote_fingerprint: Fingerprint, +) -> Result { + let data_channel = create_substream_for_noise_handshake(&peer_connection).await?; + + // get local_fingerprint from local RtcPeerConnection peer_connection certificate + let local_sdp = match &peer_connection.local_description() { + Some(description) => description.sdp(), + None => return Err(Error::JsError("local_description is None".to_string())), + }; + let local_fingerprint = match sdp::fingerprint(&local_sdp) { + Ok(fingerprint) => fingerprint, + Err(e) => return Err(Error::JsError(format!("fingerprint: {}", e))), + }; + + let peer_id = + noise::outbound(id_keys, data_channel, remote_fingerprint, local_fingerprint).await?; + + Ok(peer_id) +} + +pub async fn create_substream_for_noise_handshake( + conn: &RtcPeerConnection, +) -> Result { + // NOTE: the data channel w/ `negotiated` flag set to `true` MUST be created on both ends. + let handshake_data_channel: RtcDataChannel = + DataChannelConfig::new().negotiated(true).id(0).open(&conn); + + Ok(WebRTCStream::new(handshake_data_channel)) +} diff --git a/transports/webrtc-websys/src/upgrade/noise.rs b/transports/webrtc-websys/src/upgrade/noise.rs new file mode 100644 index 00000000000..a2ed241d6bf --- /dev/null +++ b/transports/webrtc-websys/src/upgrade/noise.rs @@ -0,0 +1,104 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; +use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; +use libp2p_identity as identity; +use libp2p_identity::PeerId; +use libp2p_noise as noise; + +use crate::fingerprint::Fingerprint; +use crate::Error; + +pub(crate) async fn inbound( + id_keys: identity::Keypair, + stream: T, + client_fingerprint: Fingerprint, + server_fingerprint: Fingerprint, +) -> Result +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let noise = noise::Config::new(&id_keys) + .unwrap() + .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); + let info = noise.protocol_info().next().unwrap(); + // Note the roles are reversed because it allows the server (webrtc connection responder) to + // send application data 0.5 RTT earlier. + let (peer_id, _io) = noise.upgrade_outbound(stream, info).await?; + + Ok(peer_id) +} + +pub(crate) async fn outbound( + id_keys: identity::Keypair, + stream: T, + server_fingerprint: Fingerprint, + client_fingerprint: Fingerprint, +) -> Result +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let noise = noise::Config::new(&id_keys) + .unwrap() + .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); + let info = noise.protocol_info().next().unwrap(); + // Note the roles are reversed because it allows the server (webrtc connection responder) to + // send application data 0.5 RTT earlier. + let (peer_id, _io) = noise.upgrade_inbound(stream, info).await?; + + Ok(peer_id) +} + +pub(crate) fn noise_prologue( + client_fingerprint: Fingerprint, + server_fingerprint: Fingerprint, +) -> Vec { + let client = client_fingerprint.to_multihash().to_bytes(); + let server = server_fingerprint.to_multihash().to_bytes(); + const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; + let mut out = Vec::with_capacity(PREFIX.len() + client.len() + server.len()); + out.extend_from_slice(PREFIX); + out.extend_from_slice(&client); + out.extend_from_slice(&server); + out +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn noise_prologue_tests() { + let a = Fingerprint::raw(hex!( + "3e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870" + )); + let b = Fingerprint::raw(hex!( + "30fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99" + )); + + let prologue1 = noise_prologue(a, b); + let prologue2 = noise_prologue(b, a); + + assert_eq!(hex::encode(prologue1), "6c69627032702d7765627274632d6e6f6973653a12203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); + assert_eq!(hex::encode(prologue2), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); + } +} From 132c1e65e9b06c2acae5b93f28d8b427797dffd9 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 17:18:12 -0300 Subject: [PATCH 012/235] commit lockfile --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 861b13b00af..555e84505d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5040,9 +5040,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" dependencies = [ "indexmap 2.0.0", "itoa", From 15fa4775a8885babf4c951227f1f65774cfd3c10 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 17:20:23 -0300 Subject: [PATCH 013/235] commit lockfile --- Cargo.lock | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 861b13b00af..2af2542e948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -753,6 +753,15 @@ dependencies = [ "serde", ] +[[package]] +name = "callback-future" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b76a63a41df0ba9490dd817a3d98511c6d3d9e530eff9bcf5f3de2a6723d1c3f" +dependencies = [ + "futures", +] + [[package]] name = "cast" version = "0.3.0" @@ -3305,6 +3314,35 @@ dependencies = [ "webrtc", ] +[[package]] +name = "libp2p-webrtc-websys" +version = "0.1.0" +dependencies = [ + "anyhow", + "callback-future", + "futures", + "getrandom 0.2.10", + "hex", + "hex-literal", + "js-sys", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "log", + "multiaddr", + "multihash", + "regex", + "send_wrapper 0.6.0", + "serde", + "serde_json", + "sha2 0.10.7", + "thiserror", + "tinytemplate", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "libp2p-websocket" version = "0.42.0" @@ -5040,9 +5078,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" dependencies = [ "indexmap 2.0.0", "itoa", @@ -5618,6 +5656,17 @@ dependencies = [ "webpki 0.22.0", ] +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite 0.2.9", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -6425,6 +6474,41 @@ dependencies = [ "winapi", ] +[[package]] +name = "webrtc-websys-test-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "futures", + "libp2p", + "libp2p-identity", + "libp2p-webrtc", + "rand 0.8.5", + "tokio", + "tokio-stream", + "tokio-util", + "tower-http", + "void", + "webrtc-websys-tests", +] + +[[package]] +name = "webrtc-websys-tests" +version = "0.1.0" +dependencies = [ + "futures", + "getrandom 0.2.10", + "libp2p", + "libp2p-identity", + "multiaddr", + "multihash", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + [[package]] name = "webtransport-tests" version = "0.1.0" From dd30772f52b35f58d9dc50d4c4296d68cc818b57 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 17:31:46 -0300 Subject: [PATCH 014/235] add license = "MIT" --- transports/webrtc-websys/Cargo.toml | 1 + wasm-tests/webrtc/server/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index 70861491148..b08d205f88f 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -3,6 +3,7 @@ name = "libp2p-webrtc-websys" version = "0.1.0" edition = "2021" rust-version.workspace = true +license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/wasm-tests/webrtc/server/Cargo.toml b/wasm-tests/webrtc/server/Cargo.toml index a03ca5ba9ab..8e9c018ea14 100644 --- a/wasm-tests/webrtc/server/Cargo.toml +++ b/wasm-tests/webrtc/server/Cargo.toml @@ -2,6 +2,7 @@ name = "webrtc-websys-test-server" version = "0.1.0" edition = "2021" +license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 8e3a65e049aa04de4e22eb756e8f31d81c541cbb Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 17:35:39 -0300 Subject: [PATCH 015/235] rm unused code --- transports/webrtc-websys/src/fingerprint.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/transports/webrtc-websys/src/fingerprint.rs b/transports/webrtc-websys/src/fingerprint.rs index 251e3efa87e..d2dac63e032 100644 --- a/transports/webrtc-websys/src/fingerprint.rs +++ b/transports/webrtc-websys/src/fingerprint.rs @@ -67,22 +67,6 @@ impl Fingerprint { self.0.map(|byte| format!("{byte:02X}")).join(":") } - /// From SDP format - /// fingerprintRegex = /^a=fingerprint:(?:\w+-[0-9]+)\s(?(:?[0-9a-fA-F]{2})+)$/m - pub fn from_sdp_format(sdp_format: &str) -> Option { - let mut bytes = [0u8; 32]; - let mut i = 0; - for byte in sdp_format.split(':') { - if byte.len() != 2 { - return None; - } - let byte = u8::from_str_radix(byte, 16).ok()?; - bytes[i] = byte; - i += 1; - } - Some(Self(bytes)) - } - /// Returns the algorithm used (e.g. "sha-256"). /// See pub fn algorithm(&self) -> String { From ee537f241ff82b303a4c71745db9a41d15455220 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 24 Jul 2023 19:26:55 -0300 Subject: [PATCH 016/235] corrections for ci --- Cargo.lock | 5 ++++- Cargo.toml | 1 + wasm-tests/webrtc/server/Cargo.toml | 10 +++++----- wasm-tests/webrtc/server/src/main.rs | 20 ++++++++------------ wasm-tests/webrtc/src/lib.rs | 19 +++++-------------- 5 files changed, 23 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4429a8828e..26389c475bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6505,9 +6505,12 @@ dependencies = [ "anyhow", "axum", "futures", - "libp2p", + "libp2p-core", "libp2p-identity", + "libp2p-ping", + "libp2p-relay", "libp2p-webrtc", + "multiaddr", "rand 0.8.5", "tokio", "tokio-stream", diff --git a/Cargo.toml b/Cargo.toml index 2c914ef8f33..dec93646969 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,6 +98,7 @@ libp2p-tls = { version = "0.2.0", path = "transports/tls" } libp2p-uds = { version = "0.39.0", path = "transports/uds" } libp2p-wasm-ext = { version = "0.40.0", path = "transports/wasm-ext" } libp2p-webrtc = { version = "0.6.0-alpha", path = "transports/webrtc" } +libp2p-webrtc-websys = { version = "0.1.0", path = "transports/webrtc-websys" } 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" } diff --git a/wasm-tests/webrtc/server/Cargo.toml b/wasm-tests/webrtc/server/Cargo.toml index 8e9c018ea14..f4048ac8704 100644 --- a/wasm-tests/webrtc/server/Cargo.toml +++ b/wasm-tests/webrtc/server/Cargo.toml @@ -13,14 +13,14 @@ futures = "0.3.28" rand = "0.8" void = "1" libp2p-identity = { workspace = true } +libp2p-core = { workspace = true, features = [] } +libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } +multiaddr = { workspace = true } +libp2p-ping = { workspace = true } +libp2p-relay = { workspace = true } tokio = { version = "1.29", features = ["rt", "macros", "signal", "net"] } tokio-util = { version = "0.7", features = ["compat"] } tokio-stream = "0.1" libp2p-webrtc = { workspace = true, features = ["tokio"] } webrtc-websys-tests = { path = "../" } # for PORT tower-http = { version = "0.4.0", features = ["cors"] } # axum middleware - -[dependencies.libp2p] -path = "../../../libp2p" -version = "0.52.1" -features = ["tokio", "noise", "relay", "ping", "macros"] diff --git a/wasm-tests/webrtc/server/src/main.rs b/wasm-tests/webrtc/server/src/main.rs index 40bc3056527..859cb36cf0f 100644 --- a/wasm-tests/webrtc/server/src/main.rs +++ b/wasm-tests/webrtc/server/src/main.rs @@ -1,18 +1,14 @@ use anyhow::Result; -use axum::{ - http::{HeaderValue, Method}, - routing::get, - Router, -}; +use axum::{http::Method, routing::get, Router}; use futures::StreamExt; -use libp2p::core::muxing::StreamMuxerBox; -use libp2p::core::Transport; -use libp2p::multiaddr::{Multiaddr, Protocol}; -use libp2p::ping; -use libp2p::relay; -use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmBuilder, SwarmEvent}; +use libp2p_core::muxing::StreamMuxerBox; +use libp2p_core::Transport; use libp2p_identity as identity; +use libp2p_ping as ping; +use libp2p_relay as relay; +use libp2p_swarm::{keep_alive, NetworkBehaviour, SwarmBuilder, SwarmEvent}; use libp2p_webrtc as webrtc; +use multiaddr::{Multiaddr, Protocol}; use rand::thread_rng; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use tower_http::cors::{Any, CorsLayer}; @@ -89,7 +85,7 @@ async fn main() -> Result<()> { } #[derive(NetworkBehaviour)] -#[behaviour(to_swarm = "Event", prelude = "libp2p::swarm::derive_prelude")] +#[behaviour(to_swarm = "Event", prelude = "libp2p_swarm::derive_prelude")] struct Behaviour { ping: ping::Behaviour, keep_alive: keep_alive::Behaviour, diff --git a/wasm-tests/webrtc/src/lib.rs b/wasm-tests/webrtc/src/lib.rs index e39030c3497..841b4617cb4 100644 --- a/wasm-tests/webrtc/src/lib.rs +++ b/wasm-tests/webrtc/src/lib.rs @@ -1,16 +1,7 @@ -use futures::channel::oneshot; -use futures::{AsyncReadExt, AsyncWriteExt}; -use getrandom::getrandom; -use libp2p::core::{StreamMuxer, Transport as _}; -use libp2p::noise; -use libp2p_identity::{Keypair, PeerId}; -// 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 libp2p_identity::Keypair; +use multiaddr::Multiaddr; use wasm_bindgen::JsCast; -use wasm_bindgen_futures::{spawn_local, JsFuture}; +use wasm_bindgen_futures::JsFuture; use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; use web_sys::{window, Response}; @@ -20,8 +11,8 @@ pub const PORT: u16 = 4455; #[wasm_bindgen_test] async fn connect_without_peer_id() { - let mut addr = fetch_server_addr().await; - let keypair = Keypair::generate_ed25519(); + let addr = fetch_server_addr().await; + let _keypair = Keypair::generate_ed25519(); // eprintln eprintln!("addr: {:?}", addr); From 56e66dacc987cfb579a99fe52aa774385aa0328e Mon Sep 17 00:00:00 2001 From: Doug A Date: Tue, 25 Jul 2023 16:15:35 -0300 Subject: [PATCH 017/235] add webrtc-websys-test-server --- Cargo.lock | 1 + Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 26389c475bc..2c1d0887efe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6509,6 +6509,7 @@ dependencies = [ "libp2p-identity", "libp2p-ping", "libp2p-relay", + "libp2p-swarm", "libp2p-webrtc", "multiaddr", "rand 0.8.5", diff --git a/Cargo.toml b/Cargo.toml index dec93646969..c6e17930d52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ quickcheck = { package = "quickcheck-ext", path = "misc/quickcheck-ext" } rw-stream-sink = { version = "0.4.0", path = "misc/rw-stream-sink" } multiaddr = "0.18.0" multihash = "0.19.0" - +webrtc-websys-test-server = { version = "0.1.0", path = "wasm-tests/webrtc/server" } [patch.crates-io] From 0302655258324226d917ebea0e8fd116987510dc Mon Sep 17 00:00:00 2001 From: Doug A Date: Tue, 25 Jul 2023 16:15:46 -0300 Subject: [PATCH 018/235] clean up deps for ci --- transports/webrtc-websys/src/connection.rs | 11 +- transports/webrtc-websys/src/error.rs | 1 - transports/webrtc-websys/src/stream.rs | 12 +- transports/webrtc-websys/src/upgrade/noise.rs | 2 +- transports/webrtc-websys/src/utils.rs | 137 +++++++++--------- 5 files changed, 77 insertions(+), 86 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 33d922a3dfb..2d7ac736107 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -16,7 +16,7 @@ use std::net::SocketAddr; use std::pin::Pin; use std::task::{ready, Context, Poll}; use wasm_bindgen_futures::JsFuture; -use web_sys::{RtcConfiguration, RtcDataChannel, RtcDataChannelInit, RtcPeerConnection}; +use web_sys::{RtcConfiguration, RtcDataChannel, RtcPeerConnection}; pub const SHA2_256: u64 = 0x12; pub const SHA2_512: u64 = 0x13; @@ -160,12 +160,9 @@ impl ConnectionInner { /// This closes the data channels also and they will return an error /// if they are used. fn close_connection(&mut self) { - match (&self.peer_connection, self.closed) { - (Some(conn), false) => { - conn.close(); - self.closed = true; - } - _ => (), + if let (Some(conn), false) = (&self.peer_connection, self.closed) { + conn.close(); + self.closed = true; } } } diff --git a/transports/webrtc-websys/src/error.rs b/transports/webrtc-websys/src/error.rs index 887b040317a..6baf6685f34 100644 --- a/transports/webrtc-websys/src/error.rs +++ b/transports/webrtc-websys/src/error.rs @@ -1,4 +1,3 @@ -use libp2p_core::transport::TransportError; use wasm_bindgen::{JsCast, JsValue}; /// Errors that may happen on the [`Transport`](crate::Transport) or the diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 3fb0dc1aa4c..462fad83c03 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -1,6 +1,5 @@ //! The Substream over the Connection use crate::cbfutures::CbFuture; -use futures::future::poll_fn; use futures::{AsyncRead, AsyncWrite, FutureExt}; use send_wrapper::SendWrapper; use std::io; @@ -15,9 +14,6 @@ use web_sys::{ macro_rules! console_log { ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) } -macro_rules! console_warn { - ($($t:tt)*) => (warn(&format_args!($($t)*).to_string())) -} #[wasm_bindgen] extern "C" { @@ -85,7 +81,7 @@ impl DataChannelConfig { pub fn open(&self, peer_connection: &RtcPeerConnection) -> RtcDataChannel { const LABEL: &str = ""; - let mut dc = match self.negotiated { + let dc = match self.negotiated { true => { let mut data_channel_dict = RtcDataChannelInit::new(); data_channel_dict.negotiated(true).id(self.id); @@ -127,7 +123,7 @@ impl StreamInner { let onclose_fut = CbFuture::new(); let cback_clone = onclose_fut.clone(); - let onclose_callback = Closure::::new(move |ev: RtcDataChannelEvent| { + let onclose_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { console_log!("Data Channel closed. onclose_callback"); cback_clone.publish(()); }); @@ -170,7 +166,7 @@ pub fn create_data_channel( conn.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); - let dc = config.open(&conn); + let _dc = config.open(conn); ondatachannel_fut } @@ -236,7 +232,7 @@ impl AsyncWrite for WebRTCStream { RtcDataChannelState::Open => { console_log!("DataChannel is Open"); // let data = js_sys::Uint8Array::from(buf); - self.inner.channel.send_with_u8_array(&buf); + let _ = self.inner.channel.send_with_u8_array(&buf); Poll::Ready(Ok(buf.len())) } RtcDataChannelState::Closing => { diff --git a/transports/webrtc-websys/src/upgrade/noise.rs b/transports/webrtc-websys/src/upgrade/noise.rs index a2ed241d6bf..c132436c4d7 100644 --- a/transports/webrtc-websys/src/upgrade/noise.rs +++ b/transports/webrtc-websys/src/upgrade/noise.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; +use futures::{AsyncRead, AsyncWrite}; use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use libp2p_identity as identity; use libp2p_identity::PeerId; diff --git a/transports/webrtc-websys/src/utils.rs b/transports/webrtc-websys/src/utils.rs index 62a4cb8ce7d..39962cf6c9d 100644 --- a/transports/webrtc-websys/src/utils.rs +++ b/transports/webrtc-websys/src/utils.rs @@ -1,69 +1,68 @@ -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) - } -} - -pub fn gen_ufrag(len: usize) -> String { - let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - let mut ufrag = String::new(); - let mut buf = vec![0; len]; - getrandom::getrandom(&mut buf).unwrap(); - for i in buf { - let idx = i as usize % charset.len(); - ufrag.push(charset.chars().nth(idx).unwrap()); - } - ufrag -} +use js_sys::Promise; +use send_wrapper::SendWrapper; +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) + } +} + +pub(crate) fn gen_ufrag(len: usize) -> String { + let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + let mut ufrag = String::new(); + let mut buf = vec![0; len]; + getrandom::getrandom(&mut buf).unwrap(); + for i in buf { + let idx = i as usize % charset.len(); + ufrag.push(charset.chars().nth(idx).unwrap()); + } + ufrag +} From 74ca3c0d2c72d2f695e672ea12365e46397e4784 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 26 Jul 2023 16:55:23 -0300 Subject: [PATCH 019/235] configure Manifest for split: tokio/websys --- transports/webrtc/Cargo.toml | 58 ++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 70d5553e293..db3b37ef179 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -17,28 +17,74 @@ bytes = "1" futures = "0.3" futures-timer = "3" hex = "0.4" -if-watch = "3.0" libp2p-core = { workspace = true } libp2p-noise = { workspace = true } libp2p-identity = { workspace = true } log = "0.4" sha2 = "0.10.7" -multihash = { workspace = true } +multihash = { workspace = true } quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } -rand = "0.8" rcgen = "0.10.0" serde = { version = "1.0", features = ["derive"] } -stun = "0.4" thiserror = "1" tinytemplate = "1.2" -tokio = { version = "1.29", features = ["net"], optional = true} + +# tokio optional deps +tokio = { version = "1.29", features = ["net"], optional = true } tokio-util = { version = "0.7", features = ["compat"], optional = true } webrtc = { version = "0.8.0", optional = true } +stun = { version = "0.4", optional = true } +rand = { version = "0.8", optional = true } +if-watch = { version = "3.0", optional = true } + +# wasm-bindgen optional deps +js-sys = { version = "0.3", optional = true } +getrandom = { version = "0.2.9", features = ["js"], optional = true } +regex = { version = "1.9", optional = true } +send_wrapper = { version = "0.6.0", features = ["futures"], optional = true } +wasm-bindgen = { version = "0.2.87", optional = true } +wasm-bindgen-futures = { version = "0.4.37", optional = true } +web-sys = { version = "0.3.64", optional = true, features = [ + "MessageEvent", + "RtcPeerConnection", + "RtcSignalingState", + "RtcSdpType", + "RtcSessionDescription", + "RtcSessionDescriptionInit", + "RtcPeerConnectionIceEvent", + "RtcIceCandidate", + "RtcDataChannel", + "RtcDataChannelEvent", + "RtcCertificate", + "RtcConfiguration", + "RtcDataChannelInit", + "RtcDataChannelType", + "RtcDataChannelState", +] } [features] -tokio = ["dep:tokio", "dep:tokio-util", "dep:webrtc", "if-watch/tokio"] +tokio = [ + "dep:tokio", + "dep:tokio-util", + "dep:webrtc", + "dep:if-watch", + "if-watch/tokio", + "dep:rand", + "dep:stun", +] pem = ["webrtc?/pem"] +wasm-bindgen = [ + "dep:js-sys", + "dep:getrandom", + "dep:regex", + "dep:send_wrapper", + "dep:wasm-bindgen", + "dep:wasm-bindgen-futures", + "dep:web-sys", + "futures-timer/wasm-bindgen", +] + [dev-dependencies] anyhow = "1.0" From 950569fd9768995a9e844a1ac32f1c139feafe8f Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 26 Jul 2023 16:56:10 -0300 Subject: [PATCH 020/235] mv into /webrtc/websys path under feature flag --- Cargo.lock | 497 +++++++----------- Cargo.toml | 2 - transports/webrtc-websys/Cargo.toml | 51 -- transports/webrtc-websys/src/upgrade.rs | 43 -- transports/webrtc/src/lib.rs | 3 + .../src => webrtc/src/websys}/cbfutures.rs | 4 +- .../src => webrtc/src/websys}/connection.rs | 68 ++- .../src => webrtc/src/websys}/error.rs | 0 .../src => webrtc/src/websys}/fingerprint.rs | 0 .../src/websys}/fused_js_promise.rs | 0 .../src/lib.rs => webrtc/src/websys/mod.rs} | 2 +- .../src => webrtc/src/websys}/sdp.rs | 27 +- .../src => webrtc/src/websys}/stream.rs | 64 ++- .../src => webrtc/src/websys}/transport.rs | 8 +- transports/webrtc/src/websys/upgrade.rs | 43 ++ .../src/websys}/upgrade/noise.rs | 4 +- .../src => webrtc/src/websys}/utils.rs | 2 +- wasm-tests/webrtc/Cargo.toml | 6 +- wasm-tests/webrtc/README.md | 4 +- wasm-tests/webrtc/server/Cargo.toml | 2 +- 20 files changed, 378 insertions(+), 452 deletions(-) delete mode 100644 transports/webrtc-websys/Cargo.toml delete mode 100644 transports/webrtc-websys/src/upgrade.rs rename transports/{webrtc-websys/src => webrtc/src/websys}/cbfutures.rs (94%) rename transports/{webrtc-websys/src => webrtc/src/websys}/connection.rs (75%) rename transports/{webrtc-websys/src => webrtc/src/websys}/error.rs (100%) rename transports/{webrtc-websys/src => webrtc/src/websys}/fingerprint.rs (100%) rename transports/{webrtc-websys/src => webrtc/src/websys}/fused_js_promise.rs (100%) rename transports/{webrtc-websys/src/lib.rs => webrtc/src/websys/mod.rs} (90%) rename transports/{webrtc-websys/src => webrtc/src/websys}/sdp.rs (91%) rename transports/{webrtc-websys/src => webrtc/src/websys}/stream.rs (81%) rename transports/{webrtc-websys/src => webrtc/src/websys}/transport.rs (96%) create mode 100644 transports/webrtc/src/websys/upgrade.rs rename transports/{webrtc-websys/src => webrtc/src/websys}/upgrade/noise.rs (98%) rename transports/{webrtc-websys/src => webrtc/src/websys}/utils.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 2c1d0887efe..1ae3dc16817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,9 +71,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if 1.0.0", "cipher 0.4.4", @@ -82,14 +82,14 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.9.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" dependencies = [ "aead 0.4.3", "aes 0.7.5", "cipher 0.3.0", - "ctr 0.8.0", + "ctr 0.7.0", "ghash 0.4.4", "subtle", ] @@ -101,7 +101,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" dependencies = [ "aead 0.5.2", - "aes 0.8.2", + "aes 0.8.3", "cipher 0.4.4", "ctr 0.9.2", "ghash 0.5.0", @@ -177,15 +177,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -196,7 +196,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -206,7 +206,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -324,9 +324,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener", @@ -388,7 +388,7 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.19", + "rustix 0.37.23", "slab", "socket2 0.4.9", "waker-fn", @@ -428,9 +428,9 @@ dependencies = [ "cfg-if 1.0.0", "event-listener", "futures-lite", - "rustix 0.37.19", + "rustix 0.37.23", "signal-hook", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -455,7 +455,7 @@ dependencies = [ "log", "memchr", "once_cell", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "pin-utils", "slab", "wasm-bindgen-futures", @@ -503,7 +503,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", ] [[package]] @@ -548,7 +548,7 @@ dependencies = [ "memchr", "mime", "percent-encoding", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "rustversion", "serde", "serde_json", @@ -631,9 +631,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-toml" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1" +checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" dependencies = [ "serde", ] @@ -753,15 +753,6 @@ dependencies = [ "serde", ] -[[package]] -name = "callback-future" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b76a63a41df0ba9490dd817a3d98511c6d3d9e530eff9bcf5f3de2a6723d1c3f" -dependencies = [ - "futures", -] - [[package]] name = "cast" version = "0.3.0" @@ -900,9 +891,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.12" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eab9e8ceb9afdade1ab3f0fd8dbce5b1b2f468ad653baf10e771781b2b67b73" +checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" dependencies = [ "clap_builder", "clap_derive", @@ -911,9 +902,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.12" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2763db829349bf00cfc06251268865ed4363b93a943174f638daf3ecdba2cd" +checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" dependencies = [ "anstream", "anstyle", @@ -954,7 +945,7 @@ dependencies = [ "bytes", "futures-core", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", "tokio-util", ] @@ -980,9 +971,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" [[package]] name = "cookie" @@ -1022,9 +1013,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -1112,22 +1103,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "memoffset 0.8.0", + "memoffset 0.9.0", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if 1.0.0", ] @@ -1183,6 +1174,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "ctr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" +dependencies = [ + "cipher 0.3.0", +] + [[package]] name = "ctr" version = "0.8.0" @@ -1326,9 +1326,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" +checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" dependencies = [ "const-oid", "pem-rfc7468 0.7.0", @@ -1439,9 +1439,9 @@ dependencies = [ [[package]] name = "dtoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65d09067bfacaa79114679b279d7f5885b53295b1e2cfb4e79c8e4bd3d633169" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "ecdsa" @@ -1457,15 +1457,15 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.16.7" +version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ - "der 0.7.6", + "der 0.7.7", "digest 0.10.7", "elliptic-curve 0.13.5", "rfc6979 0.4.0", - "signature 2.0.0", + "signature 2.1.0", "spki 0.7.2", ] @@ -1586,9 +1586,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" @@ -1598,7 +1598,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1794,7 +1794,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "waker-fn", ] @@ -1827,7 +1827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28" dependencies = [ "futures-io", - "rustls 0.21.2", + "rustls 0.21.5", ] [[package]] @@ -1876,7 +1876,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "pin-utils", "slab", ] @@ -1945,7 +1945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ "opaque-debug", - "polyval 0.6.0", + "polyval 0.6.1", ] [[package]] @@ -1996,9 +1996,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -2043,18 +2043,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -2143,14 +2134,14 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", ] [[package]] name = "http-range-header" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] name = "httparse" @@ -2186,7 +2177,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "socket2 0.4.9", "tokio", "tower-service", @@ -2389,9 +2380,9 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2402,7 +2393,7 @@ checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2 0.5.3", "widestring", - "windows-sys 0.48.0", + "windows-sys", "winreg 0.50.0", ] @@ -2431,20 +2422,19 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix 0.37.19", - "windows-sys 0.48.0", + "hermit-abi", + "rustix 0.38.4", + "windows-sys", ] [[package]] @@ -2458,9 +2448,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" @@ -3069,7 +3059,7 @@ dependencies = [ "quickcheck", "quinn-proto", "rand 0.8.5", - "rustls 0.21.2", + "rustls 0.21.5", "thiserror", "tokio", ] @@ -3250,7 +3240,7 @@ dependencies = [ "libp2p-yamux", "rcgen 0.10.0", "ring", - "rustls 0.21.2", + "rustls 0.21.5", "thiserror", "tokio", "webpki 0.22.0", @@ -3293,9 +3283,11 @@ dependencies = [ "env_logger 0.10.0", "futures", "futures-timer", + "getrandom 0.2.10", "hex", "hex-literal", "if-watch", + "js-sys", "libp2p-core", "libp2p-identity", "libp2p-noise", @@ -3308,6 +3300,8 @@ dependencies = [ "quickcheck", "rand 0.8.5", "rcgen 0.10.0", + "regex", + "send_wrapper 0.6.0", "serde", "sha2 0.10.7", "stun", @@ -3317,36 +3311,10 @@ dependencies = [ "tokio-util", "unsigned-varint", "void", - "webrtc", -] - -[[package]] -name = "libp2p-webrtc-websys" -version = "0.1.0" -dependencies = [ - "anyhow", - "callback-future", - "futures", - "getrandom 0.2.10", - "hex", - "hex-literal", - "js-sys", - "libp2p-core", - "libp2p-identity", - "libp2p-noise", - "log", - "multiaddr", - "multihash", - "regex", - "send_wrapper 0.6.0", - "serde", - "serde_json", - "sha2 0.10.7", - "thiserror", - "tinytemplate", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webrtc", ] [[package]] @@ -3560,9 +3528,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -3619,7 +3587,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -3839,11 +3807,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] @@ -3959,7 +3927,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "ecdsa 0.16.7", + "ecdsa 0.16.8", "elliptic-curve 0.13.5", "primeorder", "sha2 0.10.7", @@ -4017,9 +3985,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pem" @@ -4082,9 +4050,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" @@ -4118,7 +4086,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.6", + "der 0.7.7", "spki 0.7.2", ] @@ -4136,9 +4104,9 @@ checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" [[package]] name = "plotters" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", @@ -4149,15 +4117,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] @@ -4174,8 +4142,8 @@ dependencies = [ "concurrent-queue", "libc", "log", - "pin-project-lite 0.2.9", - "windows-sys 0.48.0", + "pin-project-lite 0.2.10", + "windows-sys", ] [[package]] @@ -4186,7 +4154,7 @@ checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", "opaque-debug", - "universal-hash 0.4.1", + "universal-hash 0.4.0", ] [[package]] @@ -4198,14 +4166,14 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "opaque-debug", - "universal-hash 0.4.1", + "universal-hash 0.4.0", ] [[package]] name = "polyval" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -4361,7 +4329,7 @@ dependencies = [ "rand 0.8.5", "ring", "rustc-hash", - "rustls 0.21.2", + "rustls 0.21.5", "slab", "thiserror", "tinyvec", @@ -4507,7 +4475,7 @@ dependencies = [ "futures-util", "itoa", "percent-encoding", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "ryu", "tokio", "tokio-util", @@ -4531,8 +4499,8 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.2", - "regex-syntax 0.7.3", + "regex-automata 0.3.3", + "regex-syntax 0.7.4", ] [[package]] @@ -4546,13 +4514,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.3", + "regex-syntax 0.7.4", ] [[package]] @@ -4563,9 +4531,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "relay-server-example" @@ -4616,7 +4584,7 @@ dependencies = [ "native-tls", "once_cell", "percent-encoding", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "serde", "serde_json", "serde_urlencoded", @@ -4687,9 +4655,9 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" dependencies = [ "byteorder", "num-traits", @@ -4813,16 +4781,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -4835,7 +4803,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.3", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -4865,21 +4833,21 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" +checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" dependencies = [ "log", "ring", - "rustls-webpki 0.100.1", + "rustls-webpki", "sct 0.7.0", ] [[package]] name = "rustls-native-certs" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -4889,28 +4857,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ "base64 0.21.2", ] [[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.101.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" +checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" dependencies = [ "ring", "untrusted", @@ -4918,9 +4876,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "rw-stream-sink" @@ -4934,9 +4892,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "salsa20" @@ -4958,11 +4916,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -4973,9 +4931,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -5030,7 +4988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", - "der 0.7.6", + "der 0.7.7", "generic-array", "pkcs8 0.10.2", "subtle", @@ -5039,9 +4997,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -5052,9 +5010,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -5062,9 +5020,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "send_wrapper" @@ -5115,18 +5073,19 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" dependencies = [ + "itoa", "serde", ] [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "e168eaaf71e8f9bd6037feb05190485708e019f4fd87d161b3c0a0d37daf85e5" dependencies = [ "proc-macro2", "quote", @@ -5214,9 +5173,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", @@ -5243,9 +5202,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", @@ -5298,7 +5257,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733" dependencies = [ - "aes-gcm 0.9.4", + "aes-gcm 0.9.2", "blake2", "chacha20poly1305", "curve25519-dalek 4.0.0-rc.1", @@ -5326,7 +5285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -5367,7 +5326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der 0.7.6", + "der 0.7.7", ] [[package]] @@ -5421,9 +5380,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -5496,7 +5455,7 @@ dependencies = [ "fastrand 2.0.0", "redox_syscall", "rustix 0.38.4", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -5578,9 +5537,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.21" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ "itoa", "serde", @@ -5596,9 +5555,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] @@ -5641,11 +5600,11 @@ dependencies = [ "mio", "num_cpus", "parking_lot", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "signal-hook-registry", "socket2 0.4.9", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -5687,7 +5646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", ] @@ -5701,7 +5660,7 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", "tracing", ] @@ -5715,7 +5674,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", "tower-layer", "tower-service", @@ -5739,7 +5698,7 @@ dependencies = [ "mime", "mime_guess", "percent-encoding", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", "tokio-util", "tower-layer", @@ -5767,16 +5726,16 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", @@ -5887,9 +5846,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "trybuild" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501dbdbb99861e4ab6b60eb6a7493956a9defb644fd034bc4a5ef27c693c8a3a" +checksum = "a84e0202ea606ba5ebee8507ab2bfbe89b98551ed9b8f0be198109275cff284b" dependencies = [ "basic-toml", "glob", @@ -5954,9 +5913,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -5981,9 +5940,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" dependencies = [ "generic-array", "subtle", @@ -6034,9 +5993,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom 0.2.10", ] @@ -6049,9 +6008,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" [[package]] name = "vcpkg" @@ -6098,11 +6057,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -6283,7 +6241,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" dependencies = [ - "rustls-webpki 0.101.1", + "rustls-webpki", ] [[package]] @@ -6527,8 +6485,8 @@ version = "0.1.0" dependencies = [ "futures", "getrandom 0.2.10", - "libp2p", "libp2p-identity", + "libp2p-webrtc", "multiaddr", "multihash", "wasm-bindgen", @@ -6605,21 +6563,6 @@ dependencies = [ "windows_x86_64_msvc 0.34.0", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -6631,25 +6574,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_gnullvm", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_gnullvm", "windows_x86_64_msvc 0.48.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" @@ -6662,12 +6599,6 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.0" @@ -6680,12 +6611,6 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.0" @@ -6698,12 +6623,6 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.0" @@ -6716,24 +6635,12 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" @@ -6746,12 +6653,6 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.0" @@ -6774,7 +6675,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if 1.0.0", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c6e17930d52..c376c79f061 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ members = [ "transports/uds", "transports/wasm-ext", "transports/webrtc", - "transports/webrtc-websys", "transports/websocket", "transports/webtransport-websys", "wasm-tests/webtransport-tests", @@ -98,7 +97,6 @@ libp2p-tls = { version = "0.2.0", path = "transports/tls" } libp2p-uds = { version = "0.39.0", path = "transports/uds" } libp2p-wasm-ext = { version = "0.40.0", path = "transports/wasm-ext" } libp2p-webrtc = { version = "0.6.0-alpha", path = "transports/webrtc" } -libp2p-webrtc-websys = { version = "0.1.0", path = "transports/webrtc-websys" } 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" } diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml deleted file mode 100644 index b08d205f88f..00000000000 --- a/transports/webrtc-websys/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -[package] -name = "libp2p-webrtc-websys" -version = "0.1.0" -edition = "2021" -rust-version.workspace = true -license = "MIT" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -futures = "0.3.28" -callback-future = "0.1.0" # because web_sys::Rtc is callback based, but we need futures -js-sys = "0.3.64" -libp2p-core = { workspace = true } -libp2p-identity = { workspace = true } -libp2p-noise = { workspace = true } -log = "0.4.19" -hex = "0.4" -getrandom = { version = "0.2.9", features = ["js"] } -multiaddr = { workspace = true } -multihash = { workspace = true } -regex = "1.9.1" -send_wrapper = { version = "0.6.0", features = ["futures"] } -thiserror = "1.0.43" -serde_json = "1.0.103" -sha2 = "0.10.7" -serde = { version = "1.0", features = ["derive"] } -tinytemplate = "1.2.1" # used for the SDP creation -wasm-bindgen = "0.2.87" -wasm-bindgen-futures = "0.4.37" -web-sys = { version = "0.3.64", features = [ - "MessageEvent", - "RtcPeerConnection", - "RtcSignalingState", - "RtcSdpType", - "RtcSessionDescription", - "RtcSessionDescriptionInit", - "RtcPeerConnectionIceEvent", - "RtcIceCandidate", - "RtcDataChannel", - "RtcDataChannelEvent", - "RtcCertificate", - "RtcConfiguration", - "RtcDataChannelInit", - "RtcDataChannelType", - "RtcDataChannelState", -] } - -[dev-dependencies] -anyhow = "1.0" -hex-literal = "0.4" diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs deleted file mode 100644 index a6881c64b08..00000000000 --- a/transports/webrtc-websys/src/upgrade.rs +++ /dev/null @@ -1,43 +0,0 @@ -pub mod noise; - -use crate::fingerprint::Fingerprint; -use crate::sdp; -use crate::stream::{DataChannelConfig, WebRTCStream}; - -use crate::Error; -use libp2p_identity::{Keypair, PeerId}; -use web_sys::{RtcDataChannel, RtcDataChannelInit, RtcPeerConnection}; - -/// Creates a new outbound WebRTC connection. -pub(crate) async fn outbound( - peer_connection: &RtcPeerConnection, - id_keys: Keypair, - remote_fingerprint: Fingerprint, -) -> Result { - let data_channel = create_substream_for_noise_handshake(&peer_connection).await?; - - // get local_fingerprint from local RtcPeerConnection peer_connection certificate - let local_sdp = match &peer_connection.local_description() { - Some(description) => description.sdp(), - None => return Err(Error::JsError("local_description is None".to_string())), - }; - let local_fingerprint = match sdp::fingerprint(&local_sdp) { - Ok(fingerprint) => fingerprint, - Err(e) => return Err(Error::JsError(format!("fingerprint: {}", e))), - }; - - let peer_id = - noise::outbound(id_keys, data_channel, remote_fingerprint, local_fingerprint).await?; - - Ok(peer_id) -} - -pub async fn create_substream_for_noise_handshake( - conn: &RtcPeerConnection, -) -> Result { - // NOTE: the data channel w/ `negotiated` flag set to `true` MUST be created on both ends. - let handshake_data_channel: RtcDataChannel = - DataChannelConfig::new().negotiated(true).id(0).open(&conn); - - Ok(WebRTCStream::new(handshake_data_channel)) -} diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 012796a6b69..460f9749ba0 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -88,3 +88,6 @@ mod proto { #[cfg(feature = "tokio")] pub mod tokio; + +#[cfg(feature = "wasm-bindgen")] +pub mod websys; diff --git a/transports/webrtc-websys/src/cbfutures.rs b/transports/webrtc/src/websys/cbfutures.rs similarity index 94% rename from transports/webrtc-websys/src/cbfutures.rs rename to transports/webrtc/src/websys/cbfutures.rs index 762251d8c48..5c1132ea420 100644 --- a/transports/webrtc-websys/src/cbfutures.rs +++ b/transports/webrtc/src/websys/cbfutures.rs @@ -29,12 +29,12 @@ impl Default for CallbackFutureInner { impl CbFuture { /// New Callback Future - pub fn new() -> Self { + pub(crate) fn new() -> Self { Self(Rc::new(CallbackFutureInner::::default())) } // call this from your callback - pub fn publish(&self, result: T) { + pub(crate) fn publish(&self, result: T) { self.0.result.set(Some(result)); if let Some(w) = self.0.waker.take() { w.wake() diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc/src/websys/connection.rs similarity index 75% rename from transports/webrtc-websys/src/connection.rs rename to transports/webrtc/src/websys/connection.rs index 2d7ac736107..a7fe3448319 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc/src/websys/connection.rs @@ -1,11 +1,13 @@ //! Websys WebRTC Connection //! -use crate::cbfutures::CbFuture; -use crate::fingerprint::Fingerprint; -use crate::stream::DataChannelConfig; -use crate::upgrade::{self}; -use crate::utils; -use crate::{Error, WebRTCStream}; +use super::cbfutures::CbFuture; +use super::fingerprint::Fingerprint; +use super::sdp; +use super::stream::DataChannelConfig; +use super::upgrade::{self}; +use super::utils; +use super::{Error, WebRTCStream}; +use futures::join; use futures::FutureExt; use js_sys::Object; use js_sys::Reflect; @@ -18,8 +20,8 @@ use std::task::{ready, Context, Poll}; use wasm_bindgen_futures::JsFuture; use web_sys::{RtcConfiguration, RtcDataChannel, RtcPeerConnection}; -pub const SHA2_256: u64 = 0x12; -pub const SHA2_512: u64 = 0x13; +const SHA2_256: u64 = 0x12; +const SHA2_512: u64 = 0x13; pub struct Connection { // Swarm needs all types to be Send. WASM is single-threaded @@ -38,26 +40,30 @@ struct ConnectionInner { impl Connection { /// Create a new Connection - pub fn new(sock_addr: SocketAddr, remote_fingerprint: Fingerprint, id_keys: Keypair) -> Self { + pub(crate) fn new( + sock_addr: SocketAddr, + remote_fingerprint: Fingerprint, + id_keys: Keypair, + ) -> Self { Self { inner: SendWrapper::new(ConnectionInner::new(sock_addr, remote_fingerprint, id_keys)), } } /// Connect - pub async fn connect(&mut self) -> Result { + pub(crate) async fn connect(&mut self) -> Result { let fut = SendWrapper::new(self.inner.connect()); fut.await } /// Peer Connection Getter - pub fn peer_connection(&self) -> Option<&RtcPeerConnection> { + pub(crate) fn peer_connection(&self) -> Option<&RtcPeerConnection> { self.inner.peer_connection.as_ref() } } impl ConnectionInner { - pub fn new(sock_addr: SocketAddr, remote_fingerprint: Fingerprint, id_keys: Keypair) -> Self { + fn new(sock_addr: SocketAddr, remote_fingerprint: Fingerprint, id_keys: Keypair) -> Self { Self { peer_connection: None, sock_addr, @@ -68,19 +74,13 @@ impl ConnectionInner { } } - pub async fn connect(&mut self) -> Result { + async fn connect(&mut self) -> Result { let hash = match self.remote_fingerprint.to_multihash().code() { SHA2_256 => "sha-256", SHA2_512 => "sha2-512", _ => return Err(Error::JsError("unsupported hash".to_string())), }; - // let keygen_algorithm = json!({ - // "name": "ECDSA", - // "namedCurve": "P-256", - // "hash": hash - // }); - let algo: js_sys::Object = Object::new(); Reflect::set(&algo, &"name".into(), &"ECDSA".into()).unwrap(); Reflect::set(&algo, &"namedCurve".into(), &"P-256".into()).unwrap(); @@ -101,16 +101,32 @@ impl ConnectionInner { * OFFER */ let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send - let offer_obj = crate::sdp::offer(offer, &ufrag); + let offer_obj = sdp::offer(offer, &ufrag); let sld_promise = peer_connection.set_local_description(&offer_obj); - JsFuture::from(sld_promise).await?; /* * ANSWER */ - let answer_obj = crate::sdp::answer(self.sock_addr, &self.remote_fingerprint, &ufrag); + // TODO: Update SDP Answer format for Browser WebRTC + let answer_obj = sdp::answer(self.sock_addr, &self.remote_fingerprint, &ufrag); let srd_promise = peer_connection.set_remote_description(&answer_obj); - JsFuture::from(srd_promise).await?; + + let (local_desc, remote_desc) = + match join!(JsFuture::from(sld_promise), JsFuture::from(srd_promise)) { + (Ok(local_desc), Ok(remote_desc)) => (local_desc, remote_desc), + (Err(e), _) => { + return Err(Error::JsError(format!( + "Error setting local_description: {:?}", + e + ))) + } + (_, Err(e)) => { + return Err(Error::JsError(format!( + "Error setting remote_description: {:?}", + e + ))) + } + }; let peer_id = upgrade::outbound( &peer_connection, @@ -132,7 +148,7 @@ impl ConnectionInner { // Create Data Channel // take the peer_connection and DataChannelConfig and create a pollable future let mut dc = - crate::stream::create_data_channel(&self.peer_connection.as_ref().unwrap(), config); + super::stream::create_data_channel(&self.peer_connection.as_ref().unwrap(), config); let val = ready!(dc.poll_unpin(cx)); @@ -142,8 +158,8 @@ impl ConnectionInner { } /// Polls Incoming Peer Connections? Or Data Channels? - pub fn poll_incoming(&mut self, cx: &mut Context) -> Poll> { - let mut dc = crate::stream::create_data_channel( + fn poll_incoming(&mut self, cx: &mut Context) -> Poll> { + let mut dc = super::stream::create_data_channel( &self.peer_connection.as_ref().unwrap(), DataChannelConfig::default(), ); diff --git a/transports/webrtc-websys/src/error.rs b/transports/webrtc/src/websys/error.rs similarity index 100% rename from transports/webrtc-websys/src/error.rs rename to transports/webrtc/src/websys/error.rs diff --git a/transports/webrtc-websys/src/fingerprint.rs b/transports/webrtc/src/websys/fingerprint.rs similarity index 100% rename from transports/webrtc-websys/src/fingerprint.rs rename to transports/webrtc/src/websys/fingerprint.rs diff --git a/transports/webrtc-websys/src/fused_js_promise.rs b/transports/webrtc/src/websys/fused_js_promise.rs similarity index 100% rename from transports/webrtc-websys/src/fused_js_promise.rs rename to transports/webrtc/src/websys/fused_js_promise.rs diff --git a/transports/webrtc-websys/src/lib.rs b/transports/webrtc/src/websys/mod.rs similarity index 90% rename from transports/webrtc-websys/src/lib.rs rename to transports/webrtc/src/websys/mod.rs index 5a97cd281b6..9392527d2a2 100644 --- a/transports/webrtc-websys/src/lib.rs +++ b/transports/webrtc/src/websys/mod.rs @@ -1,7 +1,7 @@ mod cbfutures; mod connection; mod error; -mod fingerprint; +pub(crate) mod fingerprint; mod fused_js_promise; mod sdp; mod stream; diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc/src/websys/sdp.rs similarity index 91% rename from transports/webrtc-websys/src/sdp.rs rename to transports/webrtc/src/websys/sdp.rs index 437c8896019..ee816f976c5 100644 --- a/transports/webrtc-websys/src/sdp.rs +++ b/transports/webrtc/src/websys/sdp.rs @@ -26,7 +26,7 @@ use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; use std::net::{IpAddr, SocketAddr}; -use crate::fingerprint::Fingerprint; +use super::fingerprint::Fingerprint; /// Creates the SDP answer used by the client. pub(crate) fn answer( @@ -269,12 +269,23 @@ fn render_description( /// Fingerprint from SDP pub fn fingerprint(sdp: &str) -> Result { - let fingerprint_regex = regex::Regex::new( + let fingerprint_regex = match regex::Regex::new( r"(?m)^a=fingerprint:(?:\w+-[0-9]+)\s(?P(:?[0-9a-fA-F]{2})+)$", - ) - .unwrap(); - let captures = fingerprint_regex.captures(sdp).unwrap(); - let fingerprint = captures.name("fingerprint").unwrap().as_str(); - let fingerprint = hex::decode(fingerprint).unwrap(); - Ok(Fingerprint::from_certificate(&fingerprint)) + ) { + Ok(fingerprint_regex) => fingerprint_regex, + Err(e) => return Err(regex::Error::Syntax(format!("fingerprint: {}", e))), + }; + let captures = match fingerprint_regex.captures(sdp) { + Some(captures) => captures, + None => return Err(regex::Error::Syntax("fingerprint not found".to_string())), + }; + let fingerprint = match captures.name("fingerprint") { + Some(fingerprint) => fingerprint.as_str(), + None => return Err(regex::Error::Syntax("fingerprint not found".to_string())), + }; + let decoded = match hex::decode(fingerprint) { + Ok(fingerprint) => fingerprint, + Err(e) => return Err(regex::Error::Syntax(format!("fingerprint: {}", e))), + }; + Ok(Fingerprint::from_certificate(&decoded)) } diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc/src/websys/stream.rs similarity index 81% rename from transports/webrtc-websys/src/stream.rs rename to transports/webrtc/src/websys/stream.rs index 462fad83c03..a0d75f2b339 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc/src/websys/stream.rs @@ -1,5 +1,5 @@ //! The Substream over the Connection -use crate::cbfutures::CbFuture; +use super::cbfutures::CbFuture; use futures::{AsyncRead, AsyncWrite, FutureExt}; use send_wrapper::SendWrapper; use std::io; @@ -120,16 +120,66 @@ struct StreamInner { impl StreamInner { pub fn new(channel: RtcDataChannel) -> Self { + // On Open + let onopen_fut = CbFuture::new(); + let onopen_cback_clone = onopen_fut.clone(); + + channel.set_onopen(Some( + Closure::::new(move |_ev: RtcDataChannelEvent| { + // TODO: Send any queued messages + console_log!("Data Channel opened. onopen_callback"); + onopen_cback_clone.publish(()); + }) + .as_ref() + .unchecked_ref(), + )); + + // On Close let onclose_fut = CbFuture::new(); - let cback_clone = onclose_fut.clone(); + let onclose_cback_clone = onclose_fut.clone(); + + channel.set_onclose(Some( + Closure::::new(move |_ev: RtcDataChannelEvent| { + // TODO: Set state to closed? + // TODO: Set futures::Stream Poll::Ready(None)? + console_log!("Data Channel closed. onclose_callback"); + onclose_cback_clone.publish(()); + }) + .as_ref() + .unchecked_ref(), + )); + + /* + * On Error + */ + let onerror_fut = CbFuture::new(); + let onerror_cback_clone = onerror_fut.clone(); + + channel.set_onerror(Some( + Closure::::new(move |_ev: RtcDataChannelEvent| { + console_log!("Data Channel error. onerror_callback"); + onerror_cback_clone.publish(()); + }) + .as_ref() + .unchecked_ref(), + )); + + /* + * On Message + */ + let onmessage_fut = CbFuture::new(); + let onmessage_cback_clone = onmessage_fut.clone(); - let onclose_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { - console_log!("Data Channel closed. onclose_callback"); - cback_clone.publish(()); + let onmessage_callback = Closure::::new(move |ev: MessageEvent| { + // TODO: Use Protobuf decoder? + let data = ev.data(); + let data = js_sys::Uint8Array::from(data); + let data = data.to_vec(); + console_log!("onmessage: {:?}", data); + // TODO: Howto? Should this feed a queue? futures::Stream? + onmessage_cback_clone.publish(data); }); - channel.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); - Self { channel, onclose_fut, diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc/src/websys/transport.rs similarity index 96% rename from transports/webrtc-websys/src/transport.rs rename to transports/webrtc/src/websys/transport.rs index 8a63825619a..f0973b350f5 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc/src/websys/transport.rs @@ -1,17 +1,17 @@ use futures::future::FutureExt; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; use libp2p_core::muxing::StreamMuxerBox; use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; use libp2p_identity::{Keypair, PeerId}; -use multiaddr::{Multiaddr, Protocol}; use std::future::Future; use std::net::{IpAddr, SocketAddr}; use std::pin::Pin; use std::task::{Context, Poll}; // use crate::endpoint::Endpoint; -use crate::fingerprint::Fingerprint; -use crate::Connection; -use crate::Error; +use super::fingerprint::Fingerprint; +use super::Connection; +use super::Error; const HANDSHAKE_TIMEOUT_MS: u64 = 10_000; diff --git a/transports/webrtc/src/websys/upgrade.rs b/transports/webrtc/src/websys/upgrade.rs new file mode 100644 index 00000000000..cb41f6fc062 --- /dev/null +++ b/transports/webrtc/src/websys/upgrade.rs @@ -0,0 +1,43 @@ +pub(crate) mod noise; + +pub(crate) use super::fingerprint::Fingerprint; +use super::sdp; +use super::stream::{DataChannelConfig, WebRTCStream}; + +use super::Error; +use libp2p_identity::{Keypair, PeerId}; +use web_sys::{RtcDataChannel, RtcPeerConnection}; + +/// Creates a new outbound WebRTC connection. +pub(crate) async fn outbound( + peer_connection: &RtcPeerConnection, + id_keys: Keypair, + remote_fingerprint: Fingerprint, +) -> Result { + let handshake_data_channel: RtcDataChannel = DataChannelConfig::new() + .negotiated(true) + .id(0) + .open(peer_connection); + + let webrtc_stream = WebRTCStream::new(handshake_data_channel); + + // get local_fingerprint from local RtcPeerConnection peer_connection certificate + let local_sdp = match &peer_connection.local_description() { + Some(description) => description.sdp(), + None => return Err(Error::JsError("local_description is None".to_string())), + }; + let local_fingerprint = match sdp::fingerprint(&local_sdp) { + Ok(fingerprint) => fingerprint, + Err(e) => return Err(Error::JsError(format!("fingerprint: {}", e))), + }; + + let peer_id = noise::outbound( + id_keys, + webrtc_stream, + remote_fingerprint, + local_fingerprint, + ) + .await?; + + Ok(peer_id) +} diff --git a/transports/webrtc-websys/src/upgrade/noise.rs b/transports/webrtc/src/websys/upgrade/noise.rs similarity index 98% rename from transports/webrtc-websys/src/upgrade/noise.rs rename to transports/webrtc/src/websys/upgrade/noise.rs index c132436c4d7..22c59fd9081 100644 --- a/transports/webrtc-websys/src/upgrade/noise.rs +++ b/transports/webrtc/src/websys/upgrade/noise.rs @@ -24,8 +24,8 @@ use libp2p_identity as identity; use libp2p_identity::PeerId; use libp2p_noise as noise; -use crate::fingerprint::Fingerprint; -use crate::Error; +use super::Error; +use super::Fingerprint; pub(crate) async fn inbound( id_keys: identity::Keypair, diff --git a/transports/webrtc-websys/src/utils.rs b/transports/webrtc/src/websys/utils.rs similarity index 99% rename from transports/webrtc-websys/src/utils.rs rename to transports/webrtc/src/websys/utils.rs index 39962cf6c9d..4b2862d79e5 100644 --- a/transports/webrtc-websys/src/utils.rs +++ b/transports/webrtc/src/websys/utils.rs @@ -2,7 +2,7 @@ use js_sys::Promise; use send_wrapper::SendWrapper; use wasm_bindgen::{JsCast, JsValue}; -use crate::Error; +use super::Error; /// Properly detach a promise. /// diff --git a/wasm-tests/webrtc/Cargo.toml b/wasm-tests/webrtc/Cargo.toml index 5af38bcb32e..e8400b6ccd7 100644 --- a/wasm-tests/webrtc/Cargo.toml +++ b/wasm-tests/webrtc/Cargo.toml @@ -15,8 +15,4 @@ wasm-bindgen-futures = "0.4.37" wasm-bindgen-test = "0.3.37" web-sys = { version = "0.3.64", features = ["Response", "Window"] } libp2p-identity = { workspace = true } - -[dependencies.libp2p] -path = "../../libp2p" -version = "0.52.1" -features = ["tokio", "noise"] +libp2p-webrtc = { workspace = true, features = ["wasm-bindgen"] } diff --git a/wasm-tests/webrtc/README.md b/wasm-tests/webrtc/README.md index 34edbf7bd92..505d49c8bfd 100644 --- a/wasm-tests/webrtc/README.md +++ b/wasm-tests/webrtc/README.md @@ -3,12 +3,14 @@ First you need to build and start the echo-server: ```bash +# ie. in wasm-tests/webrtc/server cd server cargo run ``` -In another terminal run: +In another terminal run in this current directory (`wasm-tests/webrtc`): ```bash +# ie. in wasm-tests/webrtc wasm-pack test --chrome --headless ``` diff --git a/wasm-tests/webrtc/server/Cargo.toml b/wasm-tests/webrtc/server/Cargo.toml index f4048ac8704..ff145b096bf 100644 --- a/wasm-tests/webrtc/server/Cargo.toml +++ b/wasm-tests/webrtc/server/Cargo.toml @@ -22,5 +22,5 @@ tokio = { version = "1.29", features = ["rt", "macros", "signal", "net"] } tokio-util = { version = "0.7", features = ["compat"] } tokio-stream = "0.1" libp2p-webrtc = { workspace = true, features = ["tokio"] } -webrtc-websys-tests = { path = "../" } # for PORT +webrtc-websys-tests = { path = "../" } # for PORT import tower-http = { version = "0.4.0", features = ["cors"] } # axum middleware From d1ee35ef8dba9c72d6154aff5e07cb1501d4ac63 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 26 Jul 2023 18:06:11 -0300 Subject: [PATCH 021/235] add Fused Callback Future & maybe_init --- transports/webrtc/src/websys/cbfutures.rs | 44 ++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/transports/webrtc/src/websys/cbfutures.rs b/transports/webrtc/src/websys/cbfutures.rs index 5c1132ea420..5b6fe43ebd4 100644 --- a/transports/webrtc/src/websys/cbfutures.rs +++ b/transports/webrtc/src/websys/cbfutures.rs @@ -1,8 +1,33 @@ +use futures::FutureExt; use std::cell::Cell; use std::future::Future; use std::pin::Pin; use std::rc::Rc; -use std::task::{Context, Poll, Waker}; +use std::task::{ready, Context, Poll, Waker}; + +pub struct FusedCbFuture(Option>); + +impl FusedCbFuture { + pub(crate) fn new() -> Self { + Self(None) + } + + pub(crate) fn maybe_init(&mut self, init: F) -> &mut Self + where + F: FnOnce() -> CbFuture, + { + if self.0.is_none() { + self.0 = Some(init()); + } + + self + } + + /// Checks if future is already running + pub(crate) fn is_active(&self) -> bool { + self.0.is_some() + } +} #[derive(Clone, Debug)] pub struct CbFuture(Rc>); @@ -54,3 +79,20 @@ impl Future for CbFuture { } } } + +impl Future for FusedCbFuture { + type Output = T; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let val = ready!(self + .0 + .as_mut() + .expect("FusedCbFuture not initialized") + .poll_unpin(cx)); + + // Future finished, drop it + self.0.take(); + + Poll::Ready(val) + } +} From 4d4eae0fb160b21ad83ea4e64efd5665cab05e20 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 26 Jul 2023 19:52:16 -0300 Subject: [PATCH 022/235] move state to common folder --- transports/webrtc/src/common/mod.rs | 1 + transports/webrtc/src/common/substream/mod.rs | 14 ++++++++++ .../src/{tokio => common}/substream/state.rs | 0 transports/webrtc/src/tokio/substream.rs | 26 +++++-------------- .../webrtc/src/tokio/substream/framed_dc.rs | 5 +++- 5 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 transports/webrtc/src/common/mod.rs create mode 100644 transports/webrtc/src/common/substream/mod.rs rename transports/webrtc/src/{tokio => common}/substream/state.rs (100%) diff --git a/transports/webrtc/src/common/mod.rs b/transports/webrtc/src/common/mod.rs new file mode 100644 index 00000000000..3c5dcaefef2 --- /dev/null +++ b/transports/webrtc/src/common/mod.rs @@ -0,0 +1 @@ +pub mod substream; diff --git a/transports/webrtc/src/common/substream/mod.rs b/transports/webrtc/src/common/substream/mod.rs new file mode 100644 index 00000000000..b40a94e8336 --- /dev/null +++ b/transports/webrtc/src/common/substream/mod.rs @@ -0,0 +1,14 @@ +pub(crate) mod state; + +/// Maximum length of a message. +/// +/// "As long as message interleaving is not supported, the sender SHOULD limit the maximum message +/// size to 16 KB to avoid monopolization." +/// Source: +pub(crate) const MAX_MSG_LEN: usize = 16384; // 16kiB +/// Length of varint, in bytes. +pub(crate) const VARINT_LEN: usize = 2; +/// Overhead of the protobuf encoding, in bytes. +pub(crate) const PROTO_OVERHEAD: usize = 5; +/// Maximum length of data, in bytes. +pub(crate) const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; diff --git a/transports/webrtc/src/tokio/substream/state.rs b/transports/webrtc/src/common/substream/state.rs similarity index 100% rename from transports/webrtc/src/tokio/substream/state.rs rename to transports/webrtc/src/common/substream/state.rs diff --git a/transports/webrtc/src/tokio/substream.rs b/transports/webrtc/src/tokio/substream.rs index 89e52376a48..3c5f46ce0f9 100644 --- a/transports/webrtc/src/tokio/substream.rs +++ b/transports/webrtc/src/tokio/substream.rs @@ -31,29 +31,15 @@ use std::{ task::{Context, Poll}, }; -use crate::proto::{Flag, Message}; -use crate::tokio::{ - substream::drop_listener::GracefullyClosed, - substream::framed_dc::FramedDc, - substream::state::{Closing, State}, +use crate::common::substream::{ + state::{Closing, State}, + MAX_DATA_LEN, }; +use crate::proto::{Flag, Message}; +use crate::tokio::{substream::drop_listener::GracefullyClosed, substream::framed_dc::FramedDc}; mod drop_listener; mod framed_dc; -mod state; - -/// Maximum length of a message. -/// -/// "As long as message interleaving is not supported, the sender SHOULD limit the maximum message -/// size to 16 KB to avoid monopolization." -/// Source: -const MAX_MSG_LEN: usize = 16384; // 16kiB -/// Length of varint, in bytes. -const VARINT_LEN: usize = 2; -/// Overhead of the protobuf encoding, in bytes. -const PROTO_OVERHEAD: usize = 5; -/// Maximum length of data, in bytes. -const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; pub(crate) use drop_listener::DropListener; /// A substream on top of a WebRTC data channel. @@ -251,6 +237,8 @@ fn io_poll_next( #[cfg(test)] mod tests { + use crate::common::substream::{MAX_MSG_LEN, PROTO_OVERHEAD, VARINT_LEN}; + use super::*; use asynchronous_codec::Encoder; use bytes::BytesMut; diff --git a/transports/webrtc/src/tokio/substream/framed_dc.rs b/transports/webrtc/src/tokio/substream/framed_dc.rs index 1b3860b662b..60646b9ce03 100644 --- a/transports/webrtc/src/tokio/substream/framed_dc.rs +++ b/transports/webrtc/src/tokio/substream/framed_dc.rs @@ -25,7 +25,10 @@ use webrtc::data::data_channel::{DataChannel, PollDataChannel}; use std::sync::Arc; -use super::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; +use crate::common::substream::{ + state::{Closing, State}, + MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN, +}; use crate::proto::Message; pub(crate) type FramedDc = Framed, quick_protobuf_codec::Codec>; From fb6e3bc6400da276c8ef39469138613839e9a977 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 27 Jul 2023 19:36:11 -0300 Subject: [PATCH 023/235] add common mod --- transports/webrtc/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 460f9749ba0..7f5914ccb5e 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -91,3 +91,6 @@ pub mod tokio; #[cfg(feature = "wasm-bindgen")] pub mod websys; + +/// Crates common to all features +mod common; From a3d281bbc1adae6ab03fb529fecfe9939b9095ef Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 27 Jul 2023 19:36:46 -0300 Subject: [PATCH 024/235] add libp2p-core to test --- Cargo.lock | 1 + wasm-tests/webrtc/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 1ae3dc16817..90fbdf8bff6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6485,6 +6485,7 @@ version = "0.1.0" dependencies = [ "futures", "getrandom 0.2.10", + "libp2p-core", "libp2p-identity", "libp2p-webrtc", "multiaddr", diff --git a/wasm-tests/webrtc/Cargo.toml b/wasm-tests/webrtc/Cargo.toml index e8400b6ccd7..2c76a9dfffe 100644 --- a/wasm-tests/webrtc/Cargo.toml +++ b/wasm-tests/webrtc/Cargo.toml @@ -16,3 +16,4 @@ wasm-bindgen-test = "0.3.37" web-sys = { version = "0.3.64", features = ["Response", "Window"] } libp2p-identity = { workspace = true } libp2p-webrtc = { workspace = true, features = ["wasm-bindgen"] } +libp2p-core = { workspace = true } From 431dcfc3d2ee68c4adfcd97df810399daa60a4d2 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 27 Jul 2023 19:37:06 -0300 Subject: [PATCH 025/235] add dial server test --- wasm-tests/webrtc/src/lib.rs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/wasm-tests/webrtc/src/lib.rs b/wasm-tests/webrtc/src/lib.rs index 841b4617cb4..9bf4294fd15 100644 --- a/wasm-tests/webrtc/src/lib.rs +++ b/wasm-tests/webrtc/src/lib.rs @@ -1,5 +1,8 @@ +use libp2p_core::transport::Transport; // So we can use the Traits that come with it use libp2p_identity::Keypair; +use libp2p_webrtc::websys::{Config, Connection, Transport as WebRTCTransport}; // So we can dial the server use multiaddr::Multiaddr; +use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; @@ -9,14 +12,32 @@ wasm_bindgen_test_configure!(run_in_browser); pub const PORT: u16 = 4455; +macro_rules! console_log { + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); + #[wasm_bindgen(js_namespace = console)] + fn warn(s: &str); +} + #[wasm_bindgen_test] -async fn connect_without_peer_id() { +async fn dial_webrtc_server() { let addr = fetch_server_addr().await; - let _keypair = Keypair::generate_ed25519(); + let keypair = Keypair::generate_ed25519(); + + console_log!("Got addr: {:?}", addr); + + let mut transport = WebRTCTransport::new(Config::new(&keypair)); + let connection = match transport.dial(addr) { + Ok(fut) => fut.await.expect("dial failed"), + Err(e) => panic!("dial failed: {:?}", e), + }; - // eprintln - eprintln!("addr: {:?}", addr); - println!("addr: {:?}", addr); + console_log!("Connection established"); } /// Helper that returns the multiaddress of echo-server From 5248eaf22f90c1284d0b0be8ccb2c9b6ce4c4d5d Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 31 Jul 2023 09:51:56 -0300 Subject: [PATCH 026/235] add logging to test server --- wasm-tests/webrtc/server/.cargo/config.toml | 3 ++ wasm-tests/webrtc/server/Cargo.toml | 4 ++- wasm-tests/webrtc/server/src/main.rs | 37 +++++++++++++++------ 3 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 wasm-tests/webrtc/server/.cargo/config.toml diff --git a/wasm-tests/webrtc/server/.cargo/config.toml b/wasm-tests/webrtc/server/.cargo/config.toml new file mode 100644 index 00000000000..149e30a5259 --- /dev/null +++ b/wasm-tests/webrtc/server/.cargo/config.toml @@ -0,0 +1,3 @@ +# // RUST_LOG environment variable in .cargo/config.toml file +[env] +RUST_LOG = "debug,libp2p_webrtc" diff --git a/wasm-tests/webrtc/server/Cargo.toml b/wasm-tests/webrtc/server/Cargo.toml index ff145b096bf..79f26bb0cc1 100644 --- a/wasm-tests/webrtc/server/Cargo.toml +++ b/wasm-tests/webrtc/server/Cargo.toml @@ -12,7 +12,8 @@ anyhow = "1.0.72" futures = "0.3.28" rand = "0.8" void = "1" -libp2p-identity = { workspace = true } +libp2p-identity = { workspace = true, features = ["peerid", "ed25519"] } +libp2p-identify = { workspace = true } libp2p-core = { workspace = true, features = [] } libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } multiaddr = { workspace = true } @@ -24,3 +25,4 @@ tokio-stream = "0.1" libp2p-webrtc = { workspace = true, features = ["tokio"] } webrtc-websys-tests = { path = "../" } # for PORT import tower-http = { version = "0.4.0", features = ["cors"] } # axum middleware +env_logger = "0.10" diff --git a/wasm-tests/webrtc/server/src/main.rs b/wasm-tests/webrtc/server/src/main.rs index 859cb36cf0f..63115393b49 100644 --- a/wasm-tests/webrtc/server/src/main.rs +++ b/wasm-tests/webrtc/server/src/main.rs @@ -14,19 +14,18 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use tower_http::cors::{Any, CorsLayer}; use void::Void; -/// An example WebRTC server that will accept connections and run the ping protocol on them. #[tokio::main] async fn main() -> Result<()> { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + let id_keys = identity::Keypair::generate_ed25519(); let local_peer_id = id_keys.public().to_peer_id(); let transport = webrtc::tokio::Transport::new( id_keys, webrtc::tokio::Certificate::generate(&mut thread_rng())?, - ); - - let transport = transport - .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) - .boxed(); + ) + .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) + .boxed(); let behaviour = Behaviour { relay: relay::Behaviour::new(local_peer_id, Default::default()), @@ -42,11 +41,19 @@ async fn main() -> Result<()> { swarm.listen_on(address_webrtc.clone())?; + // Dial the peer identified by the multi-address given as the second + // command-line argument, if any. + if let Some(addr) = std::env::args().nth(1) { + let remote: Multiaddr = addr.parse()?; + swarm.dial(remote)?; + println!("Dialed {addr}") + } + loop { tokio::select! { - evt = swarm.next() => { + evt = swarm.select_next_some() => { match evt { - Some(SwarmEvent::NewListenAddr { address, .. }) => { + SwarmEvent::NewListenAddr { address, .. } => { let addr = address .with(Protocol::P2p(*swarm.local_peer_id())) @@ -70,9 +77,19 @@ async fn main() -> Result<()> { .await .unwrap(); }); + + eprintln!("Server spawned"); + } + SwarmEvent::Behaviour(Event::Ping(ping::Event { + peer, + result: Ok(rtt), + .. + })) => { + let id = peer.to_string().to_owned(); + eprintln!("🏓 Pinged {id} ({rtt:?})") } - _ => { - // do nothing + evt => { + eprintln!("SwarmEvent: {:?}", evt); }, } }, From 16d8943bc5a6b75c864b9b8105009359d958aaae Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 31 Jul 2023 09:53:16 -0300 Subject: [PATCH 027/235] set up client test logging --- wasm-tests/webrtc/Cargo.toml | 6 +++++- wasm-tests/webrtc/src/lib.rs | 22 +++++----------------- wasm-tests/webrtc/src/logging.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 wasm-tests/webrtc/src/logging.rs diff --git a/wasm-tests/webrtc/Cargo.toml b/wasm-tests/webrtc/Cargo.toml index 2c76a9dfffe..0ebafe23080 100644 --- a/wasm-tests/webrtc/Cargo.toml +++ b/wasm-tests/webrtc/Cargo.toml @@ -15,5 +15,9 @@ wasm-bindgen-futures = "0.4.37" wasm-bindgen-test = "0.3.37" web-sys = { version = "0.3.64", features = ["Response", "Window"] } libp2p-identity = { workspace = true } -libp2p-webrtc = { workspace = true, features = ["wasm-bindgen"] } +libp2p-webrtc-websys = { workspace = true } libp2p-core = { workspace = true } +console_log = { version = "1.0.0", features = ["wasm-bindgen"] } +log = "0.4.14" +fern = { version = "0.6", features = ["colored"] } +chrono = { version = "0.4.26", features = ["wasm-bindgen", "wasmbind"] } diff --git a/wasm-tests/webrtc/src/lib.rs b/wasm-tests/webrtc/src/lib.rs index 9bf4294fd15..37cb151a79b 100644 --- a/wasm-tests/webrtc/src/lib.rs +++ b/wasm-tests/webrtc/src/lib.rs @@ -1,6 +1,6 @@ use libp2p_core::transport::Transport; // So we can use the Traits that come with it use libp2p_identity::Keypair; -use libp2p_webrtc::websys::{Config, Connection, Transport as WebRTCTransport}; // So we can dial the server +use libp2p_webrtc_websys::{Config, Connection, Transport as WebRTCTransport}; // So we can dial the server use multiaddr::Multiaddr; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; @@ -8,36 +8,24 @@ use wasm_bindgen_futures::JsFuture; use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; use web_sys::{window, Response}; +mod logging; + wasm_bindgen_test_configure!(run_in_browser); pub const PORT: u16 = 4455; -macro_rules! console_log { - ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) -} - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); - #[wasm_bindgen(js_namespace = console)] - fn warn(s: &str); -} - #[wasm_bindgen_test] async fn dial_webrtc_server() { + logging::set_up_logging(); + let addr = fetch_server_addr().await; let keypair = Keypair::generate_ed25519(); - console_log!("Got addr: {:?}", addr); - let mut transport = WebRTCTransport::new(Config::new(&keypair)); let connection = match transport.dial(addr) { Ok(fut) => fut.await.expect("dial failed"), Err(e) => panic!("dial failed: {:?}", e), }; - - console_log!("Connection established"); } /// Helper that returns the multiaddress of echo-server diff --git a/wasm-tests/webrtc/src/logging.rs b/wasm-tests/webrtc/src/logging.rs new file mode 100644 index 00000000000..84dd6705a3c --- /dev/null +++ b/wasm-tests/webrtc/src/logging.rs @@ -0,0 +1,27 @@ +use fern::colors::{Color, ColoredLevelConfig}; + +pub fn set_up_logging() { + let colors_line = ColoredLevelConfig::new() + .error(Color::Red) + .warn(Color::Yellow) + // we actually don't need to specify the color for debug and info, they are white by default + .info(Color::Green) + .debug(Color::Yellow) + // depending on the terminals color scheme, this is the same as the background color + .trace(Color::BrightBlack); + let colors_level = colors_line.info(Color::Green); + fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "[{date} {level} {target}] {message}\x1B[0m", + date = chrono::Local::now().format("%T%.3f"), + target = record.target(), + level = colors_level.color(record.level()), + message = message, + )); + }) + .level(log::LevelFilter::Trace) + .chain(fern::Output::call(console_log::log)) + .apply() + .unwrap(); +} From ccfef61ee022c42d09b92b1d902ccf11bdf1d031 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 2 Aug 2023 14:06:09 -0300 Subject: [PATCH 028/235] create libp2p-webrtc-utils --- transports/webrtc-utils/Cargo.toml | 12 + transports/webrtc-utils/README.md | 6 + .../webrtc-utils/src/generated/message.proto | 20 + transports/webrtc-utils/src/generated/mod.rs | 2 + .../webrtc-utils/src/generated/webrtc/mod.rs | 2 + .../webrtc-utils/src/generated/webrtc/pb.rs | 91 ++++ transports/webrtc-utils/src/lib.rs | 9 + transports/webrtc-utils/src/substream/mod.rs | 14 + .../webrtc-utils/src/substream/state.rs | 508 ++++++++++++++++++ 9 files changed, 664 insertions(+) create mode 100644 transports/webrtc-utils/Cargo.toml create mode 100644 transports/webrtc-utils/README.md create mode 100644 transports/webrtc-utils/src/generated/message.proto create mode 100644 transports/webrtc-utils/src/generated/mod.rs create mode 100644 transports/webrtc-utils/src/generated/webrtc/mod.rs create mode 100644 transports/webrtc-utils/src/generated/webrtc/pb.rs create mode 100644 transports/webrtc-utils/src/lib.rs create mode 100644 transports/webrtc-utils/src/substream/mod.rs create mode 100644 transports/webrtc-utils/src/substream/state.rs diff --git a/transports/webrtc-utils/Cargo.toml b/transports/webrtc-utils/Cargo.toml new file mode 100644 index 00000000000..2404d48d806 --- /dev/null +++ b/transports/webrtc-utils/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "libp2p-webrtc-utils" +version = "0.1.0" +edition = "2021" +rust-version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bytes = "1" +quick-protobuf = "0.8" +quick-protobuf-codec = { workspace = true } diff --git a/transports/webrtc-utils/README.md b/transports/webrtc-utils/README.md new file mode 100644 index 00000000000..127023b0573 --- /dev/null +++ b/transports/webrtc-utils/README.md @@ -0,0 +1,6 @@ +# WebRTC Common Utilities + +Tools that are common to more than one WebRTC component. + +- [Protobuf Generated Code](generated) +- [Substream State](substream/state.rs) diff --git a/transports/webrtc-utils/src/generated/message.proto b/transports/webrtc-utils/src/generated/message.proto new file mode 100644 index 00000000000..eab3ceb720b --- /dev/null +++ b/transports/webrtc-utils/src/generated/message.proto @@ -0,0 +1,20 @@ +syntax = "proto2"; + +package webrtc.pb; + +message Message { + enum Flag { + // The sender will no longer send messages on the stream. + FIN = 0; + // The sender will no longer read messages on the stream. Incoming data is + // being discarded on receipt. + STOP_SENDING = 1; + // The sender abruptly terminates the sending part of the stream. The + // receiver can discard any data that it already received on that stream. + RESET = 2; + } + + optional Flag flag=1; + + optional bytes message = 2; +} diff --git a/transports/webrtc-utils/src/generated/mod.rs b/transports/webrtc-utils/src/generated/mod.rs new file mode 100644 index 00000000000..5e9f6373b12 --- /dev/null +++ b/transports/webrtc-utils/src/generated/mod.rs @@ -0,0 +1,2 @@ +// Automatically generated mod.rs +pub mod webrtc; diff --git a/transports/webrtc-utils/src/generated/webrtc/mod.rs b/transports/webrtc-utils/src/generated/webrtc/mod.rs new file mode 100644 index 00000000000..aec6164c7ef --- /dev/null +++ b/transports/webrtc-utils/src/generated/webrtc/mod.rs @@ -0,0 +1,2 @@ +// Automatically generated mod.rs +pub mod pb; diff --git a/transports/webrtc-utils/src/generated/webrtc/pb.rs b/transports/webrtc-utils/src/generated/webrtc/pb.rs new file mode 100644 index 00000000000..9e33e97188c --- /dev/null +++ b/transports/webrtc-utils/src/generated/webrtc/pb.rs @@ -0,0 +1,91 @@ +// Automatically generated rust module for 'message.proto' file + +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(unused_imports)] +#![allow(unknown_lints)] +#![allow(clippy::all)] +#![cfg_attr(rustfmt, rustfmt_skip)] + + +use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; +use quick_protobuf::sizeofs::*; +use super::super::*; + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct Message { + pub flag: Option, + pub message: Option>, +} + +impl<'a> MessageRead<'a> for Message { + 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(8) => msg.flag = Some(r.read_enum(bytes)?), + Ok(18) => msg.message = Some(r.read_bytes(bytes)?.to_owned()), + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for Message { + fn get_size(&self) -> usize { + 0 + + self.flag.as_ref().map_or(0, |m| 1 + sizeof_varint(*(m) as u64)) + + self.message.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if let Some(ref s) = self.flag { w.write_with_tag(8, |w| w.write_enum(*s as i32))?; } + if let Some(ref s) = self.message { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } + Ok(()) + } +} + +pub mod mod_Message { + + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Flag { + FIN = 0, + STOP_SENDING = 1, + RESET = 2, +} + +impl Default for Flag { + fn default() -> Self { + Flag::FIN + } +} + +impl From for Flag { + fn from(i: i32) -> Self { + match i { + 0 => Flag::FIN, + 1 => Flag::STOP_SENDING, + 2 => Flag::RESET, + _ => Self::default(), + } + } +} + +impl<'a> From<&'a str> for Flag { + fn from(s: &'a str) -> Self { + match s { + "FIN" => Flag::FIN, + "STOP_SENDING" => Flag::STOP_SENDING, + "RESET" => Flag::RESET, + _ => Self::default(), + } + } +} + +} + diff --git a/transports/webrtc-utils/src/lib.rs b/transports/webrtc-utils/src/lib.rs new file mode 100644 index 00000000000..1a246c39e6f --- /dev/null +++ b/transports/webrtc-utils/src/lib.rs @@ -0,0 +1,9 @@ +#![doc = include_str!("../README.md")] + +pub mod proto { + #![allow(unreachable_pub)] + include!("generated/mod.rs"); + pub use self::webrtc::pb::{mod_Message::Flag, Message}; +} + +pub mod substream; diff --git a/transports/webrtc-utils/src/substream/mod.rs b/transports/webrtc-utils/src/substream/mod.rs new file mode 100644 index 00000000000..cff72e63645 --- /dev/null +++ b/transports/webrtc-utils/src/substream/mod.rs @@ -0,0 +1,14 @@ +pub mod state; + +/// Maximum length of a message. +/// +/// "As long as message interleaving is not supported, the sender SHOULD limit the maximum message +/// size to 16 KB to avoid monopolization." +/// Source: +pub const MAX_MSG_LEN: usize = 16384; // 16kiB +/// Length of varint, in bytes. +pub const VARINT_LEN: usize = 2; +/// Overhead of the protobuf encoding, in bytes. +pub const PROTO_OVERHEAD: usize = 5; +/// Maximum length of data, in bytes. +pub const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; diff --git a/transports/webrtc-utils/src/substream/state.rs b/transports/webrtc-utils/src/substream/state.rs new file mode 100644 index 00000000000..5df92d42838 --- /dev/null +++ b/transports/webrtc-utils/src/substream/state.rs @@ -0,0 +1,508 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use bytes::Bytes; + +use std::io; + +use crate::proto::Flag; + +#[derive(Debug, Copy, Clone)] +pub enum State { + Open, + ReadClosed, + WriteClosed, + ClosingRead { + /// Whether the write side of our channel was already closed. + write_closed: bool, + inner: Closing, + }, + ClosingWrite { + /// Whether the write side of our channel was already closed. + read_closed: bool, + inner: Closing, + }, + BothClosed { + reset: bool, + }, +} + +/// Represents the state of closing one half (either read or write) of the connection. +/// +/// Gracefully closing the read or write requires sending the `STOP_SENDING` or `FIN` flag respectively +/// and flushing the underlying connection. +#[derive(Debug, Copy, Clone)] +pub enum Closing { + Requested, + MessageSent, +} + +impl State { + /// Performs a state transition for a flag contained in an inbound message. + pub fn handle_inbound_flag(&mut self, flag: Flag, buffer: &mut Bytes) { + let current = *self; + + match (current, flag) { + (Self::Open, Flag::FIN) => { + *self = Self::ReadClosed; + } + (Self::WriteClosed, Flag::FIN) => { + *self = Self::BothClosed { reset: false }; + } + (Self::Open, Flag::STOP_SENDING) => { + *self = Self::WriteClosed; + } + (Self::ReadClosed, Flag::STOP_SENDING) => { + *self = Self::BothClosed { reset: false }; + } + (_, Flag::RESET) => { + buffer.clear(); + *self = Self::BothClosed { reset: true }; + } + _ => {} + } + } + + pub fn write_closed(&mut self) { + match self { + State::ClosingWrite { + read_closed: true, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::BothClosed { reset: false }; + } + State::ClosingWrite { + read_closed: false, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::WriteClosed; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingRead { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } + + pub fn close_write_message_sent(&mut self) { + match self { + State::ClosingWrite { inner, read_closed } => { + debug_assert!(matches!(inner, Closing::Requested)); + + *self = State::ClosingWrite { + read_closed: *read_closed, + inner: Closing::MessageSent, + }; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingRead { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } + + pub fn read_closed(&mut self) { + match self { + State::ClosingRead { + write_closed: true, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::BothClosed { reset: false }; + } + State::ClosingRead { + write_closed: false, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::ReadClosed; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingWrite { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } + + pub fn close_read_message_sent(&mut self) { + match self { + State::ClosingRead { + inner, + write_closed, + } => { + debug_assert!(matches!(inner, Closing::Requested)); + + *self = State::ClosingRead { + write_closed: *write_closed, + inner: Closing::MessageSent, + }; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingWrite { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } + + /// Whether we should read from the stream in the [`futures::AsyncWrite`] implementation. + /// + /// This is necessary for read-closed streams because we would otherwise not read any more flags from + /// the socket. + pub fn read_flags_in_async_write(&self) -> bool { + matches!(self, Self::ReadClosed) + } + + /// Acts as a "barrier" for [`futures::AsyncRead::poll_read`]. + pub fn read_barrier(&self) -> io::Result<()> { + use State::*; + + let kind = match self { + Open + | WriteClosed + | ClosingWrite { + read_closed: false, .. + } => return Ok(()), + ClosingWrite { + read_closed: true, .. + } + | ReadClosed + | ClosingRead { .. } + | BothClosed { reset: false } => io::ErrorKind::BrokenPipe, + BothClosed { reset: true } => io::ErrorKind::ConnectionReset, + }; + + Err(kind.into()) + } + + /// Acts as a "barrier" for [`futures::AsyncWrite::poll_write`]. + pub fn write_barrier(&self) -> io::Result<()> { + use State::*; + + let kind = match self { + Open + | ReadClosed + | ClosingRead { + write_closed: false, + .. + } => return Ok(()), + ClosingRead { + write_closed: true, .. + } + | WriteClosed + | ClosingWrite { .. } + | BothClosed { reset: false } => io::ErrorKind::BrokenPipe, + BothClosed { reset: true } => io::ErrorKind::ConnectionReset, + }; + + Err(kind.into()) + } + + /// Acts as a "barrier" for [`futures::AsyncWrite::poll_close`]. + pub fn close_write_barrier(&mut self) -> io::Result> { + loop { + match &self { + State::WriteClosed => return Ok(None), + + State::ClosingWrite { inner, .. } => return Ok(Some(*inner)), + + State::Open => { + *self = Self::ClosingWrite { + read_closed: false, + inner: Closing::Requested, + }; + } + State::ReadClosed => { + *self = Self::ClosingWrite { + read_closed: true, + inner: Closing::Requested, + }; + } + + State::ClosingRead { + write_closed: true, .. + } + | State::BothClosed { reset: false } => { + return Err(io::ErrorKind::BrokenPipe.into()) + } + + State::ClosingRead { + write_closed: false, + .. + } => { + return Err(io::Error::new( + io::ErrorKind::Other, + "cannot close read half while closing write half", + )) + } + + State::BothClosed { reset: true } => { + return Err(io::ErrorKind::ConnectionReset.into()) + } + } + } + } + + /// Acts as a "barrier" for [`Substream::poll_close_read`](super::Substream::poll_close_read). + pub fn close_read_barrier(&mut self) -> io::Result> { + loop { + match self { + State::ReadClosed => return Ok(None), + + State::ClosingRead { inner, .. } => return Ok(Some(*inner)), + + State::Open => { + *self = Self::ClosingRead { + write_closed: false, + inner: Closing::Requested, + }; + } + State::WriteClosed => { + *self = Self::ClosingRead { + write_closed: true, + inner: Closing::Requested, + }; + } + + State::ClosingWrite { + read_closed: true, .. + } + | State::BothClosed { reset: false } => { + return Err(io::ErrorKind::BrokenPipe.into()) + } + + State::ClosingWrite { + read_closed: false, .. + } => { + return Err(io::Error::new( + io::ErrorKind::Other, + "cannot close write half while closing read half", + )) + } + + State::BothClosed { reset: true } => { + return Err(io::ErrorKind::ConnectionReset.into()) + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::ErrorKind; + + #[test] + fn cannot_read_after_receiving_fin() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::FIN, &mut Bytes::default()); + let error = open.read_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn cannot_read_after_closing_read() { + let mut open = State::Open; + + open.close_read_barrier().unwrap(); + open.close_read_message_sent(); + open.read_closed(); + let error = open.read_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn cannot_write_after_receiving_stop_sending() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::STOP_SENDING, &mut Bytes::default()); + let error = open.write_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn cannot_write_after_closing_write() { + let mut open = State::Open; + + open.close_write_barrier().unwrap(); + open.close_write_message_sent(); + open.write_closed(); + let error = open.write_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn everything_broken_after_receiving_reset() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::RESET, &mut Bytes::default()); + let error1 = open.read_barrier().unwrap_err(); + let error2 = open.write_barrier().unwrap_err(); + let error3 = open.close_write_barrier().unwrap_err(); + let error4 = open.close_read_barrier().unwrap_err(); + + assert_eq!(error1.kind(), ErrorKind::ConnectionReset); + assert_eq!(error2.kind(), ErrorKind::ConnectionReset); + assert_eq!(error3.kind(), ErrorKind::ConnectionReset); + assert_eq!(error4.kind(), ErrorKind::ConnectionReset); + } + + #[test] + fn should_read_flags_in_async_write_after_read_closed() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::FIN, &mut Bytes::default()); + + assert!(open.read_flags_in_async_write()) + } + + #[test] + fn cannot_read_or_write_after_receiving_fin_and_stop_sending() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::FIN, &mut Bytes::default()); + open.handle_inbound_flag(Flag::STOP_SENDING, &mut Bytes::default()); + + let error1 = open.read_barrier().unwrap_err(); + let error2 = open.write_barrier().unwrap_err(); + + assert_eq!(error1.kind(), ErrorKind::BrokenPipe); + assert_eq!(error2.kind(), ErrorKind::BrokenPipe); + } + + #[test] + fn can_read_after_closing_write() { + let mut open = State::Open; + + open.close_write_barrier().unwrap(); + open.close_write_message_sent(); + open.write_closed(); + + open.read_barrier().unwrap(); + } + + #[test] + fn can_write_after_closing_read() { + let mut open = State::Open; + + open.close_read_barrier().unwrap(); + open.close_read_message_sent(); + open.read_closed(); + + open.write_barrier().unwrap(); + } + + #[test] + fn cannot_write_after_starting_close() { + let mut open = State::Open; + + open.close_write_barrier().expect("to close in open"); + let error = open.write_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe); + } + + #[test] + fn cannot_read_after_starting_close() { + let mut open = State::Open; + + open.close_read_barrier().expect("to close in open"); + let error = open.read_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe); + } + + #[test] + fn can_read_in_open() { + let open = State::Open; + + let result = open.read_barrier(); + + result.unwrap(); + } + + #[test] + fn can_write_in_open() { + let open = State::Open; + + let result = open.write_barrier(); + + result.unwrap(); + } + + #[test] + fn write_close_barrier_returns_ok_when_closed() { + let mut open = State::Open; + + open.close_write_barrier().unwrap(); + open.close_write_message_sent(); + open.write_closed(); + + let maybe = open.close_write_barrier().unwrap(); + + assert!(maybe.is_none()) + } + + #[test] + fn read_close_barrier_returns_ok_when_closed() { + let mut open = State::Open; + + open.close_read_barrier().unwrap(); + open.close_read_message_sent(); + open.read_closed(); + + let maybe = open.close_read_barrier().unwrap(); + + assert!(maybe.is_none()) + } + + #[test] + fn reset_flag_clears_buffer() { + let mut open = State::Open; + let mut buffer = Bytes::copy_from_slice(b"foobar"); + + open.handle_inbound_flag(Flag::RESET, &mut buffer); + + assert!(buffer.is_empty()); + } +} From ba5bf7f909600e8c5cec8f705797965e9063c10b Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 2 Aug 2023 14:06:31 -0300 Subject: [PATCH 029/235] move common out of webrtc/tokio --- transports/webrtc/Cargo.toml | 55 +- transports/webrtc/src/common/mod.rs | 1 - transports/webrtc/src/common/substream/mod.rs | 14 - .../webrtc/src/common/substream/state.rs | 508 ------------------ transports/webrtc/src/generated/message.proto | 20 - transports/webrtc/src/generated/mod.rs | 2 - transports/webrtc/src/generated/webrtc/mod.rs | 2 - transports/webrtc/src/generated/webrtc/pb.rs | 91 ---- transports/webrtc/src/lib.rs | 13 +- transports/webrtc/src/tokio/substream.rs | 13 +- .../src/tokio/substream/drop_listener.rs | 2 +- .../webrtc/src/tokio/substream/framed_dc.rs | 7 +- transports/webrtc/src/tokio/upgrade.rs | 13 +- transports/webrtc/src/websys/cbfutures.rs | 98 ---- transports/webrtc/src/websys/connection.rs | 231 -------- transports/webrtc/src/websys/error.rs | 52 -- transports/webrtc/src/websys/fingerprint.rs | 97 ---- .../webrtc/src/websys/fused_js_promise.rs | 58 -- transports/webrtc/src/websys/mod.rs | 15 - transports/webrtc/src/websys/sdp.rs | 291 ---------- transports/webrtc/src/websys/stream.rs | 335 ------------ transports/webrtc/src/websys/transport.rs | 140 ----- transports/webrtc/src/websys/upgrade.rs | 43 -- transports/webrtc/src/websys/upgrade/noise.rs | 104 ---- transports/webrtc/src/websys/utils.rs | 68 --- 25 files changed, 23 insertions(+), 2250 deletions(-) delete mode 100644 transports/webrtc/src/common/mod.rs delete mode 100644 transports/webrtc/src/common/substream/mod.rs delete mode 100644 transports/webrtc/src/common/substream/state.rs delete mode 100644 transports/webrtc/src/generated/message.proto delete mode 100644 transports/webrtc/src/generated/mod.rs delete mode 100644 transports/webrtc/src/generated/webrtc/mod.rs delete mode 100644 transports/webrtc/src/generated/webrtc/pb.rs delete mode 100644 transports/webrtc/src/websys/cbfutures.rs delete mode 100644 transports/webrtc/src/websys/connection.rs delete mode 100644 transports/webrtc/src/websys/error.rs delete mode 100644 transports/webrtc/src/websys/fingerprint.rs delete mode 100644 transports/webrtc/src/websys/fused_js_promise.rs delete mode 100644 transports/webrtc/src/websys/mod.rs delete mode 100644 transports/webrtc/src/websys/sdp.rs delete mode 100644 transports/webrtc/src/websys/stream.rs delete mode 100644 transports/webrtc/src/websys/transport.rs delete mode 100644 transports/webrtc/src/websys/upgrade.rs delete mode 100644 transports/webrtc/src/websys/upgrade/noise.rs delete mode 100644 transports/webrtc/src/websys/utils.rs diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index db3b37ef179..31a9c0fde98 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -17,74 +17,29 @@ bytes = "1" futures = "0.3" futures-timer = "3" hex = "0.4" +if-watch = "3.0" libp2p-core = { workspace = true } libp2p-noise = { workspace = true } libp2p-identity = { workspace = true } +libp2p-webrtc-utils = { workspace = true } log = "0.4" sha2 = "0.10.7" multihash = { workspace = true } quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } +rand = "0.8" rcgen = "0.10.0" serde = { version = "1.0", features = ["derive"] } +stun = "0.4" thiserror = "1" tinytemplate = "1.2" - -# tokio optional deps tokio = { version = "1.29", features = ["net"], optional = true } tokio-util = { version = "0.7", features = ["compat"], optional = true } webrtc = { version = "0.8.0", optional = true } -stun = { version = "0.4", optional = true } -rand = { version = "0.8", optional = true } -if-watch = { version = "3.0", optional = true } - -# wasm-bindgen optional deps -js-sys = { version = "0.3", optional = true } -getrandom = { version = "0.2.9", features = ["js"], optional = true } -regex = { version = "1.9", optional = true } -send_wrapper = { version = "0.6.0", features = ["futures"], optional = true } -wasm-bindgen = { version = "0.2.87", optional = true } -wasm-bindgen-futures = { version = "0.4.37", optional = true } -web-sys = { version = "0.3.64", optional = true, features = [ - "MessageEvent", - "RtcPeerConnection", - "RtcSignalingState", - "RtcSdpType", - "RtcSessionDescription", - "RtcSessionDescriptionInit", - "RtcPeerConnectionIceEvent", - "RtcIceCandidate", - "RtcDataChannel", - "RtcDataChannelEvent", - "RtcCertificate", - "RtcConfiguration", - "RtcDataChannelInit", - "RtcDataChannelType", - "RtcDataChannelState", -] } [features] -tokio = [ - "dep:tokio", - "dep:tokio-util", - "dep:webrtc", - "dep:if-watch", - "if-watch/tokio", - "dep:rand", - "dep:stun", -] +tokio = ["dep:tokio", "dep:tokio-util", "dep:webrtc", "if-watch/tokio"] pem = ["webrtc?/pem"] -wasm-bindgen = [ - "dep:js-sys", - "dep:getrandom", - "dep:regex", - "dep:send_wrapper", - "dep:wasm-bindgen", - "dep:wasm-bindgen-futures", - "dep:web-sys", - "futures-timer/wasm-bindgen", -] - [dev-dependencies] anyhow = "1.0" diff --git a/transports/webrtc/src/common/mod.rs b/transports/webrtc/src/common/mod.rs deleted file mode 100644 index 3c5dcaefef2..00000000000 --- a/transports/webrtc/src/common/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod substream; diff --git a/transports/webrtc/src/common/substream/mod.rs b/transports/webrtc/src/common/substream/mod.rs deleted file mode 100644 index b40a94e8336..00000000000 --- a/transports/webrtc/src/common/substream/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub(crate) mod state; - -/// Maximum length of a message. -/// -/// "As long as message interleaving is not supported, the sender SHOULD limit the maximum message -/// size to 16 KB to avoid monopolization." -/// Source: -pub(crate) const MAX_MSG_LEN: usize = 16384; // 16kiB -/// Length of varint, in bytes. -pub(crate) const VARINT_LEN: usize = 2; -/// Overhead of the protobuf encoding, in bytes. -pub(crate) const PROTO_OVERHEAD: usize = 5; -/// Maximum length of data, in bytes. -pub(crate) const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; diff --git a/transports/webrtc/src/common/substream/state.rs b/transports/webrtc/src/common/substream/state.rs deleted file mode 100644 index b1768aa2165..00000000000 --- a/transports/webrtc/src/common/substream/state.rs +++ /dev/null @@ -1,508 +0,0 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use bytes::Bytes; - -use std::io; - -use crate::proto::Flag; - -#[derive(Debug, Copy, Clone)] -pub(crate) enum State { - Open, - ReadClosed, - WriteClosed, - ClosingRead { - /// Whether the write side of our channel was already closed. - write_closed: bool, - inner: Closing, - }, - ClosingWrite { - /// Whether the write side of our channel was already closed. - read_closed: bool, - inner: Closing, - }, - BothClosed { - reset: bool, - }, -} - -/// Represents the state of closing one half (either read or write) of the connection. -/// -/// Gracefully closing the read or write requires sending the `STOP_SENDING` or `FIN` flag respectively -/// and flushing the underlying connection. -#[derive(Debug, Copy, Clone)] -pub(crate) enum Closing { - Requested, - MessageSent, -} - -impl State { - /// Performs a state transition for a flag contained in an inbound message. - pub(crate) fn handle_inbound_flag(&mut self, flag: Flag, buffer: &mut Bytes) { - let current = *self; - - match (current, flag) { - (Self::Open, Flag::FIN) => { - *self = Self::ReadClosed; - } - (Self::WriteClosed, Flag::FIN) => { - *self = Self::BothClosed { reset: false }; - } - (Self::Open, Flag::STOP_SENDING) => { - *self = Self::WriteClosed; - } - (Self::ReadClosed, Flag::STOP_SENDING) => { - *self = Self::BothClosed { reset: false }; - } - (_, Flag::RESET) => { - buffer.clear(); - *self = Self::BothClosed { reset: true }; - } - _ => {} - } - } - - pub(crate) fn write_closed(&mut self) { - match self { - State::ClosingWrite { - read_closed: true, - inner, - } => { - debug_assert!(matches!(inner, Closing::MessageSent)); - - *self = State::BothClosed { reset: false }; - } - State::ClosingWrite { - read_closed: false, - inner, - } => { - debug_assert!(matches!(inner, Closing::MessageSent)); - - *self = State::WriteClosed; - } - State::Open - | State::ReadClosed - | State::WriteClosed - | State::ClosingRead { .. } - | State::BothClosed { .. } => { - unreachable!("bad state machine impl") - } - } - } - - pub(crate) fn close_write_message_sent(&mut self) { - match self { - State::ClosingWrite { inner, read_closed } => { - debug_assert!(matches!(inner, Closing::Requested)); - - *self = State::ClosingWrite { - read_closed: *read_closed, - inner: Closing::MessageSent, - }; - } - State::Open - | State::ReadClosed - | State::WriteClosed - | State::ClosingRead { .. } - | State::BothClosed { .. } => { - unreachable!("bad state machine impl") - } - } - } - - pub(crate) fn read_closed(&mut self) { - match self { - State::ClosingRead { - write_closed: true, - inner, - } => { - debug_assert!(matches!(inner, Closing::MessageSent)); - - *self = State::BothClosed { reset: false }; - } - State::ClosingRead { - write_closed: false, - inner, - } => { - debug_assert!(matches!(inner, Closing::MessageSent)); - - *self = State::ReadClosed; - } - State::Open - | State::ReadClosed - | State::WriteClosed - | State::ClosingWrite { .. } - | State::BothClosed { .. } => { - unreachable!("bad state machine impl") - } - } - } - - pub(crate) fn close_read_message_sent(&mut self) { - match self { - State::ClosingRead { - inner, - write_closed, - } => { - debug_assert!(matches!(inner, Closing::Requested)); - - *self = State::ClosingRead { - write_closed: *write_closed, - inner: Closing::MessageSent, - }; - } - State::Open - | State::ReadClosed - | State::WriteClosed - | State::ClosingWrite { .. } - | State::BothClosed { .. } => { - unreachable!("bad state machine impl") - } - } - } - - /// Whether we should read from the stream in the [`futures::AsyncWrite`] implementation. - /// - /// This is necessary for read-closed streams because we would otherwise not read any more flags from - /// the socket. - pub(crate) fn read_flags_in_async_write(&self) -> bool { - matches!(self, Self::ReadClosed) - } - - /// Acts as a "barrier" for [`futures::AsyncRead::poll_read`]. - pub(crate) fn read_barrier(&self) -> io::Result<()> { - use State::*; - - let kind = match self { - Open - | WriteClosed - | ClosingWrite { - read_closed: false, .. - } => return Ok(()), - ClosingWrite { - read_closed: true, .. - } - | ReadClosed - | ClosingRead { .. } - | BothClosed { reset: false } => io::ErrorKind::BrokenPipe, - BothClosed { reset: true } => io::ErrorKind::ConnectionReset, - }; - - Err(kind.into()) - } - - /// Acts as a "barrier" for [`futures::AsyncWrite::poll_write`]. - pub(crate) fn write_barrier(&self) -> io::Result<()> { - use State::*; - - let kind = match self { - Open - | ReadClosed - | ClosingRead { - write_closed: false, - .. - } => return Ok(()), - ClosingRead { - write_closed: true, .. - } - | WriteClosed - | ClosingWrite { .. } - | BothClosed { reset: false } => io::ErrorKind::BrokenPipe, - BothClosed { reset: true } => io::ErrorKind::ConnectionReset, - }; - - Err(kind.into()) - } - - /// Acts as a "barrier" for [`futures::AsyncWrite::poll_close`]. - pub(crate) fn close_write_barrier(&mut self) -> io::Result> { - loop { - match &self { - State::WriteClosed => return Ok(None), - - State::ClosingWrite { inner, .. } => return Ok(Some(*inner)), - - State::Open => { - *self = Self::ClosingWrite { - read_closed: false, - inner: Closing::Requested, - }; - } - State::ReadClosed => { - *self = Self::ClosingWrite { - read_closed: true, - inner: Closing::Requested, - }; - } - - State::ClosingRead { - write_closed: true, .. - } - | State::BothClosed { reset: false } => { - return Err(io::ErrorKind::BrokenPipe.into()) - } - - State::ClosingRead { - write_closed: false, - .. - } => { - return Err(io::Error::new( - io::ErrorKind::Other, - "cannot close read half while closing write half", - )) - } - - State::BothClosed { reset: true } => { - return Err(io::ErrorKind::ConnectionReset.into()) - } - } - } - } - - /// Acts as a "barrier" for [`Substream::poll_close_read`](super::Substream::poll_close_read). - pub(crate) fn close_read_barrier(&mut self) -> io::Result> { - loop { - match self { - State::ReadClosed => return Ok(None), - - State::ClosingRead { inner, .. } => return Ok(Some(*inner)), - - State::Open => { - *self = Self::ClosingRead { - write_closed: false, - inner: Closing::Requested, - }; - } - State::WriteClosed => { - *self = Self::ClosingRead { - write_closed: true, - inner: Closing::Requested, - }; - } - - State::ClosingWrite { - read_closed: true, .. - } - | State::BothClosed { reset: false } => { - return Err(io::ErrorKind::BrokenPipe.into()) - } - - State::ClosingWrite { - read_closed: false, .. - } => { - return Err(io::Error::new( - io::ErrorKind::Other, - "cannot close write half while closing read half", - )) - } - - State::BothClosed { reset: true } => { - return Err(io::ErrorKind::ConnectionReset.into()) - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::io::ErrorKind; - - #[test] - fn cannot_read_after_receiving_fin() { - let mut open = State::Open; - - open.handle_inbound_flag(Flag::FIN, &mut Bytes::default()); - let error = open.read_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe) - } - - #[test] - fn cannot_read_after_closing_read() { - let mut open = State::Open; - - open.close_read_barrier().unwrap(); - open.close_read_message_sent(); - open.read_closed(); - let error = open.read_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe) - } - - #[test] - fn cannot_write_after_receiving_stop_sending() { - let mut open = State::Open; - - open.handle_inbound_flag(Flag::STOP_SENDING, &mut Bytes::default()); - let error = open.write_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe) - } - - #[test] - fn cannot_write_after_closing_write() { - let mut open = State::Open; - - open.close_write_barrier().unwrap(); - open.close_write_message_sent(); - open.write_closed(); - let error = open.write_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe) - } - - #[test] - fn everything_broken_after_receiving_reset() { - let mut open = State::Open; - - open.handle_inbound_flag(Flag::RESET, &mut Bytes::default()); - let error1 = open.read_barrier().unwrap_err(); - let error2 = open.write_barrier().unwrap_err(); - let error3 = open.close_write_barrier().unwrap_err(); - let error4 = open.close_read_barrier().unwrap_err(); - - assert_eq!(error1.kind(), ErrorKind::ConnectionReset); - assert_eq!(error2.kind(), ErrorKind::ConnectionReset); - assert_eq!(error3.kind(), ErrorKind::ConnectionReset); - assert_eq!(error4.kind(), ErrorKind::ConnectionReset); - } - - #[test] - fn should_read_flags_in_async_write_after_read_closed() { - let mut open = State::Open; - - open.handle_inbound_flag(Flag::FIN, &mut Bytes::default()); - - assert!(open.read_flags_in_async_write()) - } - - #[test] - fn cannot_read_or_write_after_receiving_fin_and_stop_sending() { - let mut open = State::Open; - - open.handle_inbound_flag(Flag::FIN, &mut Bytes::default()); - open.handle_inbound_flag(Flag::STOP_SENDING, &mut Bytes::default()); - - let error1 = open.read_barrier().unwrap_err(); - let error2 = open.write_barrier().unwrap_err(); - - assert_eq!(error1.kind(), ErrorKind::BrokenPipe); - assert_eq!(error2.kind(), ErrorKind::BrokenPipe); - } - - #[test] - fn can_read_after_closing_write() { - let mut open = State::Open; - - open.close_write_barrier().unwrap(); - open.close_write_message_sent(); - open.write_closed(); - - open.read_barrier().unwrap(); - } - - #[test] - fn can_write_after_closing_read() { - let mut open = State::Open; - - open.close_read_barrier().unwrap(); - open.close_read_message_sent(); - open.read_closed(); - - open.write_barrier().unwrap(); - } - - #[test] - fn cannot_write_after_starting_close() { - let mut open = State::Open; - - open.close_write_barrier().expect("to close in open"); - let error = open.write_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe); - } - - #[test] - fn cannot_read_after_starting_close() { - let mut open = State::Open; - - open.close_read_barrier().expect("to close in open"); - let error = open.read_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe); - } - - #[test] - fn can_read_in_open() { - let open = State::Open; - - let result = open.read_barrier(); - - result.unwrap(); - } - - #[test] - fn can_write_in_open() { - let open = State::Open; - - let result = open.write_barrier(); - - result.unwrap(); - } - - #[test] - fn write_close_barrier_returns_ok_when_closed() { - let mut open = State::Open; - - open.close_write_barrier().unwrap(); - open.close_write_message_sent(); - open.write_closed(); - - let maybe = open.close_write_barrier().unwrap(); - - assert!(maybe.is_none()) - } - - #[test] - fn read_close_barrier_returns_ok_when_closed() { - let mut open = State::Open; - - open.close_read_barrier().unwrap(); - open.close_read_message_sent(); - open.read_closed(); - - let maybe = open.close_read_barrier().unwrap(); - - assert!(maybe.is_none()) - } - - #[test] - fn reset_flag_clears_buffer() { - let mut open = State::Open; - let mut buffer = Bytes::copy_from_slice(b"foobar"); - - open.handle_inbound_flag(Flag::RESET, &mut buffer); - - assert!(buffer.is_empty()); - } -} diff --git a/transports/webrtc/src/generated/message.proto b/transports/webrtc/src/generated/message.proto deleted file mode 100644 index eab3ceb720b..00000000000 --- a/transports/webrtc/src/generated/message.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto2"; - -package webrtc.pb; - -message Message { - enum Flag { - // The sender will no longer send messages on the stream. - FIN = 0; - // The sender will no longer read messages on the stream. Incoming data is - // being discarded on receipt. - STOP_SENDING = 1; - // The sender abruptly terminates the sending part of the stream. The - // receiver can discard any data that it already received on that stream. - RESET = 2; - } - - optional Flag flag=1; - - optional bytes message = 2; -} diff --git a/transports/webrtc/src/generated/mod.rs b/transports/webrtc/src/generated/mod.rs deleted file mode 100644 index 5e9f6373b12..00000000000 --- a/transports/webrtc/src/generated/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Automatically generated mod.rs -pub mod webrtc; diff --git a/transports/webrtc/src/generated/webrtc/mod.rs b/transports/webrtc/src/generated/webrtc/mod.rs deleted file mode 100644 index aec6164c7ef..00000000000 --- a/transports/webrtc/src/generated/webrtc/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Automatically generated mod.rs -pub mod pb; diff --git a/transports/webrtc/src/generated/webrtc/pb.rs b/transports/webrtc/src/generated/webrtc/pb.rs deleted file mode 100644 index 9e33e97188c..00000000000 --- a/transports/webrtc/src/generated/webrtc/pb.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Automatically generated rust module for 'message.proto' file - -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(unused_imports)] -#![allow(unknown_lints)] -#![allow(clippy::all)] -#![cfg_attr(rustfmt, rustfmt_skip)] - - -use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; -use quick_protobuf::sizeofs::*; -use super::super::*; - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct Message { - pub flag: Option, - pub message: Option>, -} - -impl<'a> MessageRead<'a> for Message { - 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(8) => msg.flag = Some(r.read_enum(bytes)?), - Ok(18) => msg.message = Some(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for Message { - fn get_size(&self) -> usize { - 0 - + self.flag.as_ref().map_or(0, |m| 1 + sizeof_varint(*(m) as u64)) - + self.message.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.flag { w.write_with_tag(8, |w| w.write_enum(*s as i32))?; } - if let Some(ref s) = self.message { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -pub mod mod_Message { - - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum Flag { - FIN = 0, - STOP_SENDING = 1, - RESET = 2, -} - -impl Default for Flag { - fn default() -> Self { - Flag::FIN - } -} - -impl From for Flag { - fn from(i: i32) -> Self { - match i { - 0 => Flag::FIN, - 1 => Flag::STOP_SENDING, - 2 => Flag::RESET, - _ => Self::default(), - } - } -} - -impl<'a> From<&'a str> for Flag { - fn from(s: &'a str) -> Self { - match s { - "FIN" => Flag::FIN, - "STOP_SENDING" => Flag::STOP_SENDING, - "RESET" => Flag::RESET, - _ => Self::default(), - } - } -} - -} - diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 7f5914ccb5e..7757832a2a7 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -79,18 +79,7 @@ //! hand-crate the SDP answer generated by the remote, this is problematic. A way to solve this //! is to make the hash a part of the remote's multiaddr. On the server side, we turn //! certificate verification off. - -mod proto { - #![allow(unreachable_pub)] - include!("generated/mod.rs"); - pub(crate) use self::webrtc::pb::{mod_Message::Flag, Message}; -} - #[cfg(feature = "tokio")] pub mod tokio; -#[cfg(feature = "wasm-bindgen")] -pub mod websys; - -/// Crates common to all features -mod common; +pub use libp2p_webrtc_utils as utils; diff --git a/transports/webrtc/src/tokio/substream.rs b/transports/webrtc/src/tokio/substream.rs index 3c5f46ce0f9..453678ec89b 100644 --- a/transports/webrtc/src/tokio/substream.rs +++ b/transports/webrtc/src/tokio/substream.rs @@ -31,12 +31,12 @@ use std::{ task::{Context, Poll}, }; -use crate::common::substream::{ +use crate::tokio::{substream::drop_listener::GracefullyClosed, substream::framed_dc::FramedDc}; +use crate::utils::proto::{Flag, Message}; +use crate::utils::substream::{ state::{Closing, State}, MAX_DATA_LEN, }; -use crate::proto::{Flag, Message}; -use crate::tokio::{substream::drop_listener::GracefullyClosed, substream::framed_dc::FramedDc}; mod drop_listener; mod framed_dc; @@ -149,6 +149,7 @@ impl AsyncWrite for Substream { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { + // while ReadClosed while self.state.read_flags_in_async_write() { // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? @@ -237,7 +238,7 @@ fn io_poll_next( #[cfg(test)] mod tests { - use crate::common::substream::{MAX_MSG_LEN, PROTO_OVERHEAD, VARINT_LEN}; + use crate::utils::substream::{MAX_MSG_LEN, PROTO_OVERHEAD, VARINT_LEN}; use super::*; use asynchronous_codec::Encoder; @@ -250,8 +251,8 @@ mod tests { // Largest possible message. let message = [0; MAX_DATA_LEN]; - let protobuf = crate::proto::Message { - flag: Some(crate::proto::Flag::FIN), + let protobuf = crate::utils::proto::Message { + flag: Some(crate::utils::proto::Flag::FIN), message: Some(message.to_vec()), }; diff --git a/transports/webrtc/src/tokio/substream/drop_listener.rs b/transports/webrtc/src/tokio/substream/drop_listener.rs index 735240456fe..21e4082c637 100644 --- a/transports/webrtc/src/tokio/substream/drop_listener.rs +++ b/transports/webrtc/src/tokio/substream/drop_listener.rs @@ -27,8 +27,8 @@ use std::io; use std::pin::Pin; use std::task::{Context, Poll}; -use crate::proto::{Flag, Message}; use crate::tokio::substream::framed_dc::FramedDc; +use crate::utils::proto::{Flag, Message}; #[must_use] pub(crate) struct DropListener { diff --git a/transports/webrtc/src/tokio/substream/framed_dc.rs b/transports/webrtc/src/tokio/substream/framed_dc.rs index 60646b9ce03..baacbb8ca16 100644 --- a/transports/webrtc/src/tokio/substream/framed_dc.rs +++ b/transports/webrtc/src/tokio/substream/framed_dc.rs @@ -25,11 +25,8 @@ use webrtc::data::data_channel::{DataChannel, PollDataChannel}; use std::sync::Arc; -use crate::common::substream::{ - state::{Closing, State}, - MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN, -}; -use crate::proto::Message; +use crate::utils::proto::Message; +use crate::utils::substream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; pub(crate) type FramedDc = Framed, quick_protobuf_codec::Codec>; pub(crate) fn new(data_channel: Arc) -> FramedDc { diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index 2d5e3fe2c10..fe9b2456674 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -25,6 +25,7 @@ use futures::future::Either; use futures_timer::Delay; use libp2p_identity as identity; use libp2p_identity::PeerId; +use log::debug; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use webrtc::api::setting_engine::SettingEngine; @@ -51,16 +52,16 @@ pub(crate) async fn outbound( server_fingerprint: Fingerprint, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { - log::debug!("new outbound connection to {addr})"); + debug!("new outbound connection to {addr})"); let (peer_connection, ufrag) = new_outbound_connection(addr, config, udp_mux).await?; let offer = peer_connection.create_offer(None).await?; - log::debug!("created SDP offer for outbound connection: {:?}", offer.sdp); + debug!("created SDP offer for outbound connection: {:?}", offer.sdp); peer_connection.set_local_description(offer).await?; let answer = sdp::answer(addr, &server_fingerprint, &ufrag); - log::debug!( + debug!( "calculated SDP answer for outbound connection: {:?}", answer ); @@ -87,16 +88,16 @@ pub(crate) async fn inbound( remote_ufrag: String, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { - log::debug!("new inbound connection from {addr} (ufrag: {remote_ufrag})"); + debug!("new inbound connection from {addr} (ufrag: {remote_ufrag})"); let peer_connection = new_inbound_connection(addr, config, udp_mux, &remote_ufrag).await?; let offer = sdp::offer(addr, &remote_ufrag); - log::debug!("calculated SDP offer for inbound connection: {:?}", offer); + debug!("calculated SDP offer for inbound connection: {:?}", offer); peer_connection.set_remote_description(offer).await?; let answer = peer_connection.create_answer(None).await?; - log::debug!("created SDP answer for inbound connection: {:?}", answer); + debug!("created SDP answer for inbound connection: {:?}", answer); peer_connection.set_local_description(answer).await?; // This will start the gathering of ICE candidates. let data_channel = create_substream_for_noise_handshake(&peer_connection).await?; diff --git a/transports/webrtc/src/websys/cbfutures.rs b/transports/webrtc/src/websys/cbfutures.rs deleted file mode 100644 index 5b6fe43ebd4..00000000000 --- a/transports/webrtc/src/websys/cbfutures.rs +++ /dev/null @@ -1,98 +0,0 @@ -use futures::FutureExt; -use std::cell::Cell; -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{ready, Context, Poll, Waker}; - -pub struct FusedCbFuture(Option>); - -impl FusedCbFuture { - pub(crate) fn new() -> Self { - Self(None) - } - - pub(crate) fn maybe_init(&mut self, init: F) -> &mut Self - where - F: FnOnce() -> CbFuture, - { - if self.0.is_none() { - self.0 = Some(init()); - } - - self - } - - /// Checks if future is already running - pub(crate) fn is_active(&self) -> bool { - self.0.is_some() - } -} - -#[derive(Clone, Debug)] -pub struct CbFuture(Rc>); - -struct CallbackFutureInner { - waker: Cell>, - result: Cell>, -} - -impl std::fmt::Debug for CallbackFutureInner { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CallbackFutureInner").finish() - } -} - -impl Default for CallbackFutureInner { - fn default() -> Self { - Self { - waker: Cell::new(None), - result: Cell::new(None), - } - } -} - -impl CbFuture { - /// New Callback Future - pub(crate) fn new() -> Self { - Self(Rc::new(CallbackFutureInner::::default())) - } - - // call this from your callback - pub(crate) fn publish(&self, result: T) { - self.0.result.set(Some(result)); - if let Some(w) = self.0.waker.take() { - w.wake() - }; - } -} - -impl Future for CbFuture { - type Output = T; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.0.result.take() { - Some(x) => Poll::Ready(x), - None => { - self.0.waker.set(Some(cx.waker().clone())); - Poll::Pending - } - } - } -} - -impl Future for FusedCbFuture { - type Output = T; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let val = ready!(self - .0 - .as_mut() - .expect("FusedCbFuture not initialized") - .poll_unpin(cx)); - - // Future finished, drop it - self.0.take(); - - Poll::Ready(val) - } -} diff --git a/transports/webrtc/src/websys/connection.rs b/transports/webrtc/src/websys/connection.rs deleted file mode 100644 index a7fe3448319..00000000000 --- a/transports/webrtc/src/websys/connection.rs +++ /dev/null @@ -1,231 +0,0 @@ -//! Websys WebRTC Connection -//! -use super::cbfutures::CbFuture; -use super::fingerprint::Fingerprint; -use super::sdp; -use super::stream::DataChannelConfig; -use super::upgrade::{self}; -use super::utils; -use super::{Error, WebRTCStream}; -use futures::join; -use futures::FutureExt; -use js_sys::Object; -use js_sys::Reflect; -use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; -use libp2p_identity::{Keypair, PeerId}; -use send_wrapper::SendWrapper; -use std::net::SocketAddr; -use std::pin::Pin; -use std::task::{ready, Context, Poll}; -use wasm_bindgen_futures::JsFuture; -use web_sys::{RtcConfiguration, RtcDataChannel, RtcPeerConnection}; - -const SHA2_256: u64 = 0x12; -const SHA2_512: u64 = 0x13; - -pub struct Connection { - // Swarm needs all types to be Send. WASM is single-threaded - // and it is safe to use SendWrapper. - inner: SendWrapper, -} - -struct ConnectionInner { - peer_connection: Option, - sock_addr: SocketAddr, - remote_fingerprint: Fingerprint, - id_keys: Keypair, - create_data_channel_cbfuture: CbFuture, - closed: bool, -} - -impl Connection { - /// Create a new Connection - pub(crate) fn new( - sock_addr: SocketAddr, - remote_fingerprint: Fingerprint, - id_keys: Keypair, - ) -> Self { - Self { - inner: SendWrapper::new(ConnectionInner::new(sock_addr, remote_fingerprint, id_keys)), - } - } - - /// Connect - pub(crate) async fn connect(&mut self) -> Result { - let fut = SendWrapper::new(self.inner.connect()); - fut.await - } - - /// Peer Connection Getter - pub(crate) fn peer_connection(&self) -> Option<&RtcPeerConnection> { - self.inner.peer_connection.as_ref() - } -} - -impl ConnectionInner { - fn new(sock_addr: SocketAddr, remote_fingerprint: Fingerprint, id_keys: Keypair) -> Self { - Self { - peer_connection: None, - sock_addr, - remote_fingerprint, - id_keys, - create_data_channel_cbfuture: CbFuture::new(), - closed: false, - } - } - - async fn connect(&mut self) -> Result { - let hash = match self.remote_fingerprint.to_multihash().code() { - SHA2_256 => "sha-256", - SHA2_512 => "sha2-512", - _ => return Err(Error::JsError("unsupported hash".to_string())), - }; - - let algo: js_sys::Object = Object::new(); - Reflect::set(&algo, &"name".into(), &"ECDSA".into()).unwrap(); - Reflect::set(&algo, &"namedCurve".into(), &"P-256".into()).unwrap(); - Reflect::set(&algo, &"hash".into(), &hash.into()).unwrap(); - - let certificate_promise = RtcPeerConnection::generate_certificate_with_object(&algo) - .expect("certificate to be valid"); - - let certificate = JsFuture::from(certificate_promise).await?; // Needs to be Send - - let mut config = RtcConfiguration::new(); - config.certificates(&certificate); - - let peer_connection = web_sys::RtcPeerConnection::new_with_configuration(&config)?; - - let ufrag = format!("libp2p+webrtc+v1/{}", utils::gen_ufrag(32)); - /* - * OFFER - */ - let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send - let offer_obj = sdp::offer(offer, &ufrag); - let sld_promise = peer_connection.set_local_description(&offer_obj); - - /* - * ANSWER - */ - // TODO: Update SDP Answer format for Browser WebRTC - let answer_obj = sdp::answer(self.sock_addr, &self.remote_fingerprint, &ufrag); - let srd_promise = peer_connection.set_remote_description(&answer_obj); - - let (local_desc, remote_desc) = - match join!(JsFuture::from(sld_promise), JsFuture::from(srd_promise)) { - (Ok(local_desc), Ok(remote_desc)) => (local_desc, remote_desc), - (Err(e), _) => { - return Err(Error::JsError(format!( - "Error setting local_description: {:?}", - e - ))) - } - (_, Err(e)) => { - return Err(Error::JsError(format!( - "Error setting remote_description: {:?}", - e - ))) - } - }; - - let peer_id = upgrade::outbound( - &peer_connection, - self.id_keys.clone(), - self.remote_fingerprint, - ) - .await?; - - self.peer_connection = Some(peer_connection); - Ok(peer_id) - } - - /// Initiates and polls a future from `create_data_channel`. - fn poll_create_data_channel( - &mut self, - cx: &mut Context, - config: DataChannelConfig, - ) -> Poll> { - // Create Data Channel - // take the peer_connection and DataChannelConfig and create a pollable future - let mut dc = - super::stream::create_data_channel(&self.peer_connection.as_ref().unwrap(), config); - - let val = ready!(dc.poll_unpin(cx)); - - let channel = WebRTCStream::new(val); - - Poll::Ready(Ok(channel)) - } - - /// Polls Incoming Peer Connections? Or Data Channels? - fn poll_incoming(&mut self, cx: &mut Context) -> Poll> { - let mut dc = super::stream::create_data_channel( - &self.peer_connection.as_ref().unwrap(), - DataChannelConfig::default(), - ); - - let val = ready!(dc.poll_unpin(cx)); - - let channel = WebRTCStream::new(val); - - Poll::Ready(Ok(channel)) - } - - /// Closes the Peer Connection. - /// - /// This closes the data channels also and they will return an error - /// if they are used. - fn close_connection(&mut self) { - if let (Some(conn), false) = (&self.peer_connection, self.closed) { - conn.close(); - self.closed = true; - } - } -} - -impl Drop for ConnectionInner { - fn drop(&mut self) { - self.close_connection(); - } -} - -/// WebRTC native multiplexing -/// Allows users to open substreams -impl StreamMuxer for Connection { - type Substream = WebRTCStream; // A Substream of a WebRTC PeerConnection is a Data Channel - type Error = Error; - - fn poll_inbound( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - // Inbound substreams for Browser WebRTC? - // Can only be done through a relayed connection - self.inner.poll_incoming(cx) - } - - fn poll_outbound( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - // Since this is not a initial handshake outbound request (ie. Dialer) - // we need to create a new Data Channel without negotiated flag set to true - let config = DataChannelConfig::default(); - self.inner.poll_create_data_channel(cx, config) - } - - fn poll_close( - mut self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { - self.inner.close_connection(); - Poll::Ready(Ok(())) - } - - fn poll( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Pending - } -} diff --git a/transports/webrtc/src/websys/error.rs b/transports/webrtc/src/websys/error.rs deleted file mode 100644 index 6baf6685f34..00000000000 --- a/transports/webrtc/src/websys/error.rs +++ /dev/null @@ -1,52 +0,0 @@ -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, - - #[error("Connection error: {0}")] - Connection(String), -} - -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) - } -} - -impl std::convert::From for Error { - fn from(value: JsValue) -> Self { - Error::from_js_value(value) - } -} - -// impl From String -impl From for Error { - fn from(value: String) -> Self { - Error::JsError(value) - } -} diff --git a/transports/webrtc/src/websys/fingerprint.rs b/transports/webrtc/src/websys/fingerprint.rs deleted file mode 100644 index d2dac63e032..00000000000 --- a/transports/webrtc/src/websys/fingerprint.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use sha2::Digest as _; -use std::fmt; - -const SHA256: &str = "sha-256"; -const MULTIHASH_SHA256_CODE: u64 = 0x12; - -type Multihash = multihash::Multihash<64>; - -/// A certificate fingerprint that is assumed to be created using the SHA256 hash algorithm. -#[derive(Eq, PartialEq, Copy, Clone)] -pub struct Fingerprint([u8; 32]); - -impl Fingerprint { - pub(crate) const FF: Fingerprint = Fingerprint([0xFF; 32]); - - #[cfg(test)] - pub fn raw(bytes: [u8; 32]) -> Self { - Self(bytes) - } - - /// Creates a fingerprint from a raw certificate. - pub fn from_certificate(bytes: &[u8]) -> Self { - Fingerprint(sha2::Sha256::digest(bytes).into()) - } - - /// Converts [`Multihash`](multihash::Multihash) to [`Fingerprint`]. - pub fn try_from_multihash(hash: Multihash) -> Option { - if hash.code() != MULTIHASH_SHA256_CODE { - // Only support SHA256 for now. - return None; - } - - let bytes = hash.digest().try_into().ok()?; - - Some(Self(bytes)) - } - - /// Converts this fingerprint to [`Multihash`](multihash::Multihash). - pub fn to_multihash(self) -> Multihash { - Multihash::wrap(MULTIHASH_SHA256_CODE, &self.0).expect("fingerprint's len to be 32 bytes") - } - - /// Formats this fingerprint as uppercase hex, separated by colons (`:`). - /// - /// This is the format described in . - pub fn to_sdp_format(self) -> String { - self.0.map(|byte| format!("{byte:02X}")).join(":") - } - - /// Returns the algorithm used (e.g. "sha-256"). - /// See - pub fn algorithm(&self) -> String { - SHA256.to_owned() - } -} - -impl fmt::Debug for Fingerprint { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&hex::encode(self.0)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn sdp_format() { - let fp = Fingerprint::raw(hex_literal::hex!( - "7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC" - )); - - let sdp_format = fp.to_sdp_format(); - - assert_eq!(sdp_format, "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC") - } -} diff --git a/transports/webrtc/src/websys/fused_js_promise.rs b/transports/webrtc/src/websys/fused_js_promise.rs deleted file mode 100644 index 0ba846501c2..00000000000 --- a/transports/webrtc/src/websys/fused_js_promise.rs +++ /dev/null @@ -1,58 +0,0 @@ -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, -} - -impl FusedJsPromise { - /// Creates new uninitialized promise. - pub(crate) fn new() -> Self { - FusedJsPromise { promise: None } - } - - /// Initialize promise if needed - pub(crate) fn maybe_init(&mut self, init: F) -> &mut Self - where - F: FnOnce() -> Promise, - { - if self.promise.is_none() { - self.promise = Some(JsFuture::from(init())); - } - - self - } - - /// 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() - .expect("FusedJsPromise not initialized") - .poll_unpin(cx)); - - // Future finished, drop it - self.promise.take(); - - Poll::Ready(val) - } -} diff --git a/transports/webrtc/src/websys/mod.rs b/transports/webrtc/src/websys/mod.rs deleted file mode 100644 index 9392527d2a2..00000000000 --- a/transports/webrtc/src/websys/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod cbfutures; -mod connection; -mod error; -pub(crate) mod fingerprint; -mod fused_js_promise; -mod sdp; -mod stream; -mod transport; -mod upgrade; -mod utils; - -pub use self::connection::Connection; -pub use self::error::Error; -pub use self::stream::WebRTCStream; -pub use self::transport::{Config, Transport}; diff --git a/transports/webrtc/src/websys/sdp.rs b/transports/webrtc/src/websys/sdp.rs deleted file mode 100644 index ee816f976c5..00000000000 --- a/transports/webrtc/src/websys/sdp.rs +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use js_sys::Reflect; -use serde::Serialize; -use tinytemplate::TinyTemplate; -use wasm_bindgen::JsValue; -use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; - -use std::net::{IpAddr, SocketAddr}; - -use super::fingerprint::Fingerprint; - -/// Creates the SDP answer used by the client. -pub(crate) fn answer( - addr: SocketAddr, - server_fingerprint: &Fingerprint, - client_ufrag: &str, -) -> RtcSessionDescriptionInit { - let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); - answer_obj.sdp(&render_description( - SERVER_SESSION_DESCRIPTION, - addr, - server_fingerprint, - client_ufrag, - )); - answer_obj -} - -/// Creates the SDP offer used by the server. -/// -/// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. -pub(crate) fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescriptionInit { - let offer_sdp = Reflect::get(&offer, &JsValue::from_str("sdp")) - .expect("valid offer object") - .as_string() - .unwrap(); - - let munged_offer_sdp = offer_sdp - .replace( - "\na=ice-ufrag:[^\n]*\n", - &format!("\na=ice-ufrag:{client_ufrag}\n"), - ) - .replace( - "\na=ice-pwd:[^\n]*\n", - &format!("\na=ice-pwd:{client_ufrag}\n"), - ); - - // setLocalDescription - let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); - offer_obj.sdp(&munged_offer_sdp); - - offer_obj -} - -// An SDP message that constitutes the offer. -// -// Main RFC: -// `sctp-port` and `max-message-size` attrs RFC: -// `group` and `mid` attrs RFC: -// `ice-ufrag`, `ice-pwd` and `ice-options` attrs RFC: -// `setup` attr RFC: -// -// Short description: -// -// v= -> always 0 -// o= -// -// identifies the creator of the SDP document. We are allowed to use dummy values -// (`-` and `0.0.0.0` as ) to remain anonymous, which we do. Note that "IN" means -// "Internet". -// -// s= -// -// We are allowed to pass a dummy `-`. -// -// c= -// -// Indicates the IP address of the remote. -// Note that "IN" means "Internet". -// -// t= -// -// Start and end of the validity of the session. `0 0` means that the session never expires. -// -// m= ... -// -// A `m=` line describes a request to establish a certain protocol. The protocol in this line -// (i.e. `TCP/DTLS/SCTP` or `UDP/DTLS/SCTP`) must always be the same as the one in the offer. -// We know that this is true because we tweak the offer to match the protocol. The `` -// component must always be `webrtc-datachannel` for WebRTC. -// RFCs: 8839, 8866, 8841 -// -// a=mid: -// -// Media ID - uniquely identifies this media stream (RFC9143). -// -// a=ice-options:ice2 -// -// Indicates that we are complying with RFC8839 (as oppposed to the legacy RFC5245). -// -// a=ice-ufrag: -// a=ice-pwd: -// -// ICE username and password, which are used for establishing and -// maintaining the ICE connection. (RFC8839) -// MUST match ones used by the answerer (server). -// -// a=fingerprint:sha-256 -// -// Fingerprint of the certificate that the remote will use during the TLS -// handshake. (RFC8122) -// -// a=setup:actpass -// -// The endpoint that is the offerer MUST use the setup attribute value of setup:actpass and be -// prepared to receive a client_hello before it receives the answer. -// -// a=sctp-port: -// -// The SCTP port (RFC8841) -// Note it's different from the "m=" line port value, which indicates the port of the -// underlying transport-layer protocol (UDP or TCP). -// -// a=max-message-size: -// -// The maximum SCTP user message size (in bytes). (RFC8841) -const CLIENT_SESSION_DESCRIPTION: &str = "v=0 -o=- 0 0 IN {ip_version} {target_ip} -s=- -c=IN {ip_version} {target_ip} -t=0 0 - -m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel -a=mid:0 -a=ice-options:ice2 -a=ice-ufrag:{ufrag} -a=ice-pwd:{pwd} -a=fingerprint:{fingerprint_algorithm} {fingerprint_value} -a=setup:actpass -a=sctp-port:5000 -a=max-message-size:16384 -"; - -// See [`CLIENT_SESSION_DESCRIPTION`]. -// -// a=ice-lite -// -// A lite implementation is only appropriate for devices that will *always* be connected to -// the public Internet and have a public IP address at which it can receive packets from any -// correspondent. ICE will not function when a lite implementation is placed behind a NAT -// (RFC8445). -// -// a=tls-id: -// -// "TLS ID" uniquely identifies a TLS association. -// The ICE protocol uses a "TLS ID" system to indicate whether a fresh DTLS connection -// must be reopened in case of ICE renegotiation. Considering that ICE renegotiations -// never happen in our use case, we can simply put a random value and not care about -// it. Note however that the TLS ID in the answer must be present if and only if the -// offer contains one. (RFC8842) -// TODO: is it true that renegotiations never happen? what about a connection closing? -// "tls-id" attribute MUST be present in the initial offer and respective answer (RFC8839). -// XXX: but right now browsers don't send it. -// -// a=setup:passive -// -// "passive" indicates that the remote DTLS server will only listen for incoming -// connections. (RFC5763) -// The answerer (server) MUST not be located behind a NAT (RFC6135). -// -// The answerer MUST use either a setup attribute value of setup:active or setup:passive. -// Note that if the answerer uses setup:passive, then the DTLS handshake will not begin until -// the answerer is received, which adds additional latency. setup:active allows the answer and -// the DTLS handshake to occur in parallel. Thus, setup:active is RECOMMENDED. -// -// a=candidate: -// -// A transport address for a candidate that can be used for connectivity checks (RFC8839). -// -// a=end-of-candidates -// -// Indicate that no more candidates will ever be sent (RFC8838). -const SERVER_SESSION_DESCRIPTION: &str = "v=0 -o=- 0 0 IN {ip_version} {target_ip} -s=- -t=0 0 -a=ice-lite -m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel -c=IN {ip_version} {target_ip} -a=mid:0 -a=ice-options:ice2 -a=ice-ufrag:{ufrag} -a=ice-pwd:{pwd} -a=fingerprint:{fingerprint_algorithm} {fingerprint_value} - -a=setup:passive -a=sctp-port:5000 -a=max-message-size:16384 -a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host -a=end-of-candidates -"; - -/// Indicates the IP version used in WebRTC: `IP4` or `IP6`. -#[derive(Serialize)] -enum IpVersion { - IP4, - IP6, -} - -/// Context passed to the templating engine, which replaces the above placeholders (e.g. -/// `{IP_VERSION}`) with real values. -#[derive(Serialize)] -struct DescriptionContext { - pub(crate) ip_version: IpVersion, - pub(crate) target_ip: IpAddr, - pub(crate) target_port: u16, - pub(crate) fingerprint_algorithm: String, - pub(crate) fingerprint_value: String, - pub(crate) ufrag: String, - pub(crate) pwd: String, -} - -/// Renders a [`TinyTemplate`] description using the provided arguments. -fn render_description( - description: &str, - addr: SocketAddr, - fingerprint: &Fingerprint, - ufrag: &str, -) -> String { - let mut tt = TinyTemplate::new(); - tt.add_template("description", description).unwrap(); - - let context = DescriptionContext { - ip_version: { - if addr.is_ipv4() { - IpVersion::IP4 - } else { - IpVersion::IP6 - } - }, - target_ip: addr.ip(), - target_port: addr.port(), - fingerprint_algorithm: fingerprint.algorithm(), - fingerprint_value: fingerprint.to_sdp_format(), - // NOTE: ufrag is equal to pwd. - ufrag: ufrag.to_owned(), - pwd: ufrag.to_owned(), - }; - tt.render("description", &context).unwrap() -} - -/// Fingerprint from SDP -pub fn fingerprint(sdp: &str) -> Result { - let fingerprint_regex = match regex::Regex::new( - r"(?m)^a=fingerprint:(?:\w+-[0-9]+)\s(?P(:?[0-9a-fA-F]{2})+)$", - ) { - Ok(fingerprint_regex) => fingerprint_regex, - Err(e) => return Err(regex::Error::Syntax(format!("fingerprint: {}", e))), - }; - let captures = match fingerprint_regex.captures(sdp) { - Some(captures) => captures, - None => return Err(regex::Error::Syntax("fingerprint not found".to_string())), - }; - let fingerprint = match captures.name("fingerprint") { - Some(fingerprint) => fingerprint.as_str(), - None => return Err(regex::Error::Syntax("fingerprint not found".to_string())), - }; - let decoded = match hex::decode(fingerprint) { - Ok(fingerprint) => fingerprint, - Err(e) => return Err(regex::Error::Syntax(format!("fingerprint: {}", e))), - }; - Ok(Fingerprint::from_certificate(&decoded)) -} diff --git a/transports/webrtc/src/websys/stream.rs b/transports/webrtc/src/websys/stream.rs deleted file mode 100644 index a0d75f2b339..00000000000 --- a/transports/webrtc/src/websys/stream.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! The Substream over the Connection -use super::cbfutures::CbFuture; -use futures::{AsyncRead, AsyncWrite, FutureExt}; -use send_wrapper::SendWrapper; -use std::io; -use std::pin::Pin; -use std::task::{ready, Context, Poll}; -use wasm_bindgen::prelude::*; -use web_sys::{ - MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelInit, RtcDataChannelState, - RtcDataChannelType, RtcPeerConnection, -}; - -macro_rules! console_log { - ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) -} - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); - #[wasm_bindgen(js_namespace = console)] - fn warn(s: &str); -} -// Max message size that can be sent to the DataChannel -const MAX_MESSAGE_SIZE: u32 = 16 * 1024; - -// How much can be buffered to the DataChannel at once -const MAX_BUFFERED_AMOUNT: u32 = 16 * 1024 * 1024; - -// How long time we wait for the 'bufferedamountlow' event to be emitted -const BUFFERED_AMOUNT_LOW_TIMEOUT: u32 = 30 * 1000; - -#[derive(Debug)] -pub struct DataChannelConfig { - negotiated: bool, - id: u16, - binary_type: RtcDataChannelType, -} - -impl Default for DataChannelConfig { - fn default() -> Self { - Self { - negotiated: false, - id: 0, - /// We set our default to Arraybuffer - binary_type: RtcDataChannelType::Arraybuffer, // Blob is the default in the Browser, - } - } -} - -/// Builds a Data Channel with selected options and given peer connection -/// -/// -impl DataChannelConfig { - pub fn new() -> Self { - Self::default() - } - - pub fn negotiated(&mut self, negotiated: bool) -> &mut Self { - self.negotiated = negotiated; - self - } - - /// Set a custom id for the Data Channel - pub fn id(&mut self, id: u16) -> &mut Self { - self.id = id; - self - } - - // Set the binary type for the Data Channel - // TODO: We'll likely never use this, all channels are created with Arraybuffer? - // pub fn binary_type(&mut self, binary_type: RtcDataChannelType) -> &mut Self { - // self.binary_type = binary_type; - // self - // } - - /// Opens a WebRTC DataChannel for [RtcPeerConnection] with selected [DataChannelConfig] - /// We can cretae `ondatachannel` future before building this - /// then await it after building this - pub fn open(&self, peer_connection: &RtcPeerConnection) -> RtcDataChannel { - const LABEL: &str = ""; - - let dc = match self.negotiated { - true => { - let mut data_channel_dict = RtcDataChannelInit::new(); - data_channel_dict.negotiated(true).id(self.id); - peer_connection - .create_data_channel_with_data_channel_dict(LABEL, &data_channel_dict) - } - false => peer_connection.create_data_channel(LABEL), - }; - dc.set_binary_type(self.binary_type); - dc - } -} - -/// Substream over the Connection -pub struct WebRTCStream { - inner: SendWrapper, -} - -impl WebRTCStream { - /// Create a new Substream - pub fn new(channel: RtcDataChannel) -> Self { - Self { - inner: SendWrapper::new(StreamInner::new(channel)), - } - } -} - -#[derive(Debug, Clone)] -struct StreamInner { - channel: RtcDataChannel, - onclose_fut: CbFuture<()>, - // incoming_data: FusedJsPromise, - // message_queue: Vec, - // ondatachannel_fut: CbFuture, -} - -impl StreamInner { - pub fn new(channel: RtcDataChannel) -> Self { - // On Open - let onopen_fut = CbFuture::new(); - let onopen_cback_clone = onopen_fut.clone(); - - channel.set_onopen(Some( - Closure::::new(move |_ev: RtcDataChannelEvent| { - // TODO: Send any queued messages - console_log!("Data Channel opened. onopen_callback"); - onopen_cback_clone.publish(()); - }) - .as_ref() - .unchecked_ref(), - )); - - // On Close - let onclose_fut = CbFuture::new(); - let onclose_cback_clone = onclose_fut.clone(); - - channel.set_onclose(Some( - Closure::::new(move |_ev: RtcDataChannelEvent| { - // TODO: Set state to closed? - // TODO: Set futures::Stream Poll::Ready(None)? - console_log!("Data Channel closed. onclose_callback"); - onclose_cback_clone.publish(()); - }) - .as_ref() - .unchecked_ref(), - )); - - /* - * On Error - */ - let onerror_fut = CbFuture::new(); - let onerror_cback_clone = onerror_fut.clone(); - - channel.set_onerror(Some( - Closure::::new(move |_ev: RtcDataChannelEvent| { - console_log!("Data Channel error. onerror_callback"); - onerror_cback_clone.publish(()); - }) - .as_ref() - .unchecked_ref(), - )); - - /* - * On Message - */ - let onmessage_fut = CbFuture::new(); - let onmessage_cback_clone = onmessage_fut.clone(); - - let onmessage_callback = Closure::::new(move |ev: MessageEvent| { - // TODO: Use Protobuf decoder? - let data = ev.data(); - let data = js_sys::Uint8Array::from(data); - let data = data.to_vec(); - console_log!("onmessage: {:?}", data); - // TODO: Howto? Should this feed a queue? futures::Stream? - onmessage_cback_clone.publish(data); - }); - - Self { - channel, - onclose_fut, - // incoming_data: FusedJsPromise::new(), - // message_queue: Vec::new(), - // ondatachannel_fut: CbFuture::new(), - } - } -} - -pub fn create_data_channel( - conn: &RtcPeerConnection, - config: DataChannelConfig, -) -> CbFuture { - // peer_connection.set_ondatachannel is callback based - // but we need a Future we can poll - // so we convert this callback into a Future by using [CbFuture] - - // 1. create the ondatachannel callbackFuture - // 2. build the channel with the DataChannelConfig - // 3. await the ondatachannel callbackFutures - // 4. Now we have a ready DataChannel - let ondatachannel_fut = CbFuture::new(); - let cback_clone = ondatachannel_fut.clone(); - - // set up callback and futures - // set_onopen callback to wake the Rust Future - let ondatachannel_callback = Closure::::new(move |ev: RtcDataChannelEvent| { - let dc2 = ev.channel(); - console_log!("pc2.ondatachannel!: {:?}", dc2.label()); - - cback_clone.publish(dc2); - }); - - conn.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); - - let _dc = config.open(conn); - - ondatachannel_fut -} - -impl AsyncRead for WebRTCStream { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - // self.inner.poll_read(cx, buf) - let mut onmessage_fut = CbFuture::new(); - let cback_clone = onmessage_fut.clone(); - - let onmessage_callback = Closure::::new(move |ev: MessageEvent| { - let data = ev.data(); - let data = js_sys::Uint8Array::from(data); - let data = data.to_vec(); - console_log!("onmessage: {:?}", data); - cback_clone.publish(data); - }); - - self.inner - .channel - .set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref())); - - // poll onmessage_fut - let data = ready!(onmessage_fut.poll_unpin(cx)); - let data_len = data.len(); - let buf_len = buf.len(); - let len = std::cmp::min(data_len, buf_len); - buf[..len].copy_from_slice(&data[..len]); - Poll::Ready(Ok(len)) - } -} - -impl AsyncWrite for WebRTCStream { - /// Attempt to write bytes from buf into the object. - /// On success, returns Poll::Ready(Ok(num_bytes_written)). - /// If the object is not ready for writing, - /// the method returns Poll::Pending and - /// arranges for the current task (via cx.waker().wake_by_ref()) - /// to receive a notification when the object becomes writable - /// or is closed. - /// - /// In WebRTC DataChannels, we can always write to the channel - /// so we don't need to poll the channel for writability - /// as long as the channel is open - /// - /// So we need access to the channel or peer_connection, - /// and the State of the Channel (DataChannel.readyState) - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context, - buf: &[u8], - ) -> Poll> { - match self.inner.channel.ready_state() { - RtcDataChannelState::Connecting => { - // TODO: Buffer message queue - console_log!("DataChannel is Connecting"); - Poll::Pending - } - RtcDataChannelState::Open => { - console_log!("DataChannel is Open"); - // let data = js_sys::Uint8Array::from(buf); - let _ = self.inner.channel.send_with_u8_array(&buf); - Poll::Ready(Ok(buf.len())) - } - RtcDataChannelState::Closing => { - console_log!("DataChannel is Closing"); - Poll::Pending - } - RtcDataChannelState::Closed => { - console_log!("DataChannel is Closed"); - Poll::Ready(Err(io::Error::new( - io::ErrorKind::BrokenPipe, - "DataChannel is Closed", - ))) - } - RtcDataChannelState::__Nonexhaustive => { - console_log!("DataChannel is __Nonexhaustive"); - Poll::Pending - } - } - // data_channel.send(...) - // self.inner.poll_write(cx, buf) - } - - /// Attempt to flush the object, ensuring that any buffered data reach their destination. - /// - /// On success, returns Poll::Ready(Ok(())). - /// - /// If flushing cannot immediately complete, this method returns Poll::Pending and arranges for the current task (via cx.waker().wake_by_ref()) to receive a notification when the object can make progress towards flushing. - // Flush means that we want to send all the data we have buffered to the - // remote. We don't buffer anything, so we can just return Ready. - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - // can only flush if the channel is open - match self.inner.channel.ready_state() { - RtcDataChannelState::Open => { - // TODO: send data - console_log!("DataChannel is Open"); - Poll::Ready(Ok(())) - } - _ => { - console_log!("DataChannel is not Open, cannot flush"); - Poll::Pending - } - } - } - - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - self.inner.channel.close(); - ready!(self.inner.onclose_fut.poll_unpin(cx)); - Poll::Ready(Ok(())) - } -} diff --git a/transports/webrtc/src/websys/transport.rs b/transports/webrtc/src/websys/transport.rs deleted file mode 100644 index f0973b350f5..00000000000 --- a/transports/webrtc/src/websys/transport.rs +++ /dev/null @@ -1,140 +0,0 @@ -use futures::future::FutureExt; -use libp2p_core::multiaddr::{Multiaddr, Protocol}; -use libp2p_core::muxing::StreamMuxerBox; -use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; -use libp2p_identity::{Keypair, PeerId}; -use std::future::Future; -use std::net::{IpAddr, SocketAddr}; -use std::pin::Pin; -use std::task::{Context, Poll}; - -// use crate::endpoint::Endpoint; -use super::fingerprint::Fingerprint; -use super::Connection; -use super::Error; - -const HANDSHAKE_TIMEOUT_MS: u64 = 10_000; - -/// Config for the [`Transport`]. -#[derive(Clone)] -pub struct Config { - keypair: Keypair, -} - -/// A WebTransport [`Transport`](libp2p_core::Transport) that works with `web-sys`. -pub struct Transport { - config: Config, -} - -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)> { - 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> + Send>>; - type Dial = Pin> + Send>>; - - 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 (sock_addr, server_fingerprint) = parse_webrtc_dial_addr(&addr) - .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; - - if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { - return Err(TransportError::MultiaddrNotSupported(addr)); - } - - let config = self.config.clone(); - let mut connection = Connection::new(sock_addr, server_fingerprint, config.keypair.clone()); - - Ok(async move { - let peer_id = connection.connect().await?; - - Ok((peer_id, connection)) - } - .boxed()) - } - - 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 - } -} - -/// Parse the given [`Multiaddr`] into a [`SocketAddr`] and a [`Fingerprint`] for dialing. -fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint)> { - let mut iter = addr.iter(); - - let ip = match iter.next()? { - Protocol::Ip4(ip) => IpAddr::from(ip), - Protocol::Ip6(ip) => IpAddr::from(ip), - _ => return None, - }; - - let port = iter.next()?; - let webrtc = iter.next()?; - let certhash = iter.next()?; - - let (port, fingerprint) = match (port, webrtc, certhash) { - (Protocol::Udp(port), Protocol::WebRTCDirect, Protocol::Certhash(cert_hash)) => { - let fingerprint = Fingerprint::try_from_multihash(cert_hash)?; - - (port, fingerprint) - } - _ => return None, - }; - - match iter.next() { - Some(Protocol::P2p(_)) => {} - // peer ID is optional - None => {} - // unexpected protocol - Some(_) => return None, - } - - Some((SocketAddr::new(ip, port), fingerprint)) -} diff --git a/transports/webrtc/src/websys/upgrade.rs b/transports/webrtc/src/websys/upgrade.rs deleted file mode 100644 index cb41f6fc062..00000000000 --- a/transports/webrtc/src/websys/upgrade.rs +++ /dev/null @@ -1,43 +0,0 @@ -pub(crate) mod noise; - -pub(crate) use super::fingerprint::Fingerprint; -use super::sdp; -use super::stream::{DataChannelConfig, WebRTCStream}; - -use super::Error; -use libp2p_identity::{Keypair, PeerId}; -use web_sys::{RtcDataChannel, RtcPeerConnection}; - -/// Creates a new outbound WebRTC connection. -pub(crate) async fn outbound( - peer_connection: &RtcPeerConnection, - id_keys: Keypair, - remote_fingerprint: Fingerprint, -) -> Result { - let handshake_data_channel: RtcDataChannel = DataChannelConfig::new() - .negotiated(true) - .id(0) - .open(peer_connection); - - let webrtc_stream = WebRTCStream::new(handshake_data_channel); - - // get local_fingerprint from local RtcPeerConnection peer_connection certificate - let local_sdp = match &peer_connection.local_description() { - Some(description) => description.sdp(), - None => return Err(Error::JsError("local_description is None".to_string())), - }; - let local_fingerprint = match sdp::fingerprint(&local_sdp) { - Ok(fingerprint) => fingerprint, - Err(e) => return Err(Error::JsError(format!("fingerprint: {}", e))), - }; - - let peer_id = noise::outbound( - id_keys, - webrtc_stream, - remote_fingerprint, - local_fingerprint, - ) - .await?; - - Ok(peer_id) -} diff --git a/transports/webrtc/src/websys/upgrade/noise.rs b/transports/webrtc/src/websys/upgrade/noise.rs deleted file mode 100644 index 22c59fd9081..00000000000 --- a/transports/webrtc/src/websys/upgrade/noise.rs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use futures::{AsyncRead, AsyncWrite}; -use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; -use libp2p_identity as identity; -use libp2p_identity::PeerId; -use libp2p_noise as noise; - -use super::Error; -use super::Fingerprint; - -pub(crate) async fn inbound( - id_keys: identity::Keypair, - stream: T, - client_fingerprint: Fingerprint, - server_fingerprint: Fingerprint, -) -> Result -where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - let noise = noise::Config::new(&id_keys) - .unwrap() - .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); - let info = noise.protocol_info().next().unwrap(); - // Note the roles are reversed because it allows the server (webrtc connection responder) to - // send application data 0.5 RTT earlier. - let (peer_id, _io) = noise.upgrade_outbound(stream, info).await?; - - Ok(peer_id) -} - -pub(crate) async fn outbound( - id_keys: identity::Keypair, - stream: T, - server_fingerprint: Fingerprint, - client_fingerprint: Fingerprint, -) -> Result -where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - let noise = noise::Config::new(&id_keys) - .unwrap() - .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); - let info = noise.protocol_info().next().unwrap(); - // Note the roles are reversed because it allows the server (webrtc connection responder) to - // send application data 0.5 RTT earlier. - let (peer_id, _io) = noise.upgrade_inbound(stream, info).await?; - - Ok(peer_id) -} - -pub(crate) fn noise_prologue( - client_fingerprint: Fingerprint, - server_fingerprint: Fingerprint, -) -> Vec { - let client = client_fingerprint.to_multihash().to_bytes(); - let server = server_fingerprint.to_multihash().to_bytes(); - const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; - let mut out = Vec::with_capacity(PREFIX.len() + client.len() + server.len()); - out.extend_from_slice(PREFIX); - out.extend_from_slice(&client); - out.extend_from_slice(&server); - out -} - -#[cfg(test)] -mod tests { - use super::*; - use hex_literal::hex; - - #[test] - fn noise_prologue_tests() { - let a = Fingerprint::raw(hex!( - "3e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870" - )); - let b = Fingerprint::raw(hex!( - "30fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99" - )); - - let prologue1 = noise_prologue(a, b); - let prologue2 = noise_prologue(b, a); - - assert_eq!(hex::encode(prologue1), "6c69627032702d7765627274632d6e6f6973653a12203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); - assert_eq!(hex::encode(prologue2), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); - } -} diff --git a/transports/webrtc/src/websys/utils.rs b/transports/webrtc/src/websys/utils.rs deleted file mode 100644 index 4b2862d79e5..00000000000 --- a/transports/webrtc/src/websys/utils.rs +++ /dev/null @@ -1,68 +0,0 @@ -use js_sys::Promise; -use send_wrapper::SendWrapper; -use wasm_bindgen::{JsCast, JsValue}; - -use super::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) - } -} - -pub(crate) fn gen_ufrag(len: usize) -> String { - let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - let mut ufrag = String::new(); - let mut buf = vec![0; len]; - getrandom::getrandom(&mut buf).unwrap(); - for i in buf { - let idx = i as usize % charset.len(); - ufrag.push(charset.chars().nth(idx).unwrap()); - } - ufrag -} From d237dc671dfb405357212db48d4c73f707dfc05e Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 2 Aug 2023 14:13:07 -0300 Subject: [PATCH 030/235] it handshakes --- transports/webrtc-websys/Cargo.toml | 69 +++ transports/webrtc-websys/src/README.md | 3 + transports/webrtc-websys/src/cbfutures.rs | 158 +++++++ transports/webrtc-websys/src/connection.rs | 216 +++++++++ transports/webrtc-websys/src/error.rs | 59 +++ transports/webrtc-websys/src/fingerprint.rs | 110 +++++ transports/webrtc-websys/src/lib.rs | 14 + transports/webrtc-websys/src/sdp.rs | 442 ++++++++++++++++++ transports/webrtc-websys/src/stream.rs | 365 +++++++++++++++ .../webrtc-websys/src/stream/drop_listener.rs | 129 +++++ .../webrtc-websys/src/stream/framed_dc.rs | 40 ++ .../src/stream/poll_data_channel.rs | 310 ++++++++++++ transports/webrtc-websys/src/transport.rs | 144 ++++++ transports/webrtc-websys/src/upgrade.rs | 152 ++++++ transports/webrtc-websys/src/upgrade/noise.rs | 117 +++++ 15 files changed, 2328 insertions(+) create mode 100644 transports/webrtc-websys/Cargo.toml create mode 100644 transports/webrtc-websys/src/README.md create mode 100644 transports/webrtc-websys/src/cbfutures.rs create mode 100644 transports/webrtc-websys/src/connection.rs create mode 100644 transports/webrtc-websys/src/error.rs create mode 100644 transports/webrtc-websys/src/fingerprint.rs create mode 100644 transports/webrtc-websys/src/lib.rs create mode 100644 transports/webrtc-websys/src/sdp.rs create mode 100644 transports/webrtc-websys/src/stream.rs create mode 100644 transports/webrtc-websys/src/stream/drop_listener.rs create mode 100644 transports/webrtc-websys/src/stream/framed_dc.rs create mode 100644 transports/webrtc-websys/src/stream/poll_data_channel.rs create mode 100644 transports/webrtc-websys/src/transport.rs create mode 100644 transports/webrtc-websys/src/upgrade.rs create mode 100644 transports/webrtc-websys/src/upgrade/noise.rs diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml new file mode 100644 index 00000000000..66f01c00ffd --- /dev/null +++ b/transports/webrtc-websys/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "libp2p-webrtc-websys" +version = "0.1.0" +authors = ["Doug Anderson "] +description = "Web-Sys WebRTC transport for libp2p" +repository = "https://github.com/libp2p/rust-libp2p" +license = "MIT" +edition = "2021" +rust-version = { workspace = true } +keywords = ["peer-to-peer", "libp2p", "networking"] +categories = ["network-programming", "asynchronous"] + +[dependencies] +async-trait = "0.1" +asynchronous-codec = "0.6.2" +bytes = "1" +futures = "0.3" +futures-timer = "3" +hex = "0.4" +libp2p-core = { workspace = true } +libp2p-noise = { workspace = true } +libp2p-identity = { workspace = true } +libp2p-webrtc-utils = { workspace = true } +sha2 = "0.10.7" +multihash = { workspace = true } +quick-protobuf = "0.8" +quick-protobuf-codec = { workspace = true } +rcgen = "0.10.0" +serde = { version = "1.0", features = ["derive"] } +thiserror = "1" +tinytemplate = "1.2" +log = "0.4.19" + +# wasm-bindgen specific deps +js-sys = { version = "0.3" } +getrandom = { version = "0.2.9", features = ["js"] } +regex = { version = "1.9" } +send_wrapper = { version = "0.6.0", features = ["futures"] } +wasm-bindgen = { version = "0.2.87" } +wasm-bindgen-futures = { version = "0.4.37" } +web-sys = { version = "0.3.64", features = [ + "MessageEvent", + "RtcPeerConnection", + "RtcSignalingState", + "RtcSdpType", + "RtcSessionDescription", + "RtcSessionDescriptionInit", + "RtcPeerConnectionIceEvent", + "RtcIceCandidate", + "RtcDataChannel", + "RtcDataChannelEvent", + "RtcCertificate", + "RtcConfiguration", + "RtcDataChannelInit", + "RtcDataChannelType", + "RtcDataChannelState", + "RtcCertificateExpiration", + "RtcIceCandidate", # so we can set ufrag + "RtcIceCandidateInit", +] } + +[dev-dependencies] +anyhow = "1.0" +hex-literal = "0.4" +libp2p-swarm = { workspace = true, features = ["macros"] } +libp2p-ping = { workspace = true } +unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } +void = "1" +quickcheck = "1.0.3" diff --git a/transports/webrtc-websys/src/README.md b/transports/webrtc-websys/src/README.md new file mode 100644 index 00000000000..cfdb9af8eb7 --- /dev/null +++ b/transports/webrtc-websys/src/README.md @@ -0,0 +1,3 @@ +# WebRTC Websys + +TODO diff --git a/transports/webrtc-websys/src/cbfutures.rs b/transports/webrtc-websys/src/cbfutures.rs new file mode 100644 index 00000000000..1035e77860e --- /dev/null +++ b/transports/webrtc-websys/src/cbfutures.rs @@ -0,0 +1,158 @@ +use futures::FutureExt; +use std::cell::{Cell, RefCell}; +use std::future::Future; +use std::pin::Pin; +use std::rc::Rc; +use std::task::{ready, Context, Poll, Waker}; + +pub struct FusedCbFuture(Option>); + +impl FusedCbFuture { + pub(crate) fn new() -> Self { + Self(None) + } + + pub(crate) fn maybe_init(&mut self, init: F) -> &mut Self + where + F: FnOnce() -> CbFuture, + { + if self.0.is_none() { + self.0 = Some(init()); + } + + self + } + + /// Checks if future is already running + pub(crate) fn is_active(&self) -> bool { + self.0.is_some() + } +} + +impl Future for FusedCbFuture { + type Output = T; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let val = ready!(self + .0 + .as_mut() + .expect("FusedCbFuture not initialized") + .poll_unpin(cx)); + + // Future finished, drop it + self.0.take(); + + Poll::Ready(val) + } +} + +#[derive(Clone, Debug)] +pub struct CbFuture(Rc>); + +struct CallbackFutureInner { + waker: Cell>, + result: RefCell>, +} + +impl std::fmt::Debug for CallbackFutureInner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CallbackFutureInner").finish() + } +} + +impl Default for CallbackFutureInner { + fn default() -> Self { + Self { + waker: Cell::new(None), + result: RefCell::new(None), + } + } +} + +impl CbFuture { + /// New Callback Future + pub(crate) fn new() -> Self { + Self(Rc::new(CallbackFutureInner::::default())) + } + + // call this from your callback + pub(crate) fn publish(&self, result: T) { + *self.0.result.borrow_mut() = Some(result); + if let Some(w) = self.0.waker.take() { + w.wake() + }; + } + + /// Checks if future is already running + pub(crate) fn is_active(&self) -> bool { + // check if self.0.result is_some() without taking it out + // (i.e. without calling self.0.result.take()) + self.0.result.borrow().is_some() + } +} + +impl Future for CbFuture { + type Output = T; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.0.result.take() { + Some(x) => Poll::Ready(x), + None => { + self.0.waker.set(Some(cx.waker().clone())); + Poll::Pending + } + } + } +} + +/// Callback Stream +/// Implement futures::Stream for a callback function +#[derive(Clone, Debug)] +pub struct CbStream(Rc>); + +struct CallbackStreamInner { + waker: Cell>, + result: Cell>, +} + +impl std::fmt::Debug for CallbackStreamInner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CallbackStreamInner").finish() + } +} + +impl Default for CallbackStreamInner { + fn default() -> Self { + Self { + waker: Cell::new(None), + result: Cell::new(None), + } + } +} + +impl CbStream { + /// New Callback Stream + pub(crate) fn new() -> Self { + Self(Rc::new(CallbackStreamInner::::default())) + } + + // call this from your callback + pub(crate) fn publish(&self, result: T) { + self.0.result.set(Some(result)); + if let Some(w) = self.0.waker.take() { + w.wake() + }; + } +} + +impl futures::Stream for CbStream { + type Item = T; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.0.result.take() { + Some(x) => Poll::Ready(Some(x)), + None => { + self.0.waker.set(Some(cx.waker().clone())); + Poll::Pending + } + } + } +} diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs new file mode 100644 index 00000000000..4577dee9794 --- /dev/null +++ b/transports/webrtc-websys/src/connection.rs @@ -0,0 +1,216 @@ +//! Websys WebRTC Peer Connection +//! +use super::cbfutures::CbFuture; +use super::stream::DataChannelConfig; +use super::{Error, WebRTCStream}; +use futures::stream::FuturesUnordered; +use futures::{FutureExt, StreamExt}; +use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; +use log::debug; +use send_wrapper::SendWrapper; +use std::pin::Pin; +use std::task::Waker; +use std::task::{ready, Context, Poll}; +use wasm_bindgen::prelude::*; +use web_sys::{RtcDataChannel, RtcDataChannelEvent, RtcPeerConnection}; + +pub struct Connection { + // Swarm needs all types to be Send. WASM is single-threaded + // and it is safe to use SendWrapper. + inner: SendWrapper, +} + +impl Connection { + /// Create a new Connection + pub(crate) fn new(peer_connection: RtcPeerConnection) -> Self { + Self { + inner: SendWrapper::new(ConnectionInner::new(peer_connection)), + } + } + + /// Connect + // pub(crate) async fn connect(&mut self) -> Result { + // let fut = SendWrapper::new(self.inner.connect()); + // fut.await + // } + + /// Peer Connection Getter + pub(crate) fn peer_connection(&self) -> &RtcPeerConnection { + &self.inner.peer_connection + } +} +struct ConnectionInner { + peer_connection: RtcPeerConnection, + create_data_channel_cbfuture: CbFuture, + closed: bool, + ondatachannel_fut: CbFuture, + + /// A list of futures, which, once completed, signal that a [`WebRTCStream`] has been dropped. + drop_listeners: FuturesUnordered, + no_drop_listeners_waker: Option, +} + +impl ConnectionInner { + fn new(peer_connection: RtcPeerConnection) -> Self { + // An ondatachannel Future enables us to poll for incoming data channel events in poll_incoming + let ondatachannel_fut = CbFuture::new(); + let cback_clone = ondatachannel_fut.clone(); + + // Wake the Future in the ondatachannel callback + let ondatachannel_callback = + Closure::::new(move |ev: RtcDataChannelEvent| { + let dc2 = ev.channel(); + debug!("ondatachannel! Label (if any): {:?}", dc2.label()); + + cback_clone.publish(dc2); + }); + + peer_connection.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); + + Self { + peer_connection, + create_data_channel_cbfuture: CbFuture::new(), + closed: false, + drop_listeners: FuturesUnordered::default(), + no_drop_listeners_waker: None, + ondatachannel_fut, + } + } + + /// Initiates and polls a future from `create_data_channel`. + /// Takes the RtcPeerConnection and DataChannelConfig and creates a pollable future + fn poll_create_data_channel( + &mut self, + cx: &mut Context, + config: DataChannelConfig, + ) -> Poll> { + // Create Data Channel + // take the peer_connection and DataChannelConfig and create a pollable future + let mut dc = config.create_from(&self.peer_connection); + let channel = WebRTCStream::new(dc); + Poll::Ready(Ok(channel)) + } + + /// Polls the ondatachannel callback for incoming data channels. + /// + /// To poll for inbound WebRTCStreams, we need to poll for the ondatachannel callback + /// We only get that callback for inbound data channels on our connections. + /// This callback is converted to a future using CbFuture, which we can poll here + fn poll_ondatachannel(&mut self, cx: &mut Context) -> Poll> { + // Poll the ondatachannel callback for incoming data channels + let dc = ready!(self.ondatachannel_fut.poll_unpin(cx)); + + // Create a WebRTCStream from the Data Channel + let channel = WebRTCStream::new(dc); + Poll::Ready(Ok(channel)) + } + + /// Closes the Peer Connection. + /// + /// This closes the data channels also and they will return an error + /// if they are used. + fn close_connection(&mut self) { + if !self.closed { + self.peer_connection.close(); + self.closed = true; + } + } +} + +pub(crate) async fn register_data_channel( + conn: &RtcPeerConnection, + config: &DataChannelConfig, +) -> RtcDataChannel { + // peer_connection.set_ondatachannel is callback based + // but we need a Future we can poll + // so we convert this callback into a Future by using [CbFuture] + + // 1. create the ondatachannel callbackFuture + // 2. build the channel with the DataChannelConfig + // 3. await the ondatachannel callbackFutures + // 4. Now we have a ready DataChannel + let ondatachannel_fut = CbFuture::new(); + let cback_clone = ondatachannel_fut.clone(); + + debug!("register_data_channel"); + // set up callback and futures + // set_onopen callback to wake the Rust Future + let ondatachannel_callback = Closure::::new(move |ev: RtcDataChannelEvent| { + let dc2 = ev.channel(); + debug!("ondatachannel! Label (if any): {:?}", dc2.label()); + + cback_clone.publish(dc2); + }); + + conn.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); + + let _dc = config.create_from(conn); + + ondatachannel_fut.await +} + +impl Drop for ConnectionInner { + fn drop(&mut self) { + self.close_connection(); + } +} + +/// WebRTC native multiplexing +/// Allows users to open substreams +impl StreamMuxer for Connection { + type Substream = WebRTCStream; // A Substream of a WebRTC PeerConnection is a Data Channel + type Error = Error; + + /// Polls for an inbound WebRTC data channel stream + /// To poll for inbound WebRTCStreams, we need to poll for the ondatachannel callback. + /// We only get that callback for inbound data channels on our connections. + /// This callback is converted to a future using CbFuture, which we can poll here + fn poll_inbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + // Inbound substream is signalled by an ondatachannel event + self.inner.poll_ondatachannel(cx) + } + + // We create the Data Channel here from the Peer Connection + // then wait for the Data Channel to be opened + fn poll_outbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + // Since this is NOT an initial Noise handshake outbound request (ie. Dialer) + // we need to create a new Data Channel WITHOUT negotiated flag set to true + // so use the Default DataChannelConfig + let config = DataChannelConfig::default(); + self.inner.poll_create_data_channel(cx, config) + } + + fn poll_close( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + debug!("connection::poll_close"); + + self.inner.close_connection(); + Poll::Ready(Ok(())) + } + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + loop { + match ready!(self.inner.drop_listeners.poll_next_unpin(cx)) { + Some(Ok(())) => {} + Some(Err(e)) => { + log::debug!("a DropListener failed: {e}") + } + None => { + self.inner.no_drop_listeners_waker = Some(cx.waker().clone()); + return Poll::Pending; + } + } + } + } +} diff --git a/transports/webrtc-websys/src/error.rs b/transports/webrtc-websys/src/error.rs new file mode 100644 index 00000000000..f4b0079ba98 --- /dev/null +++ b/transports/webrtc-websys/src/error.rs @@ -0,0 +1,59 @@ +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), + + /// Noise authentication failed + #[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, + + #[error("Connection error: {0}")] + Connection(String), +} + +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) + } +} + +impl std::convert::From for Error { + fn from(value: JsValue) -> Self { + Error::from_js_value(value) + } +} + +// impl From String +impl From for Error { + fn from(value: String) -> Self { + Error::JsError(value) + } +} + +impl From for Error { + fn from(value: std::io::Error) -> Self { + Error::JsError(value.to_string()) + } +} diff --git a/transports/webrtc-websys/src/fingerprint.rs b/transports/webrtc-websys/src/fingerprint.rs new file mode 100644 index 00000000000..01ab4151d92 --- /dev/null +++ b/transports/webrtc-websys/src/fingerprint.rs @@ -0,0 +1,110 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use sha2::Digest as _; +use std::fmt; + +const SHA256: &str = "sha-256"; +const MULTIHASH_SHA256_CODE: u64 = 0x12; + +type Multihash = multihash::Multihash<64>; + +/// A certificate fingerprint that is assumed to be created using the SHA256 hash algorithm. +#[derive(Eq, PartialEq, Copy, Clone)] +pub struct Fingerprint([u8; 32]); + +impl Fingerprint { + pub(crate) const FF: Fingerprint = Fingerprint([0xFF; 32]); + + // #[cfg(test)] + pub fn raw(bytes: [u8; 32]) -> Self { + Self(bytes) + } + + /// Creates a fingerprint from a raw certificate. + pub fn from_certificate(bytes: &[u8]) -> Self { + Fingerprint(sha2::Sha256::digest(bytes).into()) + } + + /// Converts [`Multihash`](multihash::Multihash) to [`Fingerprint`]. + pub fn try_from_multihash(hash: Multihash) -> Option { + if hash.code() != MULTIHASH_SHA256_CODE { + // Only support SHA256 for now. + return None; + } + + let bytes = hash.digest().try_into().ok()?; + + Some(Self(bytes)) + } + + /// Converts this fingerprint to [`Multihash`](multihash::Multihash). + pub fn to_multihash(self) -> Multihash { + Multihash::wrap(MULTIHASH_SHA256_CODE, &self.0).expect("fingerprint's len to be 32 bytes") + } + + /// Formats this fingerprint as uppercase hex, separated by colons (`:`). + /// + /// This is the format described in . + pub fn to_sdp_format(self) -> String { + self.0.map(|byte| format!("{byte:02X}")).join(":") + } + + /// Returns the algorithm used (e.g. "sha-256"). + /// See + pub fn algorithm(&self) -> String { + SHA256.to_owned() + } +} + +impl fmt::Debug for Fingerprint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&hex::encode(self.0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sdp_format() { + let fp = Fingerprint::raw(hex_literal::hex!( + "7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC" + )); + + let sdp_format = fp.to_sdp_format(); + + assert_eq!(sdp_format, "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC") + } + + #[test] + fn from_sdp_to_bytes() { + let sdp_format = "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC"; + let bytes = hex::decode(sdp_format.replace(":", "")).unwrap(); + let fp = Fingerprint::from_certificate(&bytes); + assert_eq!( + fp, + Fingerprint::raw(hex_literal::hex!( + "7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC" + )) + ); + } +} diff --git a/transports/webrtc-websys/src/lib.rs b/transports/webrtc-websys/src/lib.rs new file mode 100644 index 00000000000..18cbc6d1dce --- /dev/null +++ b/transports/webrtc-websys/src/lib.rs @@ -0,0 +1,14 @@ +mod cbfutures; +mod connection; +mod error; +pub(crate) mod fingerprint; +mod sdp; +mod stream; +mod transport; +mod upgrade; +mod utils; + +pub use self::connection::Connection; +pub use self::error::Error; +pub use self::stream::WebRTCStream; +pub use self::transport::{Config, Transport}; diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc-websys/src/sdp.rs new file mode 100644 index 00000000000..71a58c364da --- /dev/null +++ b/transports/webrtc-websys/src/sdp.rs @@ -0,0 +1,442 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use super::fingerprint::Fingerprint; +use js_sys::Reflect; +use log::{debug, trace}; +use serde::Serialize; +use std::net::{IpAddr, SocketAddr}; +use tinytemplate::TinyTemplate; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsValue; +use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; + +/// Creates the SDP answer used by the client. +pub(crate) fn answer( + addr: SocketAddr, + server_fingerprint: &Fingerprint, + client_ufrag: &str, +) -> RtcSessionDescriptionInit { + let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); + answer_obj.sdp(&render_description( + SESSION_DESCRIPTION, + addr, + server_fingerprint, + client_ufrag, + )); + answer_obj +} + +/// Creates the SDP offer. +/// +/// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. +pub(crate) fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescriptionInit { + //JsValue to String + let offer = Reflect::get(&offer, &JsValue::from_str("sdp")).unwrap(); + let offer = offer.as_string().unwrap(); + + let lines = offer.split("\r\n"); + + // find line and replace a=ice-ufrag: with "\r\na=ice-ufrag:{client_ufrag}\r\n" + // find line andreplace a=ice-pwd: with "\r\na=ice-ufrag:{client_ufrag}\r\n" + + let mut munged_offer_sdp = String::new(); + + for line in lines { + if line.starts_with("a=ice-ufrag:") { + munged_offer_sdp.push_str(&format!("a=ice-ufrag:{}\r\n", client_ufrag)); + } else if line.starts_with("a=ice-pwd:") { + munged_offer_sdp.push_str(&format!("a=ice-pwd:{}\r\n", client_ufrag)); + } else if !line.is_empty() { + munged_offer_sdp.push_str(&format!("{}\r\n", line)); + } + } + + // remove any double \r\n + let munged_offer_sdp = munged_offer_sdp.replace("\r\n\r\n", "\r\n"); + + trace!("munged_offer_sdp: {}", munged_offer_sdp); + + // setLocalDescription + let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); + offer_obj.sdp(&munged_offer_sdp); + + offer_obj +} + +// An SDP message that constitutes the offer. +// +// Main RFC: +// `sctp-port` and `max-message-size` attrs RFC: +// `group` and `mid` attrs RFC: +// `ice-ufrag`, `ice-pwd` and `ice-options` attrs RFC: +// `setup` attr RFC: +// +// Short description: +// +// v= -> always 0 +// o= +// +// identifies the creator of the SDP document. We are allowed to use dummy values +// (`-` and `0.0.0.0` as ) to remain anonymous, which we do. Note that "IN" means +// "Internet". +// +// s= +// +// We are allowed to pass a dummy `-`. +// +// c= +// +// Indicates the IP address of the remote. +// Note that "IN" means "Internet". +// +// t= +// +// Start and end of the validity of the session. `0 0` means that the session never expires. +// +// m= ... +// +// A `m=` line describes a request to establish a certain protocol. The protocol in this line +// (i.e. `TCP/DTLS/SCTP` or `UDP/DTLS/SCTP`) must always be the same as the one in the offer. +// We know that this is true because we tweak the offer to match the protocol. The `` +// component must always be `webrtc-datachannel` for WebRTC. +// RFCs: 8839, 8866, 8841 +// +// a=mid: +// +// Media ID - uniquely identifies this media stream (RFC9143). +// +// a=ice-options:ice2 +// +// Indicates that we are complying with RFC8839 (as oppposed to the legacy RFC5245). +// +// a=ice-ufrag: +// a=ice-pwd: +// +// ICE username and password, which are used for establishing and +// maintaining the ICE connection. (RFC8839) +// MUST match ones used by the answerer (server). +// +// a=fingerprint:sha-256 +// +// Fingerprint of the certificate that the remote will use during the TLS +// handshake. (RFC8122) +// +// a=setup:actpass +// +// The endpoint that is the offerer MUST use the setup attribute value of setup:actpass and be +// prepared to receive a client_hello before it receives the answer. +// +// a=sctp-port: +// +// The SCTP port (RFC8841) +// Note it's different from the "m=" line port value, which indicates the port of the +// underlying transport-layer protocol (UDP or TCP). +// +// a=max-message-size: +// +// The maximum SCTP user message size (in bytes). (RFC8841) +const CLIENT_SESSION_DESCRIPTION: &str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +c=IN {ip_version} {target_ip} +t=0 0 + +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +a=mid:0 +a=ice-options:ice2 +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} +a=setup:actpass +a=sctp-port:5000 +a=max-message-size:16384 +"; + +// See [`CLIENT_SESSION_DESCRIPTION`]. +// +// a=ice-lite +// +// A lite implementation is only appropriate for devices that will *always* be connected to +// the public Internet and have a public IP address at which it can receive packets from any +// correspondent. ICE will not function when a lite implementation is placed behind a NAT +// (RFC8445). +// +// a=tls-id: +// +// "TLS ID" uniquely identifies a TLS association. +// The ICE protocol uses a "TLS ID" system to indicate whether a fresh DTLS connection +// must be reopened in case of ICE renegotiation. Considering that ICE renegotiations +// never happen in our use case, we can simply put a random value and not care about +// it. Note however that the TLS ID in the answer must be present if and only if the +// offer contains one. (RFC8842) +// TODO: is it true that renegotiations never happen? what about a connection closing? +// "tls-id" attribute MUST be present in the initial offer and respective answer (RFC8839). +// XXX: but right now browsers don't send it. +// +// a=setup:passive +// +// "passive" indicates that the remote DTLS server will only listen for incoming +// connections. (RFC5763) +// The answerer (server) MUST not be located behind a NAT (RFC6135). +// +// The answerer MUST use either a setup attribute value of setup:active or setup:passive. +// Note that if the answerer uses setup:passive, then the DTLS handshake will not begin until +// the answerer is received, which adds additional latency. setup:active allows the answer and +// the DTLS handshake to occur in parallel. Thus, setup:active is RECOMMENDED. +// +// a=candidate: +// +// A transport address for a candidate that can be used for connectivity checks (RFC8839). +// +// a=end-of-candidates +// +// Indicate that no more candidates will ever be sent (RFC8838). +// const SERVER_SESSION_DESCRIPTION: &str = "v=0 +// o=- 0 0 IN {ip_version} {target_ip} +// s=- +// t=0 0 +// a=ice-lite +// m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +// c=IN {ip_version} {target_ip} +// a=mid:0 +// a=ice-options:ice2 +// a=ice-ufrag:{ufrag} +// a=ice-pwd:{pwd} +// a=fingerprint:{fingerprint_algorithm} {fingerprint_value} + +// a=setup:passive +// a=sctp-port:5000 +// a=max-message-size:16384 +// a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host +// a=end-of-candidates"; + +// Update to this: +// v=0 +// o=- 0 0 IN ${ipVersion} ${host} +// s=- +// c=IN ${ipVersion} ${host} +// t=0 0 +// a=ice-lite +// m=application ${port} UDP/DTLS/SCTP webrtc-datachannel +// a=mid:0 +// a=setup:passive +// a=ice-ufrag:${ufrag} +// a=ice-pwd:${ufrag} +// a=fingerprint:${CERTFP} +// a=sctp-port:5000 +// a=max-message-size:100000 +// a=candidate:1467250027 1 UDP 1467250027 ${host} ${port} typ host\r\n +const SESSION_DESCRIPTION: &str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +c=IN {ip_version} {target_ip} +t=0 0 +a=ice-lite +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +a=mid:0 +a=setup:passive +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} +a=sctp-port:5000 +a=max-message-size:16384 +a=candidate:1467250027 1 UDP 1467250027 {target_ip} {target_port} typ host +"; + +/// Indicates the IP version used in WebRTC: `IP4` or `IP6`. +#[derive(Serialize)] +enum IpVersion { + IP4, + IP6, +} + +/// Context passed to the templating engine, which replaces the above placeholders (e.g. +/// `{IP_VERSION}`) with real values. +#[derive(Serialize)] +struct DescriptionContext { + pub(crate) ip_version: IpVersion, + pub(crate) target_ip: IpAddr, + pub(crate) target_port: u16, + pub(crate) fingerprint_algorithm: String, + pub(crate) fingerprint_value: String, + pub(crate) ufrag: String, + pub(crate) pwd: String, +} + +/// Renders a [`TinyTemplate`] description using the provided arguments. +fn render_description( + description: &str, + addr: SocketAddr, + fingerprint: &Fingerprint, + ufrag: &str, +) -> String { + let mut tt = TinyTemplate::new(); + tt.add_template("description", description).unwrap(); + + let context = DescriptionContext { + ip_version: { + if addr.is_ipv4() { + IpVersion::IP4 + } else { + IpVersion::IP6 + } + }, + target_ip: addr.ip(), + target_port: addr.port(), + fingerprint_algorithm: fingerprint.algorithm(), + fingerprint_value: fingerprint.to_sdp_format(), + // NOTE: ufrag is equal to pwd. + ufrag: ufrag.to_owned(), + pwd: ufrag.to_owned(), + }; + tt.render("description", &context).unwrap() +} + +/// Parse SDP String into a JsValue +pub fn candidate(sdp: &str) -> Option { + let lines = sdp.split("\r\n"); + + for line in lines { + if line.starts_with("a=candidate:") { + // return with leading "a=candidate:" replaced with "" + return Some(line.replace("a=candidate:", "")); + } + } + None +} + +/// sdpMid +/// Get the media id from the SDP +pub fn mid(sdp: &str) -> Option { + let lines = sdp.split("\r\n"); + + // lines.find(|&line| line.starts_with("a=mid:")); + + for line in lines { + if line.starts_with("a=mid:") { + return Some(line.replace("a=mid:", "")); + } + } + None +} + +/// Get Fingerprint from SDP +/// Gets the fingerprint from matching between the angle brackets: a=fingerprint: +pub fn fingerprint(sdp: &str) -> Result { + // split the sdp by new lines / carriage returns + let lines = sdp.split("\r\n"); + + // iterate through the lines to find the one starting with a=fingerprint: + // get the value after the first space + // return the value as a Fingerprint + for line in lines { + if line.starts_with("a=fingerprint:") { + let fingerprint = line.split(' ').nth(1).unwrap(); + let bytes = hex::decode(fingerprint.replace(':', "")).unwrap(); + let arr: [u8; 32] = bytes.as_slice().try_into().unwrap(); + return Ok(Fingerprint::raw(arr)); + } + } + Err(regex::Error::Syntax("fingerprint not found".to_string())) + + // let fingerprint_regex = match regex::Regex::new( + // r"/^a=fingerprint:(?:\w+-[0-9]+)\s(?P(:?[0-9a-fA-F]{2})+)", + // ) { + // Ok(fingerprint_regex) => fingerprint_regex, + // Err(e) => return Err(regex::Error::Syntax(format!("regex fingerprint: {}", e))), + // }; + // let captures = match fingerprint_regex.captures(sdp) { + // Some(captures) => captures, + // None => { + // return Err(regex::Error::Syntax(format!( + // "fingerprint captures is None {}", + // sdp + // ))) + // } + // }; + // let fingerprint = match captures.name("fingerprint") { + // Some(fingerprint) => fingerprint.as_str(), + // None => return Err(regex::Error::Syntax("fingerprint name is None".to_string())), + // }; + // let decoded = match hex::decode(fingerprint) { + // Ok(fingerprint) => fingerprint, + // Err(e) => { + // return Err(regex::Error::Syntax(format!( + // "decode fingerprint error: {}", + // e + // ))) + // } + // }; + // Ok(Fingerprint::from_certificate(&decoded)) +} + +/* +offer_obj: RtcSessionDescriptionInit { obj: Object { obj: JsValue(Object({"type":"offer","sdp":"v=0\r\no=- 7315842204271936257 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\n"})) } } + answer_obj: RtcSessionDescriptionInit { obj: Object { obj: JsValue(Object({"type":"answer","sdp":"v=0\no=- 0 0 IN IP6 ::1\ns=-\nc=IN IP6 ::1\nt=0 0\na=ice-lite\nm=application 61885 UDP/DTLS/SCTP webrtc-datachannel\na=mid:0\na=setup:passive\na=ice-ufrag:libp2p+webrtc+v1/qBN+NUAT4icgH81g63DoyBs5x/RAQ6tE\na=ice-pwd:libp2p+webrtc+v1/qBN+NUAT4icgH81g63DoyBs5x/RAQ6tE\na=fingerprint:sha-256 A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89\na=sctp-port:5000\na=max-message-size:100000\na=candidate:1467250027 1 UDP 1467250027 ::1 61885 typ host\n"})) } } + +console.log div contained: + panicked at 'dial failed: JsError("Error setting remote_description: JsValue(InvalidAccessError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: The order of m-lines in answer doesn't match order in offer. Rejecting answer + +// What has to change about the SDP offer in order for it to be acceptable by the given answer above: +// How m-lines work: +// M-lines mean "media lines". They are used to describe the media streams that are being negotiated. +// The m-line is the line that describes the media stream. It is composed of the following fields: +// m= ... +// is the type of media (audio, video, data, etc.) +// is the port number that the media stream will be sent on +// is the protocol that will be used to send the media stream (RTP/SAVPF, UDP/TLS/RTP/SAVPF, etc.) +// is the format of the media stream (VP8, H264, etc.) +// The m-line is followed by a series of attributes that describe the media stream. These attributes are called "media-level attributes" and are prefixed with an "a=". +// The order of the m-lines in the answer must match the order of the m-lines in the offer. +// The order of the media-level attributes in the answer must match the order of the media-level attributes in the offer. +// For example, if the offer has the following data channel m-lines: +// m=application 9 UDP/DTLS/SCTP webrtc-datachannel +// a=sctp-port:5000 +// a=max-message-size:16384 +// a=candidate:1 1 UDP 1 +// The answer must have the following data channel m-lines: +// m=application 9 UDP/DTLS/SCTP webrtc-datachannel +// a=sctp-port:5000 +// a=max-message-size:16384 +// a=candidate:1 1 UDP 1 +// When the browser API creates the offer, it will always put the data channel m-line first. This means that the answer must also have the data channel m-line first. + +The differences between a STUN message and the SDP are: +STUN messages are sent over UDP, while SDP messages are sent over TCP. +STUN messages are used to establish a connection, while SDP messages are used to describe the connection. +STUN message looks like: +*/ + +// run test for any, none or all features +#[cfg(test)] +mod sdp_tests { + use super::*; + + #[test] + fn test_fingerprint() -> Result<(), regex::Error> { + let val = b"A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89"; + let sdp: &str = "v=0\no=- 0 0 IN IP6 ::1\ns=-\nc=IN IP6 ::1\nt=0 0\na=ice-lite\nm=application 61885 UDP/DTLS/SCTP webrtc-datachannel\na=mid:0\na=setup:passive\na=ice-ufrag:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\na=ice-pwd:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\na=fingerprint:sha-256 A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89\na=sctp-port:5000\na=max-message-size:16384\na=candidate:1467250027 1 UDP 1467250027 ::1 61885 typ host\n"; + let fingerprint = fingerprint(sdp)?; + assert_eq!(fingerprint.algorithm(), "sha-256"); + assert_eq!(fingerprint.to_sdp_format(), "A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89"); + Ok(()) + } +} diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs new file mode 100644 index 00000000000..a69b0bb601f --- /dev/null +++ b/transports/webrtc-websys/src/stream.rs @@ -0,0 +1,365 @@ +//! The Substream over the Connection +use self::framed_dc::FramedDc; +use bytes::Bytes; +use futures::{AsyncRead, AsyncWrite, Sink, SinkExt, StreamExt}; +use libp2p_webrtc_utils::proto::{Flag, Message}; +use libp2p_webrtc_utils::substream::{ + state::{Closing, State}, + MAX_DATA_LEN, +}; +use log::debug; +use send_wrapper::SendWrapper; +use std::io; +use std::pin::Pin; +use std::task::{ready, Context, Poll}; +use web_sys::{ + RtcDataChannel, RtcDataChannelInit, RtcDataChannelState, RtcDataChannelType, RtcPeerConnection, +}; + +mod drop_listener; +mod framed_dc; +mod poll_data_channel; + +pub(crate) use drop_listener::DropListener; + +// Max message size that can be sent to the DataChannel +const MAX_MESSAGE_SIZE: u32 = 16 * 1024; + +// How much can be buffered to the DataChannel at once +const MAX_BUFFERED_AMOUNT: u32 = 16 * 1024 * 1024; + +// How long time we wait for the 'bufferedamountlow' event to be emitted +const BUFFERED_AMOUNT_LOW_TIMEOUT: u32 = 30 * 1000; + +#[derive(Debug)] +pub struct DataChannelConfig { + negotiated: bool, + id: u16, + binary_type: RtcDataChannelType, +} + +impl Default for DataChannelConfig { + fn default() -> Self { + Self { + negotiated: false, + id: 0, + /// We set our default to Arraybuffer + binary_type: RtcDataChannelType::Arraybuffer, // Blob is the default in the Browser, + } + } +} + +/// Builds a Data Channel with selected options and given peer connection +/// +/// +impl DataChannelConfig { + pub fn new() -> Self { + Self::default() + } + + pub fn negotiated(&mut self, negotiated: bool) -> &mut Self { + self.negotiated = negotiated; + self + } + + /// Set a custom id for the Data Channel + pub fn id(&mut self, id: u16) -> &mut Self { + self.id = id; + self + } + + // Set the binary type for the Data Channel + // TODO: We'll likely never use this, all channels are created with Arraybuffer? + // pub fn binary_type(&mut self, binary_type: RtcDataChannelType) -> &mut Self { + // self.binary_type = binary_type; + // self + // } + + /// Opens a WebRTC DataChannel from [RtcPeerConnection] with the selected [DataChannelConfig] + /// We can create `ondatachannel` future before building this + /// then await it after building this + pub fn create_from(&self, peer_connection: &RtcPeerConnection) -> RtcDataChannel { + const LABEL: &str = ""; + + let dc = match self.negotiated { + true => { + let mut data_channel_dict = RtcDataChannelInit::new(); + data_channel_dict.negotiated(true).id(self.id); + debug!("Creating negotiated DataChannel"); + peer_connection + .create_data_channel_with_data_channel_dict(LABEL, &data_channel_dict) + } + false => { + debug!("Creating NON negotiated DataChannel"); + peer_connection.create_data_channel(LABEL) + } + }; + dc.set_binary_type(self.binary_type); + dc + } +} + +/// Substream over the Connection +pub struct WebRTCStream { + inner: SendWrapper, + state: State, + read_buffer: Bytes, +} + +impl WebRTCStream { + /// Create a new Substream + pub fn new(channel: RtcDataChannel) -> Self { + Self { + inner: SendWrapper::new(StreamInner::new(channel)), + read_buffer: Bytes::new(), + state: State::Open, + } + } + + /// Return the underlying RtcDataChannel + pub fn channel(&self) -> &RtcDataChannel { + self.inner.io.as_ref() + } +} + +struct StreamInner { + io: FramedDc, + // channel: RtcDataChannel, + // onclose_fut: CbFuture<()>, + state: State, + // Resolves when the data channel is opened. + // onopen_fut: CbFuture<()>, + // onmessage_fut: CbFuture>, // incoming_data: FusedJsPromise, + // message_queue: Vec, + // ondatachannel_fut: CbFuture, +} + +impl StreamInner { + pub fn new(channel: RtcDataChannel) -> Self { + Self { + io: framed_dc::new(channel), + state: State::Open, // always starts open + } + } + + // 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)) + // } +} + +impl AsyncRead for WebRTCStream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + loop { + self.inner.state.read_barrier()?; + + // Return buffered data, if any + if !self.read_buffer.is_empty() { + let n = std::cmp::min(self.read_buffer.len(), buf.len()); + let data = self.read_buffer.split_to(n); + buf[0..n].copy_from_slice(&data[..]); + + return Poll::Ready(Ok(n)); + } + + let Self { + read_buffer, + state, + inner, + .. + } = &mut *self; + + match ready!(io_poll_next(&mut inner.io, cx))? { + Some((flag, message)) => { + if let Some(flag) = flag { + state.handle_inbound_flag(flag, read_buffer); + } + + debug_assert!(read_buffer.is_empty()); + if let Some(message) = message { + *read_buffer = message.into(); + } + // continue to loop + } + None => { + state.handle_inbound_flag(Flag::FIN, read_buffer); + return Poll::Ready(Ok(0)); + } + } + } + + // Kick the can down the road to inner.io.poll_ready_unpin + // ready!(self.inner.io.poll_ready_unpin(cx))?; + + // let data = ready!(self.inner.onmessage_fut.poll_unpin(cx)); + // debug!("poll_read, {:?}", data); + // let data_len = data.len(); + // let buf_len = buf.len(); + // debug!("poll_read [data, buffer]: [{:?}, {}]", data_len, buf_len); + // let len = std::cmp::min(data_len, buf_len); + // buf[..len].copy_from_slice(&data[..len]); + // Poll::Ready(Ok(len)) + } +} + +impl AsyncWrite for WebRTCStream { + /// Attempt to write bytes from buf into the object. + /// On success, returns Poll::Ready(Ok(num_bytes_written)). + /// If the object is not ready for writing, + /// the method returns Poll::Pending and + /// arranges for the current task (via cx.waker().wake_by_ref()) + /// to receive a notification when the object becomes writable + /// or is closed. + /// + /// In WebRTC DataChannels, we can always write to the channel + /// so we don't need to poll the channel for writability + /// as long as the channel is open + /// + /// So we need access to the channel or peer_connection, + /// and the State of the Channel (DataChannel.readyState) + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], + ) -> Poll> { + while self.state.read_flags_in_async_write() { + // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the + // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? + + let Self { + read_buffer, + state, + inner, + .. + } = &mut *self; + + match io_poll_next(&mut inner.io, cx)? { + Poll::Ready(Some((Some(flag), message))) => { + // Read side is closed. Discard any incoming messages. + drop(message); + // But still handle flags, e.g. a `Flag::StopSending`. + state.handle_inbound_flag(flag, read_buffer) + } + Poll::Ready(Some((None, message))) => drop(message), + Poll::Ready(None) | Poll::Pending => break, + } + } + + self.state.write_barrier()?; + + ready!(self.inner.io.poll_ready_unpin(cx))?; + + let n = usize::min(buf.len(), MAX_DATA_LEN); + + Pin::new(&mut self.inner.io).start_send(Message { + flag: None, + message: Some(buf[0..n].into()), + })?; + + Poll::Ready(Ok(n)) + } + + /// Attempt to flush the object, ensuring that any buffered data reach their destination. + /// + /// On success, returns Poll::Ready(Ok(())). + /// + /// If flushing cannot immediately complete, this method returns Poll::Pending and + /// arranges for the current task (via cx.waker().wake_by_ref()) to receive a + /// notification when the object can make progress towards flushing. + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.inner.io.poll_flush_unpin(cx).map_err(Into::into) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + debug!("poll_closing"); + + loop { + match self.state.close_write_barrier()? { + Some(Closing::Requested) => { + ready!(self.inner.io.poll_ready_unpin(cx))?; + + debug!("Sending FIN flag"); + + self.inner.io.start_send_unpin(Message { + flag: Some(Flag::FIN), + message: None, + })?; + self.state.close_write_message_sent(); + + continue; + } + Some(Closing::MessageSent) => { + ready!(self.inner.io.poll_flush_unpin(cx))?; + + self.state.write_closed(); + // TODO: implement drop_notifier + // let _ = self + // .drop_notifier + // .take() + // .expect("to not close twice") + // .send(GracefullyClosed {}); + + return Poll::Ready(Ok(())); + } + None => return Poll::Ready(Ok(())), + } + } + + // match ready!(self.inner.io.poll_close_unpin(cx)) { + // Ok(()) => { + // debug!("poll_close, Ok(())"); + // self.state.close_write_message_sent(); + // Poll::Ready(Ok(())) + // } + // Err(e) => { + // debug!("poll_close, Err({:?})", e); + // Poll::Ready(Err(io::Error::new( + // io::ErrorKind::Other, + // "poll_close failed", + // ))) + // } + // } + } +} + +fn io_poll_next( + io: &mut FramedDc, + cx: &mut Context<'_>, +) -> Poll, Option>)>>> { + match ready!(io.poll_next_unpin(cx)) + .transpose() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? + { + Some(Message { flag, message }) => Poll::Ready(Ok(Some((flag, message)))), + None => Poll::Ready(Ok(None)), + } +} diff --git a/transports/webrtc-websys/src/stream/drop_listener.rs b/transports/webrtc-websys/src/stream/drop_listener.rs new file mode 100644 index 00000000000..c0e360110eb --- /dev/null +++ b/transports/webrtc-websys/src/stream/drop_listener.rs @@ -0,0 +1,129 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use super::framed_dc::FramedDc; +use futures::channel::oneshot; +use futures::channel::oneshot::Canceled; +use futures::{FutureExt, SinkExt}; +use libp2p_webrtc_utils::proto::{Flag, Message}; +use std::future::Future; +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; + +#[must_use] +pub(crate) struct DropListener { + state: State, +} + +impl DropListener { + pub(crate) fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { + // Note: Added Pin::new() to the stream to make it compatible with the new futures version. + let substream_id = Pin::new(&stream).get_ref().stream_identifier(); + + Self { + state: State::Idle { + stream, + receiver, + substream_id, + }, + } + } +} + +enum State { + /// The [`DropListener`] is idle and waiting to be activated. + Idle { + stream: FramedDc, + receiver: oneshot::Receiver, + substream_id: u16, + }, + /// The stream got dropped and we are sending a reset flag. + SendingReset { + stream: FramedDc, + }, + Flushing { + stream: FramedDc, + }, + /// Bad state transition. + Poisoned, +} + +impl Future for DropListener { + type Output = io::Result<()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let state = &mut self.get_mut().state; + + loop { + match std::mem::replace(state, State::Poisoned) { + State::Idle { + stream, + substream_id, + mut receiver, + } => match receiver.poll_unpin(cx) { + Poll::Ready(Ok(GracefullyClosed {})) => { + return Poll::Ready(Ok(())); + } + Poll::Ready(Err(Canceled)) => { + log::info!("Substream {substream_id} dropped without graceful close, sending Reset"); + *state = State::SendingReset { stream }; + continue; + } + Poll::Pending => { + *state = State::Idle { + stream, + substream_id, + receiver, + }; + return Poll::Pending; + } + }, + State::SendingReset { mut stream } => match stream.poll_ready_unpin(cx)? { + Poll::Ready(()) => { + stream.start_send_unpin(Message { + flag: Some(Flag::RESET), + message: None, + })?; + *state = State::Flushing { stream }; + continue; + } + Poll::Pending => { + *state = State::SendingReset { stream }; + return Poll::Pending; + } + }, + State::Flushing { mut stream } => match stream.poll_flush_unpin(cx)? { + Poll::Ready(()) => return Poll::Ready(Ok(())), + Poll::Pending => { + *state = State::Flushing { stream }; + return Poll::Pending; + } + }, + State::Poisoned => { + unreachable!() + } + } + } + } +} + +/// Indicates that our substream got gracefully closed. +pub(crate) struct GracefullyClosed {} diff --git a/transports/webrtc-websys/src/stream/framed_dc.rs b/transports/webrtc-websys/src/stream/framed_dc.rs new file mode 100644 index 00000000000..9cc7422ee2c --- /dev/null +++ b/transports/webrtc-websys/src/stream/framed_dc.rs @@ -0,0 +1,40 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use super::poll_data_channel::PollDataChannel; +use asynchronous_codec::Framed; +use libp2p_webrtc_utils::proto::Message; +use libp2p_webrtc_utils::substream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; +use web_sys::RtcDataChannel; + +pub(crate) type FramedDc = Framed>; +pub(crate) fn new(data_channel: RtcDataChannel) -> FramedDc { + let mut inner = PollDataChannel::new(data_channel); + inner.set_read_buf_capacity(MAX_MSG_LEN); + + let mut framed = Framed::new( + inner, + quick_protobuf_codec::Codec::new(MAX_MSG_LEN - VARINT_LEN), + ); + // If not set, `Framed` buffers up to 131kB of data before sending, which leads to + // "outbound packet larger than maximum message size" error in webrtc-rs. + framed.set_send_high_water_mark(MAX_DATA_LEN); + framed +} diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs new file mode 100644 index 00000000000..056abb057d2 --- /dev/null +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -0,0 +1,310 @@ +// Copied from webrtc::data::data_channel::poll_data_channel.rs +// use self::error::Result; +// use crate::{ +// error::Error, message::message_channel_ack::*, message::message_channel_open::*, message::*, +// }; +use crate::cbfutures::{CbFuture, CbStream}; +use crate::error::Error; +use futures::{AsyncRead, AsyncWrite, FutureExt, StreamExt}; +use log::debug; +use std::fmt; +use std::future::Future; +use std::io; +use std::pin::Pin; +use std::result::Result; +use std::task::{ready, Context, Poll}; +use wasm_bindgen::prelude::*; +use web_sys::{MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelState}; + +/// Default capacity of the temporary read buffer used by [`webrtc_sctp::stream::PollStream`]. +const DEFAULT_READ_BUF_SIZE: usize = 8192; + +/// State of the read `Future` in [`PollStream`]. +enum ReadFut { + /// Nothing in progress. + Idle, + /// Reading data from the underlying stream. + Reading(Pin, Error>> + Send>>), + /// Finished reading, but there's unread data in the temporary buffer. + RemainingData(Vec), +} + +impl ReadFut { + /// Gets a mutable reference to the future stored inside `Reading(future)`. + /// + /// # Panics + /// + /// Panics if `ReadFut` variant is not `Reading`. + fn get_reading_mut( + &mut self, + ) -> &mut Pin, Error>> + Send>> { + match self { + ReadFut::Reading(ref mut fut) => fut, + _ => panic!("expected ReadFut to be Reading"), + } + } +} + +/// A wrapper around around [`RtcDataChannel`], which implements [`AsyncRead`] and +/// [`AsyncWrite`]. +/// +/// Both `poll_read` and `poll_write` calls allocate temporary buffers, which results in an +/// additional overhead. +pub struct PollDataChannel { + data_channel: RtcDataChannel, + + onmessage_fut: CbStream>, + onopen_fut: CbFuture<()>, + onbufferedamountlow_fut: CbFuture<()>, + + read_buf_cap: usize, +} + +impl PollDataChannel { + /// Constructs a new `PollDataChannel`. + pub fn new(data_channel: RtcDataChannel) -> Self { + // On Open + let onopen_fut = CbFuture::new(); + let onopen_cback_clone = onopen_fut.clone(); + + let onopen_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { + // TODO: Send any queued messages + debug!("Data Channel opened"); + onopen_cback_clone.publish(()); + }); + + data_channel.set_onopen(Some(onopen_callback.as_ref().unchecked_ref())); + onopen_callback.forget(); + + // On Close -- never needed, poll_close() doesn't use it. + // let onclose_fut = CbFuture::new(); + // let onclose_cback_clone = onclose_fut.clone(); + + // let onclose_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { + // // TODO: Set state to closed? + // // TODO: Set futures::Stream Poll::Ready(None)? + // debug!("Data Channel closed. onclose_callback"); + // onclose_cback_clone.publish(()); + // }); + + // data_channel.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); + // onclose_callback.forget(); + + /* + * On Error + */ + let onerror_fut = CbFuture::new(); + let onerror_cback_clone = onerror_fut.clone(); + + let onerror_callback = Closure::::new(move |ev: RtcDataChannelEvent| { + debug!("Data Channel error"); + onerror_cback_clone.publish(()); + }); + + data_channel.set_onerror(Some(onerror_callback.as_ref().unchecked_ref())); + onerror_callback.forget(); + + /* + * On Message Stream + */ + let onmessage_fut = CbStream::new(); + let onmessage_cback_clone = onmessage_fut.clone(); + + let onmessage_callback = Closure::::new(move |ev: MessageEvent| { + // TODO: Use Protobuf decoder? + let data = ev.data(); + // The convert fron Js ArrayBuffer to Vec + let data = js_sys::Uint8Array::new(&data).to_vec(); + debug!("onmessage data: {:?}", data); + // TODO: publish(None) to close the stream when the channel closes + onmessage_cback_clone.publish(data); + }); + + data_channel.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref())); + onmessage_callback.forget(); + + /* + * Convert `RTCDataChannel: bufferedamountlow event` Low Event Callback to Future + */ + let onbufferedamountlow_fut = CbFuture::new(); + let onbufferedamountlow_cback_clone = onbufferedamountlow_fut.clone(); + + let onbufferedamountlow_callback = + Closure::::new(move |_ev: RtcDataChannelEvent| { + debug!("bufferedamountlow event"); + onbufferedamountlow_cback_clone.publish(()); + }); + + data_channel + .set_onbufferedamountlow(Some(onbufferedamountlow_callback.as_ref().unchecked_ref())); + onbufferedamountlow_callback.forget(); + + Self { + data_channel, + onmessage_fut, + onopen_fut, + onbufferedamountlow_fut, + read_buf_cap: DEFAULT_READ_BUF_SIZE, + } + } + + /// Get back the inner data_channel. + pub fn into_inner(self) -> RtcDataChannel { + self.data_channel + } + + /// Obtain a clone of the inner data_channel. + pub fn clone_inner(&self) -> RtcDataChannel { + self.data_channel.clone() + } + + /// Set the capacity of the temporary read buffer (default: 8192). + pub fn set_read_buf_capacity(&mut self, capacity: usize) { + self.read_buf_cap = capacity + } + + /// Get Ready State of [RtcDataChannel] + pub fn ready_state(&self) -> RtcDataChannelState { + self.data_channel.ready_state() + } + + /// Poll onopen_fut + pub fn poll_onopen(&mut self, cx: &mut Context) -> Poll<()> { + self.onopen_fut.poll_unpin(cx) + } + + /// Send data buffer + pub fn send(&self, data: &[u8]) -> Result<(), Error> { + debug!("send: {:?}", data); + self.data_channel.send_with_u8_array(data)?; + Ok(()) + } + + /// StreamIdentifier returns the Stream identifier associated to the stream. + pub fn stream_identifier(&self) -> u16 { + // let label = self.data_channel.id(); // not available (yet), see https://github.com/rustwasm/wasm-bindgen/issues/3542 + + // label is "" so it's not unique + // FIXME: After the above issue is fixed, use the label instead of the stream id + let label = self.data_channel.label(); + let b = label.as_bytes(); + let mut stream_id: u16 = 0; + b.iter().enumerate().for_each(|(i, &b)| { + stream_id += (b as u16) << (8 * i); + }); + stream_id + } +} + +impl AsyncRead for PollDataChannel { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + if let Some(data) = ready!(self.onmessage_fut.poll_next_unpin(cx)) { + let data_len = data.len(); + let buf_len = buf.len(); + debug!("poll_read [{:?} of {} bytes]", data_len, buf_len); + let len = std::cmp::min(data_len, buf_len); + buf[..len].copy_from_slice(&data[..len]); + Poll::Ready(Ok(len)) + } else { + // if None, the stream is exhausted, no data to read + Poll::Ready(Ok(0)) + } + } +} + +impl AsyncWrite for PollDataChannel { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + debug!("poll_write: [{:?}]", buf.len()); + // As long as the data channel is open we can write + // So, poll on open state to confirm the channel is open + if self.data_channel.ready_state() != RtcDataChannelState::Open { + // poll onopen + ready!(self.onopen_fut.poll_unpin(cx)); + } + + // Now that the channel is open, send the data + match self.data_channel.send_with_u8_array(buf) { + Ok(_) => Poll::Ready(Ok(buf.len())), + Err(e) => Poll::Ready(Err(io::Error::new( + io::ErrorKind::Other, + format!("Error sending data: {:?}", e), + ))), + } + } + + /// Attempt to flush the object, ensuring that any buffered data reach their destination. + /// On success, returns Poll::Ready(Ok(())). + /// If flushing cannot immediately complete, this method returns Poll::Pending and arranges for the current task (via cx.waker().wake_by_ref()) to receive a notification when the object can make progress towards flushing. + /// + /// With RtcDataChannel, there no native future to await for flush to complete. + /// However, Whenever this value decreases to fall to or below the value specified in the + /// bufferedAmountLowThreshold property, the user agent fires the bufferedamountlow event. + /// + /// We can therefore create a callback future called `onbufferedamountlow_fut` to listen for `bufferedamountlow` event and wake the task + /// The default `bufferedAmountLowThreshold` value is 0. + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + debug!("poll_flush"); + + // if bufferedamountlow empty,return ready + if self.data_channel.buffered_amount() == 0 { + debug!("0 buffered_amount"); + return Poll::Ready(Ok(())); + } + + debug!( + "buffered_amount is {:?}", + self.data_channel.buffered_amount() + ); + + // Otherwise, wait for the event to occur, so poll on onbufferedamountlow_fut + match self.onbufferedamountlow_fut.poll_unpin(cx) { + Poll::Ready(()) => { + debug!("flushed"); + Poll::Ready(Ok(())) + } + Poll::Pending => { + debug!("pending"); + Poll::Pending + } + } + } + + fn poll_close( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + // close the data channel + debug!("poll_close"); + self.data_channel.close(); + Poll::Ready(Ok(())) + } +} + +impl Clone for PollDataChannel { + fn clone(&self) -> PollDataChannel { + PollDataChannel::new(self.clone_inner()) + } +} + +impl fmt::Debug for PollDataChannel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PollDataChannel") + .field("data_channel", &self.data_channel) + .field("read_buf_cap", &self.read_buf_cap) + .finish() + } +} + +impl AsRef for PollDataChannel { + fn as_ref(&self) -> &RtcDataChannel { + &self.data_channel + } +} diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs new file mode 100644 index 00000000000..8ed88c557c1 --- /dev/null +++ b/transports/webrtc-websys/src/transport.rs @@ -0,0 +1,144 @@ +use futures::future::FutureExt; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; +use libp2p_core::muxing::StreamMuxerBox; +use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; +use libp2p_identity::{Keypair, PeerId}; +use std::future::Future; +use std::net::{IpAddr, SocketAddr}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +// use crate::endpoint::Endpoint; +use super::fingerprint::Fingerprint; +use super::upgrade::Upgrader; +use super::Connection; +use super::Error; + +const HANDSHAKE_TIMEOUT_MS: u64 = 10_000; + +/// Config for the [`Transport`]. +#[derive(Clone)] +pub struct Config { + keypair: Keypair, +} + +/// A WebTransport [`Transport`](libp2p_core::Transport) that works with `web-sys`. +pub struct Transport { + config: Config, +} + +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)> { + 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> + Send>>; + type Dial = Pin> + Send>>; + + 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 (sock_addr, server_fingerprint) = parse_webrtc_dial_addr(&addr) + .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; + + if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + + let config = self.config.clone(); + // let mut connection = Connection::new(sock_addr, server_fingerprint, config.keypair.clone()); + + Ok(async move { + // let peer_id = connection.connect().await?; + let (peer_id, connection) = Upgrader::new() + .outbound(sock_addr, server_fingerprint, config.keypair.clone()) + .await?; + + Ok((peer_id, connection)) + } + .boxed()) + } + + 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 + } +} + +/// Parse the given [`Multiaddr`] into a [`SocketAddr`] and a [`Fingerprint`] for dialing. +fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint)> { + let mut iter = addr.iter(); + + let ip = match iter.next()? { + Protocol::Ip4(ip) => IpAddr::from(ip), + Protocol::Ip6(ip) => IpAddr::from(ip), + _ => return None, + }; + + let port = iter.next()?; + let webrtc = iter.next()?; + let certhash = iter.next()?; + + let (port, fingerprint) = match (port, webrtc, certhash) { + (Protocol::Udp(port), Protocol::WebRTCDirect, Protocol::Certhash(cert_hash)) => { + let fingerprint = Fingerprint::try_from_multihash(cert_hash)?; + + (port, fingerprint) + } + _ => return None, + }; + + match iter.next() { + Some(Protocol::P2p(_)) => {} + // peer ID is optional + None => {} + // unexpected protocol + Some(_) => return None, + } + + Some((SocketAddr::new(ip, port), fingerprint)) +} diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs new file mode 100644 index 00000000000..d2500670de4 --- /dev/null +++ b/transports/webrtc-websys/src/upgrade.rs @@ -0,0 +1,152 @@ +pub(crate) mod noise; + +pub(crate) use super::fingerprint::Fingerprint; +use super::stream::{DataChannelConfig, WebRTCStream}; +use super::utils; +use super::Error; +use super::{sdp, Connection}; +use js_sys::{Object, Reflect}; +use libp2p_identity::{Keypair, PeerId}; +use log::debug; +use send_wrapper::SendWrapper; +use std::net::SocketAddr; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::JsFuture; +use web_sys::{MessageEvent, RtcConfiguration, RtcDataChannel, RtcPeerConnection}; + +const SHA2_256: u64 = 0x12; +const SHA2_512: u64 = 0x13; + +pub struct Upgrader { + inner: SendWrapper, +} + +impl Upgrader { + pub fn new() -> Self { + Self { + inner: SendWrapper::new(UpgraderInner::new()), + } + } + + pub(crate) async fn outbound( + &mut self, + sock_addr: SocketAddr, + remote_fingerprint: Fingerprint, + id_keys: Keypair, + ) -> Result<(PeerId, Connection), Error> { + let fut = SendWrapper::new(self.inner.outbound(sock_addr, remote_fingerprint, id_keys)); + fut.await + } +} + +pub struct UpgraderInner; + +impl UpgraderInner { + pub fn new() -> Self { + Self {} + } + + async fn outbound( + &mut self, + sock_addr: SocketAddr, + remote_fingerprint: Fingerprint, + id_keys: Keypair, + ) -> Result<(PeerId, Connection), Error> { + let hash = match remote_fingerprint.to_multihash().code() { + SHA2_256 => "sha-256", + SHA2_512 => "sha-512", + _ => return Err(Error::JsError("unsupported hash".to_string())), + }; + + let algo: js_sys::Object = Object::new(); + Reflect::set(&algo, &"name".into(), &"ECDSA".into()).unwrap(); + Reflect::set(&algo, &"namedCurve".into(), &"P-256".into()).unwrap(); + Reflect::set(&algo, &"hash".into(), &hash.into()).unwrap(); + + let certificate_promise = RtcPeerConnection::generate_certificate_with_object(&algo) + .expect("certificate to be valid"); + + let certificate = JsFuture::from(certificate_promise).await?; // Needs to be Send + + let ice: js_sys::Object = Object::new(); + Reflect::set(&ice, &"urls".into(), &"stun:stun.l.google.com:19302".into()).unwrap(); + + let mut config = RtcConfiguration::default(); + // wrap certificate in a js Array first before adding it to the config object + let certificate_arr = js_sys::Array::new(); + certificate_arr.push(&certificate); + config.certificates(&certificate_arr); + + let peer_connection = web_sys::RtcPeerConnection::new_with_configuration(&config)?; + + // Create substream for Noise handshake + // Must create data channel before Offer is created for it to be included in the SDP + let handshake_data_channel: RtcDataChannel = DataChannelConfig::new() + .negotiated(true) + .id(0) + .create_from(&peer_connection); + + let webrtc_stream = WebRTCStream::new(handshake_data_channel); + + let ufrag = format!("libp2p+webrtc+v1/{}", gen_ufrag(32)); + /* + * OFFER + */ + let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send + let offer_obj = sdp::offer(offer, &ufrag); + debug!("Offer SDP: {:?}", offer_obj); + let sld_promise = peer_connection.set_local_description(&offer_obj); + JsFuture::from(sld_promise) + .await + .expect("set_local_description to succeed"); + + /* + * ANSWER + */ + // TODO: Update SDP Answer format for Browser WebRTC + let answer_obj = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); + debug!("Answer SDP: {:?}", answer_obj); + let srd_promise = peer_connection.set_remote_description(&answer_obj); + JsFuture::from(srd_promise) + .await + .expect("set_remote_description to succeed"); + + // get local_fingerprint from local RtcPeerConnection peer_connection certificate + let local_sdp = match &peer_connection.local_description() { + Some(description) => description.sdp(), + None => return Err(Error::JsError("local_description is None".to_string())), + }; + let local_fingerprint = match sdp::fingerprint(&local_sdp) { + Ok(fingerprint) => fingerprint, + Err(e) => return Err(Error::JsError(format!("local fingerprint error: {}", e))), + }; + + debug!("local_fingerprint: {:?}", local_fingerprint); + debug!("remote_fingerprint: {:?}", remote_fingerprint); + + let peer_id = noise::outbound( + id_keys, + webrtc_stream, + remote_fingerprint, + local_fingerprint, + ) + .await?; + + debug!("peer_id: {:?}", peer_id); + + Ok((peer_id, Connection::new(peer_connection))) + } +} + +fn gen_ufrag(len: usize) -> String { + let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + let mut ufrag = String::new(); + let mut buf = vec![0; len]; + getrandom::getrandom(&mut buf).unwrap(); + for i in buf { + let idx = i as usize % charset.len(); + ufrag.push(charset.chars().nth(idx).unwrap()); + } + ufrag +} diff --git a/transports/webrtc-websys/src/upgrade/noise.rs b/transports/webrtc-websys/src/upgrade/noise.rs new file mode 100644 index 00000000000..74d8cffa302 --- /dev/null +++ b/transports/webrtc-websys/src/upgrade/noise.rs @@ -0,0 +1,117 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use super::Error; +use super::Fingerprint; +use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; +use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; +use libp2p_identity as identity; +use libp2p_identity::PeerId; +use libp2p_noise as noise; +use log::debug; + +// TODO: Can the browser handle inbound connections? +pub(crate) async fn inbound( + id_keys: identity::Keypair, + stream: T, + client_fingerprint: Fingerprint, + server_fingerprint: Fingerprint, +) -> Result +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let noise = noise::Config::new(&id_keys) + .unwrap() + .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); + let info = noise.protocol_info().next().unwrap(); + // Note the roles are reversed because it allows the server to initiate + let (peer_id, _io) = noise.upgrade_outbound(stream, info).await?; + + Ok(peer_id) +} + +pub(crate) async fn outbound( + id_keys: identity::Keypair, + stream: T, + remote_fingerprint: Fingerprint, + local_fingerprint: Fingerprint, +) -> Result +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let noise = noise::Config::new(&id_keys) + .unwrap() + .with_prologue(noise_prologue(local_fingerprint, remote_fingerprint)); + + let info = noise.protocol_info().next().unwrap(); + + debug!("outbound noise upgrade info {:?}", info); + + // Server must start the Noise handshake. Browsers cannot initiate + // noise.upgrade_inbound has into_responder(), so that's the one we need + let (peer_id, mut channel) = match noise.upgrade_inbound(stream, info).await { + Ok((peer_id, channel)) => (peer_id, channel), + Err(e) => { + debug!("outbound noise upgrade error {:?}", e); + return Err(Error::Noise(e)); + } + }; + + debug!("outbound noise upgrade peer_id {:?}", peer_id); + channel.close().await?; // uses AsyncWriteExt to close the EphermalKeyExchange channel? + + Ok(peer_id) +} + +pub(crate) fn noise_prologue( + local_fingerprint: Fingerprint, + remote_fingerprint: Fingerprint, +) -> Vec { + let local = local_fingerprint.to_multihash().to_bytes(); + let remote = remote_fingerprint.to_multihash().to_bytes(); + const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; + let mut out = Vec::with_capacity(PREFIX.len() + local.len() + remote.len()); + out.extend_from_slice(PREFIX); + out.extend_from_slice(&local); + out.extend_from_slice(&remote); + out +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn noise_prologue_tests() { + let a = Fingerprint::raw(hex!( + "3e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870" + )); + let b = Fingerprint::raw(hex!( + "30fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99" + )); + + let prologue1 = noise_prologue(a, b); + let prologue2 = noise_prologue(b, a); + + assert_eq!(hex::encode(prologue1), "6c69627032702d7765627274632d6e6f6973653a12203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); + assert_eq!(hex::encode(prologue2), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); + } +} From e3a78fd24b833c9fd3f7b6b5cca2dca8bb0e65fe Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 2 Aug 2023 14:13:19 -0300 Subject: [PATCH 031/235] restrict log --- wasm-tests/webrtc/server/.cargo/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm-tests/webrtc/server/.cargo/config.toml b/wasm-tests/webrtc/server/.cargo/config.toml index 149e30a5259..b57b0e65e44 100644 --- a/wasm-tests/webrtc/server/.cargo/config.toml +++ b/wasm-tests/webrtc/server/.cargo/config.toml @@ -1,3 +1,3 @@ # // RUST_LOG environment variable in .cargo/config.toml file [env] -RUST_LOG = "debug,libp2p_webrtc" +RUST_LOG = "debug,libp2p_webrtc,webrtc_sctp=off,webrtc_mdns=off,webrtc_ice=off" From 682151a09fc930712482e9e4615445af5486162e Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 2 Aug 2023 14:13:39 -0300 Subject: [PATCH 032/235] add to roadmap --- ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index a148de5794e..cb523b5f7cd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -71,7 +71,7 @@ argue that that demand follows this roadmap item and not the other way round.) | Category | Status | Target Completion | Tracking | Dependencies | Dependents | |--------------|--------|-------------------|--------------------------------------------|-------------------------------------------------------------------------------------------|------------| -| Connectivity | todo | | https://github.com/libp2p/specs/issues/475 | [Improved WASM support](#improved-wasm-support), https://github.com/libp2p/specs/pull/497 | | +| Connectivity | todo | | https://github.com/libp2p/specs/issues/475 | [Improved WASM support](#improved-wasm-support), https://github.com/libp2p/specs/pull/497 | https://github.com/libp2p/rust-libp2p/pull/4248 | Use the browser's WebRTC stack to support [`/webrtc`](https://github.com/libp2p/specs/blob/master/webrtc/webrtc.md) and From 312e7240d6bbf6315ffcfcca40c0e695ffd68ccd Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 2 Aug 2023 14:13:53 -0300 Subject: [PATCH 033/235] use milliseconds --- wasm-tests/webrtc/server/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wasm-tests/webrtc/server/src/main.rs b/wasm-tests/webrtc/server/src/main.rs index 63115393b49..1030d2b881b 100644 --- a/wasm-tests/webrtc/server/src/main.rs +++ b/wasm-tests/webrtc/server/src/main.rs @@ -16,7 +16,9 @@ use void::Void; #[tokio::main] async fn main() -> Result<()> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format_timestamp_millis() + .init(); let id_keys = identity::Keypair::generate_ed25519(); let local_peer_id = id_keys.public().to_peer_id(); From 429f511bd8835224f2f34b2db1182fe16e93b82b Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 2 Aug 2023 14:14:14 -0300 Subject: [PATCH 034/235] re-org names, folders, crates --- Cargo.lock | 223 +++++++++++++++++++++++++++++++++++++++++++++++------ Cargo.toml | 4 + 2 files changed, 205 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90fbdf8bff6..bfe6cd1994e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,6 +154,21 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -252,7 +267,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time", + "time 0.3.23", ] [[package]] @@ -268,7 +283,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time", + "time 0.3.23", ] [[package]] @@ -512,6 +527,17 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -834,6 +860,21 @@ dependencies = [ "libp2p-quic", ] +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ciborium" version = "0.2.1" @@ -936,6 +977,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "colored" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "combine" version = "4.6.6" @@ -969,6 +1021,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "console_log" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "const-oid" version = "0.9.4" @@ -982,7 +1045,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time", + "time 0.3.23", "version_check", ] @@ -1633,7 +1696,7 @@ dependencies = [ "mime", "serde", "serde_json", - "time", + "time 0.3.23", "tokio", "url", "webdriver", @@ -1654,6 +1717,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +[[package]] +name = "fern" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +dependencies = [ + "colored", + "log", +] + [[package]] name = "ff" version = "0.12.1" @@ -2041,6 +2114,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.2" @@ -2213,6 +2295,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.48.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2277,7 +2382,7 @@ dependencies = [ "smol", "system-configuration", "tokio", - "windows", + "windows 0.34.0", ] [[package]] @@ -2380,7 +2485,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.2", "libc", "windows-sys", ] @@ -2432,7 +2537,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.2", "rustix 0.38.4", "windows-sys", ] @@ -3283,16 +3388,15 @@ dependencies = [ "env_logger 0.10.0", "futures", "futures-timer", - "getrandom 0.2.10", "hex", "hex-literal", "if-watch", - "js-sys", "libp2p-core", "libp2p-identity", "libp2p-noise", "libp2p-ping", "libp2p-swarm", + "libp2p-webrtc-utils", "log", "multihash", "quick-protobuf", @@ -3300,8 +3404,6 @@ dependencies = [ "quickcheck", "rand 0.8.5", "rcgen 0.10.0", - "regex", - "send_wrapper 0.6.0", "serde", "sha2 0.10.7", "stun", @@ -3311,10 +3413,55 @@ dependencies = [ "tokio-util", "unsigned-varint", "void", + "webrtc", +] + +[[package]] +name = "libp2p-webrtc-utils" +version = "0.1.0" +dependencies = [ + "bytes", + "quick-protobuf", + "quick-protobuf-codec", +] + +[[package]] +name = "libp2p-webrtc-websys" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "asynchronous-codec", + "bytes", + "futures", + "futures-timer", + "getrandom 0.2.10", + "hex", + "hex-literal", + "js-sys", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "libp2p-ping", + "libp2p-swarm", + "libp2p-webrtc-utils", + "log", + "multihash", + "quick-protobuf", + "quick-protobuf-codec", + "quickcheck", + "rcgen 0.10.0", + "regex", + "send_wrapper 0.6.0", + "serde", + "sha2 0.10.7", + "thiserror", + "tinytemplate", + "unsigned-varint", + "void", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webrtc", ] [[package]] @@ -3811,7 +3958,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.2", "libc", ] @@ -4446,7 +4593,7 @@ checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" dependencies = [ "pem", "ring", - "time", + "time 0.3.23", "yasna", ] @@ -4458,7 +4605,7 @@ checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ "pem", "ring", - "time", + "time 0.3.23", "x509-parser 0.14.0", "yasna", ] @@ -5535,6 +5682,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.23" @@ -6070,6 +6228,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -6201,7 +6365,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time", + "time 0.3.23", "unicode-segmentation", "url", ] @@ -6273,7 +6437,7 @@ dependencies = [ "smol_str", "stun", "thiserror", - "time", + "time 0.3.23", "tokio", "turn", "url", @@ -6462,8 +6626,10 @@ version = "0.1.0" dependencies = [ "anyhow", "axum", + "env_logger 0.10.0", "futures", "libp2p-core", + "libp2p-identify", "libp2p-identity", "libp2p-ping", "libp2p-relay", @@ -6483,11 +6649,15 @@ dependencies = [ name = "webrtc-websys-tests" version = "0.1.0" dependencies = [ + "chrono", + "console_log", + "fern", "futures", "getrandom 0.2.10", "libp2p-core", "libp2p-identity", - "libp2p-webrtc", + "libp2p-webrtc-websys", + "log", "multiaddr", "multihash", "wasm-bindgen", @@ -6564,6 +6734,15 @@ dependencies = [ "windows_x86_64_msvc 0.34.0", ] +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -6716,7 +6895,7 @@ dependencies = [ "oid-registry 0.4.0", "rusticata-macros", "thiserror", - "time", + "time 0.3.23", ] [[package]] @@ -6735,7 +6914,7 @@ dependencies = [ "ring", "rusticata-macros", "thiserror", - "time", + "time 0.3.23", ] [[package]] @@ -6752,7 +6931,7 @@ dependencies = [ "oid-registry 0.6.1", "rusticata-macros", "thiserror", - "time", + "time 0.3.23", ] [[package]] @@ -6775,7 +6954,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time", + "time 0.3.23", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c376c79f061..e5ecd5e2c3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,8 @@ members = [ "transports/uds", "transports/wasm-ext", "transports/webrtc", + "transports/webrtc-websys", + "transports/webrtc-utils", "transports/websocket", "transports/webtransport-websys", "wasm-tests/webtransport-tests", @@ -97,6 +99,8 @@ libp2p-tls = { version = "0.2.0", path = "transports/tls" } libp2p-uds = { version = "0.39.0", path = "transports/uds" } libp2p-wasm-ext = { version = "0.40.0", path = "transports/wasm-ext" } libp2p-webrtc = { version = "0.6.0-alpha", path = "transports/webrtc" } +libp2p-webrtc-websys = { version = "0.1.0-alpha", path = "transports/webrtc-websys" } +libp2p-webrtc-utils = { version = "0.1.0", path = "transports/webrtc-utils" } 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" } From bc4575050ad79fe457f139edd284109c6b2e5b08 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 2 Aug 2023 14:14:49 -0300 Subject: [PATCH 035/235] rm local utils module --- transports/webrtc-websys/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/transports/webrtc-websys/src/lib.rs b/transports/webrtc-websys/src/lib.rs index 18cbc6d1dce..359f63d105c 100644 --- a/transports/webrtc-websys/src/lib.rs +++ b/transports/webrtc-websys/src/lib.rs @@ -6,7 +6,6 @@ mod sdp; mod stream; mod transport; mod upgrade; -mod utils; pub use self::connection::Connection; pub use self::error::Error; From 65f94995e3b1a3189f7b4e4e48a61dfe012afef1 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 2 Aug 2023 14:47:50 -0300 Subject: [PATCH 036/235] rm local utils import --- transports/webrtc-websys/src/upgrade.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index d2500670de4..366cda98d15 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -2,7 +2,6 @@ pub(crate) mod noise; pub(crate) use super::fingerprint::Fingerprint; use super::stream::{DataChannelConfig, WebRTCStream}; -use super::utils; use super::Error; use super::{sdp, Connection}; use js_sys::{Object, Reflect}; From 0a8bb1883cb7cb468d83b70e596b9e61a1a282cd Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 3 Aug 2023 17:06:22 -0300 Subject: [PATCH 037/235] add WebRTC web-sys to interop tests --- interop-tests/Cargo.toml | 1 + interop-tests/README.md | 11 +++++----- interop-tests/chromium-ping-version.json | 13 +++++++----- interop-tests/src/arch.rs | 26 +++++++++++++++++------- interop-tests/src/lib.rs | 2 ++ 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/interop-tests/Cargo.toml b/interop-tests/Cargo.toml index a2e281dbb87..85e77119a7a 100644 --- a/interop-tests/Cargo.toml +++ b/interop-tests/Cargo.toml @@ -35,6 +35,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } [target.'cfg(target_arch = "wasm32")'.dependencies] libp2p = { path = "../libp2p", features = ["ping", "macros", "webtransport-websys", "wasm-bindgen"] } +libp2p-webrtc-websys = { workspace = true } wasm-bindgen = { version = "0.2" } wasm-bindgen-futures = { version = "0.4" } wasm-logger = { version = "0.2.0" } diff --git a/interop-tests/README.md b/interop-tests/README.md index 88cd7518833..d6f30f872ff 100644 --- a/interop-tests/README.md +++ b/interop-tests/README.md @@ -9,12 +9,11 @@ another peer that this test can dial or listen for. For example to test that we 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`. +redis/redis-stack`. 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` +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 native_ping` - +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. @@ -27,7 +26,8 @@ Firefox is not yet supported as it doesn't support all required features yet (in v114 there is no support for certhashes). 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` +2. Run the webtransport dialer: `redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webtransport is_dialer=true cargo run --bin wasm_ping` +3. Run the webrtc-websys dialer: `redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webrtc-websys is_dialer=true cargo run --bin wasm_ping` # Running all interop tests locally with Compose @@ -38,6 +38,7 @@ the following (from the root directory of this repository): 1. Build the image: `docker build -t rust-libp2p-head . -f interop-tests/Dockerfile`. 1. Build the images for all released versions in `libp2p/test-plans`: `(cd /libp2p/test-plans/multidim-interop/ && make)`. 1. Run the test: + ``` RUST_LIBP2P="$PWD"; (cd /libp2p/test-plans/multidim-interop/ && npm run test -- --extra-version=$RUST_LIBP2P/interop-tests/ping-version.json --name-filter="rust-libp2p-head") ``` diff --git a/interop-tests/chromium-ping-version.json b/interop-tests/chromium-ping-version.json index 9fb2cd2252c..b7f3647c327 100644 --- a/interop-tests/chromium-ping-version.json +++ b/interop-tests/chromium-ping-version.json @@ -1,7 +1,10 @@ { - "id": "chromium-rust-libp2p-head", - "containerImageID": "chromium-rust-libp2p-head", - "transports": [{ "name": "webtransport", "onlyDial": true }], - "secureChannels": [], - "muxers": [] + "id": "chromium-rust-libp2p-head", + "containerImageID": "chromium-rust-libp2p-head", + "transports": [ + { "name": "webtransport", "onlyDial": true }, + { "name": "webrtc-websys", "onlyDial": true } + ], + "secureChannels": [], + "muxers": [] } diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs index 2f4a7495a04..39f7b658e3f 100644 --- a/interop-tests/src/arch.rs +++ b/interop-tests/src/arch.rs @@ -14,6 +14,7 @@ type BoxedTransport = Boxed<(PeerId, StreamMuxerBox)>; #[cfg(not(target_arch = "wasm32"))] pub(crate) mod native { + use std::net::Ipv6Addr; use std::time::Duration; use anyhow::{bail, Context, Result}; @@ -24,9 +25,10 @@ pub(crate) mod native { use libp2p::core::muxing::StreamMuxerBox; use libp2p::core::upgrade::Version; use libp2p::identity::Keypair; + use libp2p::multiaddr::Protocol; use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; use libp2p::websocket::WsConfig; - use libp2p::{noise, tcp, tls, yamux, PeerId, Transport as _}; + use libp2p::{noise, tcp, tls, yamux, Multiaddr, PeerId, Transport as _}; use libp2p_mplex as mplex; use libp2p_quic as quic; use libp2p_webrtc as webrtc; @@ -114,11 +116,16 @@ pub(crate) mod native { ) .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) .boxed(), - format!("/ip4/{ip}/udp/0/webrtc-direct"), + // IPv4 does not pass tests locally, but IPv6 does. + Multiaddr::from(Ipv6Addr::UNSPECIFIED) + .with(Protocol::Udp(0)) + .with(Protocol::WebRTCDirect) + .to_string(), ), (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"), + (Transport::WebRTCWebSys, _) => bail!("WebRTCWebSys can only be used with wasm"), }; Ok((transport, addr)) } @@ -160,6 +167,8 @@ pub(crate) mod wasm { use libp2p::identity::Keypair; use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; use libp2p::PeerId; + use libp2p_webrtc_websys as webrtc_websys; + use std::net::IpAddr; use std::time::Duration; use crate::{BlpopRequest, Transport}; @@ -182,16 +191,19 @@ pub(crate) mod wasm { ip: &str, transport: Transport, ) -> Result<(BoxedTransport, String)> { - if let Transport::Webtransport = transport { - Ok(( + match transport { + Transport::Webtransport => 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") + )), + Transport::WebRTCWebSys => Ok(( + webrtc_websys::Transport::new(webrtc_websys::Config::new(&local_key)).boxed(), + format!("/ip4/{ip}/udp/0/webrtc-direct"), + )), + _ => bail!("Only webtransport and webrtc are supported with wasm"), } } diff --git a/interop-tests/src/lib.rs b/interop-tests/src/lib.rs index beb7c91c63d..889bda09547 100644 --- a/interop-tests/src/lib.rs +++ b/interop-tests/src/lib.rs @@ -178,6 +178,7 @@ pub enum Transport { WebRtcDirect, Ws, Webtransport, + WebRTCWebSys, } impl FromStr for Transport { @@ -190,6 +191,7 @@ impl FromStr for Transport { "webrtc-direct" => Self::WebRtcDirect, "ws" => Self::Ws, "webtransport" => Self::Webtransport, + "webrtc-websys" => Self::WebRTCWebSys, other => bail!("unknown transport {other}"), }) } From e8d97e3e5a395e33e5ba5028ad8d3bdd27e85936 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 10:58:04 -0300 Subject: [PATCH 038/235] rename Substream to Stream --- transports/webrtc/src/tokio/connection.rs | 18 +++++++++--------- transports/webrtc/src/tokio/mod.rs | 2 +- .../src/tokio/{substream.rs => stream.rs} | 6 +++--- .../{substream => stream}/drop_listener.rs | 2 +- .../tokio/{substream => stream}/framed_dc.rs | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) rename transports/webrtc/src/tokio/{substream.rs => stream.rs} (97%) rename transports/webrtc/src/tokio/{substream => stream}/drop_listener.rs (98%) rename transports/webrtc/src/tokio/{substream => stream}/framed_dc.rs (96%) diff --git a/transports/webrtc/src/tokio/connection.rs b/transports/webrtc/src/tokio/connection.rs index 72e39ce525f..830ed4144ad 100644 --- a/transports/webrtc/src/tokio/connection.rs +++ b/transports/webrtc/src/tokio/connection.rs @@ -40,7 +40,7 @@ use std::{ task::{Context, Poll}, }; -use crate::tokio::{error::Error, substream, substream::Substream}; +use crate::tokio::{error::Error, stream, stream::Substream}; /// Maximum number of unprocessed data channels. /// See [`Connection::poll_inbound`]. @@ -56,14 +56,14 @@ pub struct Connection { /// Channel onto which incoming data channels are put. incoming_data_channels_rx: mpsc::Receiver>, - /// Future, which, once polled, will result in an outbound substream. + /// Future, which, once polled, will result in an outbound stream. outbound_fut: Option, Error>>>, /// Future, which, once polled, will result in closing the entire connection. close_fut: Option>>, /// A list of futures, which, once completed, signal that a [`Substream`] has been dropped. - drop_listeners: FuturesUnordered, + drop_listeners: FuturesUnordered, no_drop_listeners_waker: Option, } @@ -156,15 +156,15 @@ impl StreamMuxer for Connection { ) -> Poll> { match ready!(self.incoming_data_channels_rx.poll_next_unpin(cx)) { Some(detached) => { - log::trace!("Incoming substream {}", detached.stream_identifier()); + log::trace!("Incoming stream {}", detached.stream_identifier()); - let (substream, drop_listener) = Substream::new(detached); + let (stream, drop_listener) = Substream::new(detached); self.drop_listeners.push(drop_listener); if let Some(waker) = self.no_drop_listeners_waker.take() { waker.wake() } - Poll::Ready(Ok(substream)) + Poll::Ready(Ok(stream)) } None => { debug_assert!( @@ -226,15 +226,15 @@ impl StreamMuxer for Connection { Ok(detached) => { self.outbound_fut = None; - log::trace!("Outbound substream {}", detached.stream_identifier()); + log::trace!("Outbound stream {}", detached.stream_identifier()); - let (substream, drop_listener) = Substream::new(detached); + let (stream, drop_listener) = Substream::new(detached); self.drop_listeners.push(drop_listener); if let Some(waker) = self.no_drop_listeners_waker.take() { waker.wake() } - Poll::Ready(Ok(substream)) + Poll::Ready(Ok(stream)) } Err(e) => { self.outbound_fut = None; diff --git a/transports/webrtc/src/tokio/mod.rs b/transports/webrtc/src/tokio/mod.rs index 85e041bf98f..4f2c0dd9116 100644 --- a/transports/webrtc/src/tokio/mod.rs +++ b/transports/webrtc/src/tokio/mod.rs @@ -24,7 +24,7 @@ mod error; mod fingerprint; mod req_res_chan; mod sdp; -mod substream; +mod stream; mod transport; mod udp_mux; mod upgrade; diff --git a/transports/webrtc/src/tokio/substream.rs b/transports/webrtc/src/tokio/stream.rs similarity index 97% rename from transports/webrtc/src/tokio/substream.rs rename to transports/webrtc/src/tokio/stream.rs index 453678ec89b..3839dd60620 100644 --- a/transports/webrtc/src/tokio/substream.rs +++ b/transports/webrtc/src/tokio/stream.rs @@ -31,9 +31,9 @@ use std::{ task::{Context, Poll}, }; -use crate::tokio::{substream::drop_listener::GracefullyClosed, substream::framed_dc::FramedDc}; +use crate::tokio::stream::{drop_listener::GracefullyClosed, framed_dc::FramedDc}; use crate::utils::proto::{Flag, Message}; -use crate::utils::substream::{ +use crate::utils::stream::{ state::{Closing, State}, MAX_DATA_LEN, }; @@ -238,7 +238,7 @@ fn io_poll_next( #[cfg(test)] mod tests { - use crate::utils::substream::{MAX_MSG_LEN, PROTO_OVERHEAD, VARINT_LEN}; + use crate::utils::stream::{MAX_MSG_LEN, PROTO_OVERHEAD, VARINT_LEN}; use super::*; use asynchronous_codec::Encoder; diff --git a/transports/webrtc/src/tokio/substream/drop_listener.rs b/transports/webrtc/src/tokio/stream/drop_listener.rs similarity index 98% rename from transports/webrtc/src/tokio/substream/drop_listener.rs rename to transports/webrtc/src/tokio/stream/drop_listener.rs index 21e4082c637..9b35a3db054 100644 --- a/transports/webrtc/src/tokio/substream/drop_listener.rs +++ b/transports/webrtc/src/tokio/stream/drop_listener.rs @@ -27,7 +27,7 @@ use std::io; use std::pin::Pin; use std::task::{Context, Poll}; -use crate::tokio::substream::framed_dc::FramedDc; +use crate::tokio::stream::framed_dc::FramedDc; use crate::utils::proto::{Flag, Message}; #[must_use] diff --git a/transports/webrtc/src/tokio/substream/framed_dc.rs b/transports/webrtc/src/tokio/stream/framed_dc.rs similarity index 96% rename from transports/webrtc/src/tokio/substream/framed_dc.rs rename to transports/webrtc/src/tokio/stream/framed_dc.rs index baacbb8ca16..242890740a1 100644 --- a/transports/webrtc/src/tokio/substream/framed_dc.rs +++ b/transports/webrtc/src/tokio/stream/framed_dc.rs @@ -26,7 +26,7 @@ use webrtc::data::data_channel::{DataChannel, PollDataChannel}; use std::sync::Arc; use crate::utils::proto::Message; -use crate::utils::substream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; +use crate::utils::stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; pub(crate) type FramedDc = Framed, quick_protobuf_codec::Codec>; pub(crate) fn new(data_channel: Arc) -> FramedDc { From 7754bd918464746a853f4d90e7af9dbef29e1663 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 10:58:57 -0300 Subject: [PATCH 039/235] use log::debug! --- transports/webrtc/src/tokio/upgrade.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index fe9b2456674..aa0d6fc2c53 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -25,7 +25,6 @@ use futures::future::Either; use futures_timer::Delay; use libp2p_identity as identity; use libp2p_identity::PeerId; -use log::debug; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use webrtc::api::setting_engine::SettingEngine; @@ -41,7 +40,7 @@ use webrtc::peer_connection::RTCPeerConnection; use std::{net::SocketAddr, sync::Arc, time::Duration}; -use crate::tokio::{error::Error, fingerprint::Fingerprint, sdp, substream::Substream, Connection}; +use crate::tokio::{error::Error, fingerprint::Fingerprint, sdp, stream::Substream, Connection}; /// Creates a new outbound WebRTC connection. pub(crate) async fn outbound( @@ -52,16 +51,16 @@ pub(crate) async fn outbound( server_fingerprint: Fingerprint, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { - debug!("new outbound connection to {addr})"); + log::debug!("new outbound connection to {addr})"); let (peer_connection, ufrag) = new_outbound_connection(addr, config, udp_mux).await?; let offer = peer_connection.create_offer(None).await?; - debug!("created SDP offer for outbound connection: {:?}", offer.sdp); + log::debug!("created SDP offer for outbound connection: {:?}", offer.sdp); peer_connection.set_local_description(offer).await?; let answer = sdp::answer(addr, &server_fingerprint, &ufrag); - debug!( + log::debug!( "calculated SDP answer for outbound connection: {:?}", answer ); @@ -88,16 +87,16 @@ pub(crate) async fn inbound( remote_ufrag: String, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { - debug!("new inbound connection from {addr} (ufrag: {remote_ufrag})"); + log::debug!("new inbound connection from {addr} (ufrag: {remote_ufrag})"); let peer_connection = new_inbound_connection(addr, config, udp_mux, &remote_ufrag).await?; let offer = sdp::offer(addr, &remote_ufrag); - debug!("calculated SDP offer for inbound connection: {:?}", offer); + log::debug!("calculated SDP offer for inbound connection: {:?}", offer); peer_connection.set_remote_description(offer).await?; let answer = peer_connection.create_answer(None).await?; - debug!("created SDP answer for inbound connection: {:?}", answer); + log::debug!("created SDP answer for inbound connection: {:?}", answer); peer_connection.set_local_description(answer).await?; // This will start the gathering of ICE candidates. let data_channel = create_substream_for_noise_handshake(&peer_connection).await?; From 3aa336c1c342c2686a6b73bfa2388369e724f5e8 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 10:59:43 -0300 Subject: [PATCH 040/235] rename utils/stream --- transports/webrtc-utils/README.md | 2 +- transports/webrtc-utils/src/lib.rs | 2 +- transports/webrtc-utils/src/{substream => stream}/mod.rs | 0 transports/webrtc-utils/src/{substream => stream}/state.rs | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename transports/webrtc-utils/src/{substream => stream}/mod.rs (100%) rename transports/webrtc-utils/src/{substream => stream}/state.rs (100%) diff --git a/transports/webrtc-utils/README.md b/transports/webrtc-utils/README.md index 127023b0573..ff4403a7bda 100644 --- a/transports/webrtc-utils/README.md +++ b/transports/webrtc-utils/README.md @@ -3,4 +3,4 @@ Tools that are common to more than one WebRTC component. - [Protobuf Generated Code](generated) -- [Substream State](substream/state.rs) +- [Stream State](substream/state.rs) diff --git a/transports/webrtc-utils/src/lib.rs b/transports/webrtc-utils/src/lib.rs index 1a246c39e6f..bbd121a6064 100644 --- a/transports/webrtc-utils/src/lib.rs +++ b/transports/webrtc-utils/src/lib.rs @@ -6,4 +6,4 @@ pub mod proto { pub use self::webrtc::pb::{mod_Message::Flag, Message}; } -pub mod substream; +pub mod stream; diff --git a/transports/webrtc-utils/src/substream/mod.rs b/transports/webrtc-utils/src/stream/mod.rs similarity index 100% rename from transports/webrtc-utils/src/substream/mod.rs rename to transports/webrtc-utils/src/stream/mod.rs diff --git a/transports/webrtc-utils/src/substream/state.rs b/transports/webrtc-utils/src/stream/state.rs similarity index 100% rename from transports/webrtc-utils/src/substream/state.rs rename to transports/webrtc-utils/src/stream/state.rs From 3d77c61544fff96d4d69d6cd42558c6c6d8ba552 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 11:09:46 -0300 Subject: [PATCH 041/235] rename stream, address thomas' comments --- transports/webrtc-websys/src/connection.rs | 94 +++++-------------- .../webrtc-websys/src/stream/framed_dc.rs | 2 +- 2 files changed, 26 insertions(+), 70 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 4577dee9794..778c8e029e7 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -1,5 +1,7 @@ //! Websys WebRTC Peer Connection //! +use crate::stream::DataChannel; + use super::cbfutures::CbFuture; use super::stream::DataChannelConfig; use super::{Error, WebRTCStream}; @@ -28,12 +30,6 @@ impl Connection { } } - /// Connect - // pub(crate) async fn connect(&mut self) -> Result { - // let fut = SendWrapper::new(self.inner.connect()); - // fut.await - // } - /// Peer Connection Getter pub(crate) fn peer_connection(&self) -> &RtcPeerConnection { &self.inner.peer_connection @@ -79,19 +75,14 @@ impl ConnectionInner { /// Initiates and polls a future from `create_data_channel`. /// Takes the RtcPeerConnection and DataChannelConfig and creates a pollable future - fn poll_create_data_channel( - &mut self, - cx: &mut Context, - config: DataChannelConfig, - ) -> Poll> { - // Create Data Channel - // take the peer_connection and DataChannelConfig and create a pollable future - let mut dc = config.create_from(&self.peer_connection); + fn poll_create_data_channel(&mut self, cx: &mut Context) -> Poll> { + // Create Regular Data Channel + let dc = DataChannel::new_regular(&self.peer_connection); let channel = WebRTCStream::new(dc); Poll::Ready(Ok(channel)) } - /// Polls the ondatachannel callback for incoming data channels. + /// Polls the ondatachannel callback for inbound data channel stream. /// /// To poll for inbound WebRTCStreams, we need to poll for the ondatachannel callback /// We only get that callback for inbound data channels on our connections. @@ -101,10 +92,26 @@ impl ConnectionInner { let dc = ready!(self.ondatachannel_fut.poll_unpin(cx)); // Create a WebRTCStream from the Data Channel - let channel = WebRTCStream::new(dc); + let channel = WebRTCStream::new(DataChannel::Regular(dc)); Poll::Ready(Ok(channel)) } + /// Poll the Inner Connection for Dropped Listeners + fn poll(&mut self, cx: &mut Context) -> Poll> { + loop { + match ready!(self.drop_listeners.poll_next_unpin(cx)) { + Some(Ok(())) => {} + Some(Err(e)) => { + log::debug!("a DropListener failed: {e}") + } + None => { + self.no_drop_listeners_waker = Some(cx.waker().clone()); + return Poll::Pending; + } + } + } + } + /// Closes the Peer Connection. /// /// This closes the data channels also and they will return an error @@ -117,38 +124,6 @@ impl ConnectionInner { } } -pub(crate) async fn register_data_channel( - conn: &RtcPeerConnection, - config: &DataChannelConfig, -) -> RtcDataChannel { - // peer_connection.set_ondatachannel is callback based - // but we need a Future we can poll - // so we convert this callback into a Future by using [CbFuture] - - // 1. create the ondatachannel callbackFuture - // 2. build the channel with the DataChannelConfig - // 3. await the ondatachannel callbackFutures - // 4. Now we have a ready DataChannel - let ondatachannel_fut = CbFuture::new(); - let cback_clone = ondatachannel_fut.clone(); - - debug!("register_data_channel"); - // set up callback and futures - // set_onopen callback to wake the Rust Future - let ondatachannel_callback = Closure::::new(move |ev: RtcDataChannelEvent| { - let dc2 = ev.channel(); - debug!("ondatachannel! Label (if any): {:?}", dc2.label()); - - cback_clone.publish(dc2); - }); - - conn.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); - - let _dc = config.create_from(conn); - - ondatachannel_fut.await -} - impl Drop for ConnectionInner { fn drop(&mut self) { self.close_connection(); @@ -161,10 +136,6 @@ impl StreamMuxer for Connection { type Substream = WebRTCStream; // A Substream of a WebRTC PeerConnection is a Data Channel type Error = Error; - /// Polls for an inbound WebRTC data channel stream - /// To poll for inbound WebRTCStreams, we need to poll for the ondatachannel callback. - /// We only get that callback for inbound data channels on our connections. - /// This callback is converted to a future using CbFuture, which we can poll here fn poll_inbound( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -179,11 +150,7 @@ impl StreamMuxer for Connection { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - // Since this is NOT an initial Noise handshake outbound request (ie. Dialer) - // we need to create a new Data Channel WITHOUT negotiated flag set to true - // so use the Default DataChannelConfig - let config = DataChannelConfig::default(); - self.inner.poll_create_data_channel(cx, config) + self.inner.poll_create_data_channel(cx) } fn poll_close( @@ -200,17 +167,6 @@ impl StreamMuxer for Connection { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - loop { - match ready!(self.inner.drop_listeners.poll_next_unpin(cx)) { - Some(Ok(())) => {} - Some(Err(e)) => { - log::debug!("a DropListener failed: {e}") - } - None => { - self.inner.no_drop_listeners_waker = Some(cx.waker().clone()); - return Poll::Pending; - } - } - } + self.inner.poll(cx) } } diff --git a/transports/webrtc-websys/src/stream/framed_dc.rs b/transports/webrtc-websys/src/stream/framed_dc.rs index 9cc7422ee2c..238fa4ee9f6 100644 --- a/transports/webrtc-websys/src/stream/framed_dc.rs +++ b/transports/webrtc-websys/src/stream/framed_dc.rs @@ -21,7 +21,7 @@ use super::poll_data_channel::PollDataChannel; use asynchronous_codec::Framed; use libp2p_webrtc_utils::proto::Message; -use libp2p_webrtc_utils::substream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; +use libp2p_webrtc_utils::stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; use web_sys::RtcDataChannel; pub(crate) type FramedDc = Framed>; From 976faef71dfee318660a30203fc95a30d23abc91 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 11:12:53 -0300 Subject: [PATCH 042/235] refactor create data channel, rename stream --- transports/webrtc-websys/src/stream.rs | 137 +++++++++++-------------- 1 file changed, 62 insertions(+), 75 deletions(-) diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index a69b0bb601f..d1d63f74838 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -3,7 +3,7 @@ use self::framed_dc::FramedDc; use bytes::Bytes; use futures::{AsyncRead, AsyncWrite, Sink, SinkExt, StreamExt}; use libp2p_webrtc_utils::proto::{Flag, Message}; -use libp2p_webrtc_utils::substream::{ +use libp2p_webrtc_utils::stream::{ state::{Closing, State}, MAX_DATA_LEN, }; @@ -31,54 +31,90 @@ const MAX_BUFFERED_AMOUNT: u32 = 16 * 1024 * 1024; // How long time we wait for the 'bufferedamountlow' event to be emitted const BUFFERED_AMOUNT_LOW_TIMEOUT: u32 = 30 * 1000; -#[derive(Debug)] +/// The Browser Default is Blob, so we must set ours to Arraybuffer explicitly +const ARRAY_BUFFER_BINARY_TYPE: RtcDataChannelType = RtcDataChannelType::Arraybuffer; + +/// Builder for DataChannel +#[derive(Default, Debug)] pub struct DataChannelConfig { negotiated: bool, id: u16, - binary_type: RtcDataChannelType, } -impl Default for DataChannelConfig { - fn default() -> Self { - Self { - negotiated: false, - id: 0, - /// We set our default to Arraybuffer - binary_type: RtcDataChannelType::Arraybuffer, // Blob is the default in the Browser, +/// DataChannel type to ensure only the properly configurations +/// are used when building a WebRTCStream +pub enum DataChannel { + Regular(RtcDataChannel), + Handshake(RtcDataChannel), +} + +impl DataChannel { + pub fn new_regular(peer_connection: &RtcPeerConnection) -> Self { + Self::Regular(DataChannelConfig::create(peer_connection)) + } + + pub fn new_handshake(peer_connection: &RtcPeerConnection) -> Self { + Self::Handshake(DataChannelConfig::create_handshake_channel(peer_connection)) + } +} + +/// From DataChannel enum to RtcDataChannel +impl From for RtcDataChannel { + fn from(dc: DataChannel) -> Self { + match dc { + DataChannel::Regular(dc) => dc, + DataChannel::Handshake(dc) => dc, } } } /// Builds a Data Channel with selected options and given peer connection /// -/// +/// The default config is used in most cases, except when negotiating a Noise handshake impl DataChannelConfig { - pub fn new() -> Self { + /// Create a new [DataChannelConfig] with the default values + /// Build the [RtcDataChannel] on a given peer connection + /// by calling + /// + /// ```no_run + /// let channel = DataChannelConfig::new().create_from(peer_conn); + /// ``` + fn create(peer_connection: &RtcPeerConnection) -> RtcDataChannel { + Self::default().create_from(peer_connection) + } + + /// Creates a new data channel with Handshake config, + /// uses negotiated: true, id: 0 + /// Build the RtcDataChannel on a given peer connection + /// by calling `create_from(peer_conn)` + /// + /// # Example + /// + /// ```rust + /// let channel = DataChannelConfig::new_handshake_channel(&peer_connection) + /// ``` + fn create_handshake_channel(peer_connection: &RtcPeerConnection) -> RtcDataChannel { Self::default() + .negotiated(true) + .id(0) + .create_from(peer_connection) } - pub fn negotiated(&mut self, negotiated: bool) -> &mut Self { + fn negotiated(&mut self, negotiated: bool) -> &mut Self { self.negotiated = negotiated; self } /// Set a custom id for the Data Channel - pub fn id(&mut self, id: u16) -> &mut Self { + fn id(&mut self, id: u16) -> &mut Self { self.id = id; self } - // Set the binary type for the Data Channel - // TODO: We'll likely never use this, all channels are created with Arraybuffer? - // pub fn binary_type(&mut self, binary_type: RtcDataChannelType) -> &mut Self { - // self.binary_type = binary_type; - // self - // } - /// Opens a WebRTC DataChannel from [RtcPeerConnection] with the selected [DataChannelConfig] /// We can create `ondatachannel` future before building this /// then await it after building this - pub fn create_from(&self, peer_connection: &RtcPeerConnection) -> RtcDataChannel { + fn create_from(&self, peer_connection: &RtcPeerConnection) -> RtcDataChannel { const LABEL: &str = ""; let dc = match self.negotiated { @@ -94,7 +130,7 @@ impl DataChannelConfig { peer_connection.create_data_channel(LABEL) } }; - dc.set_binary_type(self.binary_type); + dc.set_binary_type(ARRAY_BUFFER_BINARY_TYPE); // Hardcoded here, it's the only type we use dc } } @@ -107,10 +143,10 @@ pub struct WebRTCStream { } impl WebRTCStream { - /// Create a new Substream - pub fn new(channel: RtcDataChannel) -> Self { + /// Create a new WebRTC Substream + pub fn new(channel: DataChannel) -> Self { Self { - inner: SendWrapper::new(StreamInner::new(channel)), + inner: SendWrapper::new(StreamInner::new(channel.into())), read_buffer: Bytes::new(), state: State::Open, } @@ -124,14 +160,7 @@ impl WebRTCStream { struct StreamInner { io: FramedDc, - // channel: RtcDataChannel, - // onclose_fut: CbFuture<()>, state: State, - // Resolves when the data channel is opened. - // onopen_fut: CbFuture<()>, - // onmessage_fut: CbFuture>, // incoming_data: FusedJsPromise, - // message_queue: Vec, - // ondatachannel_fut: CbFuture, } impl StreamInner { @@ -141,36 +170,6 @@ impl StreamInner { state: State::Open, // always starts open } } - - // 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)) - // } } impl AsyncRead for WebRTCStream { @@ -216,18 +215,6 @@ impl AsyncRead for WebRTCStream { } } } - - // Kick the can down the road to inner.io.poll_ready_unpin - // ready!(self.inner.io.poll_ready_unpin(cx))?; - - // let data = ready!(self.inner.onmessage_fut.poll_unpin(cx)); - // debug!("poll_read, {:?}", data); - // let data_len = data.len(); - // let buf_len = buf.len(); - // debug!("poll_read [data, buffer]: [{:?}, {}]", data_len, buf_len); - // let len = std::cmp::min(data_len, buf_len); - // buf[..len].copy_from_slice(&data[..len]); - // Poll::Ready(Ok(len)) } } From 949b697e49422d909499a6c77c7e752c94af006a Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 11:13:34 -0300 Subject: [PATCH 043/235] refactored DataChannel::new function --- transports/webrtc-websys/src/upgrade.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index 366cda98d15..354f86d6b7b 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -1,5 +1,7 @@ pub(crate) mod noise; +use crate::stream::DataChannel; + pub(crate) use super::fingerprint::Fingerprint; use super::stream::{DataChannelConfig, WebRTCStream}; use super::Error; @@ -80,10 +82,7 @@ impl UpgraderInner { // Create substream for Noise handshake // Must create data channel before Offer is created for it to be included in the SDP - let handshake_data_channel: RtcDataChannel = DataChannelConfig::new() - .negotiated(true) - .id(0) - .create_from(&peer_connection); + let handshake_data_channel = DataChannel::new_handshake(&peer_connection); let webrtc_stream = WebRTCStream::new(handshake_data_channel); From b13337fd5008bc29f26f75f7d6f6403dec20c8f0 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 11:14:50 -0300 Subject: [PATCH 044/235] rm comment --- transports/webrtc-websys/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index 66f01c00ffd..57d5651ec22 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-webrtc-websys" version = "0.1.0" authors = ["Doug Anderson "] -description = "Web-Sys WebRTC transport for libp2p" +description = "WebRTC for libp2p under WASM environment" repository = "https://github.com/libp2p/rust-libp2p" license = "MIT" edition = "2021" @@ -31,7 +31,6 @@ thiserror = "1" tinytemplate = "1.2" log = "0.4.19" -# wasm-bindgen specific deps js-sys = { version = "0.3" } getrandom = { version = "0.2.9", features = ["js"] } regex = { version = "1.9" } From a040dc49414c46a6cab1f9bf1d717a1053ce5991 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 11:15:27 -0300 Subject: [PATCH 045/235] use WebRTCDirect --- interop-tests/chromium-ping-version.json | 2 +- interop-tests/src/arch.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interop-tests/chromium-ping-version.json b/interop-tests/chromium-ping-version.json index b7f3647c327..b0e7d69833b 100644 --- a/interop-tests/chromium-ping-version.json +++ b/interop-tests/chromium-ping-version.json @@ -3,7 +3,7 @@ "containerImageID": "chromium-rust-libp2p-head", "transports": [ { "name": "webtransport", "onlyDial": true }, - { "name": "webrtc-websys", "onlyDial": true } + { "name": "webrtc-direct", "onlyDial": true } ], "secureChannels": [], "muxers": [] diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs index 39f7b658e3f..9b0d7c80c54 100644 --- a/interop-tests/src/arch.rs +++ b/interop-tests/src/arch.rs @@ -199,7 +199,7 @@ pub(crate) mod wasm { .boxed(), format!("/ip4/{ip}/udp/0/quic/webtransport"), )), - Transport::WebRTCWebSys => Ok(( + Transport::WebRTCDirect => Ok(( webrtc_websys::Transport::new(webrtc_websys::Config::new(&local_key)).boxed(), format!("/ip4/{ip}/udp/0/webrtc-direct"), )), From 492fc63e9ae855fe92731cb4e3403d448dcf0cd8 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 11:16:01 -0300 Subject: [PATCH 046/235] only listen on non wasm32 arch --- interop-tests/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/interop-tests/src/lib.rs b/interop-tests/src/lib.rs index 889bda09547..fac31db3b06 100644 --- a/interop-tests/src/lib.rs +++ b/interop-tests/src/lib.rs @@ -45,6 +45,7 @@ pub async fn run_test( let mut maybe_id = None; // See https://github.com/libp2p/rust-libp2p/issues/4071. + #[cfg(not(target_arch = "wasm32"))] if transport == Transport::WebRtcDirect { maybe_id = Some(swarm.listen_on(local_addr.parse()?)?); } From 983e2d1bec01ac4f0b4cf481205797736a176ef8 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 11:16:46 -0300 Subject: [PATCH 047/235] readme consistent with test-plans/multidim-interop --- interop-tests/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interop-tests/README.md b/interop-tests/README.md index d6f30f872ff..ff4983cece8 100644 --- a/interop-tests/README.md +++ b/interop-tests/README.md @@ -8,8 +8,7 @@ You can run this test locally by having a local Redis instance and by having another peer that this test can dial or listen for. For example to test that we 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`. +1. Start redis (needed by the tests): `docker run --rm -p 6379:6379 redis:7-alpine`. 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 From af4eceea231f341e2d9f707ac17a5fb688c17e51 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 11:17:09 -0300 Subject: [PATCH 048/235] lock file --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index bfe6cd1994e..2ddc1814c66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2461,6 +2461,7 @@ dependencies = [ "libp2p-mplex", "libp2p-quic", "libp2p-webrtc", + "libp2p-webrtc-websys", "log", "mime_guess", "rand 0.8.5", From 97b20857f22e357997c02bc7f1f216d6ce10d625 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 11:17:39 -0300 Subject: [PATCH 049/235] add cfg windows guard for local testing --- interop-tests/src/bin/wasm_ping.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/interop-tests/src/bin/wasm_ping.rs b/interop-tests/src/bin/wasm_ping.rs index 20350170d59..eafcf5946df 100644 --- a/interop-tests/src/bin/wasm_ping.rs +++ b/interop-tests/src/bin/wasm_ping.rs @@ -103,7 +103,15 @@ 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") + + // windows needs to append `.cmd` to the command + let chromedriver = if cfg!(windows) { + "chromedriver.cmd" + } else { + "chromedriver" + }; + + let mut chrome = tokio::process::Command::new(chromedriver) .arg("--port=45782") .stdout(Stdio::piped()) .spawn()?; From f78f88dd4931b46753975888ccb03734d915e16c Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 11:21:37 -0300 Subject: [PATCH 050/235] rm pub from utils --- transports/webrtc/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 7757832a2a7..49bdd8993a4 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -82,4 +82,4 @@ #[cfg(feature = "tokio")] pub mod tokio; -pub use libp2p_webrtc_utils as utils; +use libp2p_webrtc_utils as utils; From ac1c82acd6fd0e795a468491b66ce8c641f63f04 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 12:10:53 -0300 Subject: [PATCH 051/235] reduce to two top-level free functions --- transports/webrtc-websys/src/upgrade.rs | 211 +++++++++++------------- 1 file changed, 94 insertions(+), 117 deletions(-) diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index 354f86d6b7b..d8662008575 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -3,7 +3,7 @@ pub(crate) mod noise; use crate::stream::DataChannel; pub(crate) use super::fingerprint::Fingerprint; -use super::stream::{DataChannelConfig, WebRTCStream}; +use super::stream::WebRTCStream; use super::Error; use super::{sdp, Connection}; use js_sys::{Object, Reflect}; @@ -11,129 +11,106 @@ use libp2p_identity::{Keypair, PeerId}; use log::debug; use send_wrapper::SendWrapper; use std::net::SocketAddr; -use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; -use web_sys::{MessageEvent, RtcConfiguration, RtcDataChannel, RtcPeerConnection}; +use web_sys::{RtcConfiguration, RtcPeerConnection}; const SHA2_256: u64 = 0x12; const SHA2_512: u64 = 0x13; -pub struct Upgrader { - inner: SendWrapper, +pub(crate) async fn outbound( + sock_addr: SocketAddr, + remote_fingerprint: Fingerprint, + id_keys: Keypair, +) -> Result<(PeerId, Connection), Error> { + let fut = SendWrapper::new(outbound_inner(sock_addr, remote_fingerprint, id_keys)); + fut.await } -impl Upgrader { - pub fn new() -> Self { - Self { - inner: SendWrapper::new(UpgraderInner::new()), - } - } - - pub(crate) async fn outbound( - &mut self, - sock_addr: SocketAddr, - remote_fingerprint: Fingerprint, - id_keys: Keypair, - ) -> Result<(PeerId, Connection), Error> { - let fut = SendWrapper::new(self.inner.outbound(sock_addr, remote_fingerprint, id_keys)); - fut.await - } -} - -pub struct UpgraderInner; - -impl UpgraderInner { - pub fn new() -> Self { - Self {} - } - - async fn outbound( - &mut self, - sock_addr: SocketAddr, - remote_fingerprint: Fingerprint, - id_keys: Keypair, - ) -> Result<(PeerId, Connection), Error> { - let hash = match remote_fingerprint.to_multihash().code() { - SHA2_256 => "sha-256", - SHA2_512 => "sha-512", - _ => return Err(Error::JsError("unsupported hash".to_string())), - }; - - let algo: js_sys::Object = Object::new(); - Reflect::set(&algo, &"name".into(), &"ECDSA".into()).unwrap(); - Reflect::set(&algo, &"namedCurve".into(), &"P-256".into()).unwrap(); - Reflect::set(&algo, &"hash".into(), &hash.into()).unwrap(); - - let certificate_promise = RtcPeerConnection::generate_certificate_with_object(&algo) - .expect("certificate to be valid"); - - let certificate = JsFuture::from(certificate_promise).await?; // Needs to be Send - - let ice: js_sys::Object = Object::new(); - Reflect::set(&ice, &"urls".into(), &"stun:stun.l.google.com:19302".into()).unwrap(); - - let mut config = RtcConfiguration::default(); - // wrap certificate in a js Array first before adding it to the config object - let certificate_arr = js_sys::Array::new(); - certificate_arr.push(&certificate); - config.certificates(&certificate_arr); - - let peer_connection = web_sys::RtcPeerConnection::new_with_configuration(&config)?; - - // Create substream for Noise handshake - // Must create data channel before Offer is created for it to be included in the SDP - let handshake_data_channel = DataChannel::new_handshake(&peer_connection); - - let webrtc_stream = WebRTCStream::new(handshake_data_channel); - - let ufrag = format!("libp2p+webrtc+v1/{}", gen_ufrag(32)); - /* - * OFFER - */ - let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send - let offer_obj = sdp::offer(offer, &ufrag); - debug!("Offer SDP: {:?}", offer_obj); - let sld_promise = peer_connection.set_local_description(&offer_obj); - JsFuture::from(sld_promise) - .await - .expect("set_local_description to succeed"); - - /* - * ANSWER - */ - // TODO: Update SDP Answer format for Browser WebRTC - let answer_obj = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); - debug!("Answer SDP: {:?}", answer_obj); - let srd_promise = peer_connection.set_remote_description(&answer_obj); - JsFuture::from(srd_promise) - .await - .expect("set_remote_description to succeed"); - - // get local_fingerprint from local RtcPeerConnection peer_connection certificate - let local_sdp = match &peer_connection.local_description() { - Some(description) => description.sdp(), - None => return Err(Error::JsError("local_description is None".to_string())), - }; - let local_fingerprint = match sdp::fingerprint(&local_sdp) { - Ok(fingerprint) => fingerprint, - Err(e) => return Err(Error::JsError(format!("local fingerprint error: {}", e))), - }; - - debug!("local_fingerprint: {:?}", local_fingerprint); - debug!("remote_fingerprint: {:?}", remote_fingerprint); - - let peer_id = noise::outbound( - id_keys, - webrtc_stream, - remote_fingerprint, - local_fingerprint, - ) - .await?; - - debug!("peer_id: {:?}", peer_id); - - Ok((peer_id, Connection::new(peer_connection))) - } +async fn outbound_inner( + sock_addr: SocketAddr, + remote_fingerprint: Fingerprint, + id_keys: Keypair, +) -> Result<(PeerId, Connection), Error> { + let hash = match remote_fingerprint.to_multihash().code() { + SHA2_256 => "sha-256", + SHA2_512 => "sha-512", + _ => return Err(Error::JsError("unsupported hash".to_string())), + }; + + let algo: js_sys::Object = Object::new(); + Reflect::set(&algo, &"name".into(), &"ECDSA".into()).unwrap(); + Reflect::set(&algo, &"namedCurve".into(), &"P-256".into()).unwrap(); + Reflect::set(&algo, &"hash".into(), &hash.into()).unwrap(); + + let certificate_promise = RtcPeerConnection::generate_certificate_with_object(&algo) + .expect("certificate to be valid"); + + let certificate = JsFuture::from(certificate_promise).await?; // Needs to be Send + + let ice: js_sys::Object = Object::new(); + Reflect::set(&ice, &"urls".into(), &"stun:stun.l.google.com:19302".into()).unwrap(); + + let mut config = RtcConfiguration::default(); + // wrap certificate in a js Array first before adding it to the config object + let certificate_arr = js_sys::Array::new(); + certificate_arr.push(&certificate); + config.certificates(&certificate_arr); + + let peer_connection = web_sys::RtcPeerConnection::new_with_configuration(&config)?; + + // Create substream for Noise handshake + // Must create data channel before Offer is created for it to be included in the SDP + let handshake_data_channel = DataChannel::new_handshake(&peer_connection); + + let webrtc_stream = WebRTCStream::new(handshake_data_channel); + + let ufrag = format!("libp2p+webrtc+v1/{}", gen_ufrag(32)); + /* + * OFFER + */ + let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send + let offer_obj = sdp::offer(offer, &ufrag); + debug!("Offer SDP: {:?}", offer_obj); + let sld_promise = peer_connection.set_local_description(&offer_obj); + JsFuture::from(sld_promise) + .await + .expect("set_local_description to succeed"); + + /* + * ANSWER + */ + // TODO: Update SDP Answer format for Browser WebRTC + let answer_obj = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); + debug!("Answer SDP: {:?}", answer_obj); + let srd_promise = peer_connection.set_remote_description(&answer_obj); + JsFuture::from(srd_promise) + .await + .expect("set_remote_description to succeed"); + + // get local_fingerprint from local RtcPeerConnection peer_connection certificate + let local_sdp = match &peer_connection.local_description() { + Some(description) => description.sdp(), + None => return Err(Error::JsError("local_description is None".to_string())), + }; + let local_fingerprint = match sdp::fingerprint(&local_sdp) { + Ok(fingerprint) => fingerprint, + Err(e) => return Err(Error::JsError(format!("local fingerprint error: {}", e))), + }; + + debug!("local_fingerprint: {:?}", local_fingerprint); + debug!("remote_fingerprint: {:?}", remote_fingerprint); + + let peer_id = noise::outbound( + id_keys, + webrtc_stream, + remote_fingerprint, + local_fingerprint, + ) + .await?; + + debug!("peer_id: {:?}", peer_id); + + Ok((peer_id, Connection::new(peer_connection))) } fn gen_ufrag(len: usize) -> String { From c8995ac6ddd9d9966d0118afb986d0876fe0eb8b Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 14:02:08 -0300 Subject: [PATCH 052/235] fix log::debug, general cleanup --- .../src/stream/poll_data_channel.rs | 133 +++++++----------- 1 file changed, 50 insertions(+), 83 deletions(-) diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index 056abb057d2..9d6b63f7ad9 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -1,12 +1,7 @@ -// Copied from webrtc::data::data_channel::poll_data_channel.rs -// use self::error::Result; -// use crate::{ -// error::Error, message::message_channel_ack::*, message::message_channel_open::*, message::*, -// }; +// Inspired by webrtc::data::data_channel::poll_data_channel.rs use crate::cbfutures::{CbFuture, CbStream}; use crate::error::Error; use futures::{AsyncRead, AsyncWrite, FutureExt, StreamExt}; -use log::debug; use std::fmt; use std::future::Future; use std::io; @@ -19,32 +14,6 @@ use web_sys::{MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelS /// Default capacity of the temporary read buffer used by [`webrtc_sctp::stream::PollStream`]. const DEFAULT_READ_BUF_SIZE: usize = 8192; -/// State of the read `Future` in [`PollStream`]. -enum ReadFut { - /// Nothing in progress. - Idle, - /// Reading data from the underlying stream. - Reading(Pin, Error>> + Send>>), - /// Finished reading, but there's unread data in the temporary buffer. - RemainingData(Vec), -} - -impl ReadFut { - /// Gets a mutable reference to the future stored inside `Reading(future)`. - /// - /// # Panics - /// - /// Panics if `ReadFut` variant is not `Reading`. - fn get_reading_mut( - &mut self, - ) -> &mut Pin, Error>> + Send>> { - match self { - ReadFut::Reading(ref mut fut) => fut, - _ => panic!("expected ReadFut to be Reading"), - } - } -} - /// A wrapper around around [`RtcDataChannel`], which implements [`AsyncRead`] and /// [`AsyncWrite`]. /// @@ -53,9 +22,11 @@ impl ReadFut { pub struct PollDataChannel { data_channel: RtcDataChannel, + /// onmessage is an Option, Some(data) means there is data, None means the channel has closed onmessage_fut: CbStream>, onopen_fut: CbFuture<()>, onbufferedamountlow_fut: CbFuture<()>, + onclose_fut: CbFuture<()>, read_buf_cap: usize, } @@ -69,27 +40,13 @@ impl PollDataChannel { let onopen_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { // TODO: Send any queued messages - debug!("Data Channel opened"); + log::debug!("Data Channel opened"); onopen_cback_clone.publish(()); }); data_channel.set_onopen(Some(onopen_callback.as_ref().unchecked_ref())); onopen_callback.forget(); - // On Close -- never needed, poll_close() doesn't use it. - // let onclose_fut = CbFuture::new(); - // let onclose_cback_clone = onclose_fut.clone(); - - // let onclose_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { - // // TODO: Set state to closed? - // // TODO: Set futures::Stream Poll::Ready(None)? - // debug!("Data Channel closed. onclose_callback"); - // onclose_cback_clone.publish(()); - // }); - - // data_channel.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); - // onclose_callback.forget(); - /* * On Error */ @@ -97,7 +54,7 @@ impl PollDataChannel { let onerror_cback_clone = onerror_fut.clone(); let onerror_callback = Closure::::new(move |ev: RtcDataChannelEvent| { - debug!("Data Channel error"); + log::debug!("Data Channel error"); onerror_cback_clone.publish(()); }); @@ -111,18 +68,30 @@ impl PollDataChannel { let onmessage_cback_clone = onmessage_fut.clone(); let onmessage_callback = Closure::::new(move |ev: MessageEvent| { - // TODO: Use Protobuf decoder? let data = ev.data(); - // The convert fron Js ArrayBuffer to Vec + // Convert from Js ArrayBuffer to Vec let data = js_sys::Uint8Array::new(&data).to_vec(); - debug!("onmessage data: {:?}", data); - // TODO: publish(None) to close the stream when the channel closes + log::debug!("onmessage data: {:?}", data); onmessage_cback_clone.publish(data); }); data_channel.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref())); onmessage_callback.forget(); + // On Close + let onclose_fut = CbFuture::new(); + let onclose_cback_clone = onclose_fut.clone(); + + let onclose_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { + // TODO: Set state to closed? + // TODO: Set futures::Stream Poll::Ready(None)? + log::debug!("Data Channel closed. onclose_callback"); + onclose_cback_clone.publish(()); + }); + + data_channel.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); + onclose_callback.forget(); + /* * Convert `RTCDataChannel: bufferedamountlow event` Low Event Callback to Future */ @@ -131,7 +100,7 @@ impl PollDataChannel { let onbufferedamountlow_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { - debug!("bufferedamountlow event"); + log::debug!("bufferedamountlow event"); onbufferedamountlow_cback_clone.publish(()); }); @@ -143,6 +112,7 @@ impl PollDataChannel { data_channel, onmessage_fut, onopen_fut, + onclose_fut, onbufferedamountlow_fut, read_buf_cap: DEFAULT_READ_BUF_SIZE, } @@ -175,7 +145,7 @@ impl PollDataChannel { /// Send data buffer pub fn send(&self, data: &[u8]) -> Result<(), Error> { - debug!("send: {:?}", data); + log::debug!("send: {:?}", data); self.data_channel.send_with_u8_array(data)?; Ok(()) } @@ -202,16 +172,16 @@ impl AsyncRead for PollDataChannel { cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - if let Some(data) = ready!(self.onmessage_fut.poll_next_unpin(cx)) { - let data_len = data.len(); - let buf_len = buf.len(); - debug!("poll_read [{:?} of {} bytes]", data_len, buf_len); - let len = std::cmp::min(data_len, buf_len); - buf[..len].copy_from_slice(&data[..len]); - Poll::Ready(Ok(len)) - } else { - // if None, the stream is exhausted, no data to read - Poll::Ready(Ok(0)) + match ready!(self.onmessage_fut.poll_next_unpin(cx)) { + Some(data) => { + let data_len = data.len(); + let buf_len = buf.len(); + log::debug!("poll_read [{:?} of {} bytes]", data_len, buf_len); + let len = std::cmp::min(data_len, buf_len); + buf[..len].copy_from_slice(&data[..len]); + Poll::Ready(Ok(len)) + } + None => Poll::Ready(Ok(0)), // if None, the stream is exhausted, no data to read } } } @@ -222,16 +192,15 @@ impl AsyncWrite for PollDataChannel { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - debug!("poll_write: [{:?}]", buf.len()); - // As long as the data channel is open we can write - // So, poll on open state to confirm the channel is open + log::debug!("poll_write: [{:?}]", buf.len()); + // If the data channel is not open, + // poll on open future until the channel is open if self.data_channel.ready_state() != RtcDataChannelState::Open { - // poll onopen ready!(self.onopen_fut.poll_unpin(cx)); } // Now that the channel is open, send the data - match self.data_channel.send_with_u8_array(buf) { + match self.send(buf) { Ok(_) => Poll::Ready(Ok(buf.len())), Err(e) => Poll::Ready(Err(io::Error::new( io::ErrorKind::Other, @@ -251,39 +220,37 @@ impl AsyncWrite for PollDataChannel { /// We can therefore create a callback future called `onbufferedamountlow_fut` to listen for `bufferedamountlow` event and wake the task /// The default `bufferedAmountLowThreshold` value is 0. fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - debug!("poll_flush"); - - // if bufferedamountlow empty,return ready + log::debug!( + "poll_flush buffered_amount is {:?}", + self.data_channel.buffered_amount() + ); + // if bufferedamountlow empty, return ready if self.data_channel.buffered_amount() == 0 { - debug!("0 buffered_amount"); return Poll::Ready(Ok(())); } - debug!( - "buffered_amount is {:?}", - self.data_channel.buffered_amount() - ); - // Otherwise, wait for the event to occur, so poll on onbufferedamountlow_fut match self.onbufferedamountlow_fut.poll_unpin(cx) { Poll::Ready(()) => { - debug!("flushed"); + log::debug!("flushed"); Poll::Ready(Ok(())) } Poll::Pending => { - debug!("pending"); + log::debug!("pending"); Poll::Pending } } } + /// Initiates or attempts to shut down this writer, + /// returning success when the connection callback returns has completely shut down. fn poll_close( - self: std::pin::Pin<&mut Self>, + mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - // close the data channel - debug!("poll_close"); + log::debug!("poll_close"); self.data_channel.close(); + ready!(self.onclose_fut.poll_unpin(cx)); Poll::Ready(Ok(())) } } From 7af14f71ef722abe38ca8b42fa1f7d20820c8a21 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 14:02:52 -0300 Subject: [PATCH 053/235] refactor log::debug --- transports/webrtc-websys/src/stream.rs | 30 +++++-------------- transports/webrtc-websys/src/upgrade.rs | 11 ++++--- transports/webrtc-websys/src/upgrade/noise.rs | 10 ++----- 3 files changed, 15 insertions(+), 36 deletions(-) diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index d1d63f74838..23a57e22af3 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -7,7 +7,6 @@ use libp2p_webrtc_utils::stream::{ state::{Closing, State}, MAX_DATA_LEN, }; -use log::debug; use send_wrapper::SendWrapper; use std::io; use std::pin::Pin; @@ -121,12 +120,12 @@ impl DataChannelConfig { true => { let mut data_channel_dict = RtcDataChannelInit::new(); data_channel_dict.negotiated(true).id(self.id); - debug!("Creating negotiated DataChannel"); + log::debug!("Creating negotiated DataChannel"); peer_connection .create_data_channel_with_data_channel_dict(LABEL, &data_channel_dict) } false => { - debug!("Creating NON negotiated DataChannel"); + log::debug!("Creating NON negotiated DataChannel"); peer_connection.create_data_channel(LABEL) } }; @@ -158,16 +157,15 @@ impl WebRTCStream { } } +/// Inner Stream to make Sendable struct StreamInner { io: FramedDc, - state: State, } impl StreamInner { pub fn new(channel: RtcDataChannel) -> Self { Self { io: framed_dc::new(channel), - state: State::Open, // always starts open } } } @@ -179,7 +177,7 @@ impl AsyncRead for WebRTCStream { buf: &mut [u8], ) -> Poll> { loop { - self.inner.state.read_barrier()?; + self.state.read_barrier()?; // Return buffered data, if any if !self.read_buffer.is_empty() { @@ -287,14 +285,14 @@ impl AsyncWrite for WebRTCStream { } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - debug!("poll_closing"); + log::debug!("poll_closing"); loop { match self.state.close_write_barrier()? { Some(Closing::Requested) => { ready!(self.inner.io.poll_ready_unpin(cx))?; - debug!("Sending FIN flag"); + log::debug!("Sending FIN flag"); self.inner.io.start_send_unpin(Message { flag: Some(Flag::FIN), @@ -306,6 +304,7 @@ impl AsyncWrite for WebRTCStream { } Some(Closing::MessageSent) => { ready!(self.inner.io.poll_flush_unpin(cx))?; + ready!(self.inner.io.poll_close_unpin(cx))?; self.state.write_closed(); // TODO: implement drop_notifier @@ -320,21 +319,6 @@ impl AsyncWrite for WebRTCStream { None => return Poll::Ready(Ok(())), } } - - // match ready!(self.inner.io.poll_close_unpin(cx)) { - // Ok(()) => { - // debug!("poll_close, Ok(())"); - // self.state.close_write_message_sent(); - // Poll::Ready(Ok(())) - // } - // Err(e) => { - // debug!("poll_close, Err({:?})", e); - // Poll::Ready(Err(io::Error::new( - // io::ErrorKind::Other, - // "poll_close failed", - // ))) - // } - // } } } diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index d8662008575..fbcf14a686c 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -8,7 +8,6 @@ use super::Error; use super::{sdp, Connection}; use js_sys::{Object, Reflect}; use libp2p_identity::{Keypair, PeerId}; -use log::debug; use send_wrapper::SendWrapper; use std::net::SocketAddr; use wasm_bindgen_futures::JsFuture; @@ -70,7 +69,7 @@ async fn outbound_inner( */ let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send let offer_obj = sdp::offer(offer, &ufrag); - debug!("Offer SDP: {:?}", offer_obj); + log::debug!("Offer SDP: {:?}", offer_obj); let sld_promise = peer_connection.set_local_description(&offer_obj); JsFuture::from(sld_promise) .await @@ -81,7 +80,7 @@ async fn outbound_inner( */ // TODO: Update SDP Answer format for Browser WebRTC let answer_obj = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); - debug!("Answer SDP: {:?}", answer_obj); + log::debug!("Answer SDP: {:?}", answer_obj); let srd_promise = peer_connection.set_remote_description(&answer_obj); JsFuture::from(srd_promise) .await @@ -97,8 +96,8 @@ async fn outbound_inner( Err(e) => return Err(Error::JsError(format!("local fingerprint error: {}", e))), }; - debug!("local_fingerprint: {:?}", local_fingerprint); - debug!("remote_fingerprint: {:?}", remote_fingerprint); + log::debug!("local_fingerprint: {:?}", local_fingerprint); + log::debug!("remote_fingerprint: {:?}", remote_fingerprint); let peer_id = noise::outbound( id_keys, @@ -108,7 +107,7 @@ async fn outbound_inner( ) .await?; - debug!("peer_id: {:?}", peer_id); + log::debug!("peer_id: {:?}", peer_id); Ok((peer_id, Connection::new(peer_connection))) } diff --git a/transports/webrtc-websys/src/upgrade/noise.rs b/transports/webrtc-websys/src/upgrade/noise.rs index 74d8cffa302..7911c8ca86a 100644 --- a/transports/webrtc-websys/src/upgrade/noise.rs +++ b/transports/webrtc-websys/src/upgrade/noise.rs @@ -25,7 +25,6 @@ use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use libp2p_identity as identity; use libp2p_identity::PeerId; use libp2p_noise as noise; -use log::debug; // TODO: Can the browser handle inbound connections? pub(crate) async fn inbound( @@ -62,19 +61,16 @@ where let info = noise.protocol_info().next().unwrap(); - debug!("outbound noise upgrade info {:?}", info); + log::debug!("Outbound noise upgrade info {:?}", info); // Server must start the Noise handshake. Browsers cannot initiate // noise.upgrade_inbound has into_responder(), so that's the one we need let (peer_id, mut channel) = match noise.upgrade_inbound(stream, info).await { Ok((peer_id, channel)) => (peer_id, channel), - Err(e) => { - debug!("outbound noise upgrade error {:?}", e); - return Err(Error::Noise(e)); - } + Err(e) => return Err(Error::Noise(e)), }; - debug!("outbound noise upgrade peer_id {:?}", peer_id); + log::debug!("Outbound noise upgrade peer_id {:?}", peer_id); channel.close().await?; // uses AsyncWriteExt to close the EphermalKeyExchange channel? Ok(peer_id) From ac538ac2658b273f6d3cc93afcf339e99902bb6c Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 14:03:12 -0300 Subject: [PATCH 054/235] use new upgrade::outbound() fn --- transports/webrtc-websys/src/transport.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs index 8ed88c557c1..24848656b1e 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc-websys/src/transport.rs @@ -1,3 +1,7 @@ +use super::fingerprint::Fingerprint; +use super::upgrade; +use super::Connection; +use super::Error; use futures::future::FutureExt; use libp2p_core::multiaddr::{Multiaddr, Protocol}; use libp2p_core::muxing::StreamMuxerBox; @@ -8,12 +12,6 @@ use std::net::{IpAddr, SocketAddr}; use std::pin::Pin; use std::task::{Context, Poll}; -// use crate::endpoint::Endpoint; -use super::fingerprint::Fingerprint; -use super::upgrade::Upgrader; -use super::Connection; -use super::Error; - const HANDSHAKE_TIMEOUT_MS: u64 = 10_000; /// Config for the [`Transport`]. @@ -81,9 +79,8 @@ impl libp2p_core::Transport for Transport { Ok(async move { // let peer_id = connection.connect().await?; - let (peer_id, connection) = Upgrader::new() - .outbound(sock_addr, server_fingerprint, config.keypair.clone()) - .await?; + let (peer_id, connection) = + upgrade::outbound(sock_addr, server_fingerprint, config.keypair.clone()).await?; Ok((peer_id, connection)) } From b0079d8bc0566b8b7ce4b7b5aaec430b48a64d4e Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 14:04:09 -0300 Subject: [PATCH 055/235] refactor log::debug --- transports/webrtc-websys/src/connection.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 778c8e029e7..625247e078c 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -3,12 +3,10 @@ use crate::stream::DataChannel; use super::cbfutures::CbFuture; -use super::stream::DataChannelConfig; use super::{Error, WebRTCStream}; use futures::stream::FuturesUnordered; use futures::{FutureExt, StreamExt}; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; -use log::debug; use send_wrapper::SendWrapper; use std::pin::Pin; use std::task::Waker; @@ -56,7 +54,7 @@ impl ConnectionInner { let ondatachannel_callback = Closure::::new(move |ev: RtcDataChannelEvent| { let dc2 = ev.channel(); - debug!("ondatachannel! Label (if any): {:?}", dc2.label()); + log::debug!("ondatachannel! Label (if any): {:?}", dc2.label()); cback_clone.publish(dc2); }); @@ -74,7 +72,7 @@ impl ConnectionInner { } /// Initiates and polls a future from `create_data_channel`. - /// Takes the RtcPeerConnection and DataChannelConfig and creates a pollable future + /// Takes the RtcPeerConnection and creates a regular DataChannel fn poll_create_data_channel(&mut self, cx: &mut Context) -> Poll> { // Create Regular Data Channel let dc = DataChannel::new_regular(&self.peer_connection); @@ -157,7 +155,7 @@ impl StreamMuxer for Connection { mut self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { - debug!("connection::poll_close"); + log::debug!("connection::poll_close"); self.inner.close_connection(); Poll::Ready(Ok(())) From 4b674d6fc6ebeaa000b0afc87d0fd8d6f8640c1b Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 4 Aug 2023 17:12:54 -0300 Subject: [PATCH 056/235] fix typo --- interop-tests/src/arch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs index 9b0d7c80c54..7e798e0e3a5 100644 --- a/interop-tests/src/arch.rs +++ b/interop-tests/src/arch.rs @@ -199,7 +199,7 @@ pub(crate) mod wasm { .boxed(), format!("/ip4/{ip}/udp/0/quic/webtransport"), )), - Transport::WebRTCDirect => Ok(( + Transport::WebRtcDirect => Ok(( webrtc_websys::Transport::new(webrtc_websys::Config::new(&local_key)).boxed(), format!("/ip4/{ip}/udp/0/webrtc-direct"), )), From 052b6bbb49def7cf3aabbe14c4dbe1e8d5a6a96f Mon Sep 17 00:00:00 2001 From: Doug A Date: Sat, 5 Aug 2023 08:57:08 -0300 Subject: [PATCH 057/235] sw to Localhost, rm WebRTCWebSys --- interop-tests/src/arch.rs | 3 +-- interop-tests/src/lib.rs | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs index 7e798e0e3a5..43e5b7401cb 100644 --- a/interop-tests/src/arch.rs +++ b/interop-tests/src/arch.rs @@ -117,7 +117,7 @@ pub(crate) mod native { .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) .boxed(), // IPv4 does not pass tests locally, but IPv6 does. - Multiaddr::from(Ipv6Addr::UNSPECIFIED) + Multiaddr::from(Ipv6Addr::LOCALHOST) .with(Protocol::Udp(0)) .with(Protocol::WebRTCDirect) .to_string(), @@ -125,7 +125,6 @@ pub(crate) mod native { (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"), - (Transport::WebRTCWebSys, _) => bail!("WebRTCWebSys can only be used with wasm"), }; Ok((transport, addr)) } diff --git a/interop-tests/src/lib.rs b/interop-tests/src/lib.rs index fac31db3b06..b41ea3c2732 100644 --- a/interop-tests/src/lib.rs +++ b/interop-tests/src/lib.rs @@ -179,7 +179,6 @@ pub enum Transport { WebRtcDirect, Ws, Webtransport, - WebRTCWebSys, } impl FromStr for Transport { @@ -192,7 +191,6 @@ impl FromStr for Transport { "webrtc-direct" => Self::WebRtcDirect, "ws" => Self::Ws, "webtransport" => Self::Webtransport, - "webrtc-websys" => Self::WebRTCWebSys, other => bail!("unknown transport {other}"), }) } From 190fc1bcfc844f7a314f1d596e102655a21d21bc Mon Sep 17 00:00:00 2001 From: Doug A Date: Sat, 5 Aug 2023 10:40:28 -0300 Subject: [PATCH 058/235] fix buggy close sequence --- transports/webrtc-websys/src/stream.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 23a57e22af3..7f8da8ed0ef 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -304,9 +304,12 @@ impl AsyncWrite for WebRTCStream { } Some(Closing::MessageSent) => { ready!(self.inner.io.poll_flush_unpin(cx))?; - ready!(self.inner.io.poll_close_unpin(cx))?; self.state.write_closed(); + + // send the actual RtcDataChannel `.close()` command + ready!(self.inner.io.poll_close_unpin(cx))?; + // TODO: implement drop_notifier // let _ = self // .drop_notifier From 5bdd56f4ecaf95ac69766253de5fbdcf34a75e97 Mon Sep 17 00:00:00 2001 From: Doug A Date: Sat, 5 Aug 2023 10:41:31 -0300 Subject: [PATCH 059/235] set readme to webrtc-direc naming --- interop-tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interop-tests/README.md b/interop-tests/README.md index ff4983cece8..1a909655a3d 100644 --- a/interop-tests/README.md +++ b/interop-tests/README.md @@ -26,7 +26,7 @@ Firefox is not yet supported as it doesn't support all required features yet 1. Build the wasm package: `wasm-pack build --target web` 2. Run the webtransport dialer: `redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webtransport is_dialer=true cargo run --bin wasm_ping` -3. Run the webrtc-websys dialer: `redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webrtc-websys is_dialer=true cargo run --bin wasm_ping` +3. Run the webrtc-direct dialer: `RUST_LOG=debug,hyper=off redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webrtc-direct is_dialer=true cargo run --bin wasm_ping` # Running all interop tests locally with Compose From a7491450af55e8b173b9fd3b3421f2a266b6c434 Mon Sep 17 00:00:00 2001 From: Doug A Date: Sat, 5 Aug 2023 10:56:32 -0300 Subject: [PATCH 060/235] rename WebRTCStream to simply `Stream` --- transports/webrtc-websys/src/connection.rs | 12 ++++++------ transports/webrtc-websys/src/stream.rs | 8 ++++---- transports/webrtc-websys/src/upgrade.rs | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 625247e078c..a5843cd328a 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -3,7 +3,7 @@ use crate::stream::DataChannel; use super::cbfutures::CbFuture; -use super::{Error, WebRTCStream}; +use super::{Error, Stream}; use futures::stream::FuturesUnordered; use futures::{FutureExt, StreamExt}; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; @@ -73,10 +73,10 @@ impl ConnectionInner { /// Initiates and polls a future from `create_data_channel`. /// Takes the RtcPeerConnection and creates a regular DataChannel - fn poll_create_data_channel(&mut self, cx: &mut Context) -> Poll> { + fn poll_create_data_channel(&mut self, cx: &mut Context) -> Poll> { // Create Regular Data Channel let dc = DataChannel::new_regular(&self.peer_connection); - let channel = WebRTCStream::new(dc); + let channel = Stream::new(dc); Poll::Ready(Ok(channel)) } @@ -85,12 +85,12 @@ impl ConnectionInner { /// To poll for inbound WebRTCStreams, we need to poll for the ondatachannel callback /// We only get that callback for inbound data channels on our connections. /// This callback is converted to a future using CbFuture, which we can poll here - fn poll_ondatachannel(&mut self, cx: &mut Context) -> Poll> { + fn poll_ondatachannel(&mut self, cx: &mut Context) -> Poll> { // Poll the ondatachannel callback for incoming data channels let dc = ready!(self.ondatachannel_fut.poll_unpin(cx)); // Create a WebRTCStream from the Data Channel - let channel = WebRTCStream::new(DataChannel::Regular(dc)); + let channel = Stream::new(DataChannel::Regular(dc)); Poll::Ready(Ok(channel)) } @@ -131,7 +131,7 @@ impl Drop for ConnectionInner { /// WebRTC native multiplexing /// Allows users to open substreams impl StreamMuxer for Connection { - type Substream = WebRTCStream; // A Substream of a WebRTC PeerConnection is a Data Channel + type Substream = Stream; // A Substream of a WebRTC PeerConnection is a Data Channel type Error = Error; fn poll_inbound( diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 7f8da8ed0ef..81c7edc38c7 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -135,13 +135,13 @@ impl DataChannelConfig { } /// Substream over the Connection -pub struct WebRTCStream { +pub struct Stream { inner: SendWrapper, state: State, read_buffer: Bytes, } -impl WebRTCStream { +impl Stream { /// Create a new WebRTC Substream pub fn new(channel: DataChannel) -> Self { Self { @@ -170,7 +170,7 @@ impl StreamInner { } } -impl AsyncRead for WebRTCStream { +impl AsyncRead for Stream { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -216,7 +216,7 @@ impl AsyncRead for WebRTCStream { } } -impl AsyncWrite for WebRTCStream { +impl AsyncWrite for Stream { /// Attempt to write bytes from buf into the object. /// On success, returns Poll::Ready(Ok(num_bytes_written)). /// If the object is not ready for writing, diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index fbcf14a686c..465eb996204 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -3,7 +3,7 @@ pub(crate) mod noise; use crate::stream::DataChannel; pub(crate) use super::fingerprint::Fingerprint; -use super::stream::WebRTCStream; +use super::stream::Stream; use super::Error; use super::{sdp, Connection}; use js_sys::{Object, Reflect}; @@ -61,7 +61,7 @@ async fn outbound_inner( // Must create data channel before Offer is created for it to be included in the SDP let handshake_data_channel = DataChannel::new_handshake(&peer_connection); - let webrtc_stream = WebRTCStream::new(handshake_data_channel); + let webrtc_stream = Stream::new(handshake_data_channel); let ufrag = format!("libp2p+webrtc+v1/{}", gen_ufrag(32)); /* From c982e1383497d04b34e98b30d0167e098342cb95 Mon Sep 17 00:00:00 2001 From: Doug A Date: Sat, 5 Aug 2023 10:56:46 -0300 Subject: [PATCH 061/235] rename WebRTCStream to simply `Stream` --- transports/webrtc-websys/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc-websys/src/lib.rs b/transports/webrtc-websys/src/lib.rs index 359f63d105c..29888e10ec3 100644 --- a/transports/webrtc-websys/src/lib.rs +++ b/transports/webrtc-websys/src/lib.rs @@ -9,5 +9,5 @@ mod upgrade; pub use self::connection::Connection; pub use self::error::Error; -pub use self::stream::WebRTCStream; +pub use self::stream::Stream; pub use self::transport::{Config, Transport}; From e09980718fe1428af36efc2d6e1165b33f5df854 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 7 Aug 2023 16:20:24 -0300 Subject: [PATCH 062/235] convert `CbFutures` to `futures::Channel` --- transports/webrtc-websys/src/connection.rs | 53 +++++++++++----------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index a5843cd328a..1d67151c8ea 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -2,10 +2,10 @@ //! use crate::stream::DataChannel; -use super::cbfutures::CbFuture; use super::{Error, Stream}; +use futures::channel; use futures::stream::FuturesUnordered; -use futures::{FutureExt, StreamExt}; +use futures::StreamExt; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; use send_wrapper::SendWrapper; use std::pin::Pin; @@ -27,17 +27,11 @@ impl Connection { inner: SendWrapper::new(ConnectionInner::new(peer_connection)), } } - - /// Peer Connection Getter - pub(crate) fn peer_connection(&self) -> &RtcPeerConnection { - &self.inner.peer_connection - } } struct ConnectionInner { peer_connection: RtcPeerConnection, - create_data_channel_cbfuture: CbFuture, closed: bool, - ondatachannel_fut: CbFuture, + rx_ondatachannel: channel::mpsc::Receiver, /// A list of futures, which, once completed, signal that a [`WebRTCStream`] has been dropped. drop_listeners: FuturesUnordered, @@ -47,33 +41,32 @@ struct ConnectionInner { impl ConnectionInner { fn new(peer_connection: RtcPeerConnection) -> Self { // An ondatachannel Future enables us to poll for incoming data channel events in poll_incoming - let ondatachannel_fut = CbFuture::new(); - let cback_clone = ondatachannel_fut.clone(); + let (mut tx_ondatachannel, rx_ondatachannel) = channel::mpsc::channel(1); // we may get more than one data channel opened on a single peer connection // Wake the Future in the ondatachannel callback let ondatachannel_callback = Closure::::new(move |ev: RtcDataChannelEvent| { let dc2 = ev.channel(); - log::debug!("ondatachannel! Label (if any): {:?}", dc2.label()); - - cback_clone.publish(dc2); + log::debug!("ondatachannel. Label (if any): {:?}", dc2.label()); + if let Err(err_msg) = tx_ondatachannel.start_send(dc2) { + log::error!("Error sending ondatachannel event: {:?}", err_msg); + } }); peer_connection.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); Self { peer_connection, - create_data_channel_cbfuture: CbFuture::new(), closed: false, drop_listeners: FuturesUnordered::default(), no_drop_listeners_waker: None, - ondatachannel_fut, + rx_ondatachannel, } } /// Initiates and polls a future from `create_data_channel`. /// Takes the RtcPeerConnection and creates a regular DataChannel - fn poll_create_data_channel(&mut self, cx: &mut Context) -> Poll> { + fn poll_create_data_channel(&mut self, _cx: &mut Context) -> Poll> { // Create Regular Data Channel let dc = DataChannel::new_regular(&self.peer_connection); let channel = Stream::new(dc); @@ -84,17 +77,24 @@ impl ConnectionInner { /// /// To poll for inbound WebRTCStreams, we need to poll for the ondatachannel callback /// We only get that callback for inbound data channels on our connections. - /// This callback is converted to a future using CbFuture, which we can poll here + /// This callback is converted to a future using channel, which we can poll here fn poll_ondatachannel(&mut self, cx: &mut Context) -> Poll> { - // Poll the ondatachannel callback for incoming data channels - let dc = ready!(self.ondatachannel_fut.poll_unpin(cx)); - - // Create a WebRTCStream from the Data Channel - let channel = Stream::new(DataChannel::Regular(dc)); - Poll::Ready(Ok(channel)) + match ready!(self.rx_ondatachannel.poll_next_unpin(cx)) { + Some(dc) => { + // Create a WebRTC Stream from the Data Channel + let channel = Stream::new(DataChannel::Regular(dc)); + Poll::Ready(Ok(channel)) + } + None => { + self.no_drop_listeners_waker = Some(cx.waker().clone()); + Poll::Pending + } + } } - /// Poll the Inner Connection for Dropped Listeners + /// Poll the Connection + /// + /// Mostly handles Dropped Listeners fn poll(&mut self, cx: &mut Context) -> Poll> { loop { match ready!(self.drop_listeners.poll_next_unpin(cx)) { @@ -116,6 +116,7 @@ impl ConnectionInner { /// if they are used. fn close_connection(&mut self) { if !self.closed { + log::trace!("connection::close_connection"); self.peer_connection.close(); self.closed = true; } @@ -155,7 +156,7 @@ impl StreamMuxer for Connection { mut self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { - log::debug!("connection::poll_close"); + log::trace!("connection::poll_close"); self.inner.close_connection(); Poll::Ready(Ok(())) From 3f076b72a6fd6eaab35b40ec3ccd02602fe0c673 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 7 Aug 2023 16:53:51 -0300 Subject: [PATCH 063/235] rm readme.md --- transports/webrtc-websys/src/README.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 transports/webrtc-websys/src/README.md diff --git a/transports/webrtc-websys/src/README.md b/transports/webrtc-websys/src/README.md deleted file mode 100644 index cfdb9af8eb7..00000000000 --- a/transports/webrtc-websys/src/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# WebRTC Websys - -TODO From efc2a3bb0b2cd77b303b2e5c817b841e6d875b05 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 7 Aug 2023 18:59:17 -0300 Subject: [PATCH 064/235] convert `CbFutures` to `futures::channel` --- .../src/stream/poll_data_channel.rs | 139 ++++++++---------- 1 file changed, 63 insertions(+), 76 deletions(-) diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index 9d6b63f7ad9..390dc6fac6e 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -1,14 +1,13 @@ -// Inspired by webrtc::data::data_channel::poll_data_channel.rs -use crate::cbfutures::{CbFuture, CbStream}; +// This crate inspired by webrtc::data::data_channel::poll_data_channel.rs use crate::error::Error; +use futures::channel; use futures::{AsyncRead, AsyncWrite, FutureExt, StreamExt}; use std::fmt; -use std::future::Future; use std::io; use std::pin::Pin; use std::result::Result; use std::task::{ready, Context, Poll}; -use wasm_bindgen::prelude::*; +use wasm_bindgen::{prelude::*, JsCast}; use web_sys::{MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelState}; /// Default capacity of the temporary read buffer used by [`webrtc_sctp::stream::PollStream`]. @@ -20,88 +19,85 @@ const DEFAULT_READ_BUF_SIZE: usize = 8192; /// Both `poll_read` and `poll_write` calls allocate temporary buffers, which results in an /// additional overhead. pub struct PollDataChannel { + /// The [RtcDataChannel] data_channel: RtcDataChannel, - /// onmessage is an Option, Some(data) means there is data, None means the channel has closed - onmessage_fut: CbStream>, - onopen_fut: CbFuture<()>, - onbufferedamountlow_fut: CbFuture<()>, - onclose_fut: CbFuture<()>, + /// Receive messages from the RtcDataChannel callback + /// mpsc since multiple messages may be sent + rx_onmessage: channel::mpsc::Receiver>, + /// Receive onopen event from the RtcDataChannel callback + /// oneshot since only one onopen event is sent + rx_onopen: channel::oneshot::Receiver<()>, + /// Receieve onbufferedamountlow event from the RtcDataChannel callback + /// mpsc since multiple onbufferedamountlow events may be sent + rx_onbufferedamountlow: channel::mpsc::Receiver<()>, + /// Receive onclose event from the RtcDataChannel callback + /// oneshot since only one onclose event is sent + rx_onclose: channel::oneshot::Receiver<()>, read_buf_cap: usize, } impl PollDataChannel { /// Constructs a new `PollDataChannel`. - pub fn new(data_channel: RtcDataChannel) -> Self { - // On Open - let onopen_fut = CbFuture::new(); - let onopen_cback_clone = onopen_fut.clone(); - - let onopen_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { - // TODO: Send any queued messages - log::debug!("Data Channel opened"); - onopen_cback_clone.publish(()); - }); - - data_channel.set_onopen(Some(onopen_callback.as_ref().unchecked_ref())); - onopen_callback.forget(); - + pub(crate) fn new(data_channel: RtcDataChannel) -> Self { /* - * On Error + * On Open */ - let onerror_fut = CbFuture::new(); - let onerror_cback_clone = onerror_fut.clone(); + let (tx_onopen, rx_onopen) = channel::oneshot::channel(); - let onerror_callback = Closure::::new(move |ev: RtcDataChannelEvent| { - log::debug!("Data Channel error"); - onerror_cback_clone.publish(()); + let onopen_callback = Closure::once_into_js(move |_ev: RtcDataChannelEvent| { + log::debug!("Data Channel opened"); + if let Err(e) = tx_onopen.send(()) { + log::error!("Error sending onopen event {:?}", e); + } }); - data_channel.set_onerror(Some(onerror_callback.as_ref().unchecked_ref())); - onerror_callback.forget(); + data_channel.set_onopen(Some(onopen_callback.as_ref().unchecked_ref())); + // note: `once_into_js` does NOT call `forget()`, see the wasm_bindgen::Closure docs /* * On Message Stream */ - let onmessage_fut = CbStream::new(); - let onmessage_cback_clone = onmessage_fut.clone(); + let (mut tx_onmessage, rx_onmessage) = channel::mpsc::channel(8); // TODO: How big should this be? let onmessage_callback = Closure::::new(move |ev: MessageEvent| { let data = ev.data(); // Convert from Js ArrayBuffer to Vec let data = js_sys::Uint8Array::new(&data).to_vec(); - log::debug!("onmessage data: {:?}", data); - onmessage_cback_clone.publish(data); + log::trace!("onmessage data: {:?}", data); + if let Err(e) = tx_onmessage.start_send(data) { + log::error!("Error sending onmessage event {:?}", e) + } }); data_channel.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref())); onmessage_callback.forget(); // On Close - let onclose_fut = CbFuture::new(); - let onclose_cback_clone = onclose_fut.clone(); - - let onclose_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { - // TODO: Set state to closed? - // TODO: Set futures::Stream Poll::Ready(None)? - log::debug!("Data Channel closed. onclose_callback"); - onclose_cback_clone.publish(()); + let (tx_onclose, rx_onclose) = channel::oneshot::channel(); + + let onclose_callback = Closure::once_into_js(move |_ev: RtcDataChannelEvent| { + log::trace!("Data Channel closed"); + // TODO: This is Erroring, likely because the channel is already closed by the time we try to send/receive this? + if let Err(e) = tx_onclose.send(()) { + log::error!("Error sending onclose event {:?}", e); + } }); data_channel.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); - onclose_callback.forget(); /* * Convert `RTCDataChannel: bufferedamountlow event` Low Event Callback to Future */ - let onbufferedamountlow_fut = CbFuture::new(); - let onbufferedamountlow_cback_clone = onbufferedamountlow_fut.clone(); + let (mut tx_onbufferedamountlow, rx_onbufferedamountlow) = channel::mpsc::channel(1); let onbufferedamountlow_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { log::debug!("bufferedamountlow event"); - onbufferedamountlow_cback_clone.publish(()); + if let Err(e) = tx_onbufferedamountlow.start_send(()) { + log::error!("Error sending onbufferedamountlow event {:?}", e) + } }); data_channel @@ -110,16 +106,16 @@ impl PollDataChannel { Self { data_channel, - onmessage_fut, - onopen_fut, - onclose_fut, - onbufferedamountlow_fut, + rx_onmessage, + rx_onopen, + rx_onclose, + rx_onbufferedamountlow, read_buf_cap: DEFAULT_READ_BUF_SIZE, } } /// Get back the inner data_channel. - pub fn into_inner(self) -> RtcDataChannel { + pub(crate) fn into_inner(self) -> RtcDataChannel { self.data_channel } @@ -134,28 +130,23 @@ impl PollDataChannel { } /// Get Ready State of [RtcDataChannel] - pub fn ready_state(&self) -> RtcDataChannelState { + pub(crate) fn ready_state(&self) -> RtcDataChannelState { self.data_channel.ready_state() } - /// Poll onopen_fut - pub fn poll_onopen(&mut self, cx: &mut Context) -> Poll<()> { - self.onopen_fut.poll_unpin(cx) - } - /// Send data buffer pub fn send(&self, data: &[u8]) -> Result<(), Error> { - log::debug!("send: {:?}", data); + log::trace!("send: {:?}", data); self.data_channel.send_with_u8_array(data)?; Ok(()) } /// StreamIdentifier returns the Stream identifier associated to the stream. - pub fn stream_identifier(&self) -> u16 { - // let label = self.data_channel.id(); // not available (yet), see https://github.com/rustwasm/wasm-bindgen/issues/3542 + pub(crate) fn stream_identifier(&self) -> u16 { + // self.data_channel.id() // not released (yet), see https://github.com/rustwasm/wasm-bindgen/issues/3547 - // label is "" so it's not unique - // FIXME: After the above issue is fixed, use the label instead of the stream id + // temp workaround: use label, though it is "" so it's not unique + // TODO: After the above PR is released, use the label instead of the stream id let label = self.data_channel.label(); let b = label.as_bytes(); let mut stream_id: u16 = 0; @@ -172,7 +163,7 @@ impl AsyncRead for PollDataChannel { cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - match ready!(self.onmessage_fut.poll_next_unpin(cx)) { + match ready!(self.rx_onmessage.poll_next_unpin(cx)) { Some(data) => { let data_len = data.len(); let buf_len = buf.len(); @@ -196,7 +187,7 @@ impl AsyncWrite for PollDataChannel { // If the data channel is not open, // poll on open future until the channel is open if self.data_channel.ready_state() != RtcDataChannelState::Open { - ready!(self.onopen_fut.poll_unpin(cx)); + ready!(self.rx_onopen.poll_unpin(cx)).unwrap(); } // Now that the channel is open, send the data @@ -230,15 +221,9 @@ impl AsyncWrite for PollDataChannel { } // Otherwise, wait for the event to occur, so poll on onbufferedamountlow_fut - match self.onbufferedamountlow_fut.poll_unpin(cx) { - Poll::Ready(()) => { - log::debug!("flushed"); - Poll::Ready(Ok(())) - } - Poll::Pending => { - log::debug!("pending"); - Poll::Pending - } + match self.rx_onbufferedamountlow.poll_next_unpin(cx) { + Poll::Pending => Poll::Pending, + _ => Poll::Ready(Ok(())), } } @@ -248,9 +233,11 @@ impl AsyncWrite for PollDataChannel { mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - log::debug!("poll_close"); + log::trace!("poll_close"); self.data_channel.close(); - ready!(self.onclose_fut.poll_unpin(cx)); + // TODO: polling onclose 'should' be done here but it Errors went sent, as data channals can be closed by either end + // ready!(self.rx_onclose.poll_unpin(cx)); + log::trace!("close complete"); Poll::Ready(Ok(())) } } From 61cce4679b95ce389368f3299fdcb587db756336 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 7 Aug 2023 19:01:09 -0300 Subject: [PATCH 065/235] refactor RtcDataChannelBuilder --- transports/webrtc-websys/src/connection.rs | 6 +- transports/webrtc-websys/src/stream.rs | 103 ++++----------------- transports/webrtc-websys/src/upgrade.rs | 23 +++-- 3 files changed, 30 insertions(+), 102 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 1d67151c8ea..e47ba3dbf8d 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -1,6 +1,6 @@ //! Websys WebRTC Peer Connection //! -use crate::stream::DataChannel; +use crate::stream::RtcDataChannelBuilder; use super::{Error, Stream}; use futures::channel; @@ -68,7 +68,7 @@ impl ConnectionInner { /// Takes the RtcPeerConnection and creates a regular DataChannel fn poll_create_data_channel(&mut self, _cx: &mut Context) -> Poll> { // Create Regular Data Channel - let dc = DataChannel::new_regular(&self.peer_connection); + let dc = RtcDataChannelBuilder::default().build_with(&self.peer_connection); let channel = Stream::new(dc); Poll::Ready(Ok(channel)) } @@ -82,7 +82,7 @@ impl ConnectionInner { match ready!(self.rx_ondatachannel.poll_next_unpin(cx)) { Some(dc) => { // Create a WebRTC Stream from the Data Channel - let channel = Stream::new(DataChannel::Regular(dc)); + let channel = Stream::new(dc); Poll::Ready(Ok(channel)) } None => { diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 81c7edc38c7..53e51da94ea 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -11,9 +11,7 @@ use send_wrapper::SendWrapper; use std::io; use std::pin::Pin; use std::task::{ready, Context, Poll}; -use web_sys::{ - RtcDataChannel, RtcDataChannelInit, RtcDataChannelState, RtcDataChannelType, RtcPeerConnection, -}; +use web_sys::{RtcDataChannel, RtcDataChannelInit, RtcDataChannelType, RtcPeerConnection}; mod drop_listener; mod framed_dc; @@ -22,112 +20,46 @@ mod poll_data_channel; pub(crate) use drop_listener::DropListener; // Max message size that can be sent to the DataChannel -const MAX_MESSAGE_SIZE: u32 = 16 * 1024; +// const MAX_MESSAGE_SIZE: u32 = 16 * 1024; // How much can be buffered to the DataChannel at once -const MAX_BUFFERED_AMOUNT: u32 = 16 * 1024 * 1024; +// const MAX_BUFFERED_AMOUNT: u32 = 16 * 1024 * 1024; // How long time we wait for the 'bufferedamountlow' event to be emitted -const BUFFERED_AMOUNT_LOW_TIMEOUT: u32 = 30 * 1000; +// const BUFFERED_AMOUNT_LOW_TIMEOUT: u32 = 30 * 1000; /// The Browser Default is Blob, so we must set ours to Arraybuffer explicitly const ARRAY_BUFFER_BINARY_TYPE: RtcDataChannelType = RtcDataChannelType::Arraybuffer; /// Builder for DataChannel #[derive(Default, Debug)] -pub struct DataChannelConfig { +pub struct RtcDataChannelBuilder { negotiated: bool, - id: u16, -} - -/// DataChannel type to ensure only the properly configurations -/// are used when building a WebRTCStream -pub enum DataChannel { - Regular(RtcDataChannel), - Handshake(RtcDataChannel), -} - -impl DataChannel { - pub fn new_regular(peer_connection: &RtcPeerConnection) -> Self { - Self::Regular(DataChannelConfig::create(peer_connection)) - } - - pub fn new_handshake(peer_connection: &RtcPeerConnection) -> Self { - Self::Handshake(DataChannelConfig::create_handshake_channel(peer_connection)) - } -} - -/// From DataChannel enum to RtcDataChannel -impl From for RtcDataChannel { - fn from(dc: DataChannel) -> Self { - match dc { - DataChannel::Regular(dc) => dc, - DataChannel::Handshake(dc) => dc, - } - } } /// Builds a Data Channel with selected options and given peer connection /// /// The default config is used in most cases, except when negotiating a Noise handshake -impl DataChannelConfig { - /// Create a new [DataChannelConfig] with the default values - /// Build the [RtcDataChannel] on a given peer connection - /// by calling - /// - /// ```no_run - /// let channel = DataChannelConfig::new().create_from(peer_conn); - /// ``` - fn create(peer_connection: &RtcPeerConnection) -> RtcDataChannel { - Self::default().create_from(peer_connection) - } - - /// Creates a new data channel with Handshake config, - /// uses negotiated: true, id: 0 - /// Build the RtcDataChannel on a given peer connection - /// by calling `create_from(peer_conn)` - /// - /// # Example - /// - /// ```rust - /// let channel = DataChannelConfig::new_handshake_channel(&peer_connection) - /// ``` - fn create_handshake_channel(peer_connection: &RtcPeerConnection) -> RtcDataChannel { - Self::default() - .negotiated(true) - .id(0) - .create_from(peer_connection) - } - - fn negotiated(&mut self, negotiated: bool) -> &mut Self { +impl RtcDataChannelBuilder { + /// Sets the DataChannel to be used for the Noise handshake + /// Defaults to false + pub fn negotiated(&mut self, negotiated: bool) -> &mut Self { self.negotiated = negotiated; self } - /// Set a custom id for the Data Channel - fn id(&mut self, id: u16) -> &mut Self { - self.id = id; - self - } - - /// Opens a WebRTC DataChannel from [RtcPeerConnection] with the selected [DataChannelConfig] - /// We can create `ondatachannel` future before building this - /// then await it after building this - fn create_from(&self, peer_connection: &RtcPeerConnection) -> RtcDataChannel { + /// Builds the WebRTC DataChannel from [RtcPeerConnection] with the given configuration + pub fn build_with(&self, peer_connection: &RtcPeerConnection) -> RtcDataChannel { const LABEL: &str = ""; let dc = match self.negotiated { true => { let mut data_channel_dict = RtcDataChannelInit::new(); - data_channel_dict.negotiated(true).id(self.id); - log::debug!("Creating negotiated DataChannel"); + data_channel_dict.negotiated(true).id(0); // id is only ever set to zero when negotiated is true peer_connection .create_data_channel_with_data_channel_dict(LABEL, &data_channel_dict) } - false => { - log::debug!("Creating NON negotiated DataChannel"); - peer_connection.create_data_channel(LABEL) - } + false => peer_connection.create_data_channel(LABEL), }; dc.set_binary_type(ARRAY_BUFFER_BINARY_TYPE); // Hardcoded here, it's the only type we use dc @@ -143,9 +75,9 @@ pub struct Stream { impl Stream { /// Create a new WebRTC Substream - pub fn new(channel: DataChannel) -> Self { + pub fn new(channel: RtcDataChannel) -> Self { Self { - inner: SendWrapper::new(StreamInner::new(channel.into())), + inner: SendWrapper::new(StreamInner::new(channel)), read_buffer: Bytes::new(), state: State::Open, } @@ -307,10 +239,7 @@ impl AsyncWrite for Stream { self.state.write_closed(); - // send the actual RtcDataChannel `.close()` command - ready!(self.inner.io.poll_close_unpin(cx))?; - - // TODO: implement drop_notifier + // TODO: implement drop_notifier? // let _ = self // .drop_notifier // .take() diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index 465eb996204..92bdc9a8836 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -1,6 +1,6 @@ pub(crate) mod noise; -use crate::stream::DataChannel; +use crate::stream::RtcDataChannelBuilder; pub(crate) use super::fingerprint::Fingerprint; use super::stream::Stream; @@ -46,9 +46,6 @@ async fn outbound_inner( let certificate = JsFuture::from(certificate_promise).await?; // Needs to be Send - let ice: js_sys::Object = Object::new(); - Reflect::set(&ice, &"urls".into(), &"stun:stun.l.google.com:19302".into()).unwrap(); - let mut config = RtcConfiguration::default(); // wrap certificate in a js Array first before adding it to the config object let certificate_arr = js_sys::Array::new(); @@ -59,7 +56,9 @@ async fn outbound_inner( // Create substream for Noise handshake // Must create data channel before Offer is created for it to be included in the SDP - let handshake_data_channel = DataChannel::new_handshake(&peer_connection); + let handshake_data_channel = RtcDataChannelBuilder::default() + .negotiated(true) + .build_with(&peer_connection); let webrtc_stream = Stream::new(handshake_data_channel); @@ -69,7 +68,7 @@ async fn outbound_inner( */ let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send let offer_obj = sdp::offer(offer, &ufrag); - log::debug!("Offer SDP: {:?}", offer_obj); + log::trace!("Offer SDP: {:?}", offer_obj); let sld_promise = peer_connection.set_local_description(&offer_obj); JsFuture::from(sld_promise) .await @@ -80,7 +79,7 @@ async fn outbound_inner( */ // TODO: Update SDP Answer format for Browser WebRTC let answer_obj = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); - log::debug!("Answer SDP: {:?}", answer_obj); + log::trace!("Answer SDP: {:?}", answer_obj); let srd_promise = peer_connection.set_remote_description(&answer_obj); JsFuture::from(srd_promise) .await @@ -92,12 +91,12 @@ async fn outbound_inner( None => return Err(Error::JsError("local_description is None".to_string())), }; let local_fingerprint = match sdp::fingerprint(&local_sdp) { - Ok(fingerprint) => fingerprint, - Err(e) => return Err(Error::JsError(format!("local fingerprint error: {}", e))), + Some(fingerprint) => fingerprint, + None => return Err(Error::JsError(format!("No local fingerprint found"))), }; - log::debug!("local_fingerprint: {:?}", local_fingerprint); - log::debug!("remote_fingerprint: {:?}", remote_fingerprint); + log::trace!("local_fingerprint: {:?}", local_fingerprint); + log::trace!("remote_fingerprint: {:?}", remote_fingerprint); let peer_id = noise::outbound( id_keys, @@ -107,7 +106,7 @@ async fn outbound_inner( ) .await?; - log::debug!("peer_id: {:?}", peer_id); + log::debug!("Remote peer identified as {peer_id}"); Ok((peer_id, Connection::new(peer_connection))) } From e8d1f53d4a261104a4d2fa84afae0a2499457c26 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 7 Aug 2023 19:01:58 -0300 Subject: [PATCH 066/235] rm dead sdp code --- transports/webrtc-websys/src/sdp.rs | 204 ++-------------------------- 1 file changed, 9 insertions(+), 195 deletions(-) diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc-websys/src/sdp.rs index 71a58c364da..0a41b1ae7b8 100644 --- a/transports/webrtc-websys/src/sdp.rs +++ b/transports/webrtc-websys/src/sdp.rs @@ -20,11 +20,9 @@ use super::fingerprint::Fingerprint; use js_sys::Reflect; -use log::{debug, trace}; use serde::Serialize; use std::net::{IpAddr, SocketAddr}; use tinytemplate::TinyTemplate; -use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; @@ -72,7 +70,7 @@ pub(crate) fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescription // remove any double \r\n let munged_offer_sdp = munged_offer_sdp.replace("\r\n\r\n", "\r\n"); - trace!("munged_offer_sdp: {}", munged_offer_sdp); + log::trace!("munged_offer_sdp: {}", munged_offer_sdp); // setLocalDescription let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); @@ -81,95 +79,6 @@ pub(crate) fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescription offer_obj } -// An SDP message that constitutes the offer. -// -// Main RFC: -// `sctp-port` and `max-message-size` attrs RFC: -// `group` and `mid` attrs RFC: -// `ice-ufrag`, `ice-pwd` and `ice-options` attrs RFC: -// `setup` attr RFC: -// -// Short description: -// -// v= -> always 0 -// o= -// -// identifies the creator of the SDP document. We are allowed to use dummy values -// (`-` and `0.0.0.0` as ) to remain anonymous, which we do. Note that "IN" means -// "Internet". -// -// s= -// -// We are allowed to pass a dummy `-`. -// -// c= -// -// Indicates the IP address of the remote. -// Note that "IN" means "Internet". -// -// t= -// -// Start and end of the validity of the session. `0 0` means that the session never expires. -// -// m= ... -// -// A `m=` line describes a request to establish a certain protocol. The protocol in this line -// (i.e. `TCP/DTLS/SCTP` or `UDP/DTLS/SCTP`) must always be the same as the one in the offer. -// We know that this is true because we tweak the offer to match the protocol. The `` -// component must always be `webrtc-datachannel` for WebRTC. -// RFCs: 8839, 8866, 8841 -// -// a=mid: -// -// Media ID - uniquely identifies this media stream (RFC9143). -// -// a=ice-options:ice2 -// -// Indicates that we are complying with RFC8839 (as oppposed to the legacy RFC5245). -// -// a=ice-ufrag: -// a=ice-pwd: -// -// ICE username and password, which are used for establishing and -// maintaining the ICE connection. (RFC8839) -// MUST match ones used by the answerer (server). -// -// a=fingerprint:sha-256 -// -// Fingerprint of the certificate that the remote will use during the TLS -// handshake. (RFC8122) -// -// a=setup:actpass -// -// The endpoint that is the offerer MUST use the setup attribute value of setup:actpass and be -// prepared to receive a client_hello before it receives the answer. -// -// a=sctp-port: -// -// The SCTP port (RFC8841) -// Note it's different from the "m=" line port value, which indicates the port of the -// underlying transport-layer protocol (UDP or TCP). -// -// a=max-message-size: -// -// The maximum SCTP user message size (in bytes). (RFC8841) -const CLIENT_SESSION_DESCRIPTION: &str = "v=0 -o=- 0 0 IN {ip_version} {target_ip} -s=- -c=IN {ip_version} {target_ip} -t=0 0 - -m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel -a=mid:0 -a=ice-options:ice2 -a=ice-ufrag:{ufrag} -a=ice-pwd:{pwd} -a=fingerprint:{fingerprint_algorithm} {fingerprint_value} -a=setup:actpass -a=sctp-port:5000 -a=max-message-size:16384 -"; - // See [`CLIENT_SESSION_DESCRIPTION`]. // // a=ice-lite @@ -310,37 +219,9 @@ fn render_description( tt.render("description", &context).unwrap() } -/// Parse SDP String into a JsValue -pub fn candidate(sdp: &str) -> Option { - let lines = sdp.split("\r\n"); - - for line in lines { - if line.starts_with("a=candidate:") { - // return with leading "a=candidate:" replaced with "" - return Some(line.replace("a=candidate:", "")); - } - } - None -} - -/// sdpMid -/// Get the media id from the SDP -pub fn mid(sdp: &str) -> Option { - let lines = sdp.split("\r\n"); - - // lines.find(|&line| line.starts_with("a=mid:")); - - for line in lines { - if line.starts_with("a=mid:") { - return Some(line.replace("a=mid:", "")); - } - } - None -} - /// Get Fingerprint from SDP /// Gets the fingerprint from matching between the angle brackets: a=fingerprint: -pub fn fingerprint(sdp: &str) -> Result { +pub fn fingerprint(sdp: &str) -> Option { // split the sdp by new lines / carriage returns let lines = sdp.split("\r\n"); @@ -352,91 +233,24 @@ pub fn fingerprint(sdp: &str) -> Result { let fingerprint = line.split(' ').nth(1).unwrap(); let bytes = hex::decode(fingerprint.replace(':', "")).unwrap(); let arr: [u8; 32] = bytes.as_slice().try_into().unwrap(); - return Ok(Fingerprint::raw(arr)); + return Some(Fingerprint::raw(arr)); } } - Err(regex::Error::Syntax("fingerprint not found".to_string())) - - // let fingerprint_regex = match regex::Regex::new( - // r"/^a=fingerprint:(?:\w+-[0-9]+)\s(?P(:?[0-9a-fA-F]{2})+)", - // ) { - // Ok(fingerprint_regex) => fingerprint_regex, - // Err(e) => return Err(regex::Error::Syntax(format!("regex fingerprint: {}", e))), - // }; - // let captures = match fingerprint_regex.captures(sdp) { - // Some(captures) => captures, - // None => { - // return Err(regex::Error::Syntax(format!( - // "fingerprint captures is None {}", - // sdp - // ))) - // } - // }; - // let fingerprint = match captures.name("fingerprint") { - // Some(fingerprint) => fingerprint.as_str(), - // None => return Err(regex::Error::Syntax("fingerprint name is None".to_string())), - // }; - // let decoded = match hex::decode(fingerprint) { - // Ok(fingerprint) => fingerprint, - // Err(e) => { - // return Err(regex::Error::Syntax(format!( - // "decode fingerprint error: {}", - // e - // ))) - // } - // }; - // Ok(Fingerprint::from_certificate(&decoded)) + None } -/* -offer_obj: RtcSessionDescriptionInit { obj: Object { obj: JsValue(Object({"type":"offer","sdp":"v=0\r\no=- 7315842204271936257 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\n"})) } } - answer_obj: RtcSessionDescriptionInit { obj: Object { obj: JsValue(Object({"type":"answer","sdp":"v=0\no=- 0 0 IN IP6 ::1\ns=-\nc=IN IP6 ::1\nt=0 0\na=ice-lite\nm=application 61885 UDP/DTLS/SCTP webrtc-datachannel\na=mid:0\na=setup:passive\na=ice-ufrag:libp2p+webrtc+v1/qBN+NUAT4icgH81g63DoyBs5x/RAQ6tE\na=ice-pwd:libp2p+webrtc+v1/qBN+NUAT4icgH81g63DoyBs5x/RAQ6tE\na=fingerprint:sha-256 A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89\na=sctp-port:5000\na=max-message-size:100000\na=candidate:1467250027 1 UDP 1467250027 ::1 61885 typ host\n"})) } } - -console.log div contained: - panicked at 'dial failed: JsError("Error setting remote_description: JsValue(InvalidAccessError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: The order of m-lines in answer doesn't match order in offer. Rejecting answer - -// What has to change about the SDP offer in order for it to be acceptable by the given answer above: -// How m-lines work: -// M-lines mean "media lines". They are used to describe the media streams that are being negotiated. -// The m-line is the line that describes the media stream. It is composed of the following fields: -// m= ... -// is the type of media (audio, video, data, etc.) -// is the port number that the media stream will be sent on -// is the protocol that will be used to send the media stream (RTP/SAVPF, UDP/TLS/RTP/SAVPF, etc.) -// is the format of the media stream (VP8, H264, etc.) -// The m-line is followed by a series of attributes that describe the media stream. These attributes are called "media-level attributes" and are prefixed with an "a=". -// The order of the m-lines in the answer must match the order of the m-lines in the offer. -// The order of the media-level attributes in the answer must match the order of the media-level attributes in the offer. -// For example, if the offer has the following data channel m-lines: -// m=application 9 UDP/DTLS/SCTP webrtc-datachannel -// a=sctp-port:5000 -// a=max-message-size:16384 -// a=candidate:1 1 UDP 1 -// The answer must have the following data channel m-lines: -// m=application 9 UDP/DTLS/SCTP webrtc-datachannel -// a=sctp-port:5000 -// a=max-message-size:16384 -// a=candidate:1 1 UDP 1 -// When the browser API creates the offer, it will always put the data channel m-line first. This means that the answer must also have the data channel m-line first. - -The differences between a STUN message and the SDP are: -STUN messages are sent over UDP, while SDP messages are sent over TCP. -STUN messages are used to establish a connection, while SDP messages are used to describe the connection. -STUN message looks like: -*/ - -// run test for any, none or all features #[cfg(test)] mod sdp_tests { use super::*; #[test] - fn test_fingerprint() -> Result<(), regex::Error> { - let val = b"A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89"; + fn test_fingerprint() { let sdp: &str = "v=0\no=- 0 0 IN IP6 ::1\ns=-\nc=IN IP6 ::1\nt=0 0\na=ice-lite\nm=application 61885 UDP/DTLS/SCTP webrtc-datachannel\na=mid:0\na=setup:passive\na=ice-ufrag:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\na=ice-pwd:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\na=fingerprint:sha-256 A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89\na=sctp-port:5000\na=max-message-size:16384\na=candidate:1467250027 1 UDP 1467250027 ::1 61885 typ host\n"; - let fingerprint = fingerprint(sdp)?; + let fingerprint = match fingerprint(sdp) { + Some(fingerprint) => fingerprint, + None => panic!("No fingerprint found"), + }; assert_eq!(fingerprint.algorithm(), "sha-256"); assert_eq!(fingerprint.to_sdp_format(), "A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89"); - Ok(()) } } From de485ed0c60a16283bcd9f6cd01cfbf4914f5b60 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 7 Aug 2023 19:02:32 -0300 Subject: [PATCH 067/235] rm dead code --- transports/webrtc-websys/src/transport.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs index 24848656b1e..34d8b4f3e6d 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc-websys/src/transport.rs @@ -12,8 +12,6 @@ use std::net::{IpAddr, SocketAddr}; use std::pin::Pin; use std::task::{Context, Poll}; -const HANDSHAKE_TIMEOUT_MS: u64 = 10_000; - /// Config for the [`Transport`]. #[derive(Clone)] pub struct Config { From 612732a00e5e89f5624b8acab5a2c644084a1dad Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 7 Aug 2023 19:02:54 -0300 Subject: [PATCH 068/235] clean up logging --- transports/webrtc-websys/src/upgrade/noise.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/transports/webrtc-websys/src/upgrade/noise.rs b/transports/webrtc-websys/src/upgrade/noise.rs index 7911c8ca86a..f2907b778b4 100644 --- a/transports/webrtc-websys/src/upgrade/noise.rs +++ b/transports/webrtc-websys/src/upgrade/noise.rs @@ -61,8 +61,6 @@ where let info = noise.protocol_info().next().unwrap(); - log::debug!("Outbound noise upgrade info {:?}", info); - // Server must start the Noise handshake. Browsers cannot initiate // noise.upgrade_inbound has into_responder(), so that's the one we need let (peer_id, mut channel) = match noise.upgrade_inbound(stream, info).await { @@ -70,7 +68,7 @@ where Err(e) => return Err(Error::Noise(e)), }; - log::debug!("Outbound noise upgrade peer_id {:?}", peer_id); + log::trace!("Outbound noise upgrade peer_id {:?}", peer_id); channel.close().await?; // uses AsyncWriteExt to close the EphermalKeyExchange channel? Ok(peer_id) From f957e4a5cfecf54070bd8461296fb3015a709c88 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 9 Aug 2023 09:02:16 -0300 Subject: [PATCH 069/235] fix wasm Closure --- transports/webrtc-websys/src/connection.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index e47ba3dbf8d..ce8c38fe75e 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -41,19 +41,20 @@ struct ConnectionInner { impl ConnectionInner { fn new(peer_connection: RtcPeerConnection) -> Self { // An ondatachannel Future enables us to poll for incoming data channel events in poll_incoming - let (mut tx_ondatachannel, rx_ondatachannel) = channel::mpsc::channel(1); // we may get more than one data channel opened on a single peer connection + let (mut tx_ondatachannel, rx_ondatachannel) = channel::mpsc::channel(4); // we may get more than one data channel opened on a single peer connection // Wake the Future in the ondatachannel callback let ondatachannel_callback = Closure::::new(move |ev: RtcDataChannelEvent| { let dc2 = ev.channel(); - log::debug!("ondatachannel. Label (if any): {:?}", dc2.label()); - if let Err(err_msg) = tx_ondatachannel.start_send(dc2) { + log::trace!("ondatachannel_callback triggered"); + if let Err(err_msg) = tx_ondatachannel.try_send(dc2) { log::error!("Error sending ondatachannel event: {:?}", err_msg); } }); peer_connection.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); + ondatachannel_callback.forget(); Self { peer_connection, @@ -83,6 +84,7 @@ impl ConnectionInner { Some(dc) => { // Create a WebRTC Stream from the Data Channel let channel = Stream::new(dc); + log::trace!("connection::poll_ondatachannel ready"); Poll::Ready(Ok(channel)) } None => { From bf7bf2a48d0eef09a206ae005c5775835bf92241 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 9 Aug 2023 15:33:21 -0300 Subject: [PATCH 070/235] add trace logging --- transports/webrtc-websys/src/connection.rs | 9 +++++++-- transports/webrtc-websys/src/stream.rs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index ce8c38fe75e..46f99b9dad7 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -48,8 +48,12 @@ impl ConnectionInner { Closure::::new(move |ev: RtcDataChannelEvent| { let dc2 = ev.channel(); log::trace!("ondatachannel_callback triggered"); - if let Err(err_msg) = tx_ondatachannel.try_send(dc2) { - log::error!("Error sending ondatachannel event: {:?}", err_msg); + match tx_ondatachannel.try_send(dc2) { + Ok(_) => log::trace!("ondatachannel_callback sent data channel"), + Err(e) => log::error!( + "ondatachannel_callback: failed to send data channel: {:?}", + e + ), } }); @@ -69,6 +73,7 @@ impl ConnectionInner { /// Takes the RtcPeerConnection and creates a regular DataChannel fn poll_create_data_channel(&mut self, _cx: &mut Context) -> Poll> { // Create Regular Data Channel + log::trace!("Creating outbound data channel"); let dc = RtcDataChannelBuilder::default().build_with(&self.peer_connection); let channel = Stream::new(dc); Poll::Ready(Ok(channel)) diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 53e51da94ea..7a9af5f0ab4 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -217,7 +217,7 @@ impl AsyncWrite for Stream { } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - log::debug!("poll_closing"); + log::trace!("poll_closing"); loop { match self.state.close_write_barrier()? { From b0229c1becbd5c8156b74fe5f264c5da0a6b7611 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 9 Aug 2023 15:34:20 -0300 Subject: [PATCH 071/235] convert to `try_send` and `log::trace` --- .../src/stream/poll_data_channel.rs | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index 390dc6fac6e..93fe7b9ffcc 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -27,7 +27,7 @@ pub struct PollDataChannel { rx_onmessage: channel::mpsc::Receiver>, /// Receive onopen event from the RtcDataChannel callback /// oneshot since only one onopen event is sent - rx_onopen: channel::oneshot::Receiver<()>, + rx_onopen: channel::mpsc::Receiver<()>, /// Receieve onbufferedamountlow event from the RtcDataChannel callback /// mpsc since multiple onbufferedamountlow events may be sent rx_onbufferedamountlow: channel::mpsc::Receiver<()>, @@ -44,29 +44,28 @@ impl PollDataChannel { /* * On Open */ - let (tx_onopen, rx_onopen) = channel::oneshot::channel(); + let (mut tx_onopen, rx_onopen) = channel::mpsc::channel(2); - let onopen_callback = Closure::once_into_js(move |_ev: RtcDataChannelEvent| { + let onopen_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { log::debug!("Data Channel opened"); - if let Err(e) = tx_onopen.send(()) { + if let Err(e) = tx_onopen.try_send(()) { log::error!("Error sending onopen event {:?}", e); } }); data_channel.set_onopen(Some(onopen_callback.as_ref().unchecked_ref())); - // note: `once_into_js` does NOT call `forget()`, see the wasm_bindgen::Closure docs + onopen_callback.forget(); /* * On Message Stream */ - let (mut tx_onmessage, rx_onmessage) = channel::mpsc::channel(8); // TODO: How big should this be? + let (mut tx_onmessage, rx_onmessage) = channel::mpsc::channel(2); // TODO: How big should this be? let onmessage_callback = Closure::::new(move |ev: MessageEvent| { let data = ev.data(); // Convert from Js ArrayBuffer to Vec let data = js_sys::Uint8Array::new(&data).to_vec(); - log::trace!("onmessage data: {:?}", data); - if let Err(e) = tx_onmessage.start_send(data) { + if let Err(e) = tx_onmessage.try_send(data) { log::error!("Error sending onmessage event {:?}", e) } }); @@ -86,16 +85,16 @@ impl PollDataChannel { }); data_channel.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); + // Note: `once_into_js` Closure does NOT call `forget()`, see the wasm_bindgen::Closure docs for more info /* * Convert `RTCDataChannel: bufferedamountlow event` Low Event Callback to Future */ - let (mut tx_onbufferedamountlow, rx_onbufferedamountlow) = channel::mpsc::channel(1); + let (mut tx_onbufferedamountlow, rx_onbufferedamountlow) = channel::mpsc::channel(2); let onbufferedamountlow_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { - log::debug!("bufferedamountlow event"); - if let Err(e) = tx_onbufferedamountlow.start_send(()) { + if let Err(e) = tx_onbufferedamountlow.try_send(()) { log::error!("Error sending onbufferedamountlow event {:?}", e) } }); @@ -136,7 +135,6 @@ impl PollDataChannel { /// Send data buffer pub fn send(&self, data: &[u8]) -> Result<(), Error> { - log::trace!("send: {:?}", data); self.data_channel.send_with_u8_array(data)?; Ok(()) } @@ -167,7 +165,7 @@ impl AsyncRead for PollDataChannel { Some(data) => { let data_len = data.len(); let buf_len = buf.len(); - log::debug!("poll_read [{:?} of {} bytes]", data_len, buf_len); + log::trace!("poll_read [{:?} of {} bytes]", data_len, buf_len); let len = std::cmp::min(data_len, buf_len); buf[..len].copy_from_slice(&data[..len]); Poll::Ready(Ok(len)) @@ -183,11 +181,15 @@ impl AsyncWrite for PollDataChannel { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - log::debug!("poll_write: [{:?}]", buf.len()); + log::trace!( + "poll_write: [{:?}], state: {:?}", + buf.len(), + self.data_channel.ready_state() + ); // If the data channel is not open, // poll on open future until the channel is open if self.data_channel.ready_state() != RtcDataChannelState::Open { - ready!(self.rx_onopen.poll_unpin(cx)).unwrap(); + ready!(self.rx_onopen.poll_next_unpin(cx)).unwrap(); } // Now that the channel is open, send the data @@ -211,10 +213,6 @@ impl AsyncWrite for PollDataChannel { /// We can therefore create a callback future called `onbufferedamountlow_fut` to listen for `bufferedamountlow` event and wake the task /// The default `bufferedAmountLowThreshold` value is 0. fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - log::debug!( - "poll_flush buffered_amount is {:?}", - self.data_channel.buffered_amount() - ); // if bufferedamountlow empty, return ready if self.data_channel.buffered_amount() == 0 { return Poll::Ready(Ok(())); @@ -229,14 +227,11 @@ impl AsyncWrite for PollDataChannel { /// Initiates or attempts to shut down this writer, /// returning success when the connection callback returns has completely shut down. - fn poll_close( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { log::trace!("poll_close"); self.data_channel.close(); - // TODO: polling onclose 'should' be done here but it Errors went sent, as data channals can be closed by either end - // ready!(self.rx_onclose.poll_unpin(cx)); + // TODO: Confirm that channels are closing properyl. Poll onclose 'should' be done here but it Errors when sent, as data channals can be closed by either end + let _ = ready!(self.rx_onclose.poll_unpin(cx)); log::trace!("close complete"); Poll::Ready(Ok(())) } From 5f34cc3d0f64230247c9cbb2e12e327ff1e6d8e7 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 9 Aug 2023 16:16:39 -0300 Subject: [PATCH 072/235] add example (so awesome seeing it in action) --- .gitignore | 1 + Cargo.lock | 978 +++++++++++++++--- Cargo.toml | 12 +- examples/browser-webrtc/README.md | 22 + examples/browser-webrtc/client/Cargo.toml | 29 + examples/browser-webrtc/client/README.md | 5 + examples/browser-webrtc/client/index.html | 5 + examples/browser-webrtc/client/src/lib.rs | 118 +++ examples/browser-webrtc/client/src/main.rs | 43 + examples/browser-webrtc/client/src/pinger.rs | 79 ++ .../browser-webrtc/server/.cargo/config.toml | 2 + examples/browser-webrtc/server/Cargo.toml | 30 + examples/browser-webrtc/server/README.md | 8 + .../browser-webrtc/server/src/bin/main.rs | 15 + examples/browser-webrtc/server/src/lib.rs | 135 +++ 15 files changed, 1337 insertions(+), 145 deletions(-) create mode 100644 examples/browser-webrtc/README.md create mode 100644 examples/browser-webrtc/client/Cargo.toml create mode 100644 examples/browser-webrtc/client/README.md create mode 100644 examples/browser-webrtc/client/index.html create mode 100644 examples/browser-webrtc/client/src/lib.rs create mode 100644 examples/browser-webrtc/client/src/main.rs create mode 100644 examples/browser-webrtc/client/src/pinger.rs create mode 100644 examples/browser-webrtc/server/.cargo/config.toml create mode 100644 examples/browser-webrtc/server/Cargo.toml create mode 100644 examples/browser-webrtc/server/README.md create mode 100644 examples/browser-webrtc/server/src/bin/main.rs create mode 100644 examples/browser-webrtc/server/src/lib.rs diff --git a/.gitignore b/.gitignore index eb5a316cbd1..171600ad164 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target +examples/browser-webrtc/client/dist \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2ddc1814c66..d9eef72f081 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,17 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.10", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.3" @@ -267,7 +278,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.23", + "time 0.3.25", ] [[package]] @@ -283,7 +294,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.23", + "time 0.3.25", ] [[package]] @@ -448,6 +459,17 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "async-std" version = "1.12.0" @@ -470,7 +492,7 @@ dependencies = [ "log", "memchr", "once_cell", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "pin-utils", "slab", "wasm-bindgen-futures", @@ -505,7 +527,7 @@ checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -518,7 +540,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", ] [[package]] @@ -528,14 +550,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] -name = "atty" -version = "0.2.14" +name = "attribute-derive" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "c124f12ade4e670107b132722d0ad1a5c9790bcbc1b265336369ea05626b4498" dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", + "attribute-derive-macro", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b217a07446e0fb086f83401a98297e2d81492122f5874db5391bd270a185f88" +dependencies = [ + "collection_literals", + "interpolator", + "proc-macro-error", + "proc-macro-utils", + "proc-macro2", + "quote", + "quote-use", + "syn 2.0.28", ] [[package]] @@ -557,9 +596,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a1de45611fdb535bfde7b7de4fd54f4fd2b17b1737c0a59b69bf9b92074b8c" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", @@ -574,7 +613,7 @@ dependencies = [ "memchr", "mime", "percent-encoding", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "rustversion", "serde", "serde_json", @@ -749,6 +788,25 @@ dependencies = [ "log", ] +[[package]] +name = "browser-webrtc-example-client" +version = "0.1.0" +dependencies = [ + "async-channel", + "cfg-if 0.1.10", + "chrono", + "console_log 0.2.2", + "fern", + "futures", + "leptos", + "libp2p", + "libp2p-webrtc-websys", + "log", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "bs58" version = "0.5.0" @@ -779,6 +837,12 @@ dependencies = [ "serde", ] +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" + [[package]] name = "cast" version = "0.3.0" @@ -796,9 +860,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] [[package]] name = "ccm" @@ -962,7 +1029,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -971,6 +1038,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "collection_literals" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" + [[package]] name = "colorchoice" version = "1.0.0" @@ -979,11 +1052,11 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "1.9.3" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" +checksum = "5a5f741c91823341bebf717d4c71bda820630ce065443b58bd1b7451af008355" dependencies = [ - "atty", + "is-terminal", "lazy_static", "winapi", ] @@ -997,7 +1070,7 @@ dependencies = [ "bytes", "futures-core", "memchr", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "tokio", "tokio-util", ] @@ -1011,6 +1084,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1021,6 +1113,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "console_log" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" +dependencies = [ + "log", + "web-sys", +] + [[package]] name = "console_log" version = "1.0.0" @@ -1038,6 +1140,35 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "const_format" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.16.2" @@ -1045,7 +1176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.23", + "time 0.3.25", "version_check", ] @@ -1426,6 +1557,23 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "deranged" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" + +[[package]] +name = "derive-where" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc1955a640c4464859ae700fbe48e666da6fdce99ce5fe1acd08dd295889d10" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "derive_builder" version = "0.11.2" @@ -1486,7 +1634,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -1500,6 +1648,18 @@ dependencies = [ "libp2p", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "drain_filter_polyfill" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" + [[package]] name = "dtoa" version = "1.0.9" @@ -1555,6 +1715,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "079044df30bb07de7d846d41a184c4b00e66ebdac93ee459253474f3a47e50ae" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "either" version = "1.9.0" @@ -1624,6 +1796,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "enum-ordinalize" +version = "3.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f76552f53cefc9a7f64987c3701b99d982f7690606fd67de1d09712fbf52f1" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "env_logger" version = "0.8.4" @@ -1655,9 +1840,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -1696,7 +1881,7 @@ dependencies = [ "mime", "serde", "serde_json", - "time 0.3.23", + "time 0.3.25", "tokio", "url", "webdriver", @@ -1867,7 +2052,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "waker-fn", ] @@ -1879,7 +2064,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -1900,7 +2085,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28" dependencies = [ "futures-io", - "rustls 0.21.5", + "rustls 0.21.6", ] [[package]] @@ -1949,7 +2134,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "pin-utils", "slab", ] @@ -2033,6 +2218,26 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-net" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "gloo-timers" version = "0.2.6" @@ -2045,6 +2250,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "group" version = "0.12.1" @@ -2097,6 +2315,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] [[package]] name = "hashbrown" @@ -2104,7 +2325,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ - "ahash", + "ahash 0.8.3", "allocator-api2", ] @@ -2114,15 +2335,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.2" @@ -2197,6 +2409,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "http" version = "0.2.9" @@ -2216,7 +2437,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", ] [[package]] @@ -2259,7 +2480,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "socket2 0.4.9", "tokio", "tower-service", @@ -2480,13 +2701,25 @@ dependencies = [ "wasm-logger", ] +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + +[[package]] +name = "inventory" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53088c87cf71c9d4f3372a2cb9eea1e7b8a0b1bf8b7f7d23fe5b76dbb07e63b" + [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi", "libc", "windows-sys", ] @@ -2538,8 +2771,8 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.2", - "rustix 0.38.4", + "hermit-abi", + "rustix 0.38.7", "windows-sys", ] @@ -2567,6 +2800,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "keccak" version = "0.1.4" @@ -2604,6 +2848,147 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "leptos" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d08fd7674758f996050217a8aff9e584d033c2e5c882cd3f52fb5090dc361dd" +dependencies = [ + "cfg-if 1.0.0", + "leptos_config", + "leptos_dom", + "leptos_macro", + "leptos_reactive", + "leptos_server", + "server_fn", + "tracing", + "typed-builder", +] + +[[package]] +name = "leptos_config" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e5c13a1ae92b5a545cc013205288751fb2fef521de5a092067fd8429ad343e8" +dependencies = [ + "config", + "regex", + "serde", + "thiserror", + "typed-builder", +] + +[[package]] +name = "leptos_dom" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35994afab1dca68a46c7b40a29d40d84a2e06e1b1fa0d5c5915ade4f4f2611ee" +dependencies = [ + "async-recursion", + "cfg-if 1.0.0", + "drain_filter_polyfill", + "educe", + "futures", + "getrandom 0.2.10", + "html-escape", + "indexmap 2.0.0", + "itertools", + "js-sys", + "leptos_reactive", + "once_cell", + "pad-adapter", + "paste", + "rustc-hash", + "serde_json", + "server_fn", + "smallvec", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_hot_reload" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a088a4dd5489941a9cc73719148f217c78f0d761a50e025739653c3b7f9d484" +dependencies = [ + "anyhow", + "camino", + "indexmap 2.0.0", + "parking_lot", + "proc-macro2", + "quote", + "rstml", + "serde", + "syn 2.0.28", + "walkdir", +] + +[[package]] +name = "leptos_macro" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bdd7a21d20ca21bb12d67d050d4b0ad9973b156bce98f499f8b1789f11959dd" +dependencies = [ + "attribute-derive", + "cfg-if 1.0.0", + "convert_case", + "html-escape", + "itertools", + "leptos_hot_reload", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "rstml", + "server_fn_macro", + "syn 2.0.28", + "tracing", + "uuid", +] + +[[package]] +name = "leptos_reactive" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5500318e457b4ab841722a5988e8db0def1ee7ac66b816ba9073c100c4984a" +dependencies = [ + "base64 0.21.2", + "cfg-if 1.0.0", + "futures", + "indexmap 2.0.0", + "js-sys", + "rustc-hash", + "self_cell", + "serde", + "serde-wasm-bindgen", + "serde_json", + "slotmap", + "thiserror", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_server" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28d958deee3c7ffda892a67ac4a47500aebbaf00b11d217cfe6fd494c297818" +dependencies = [ + "inventory", + "lazy_static", + "leptos_macro", + "leptos_reactive", + "serde", + "server_fn", + "thiserror", + "tracing", +] + [[package]] name = "libc" version = "0.2.147" @@ -3165,7 +3550,7 @@ dependencies = [ "quickcheck", "quinn-proto", "rand 0.8.5", - "rustls 0.21.5", + "rustls 0.21.6", "thiserror", "tokio", ] @@ -3294,7 +3679,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -3346,11 +3731,11 @@ dependencies = [ "libp2p-yamux", "rcgen 0.10.0", "ring", - "rustls 0.21.5", + "rustls 0.21.6", "thiserror", "tokio", "webpki 0.22.0", - "x509-parser 0.15.0", + "x509-parser 0.15.1", "yasna", ] @@ -3428,10 +3813,8 @@ dependencies = [ [[package]] name = "libp2p-webrtc-websys" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ - "anyhow", - "async-trait", "asynchronous-codec", "bytes", "futures", @@ -3443,23 +3826,15 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-noise", - "libp2p-ping", - "libp2p-swarm", "libp2p-webrtc-utils", "log", - "multihash", "quick-protobuf", "quick-protobuf-codec", - "quickcheck", - "rcgen 0.10.0", - "regex", "send_wrapper 0.6.0", "serde", "sha2 0.10.7", "thiserror", "tinytemplate", - "unsigned-varint", - "void", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -3582,9 +3957,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lock_api" @@ -3646,9 +4021,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" +checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" [[package]] name = "md-5" @@ -3959,7 +4334,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi", "libc", ] @@ -4010,9 +4385,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ "bitflags 1.3.2", "cfg-if 1.0.0", @@ -4031,7 +4406,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -4042,9 +4417,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ "cc", "libc", @@ -4052,6 +4427,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + [[package]] name = "overload" version = "0.1.1" @@ -4102,6 +4487,12 @@ dependencies = [ "libm", ] +[[package]] +name = "pad-adapter" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" + [[package]] name = "parking" version = "2.1.0" @@ -4137,6 +4528,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pem" version = "1.1.1" @@ -4170,24 +4567,68 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pest" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "pest_meta" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +dependencies = [ + "once_cell", + "pest", + "sha2 0.10.7", +] + [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -4198,9 +4639,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" [[package]] name = "pin-utils" @@ -4290,7 +4731,7 @@ dependencies = [ "concurrent-queue", "libc", "log", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "windows-sys", ] @@ -4335,6 +4776,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +dependencies = [ + "proc-macro2", + "syn 2.0.28", +] + [[package]] name = "primeorder" version = "0.13.2" @@ -4368,6 +4819,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + [[package]] name = "proc-macro-warning" version = "0.4.1" @@ -4376,7 +4838,7 @@ checksum = "70550716265d1ec349c41f70dd4f964b4fd88394efe4405f0c1da679c4799a07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -4388,6 +4850,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", + "version_check", + "yansi", +] + [[package]] name = "prometheus-client" version = "0.21.2" @@ -4469,15 +4944,15 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85af4ed6ee5a89f26a26086e9089a6643650544c025158449a3626ebf72884b3" +checksum = "f8c8bb234e70c863204303507d841e7fa2295e95c822b2bb4ca8ebf57f17b1cb" dependencies = [ "bytes", "rand 0.8.5", "ring", "rustc-hash", - "rustls 0.21.5", + "rustls 0.21.6", "slab", "thiserror", "tinyvec", @@ -4493,6 +4968,18 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quote-use" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e9a38ef862d7fec635661503289062bc5b3035e61859a8de3d3f81823accd2" +dependencies = [ + "derive-where", + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "rand" version = "0.7.3" @@ -4594,7 +5081,7 @@ checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" dependencies = [ "pem", "ring", - "time 0.3.23", + "time 0.3.25", "yasna", ] @@ -4606,16 +5093,16 @@ checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ "pem", "ring", - "time 0.3.23", + "time 0.3.25", "x509-parser 0.14.0", "yasna", ] [[package]] name = "redis" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea8c51b5dc1d8e5fd3350ec8167f464ec0995e79f2e90a075b63371500d557f" +checksum = "ff5d95dd18a4d76650f0c2607ed8ebdbf63baf9cb934e1c233cd220c694db1d7" dependencies = [ "async-trait", "bytes", @@ -4623,7 +5110,7 @@ dependencies = [ "futures-util", "itoa", "percent-encoding", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "ryu", "tokio", "tokio-util", @@ -4641,13 +5128,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.3", + "regex-automata 0.3.6", "regex-syntax 0.7.4", ] @@ -4662,9 +5149,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -4732,7 +5219,7 @@ dependencies = [ "native-tls", "once_cell", "percent-encoding", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "serde", "serde_json", "serde_urlencoded", @@ -4823,6 +5310,31 @@ dependencies = [ "serde", ] +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rstml" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6522514806fbc2fc4c3d54ee9cc01e928fa00e1c988af4c730a64f57637ad7cf" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.28", + "syn_derive", + "thiserror", +] + [[package]] name = "rtcp" version = "0.9.0" @@ -4883,7 +5395,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.27", + "syn 2.0.28", "walkdir", ] @@ -4897,6 +5409,16 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if 1.0.0", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -4943,14 +5465,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" dependencies = [ "bitflags 2.3.3", "errno", "libc", - "linux-raw-sys 0.4.3", + "linux-raw-sys 0.4.5", "windows-sys", ] @@ -4981,9 +5503,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.5" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" dependencies = [ "log", "ring", @@ -5166,6 +5688,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6" + [[package]] name = "semver" version = "1.0.18" @@ -5189,29 +5717,40 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.175" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" -version = "1.0.175" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "indexmap 2.0.0", "itoa", @@ -5229,15 +5768,26 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_qs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + [[package]] name = "serde_repr" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e168eaaf71e8f9bd6037feb05190485708e019f4fd87d161b3c0a0d37daf85e5" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -5252,6 +5802,55 @@ dependencies = [ "serde", ] +[[package]] +name = "server_fn" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644216cf54c944da2d7fc7a75337a35dc39de19130be3fd88fd58674719a1b5b" +dependencies = [ + "ciborium", + "const_format", + "gloo-net", + "js-sys", + "lazy_static", + "once_cell", + "proc-macro2", + "quote", + "reqwest", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "syn 2.0.28", + "thiserror", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db2cd1a054f5c6ec168982241f6cdad083591d6c68449e666c839ec421bfc54" +dependencies = [ + "const_format", + "proc-macro-error", + "proc-macro2", + "quote", + "serde", + "syn 2.0.28", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ee7b18c66e7a30b1855096cee24d540925825ce91193f42fae322033b109c1" +dependencies = [ + "server_fn_macro", + "syn 2.0.28", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -5367,6 +5966,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "serde", + "version_check", +] + [[package]] name = "smallvec" version = "1.11.0" @@ -5545,15 +6154,27 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.27" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8128874d02f9a114ade6d9ad252078cb32d3cb240e26477ac73d7e9c495c605e" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -5595,14 +6216,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if 1.0.0", "fastrand 2.0.0", "redox_syscall", - "rustix 0.38.4", + "rustix 0.38.7", "windows-sys", ] @@ -5670,7 +6291,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -5696,10 +6317,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.23" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -5714,9 +6336,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -5759,7 +6381,7 @@ dependencies = [ "mio", "num_cpus", "parking_lot", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "signal-hook-registry", "socket2 0.4.9", "tokio-macros", @@ -5774,7 +6396,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -5805,7 +6427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "tokio", ] @@ -5819,11 +6441,20 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "tokio", "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower" version = "0.4.13" @@ -5833,7 +6464,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "tokio", "tower-layer", "tower-service", @@ -5857,7 +6488,7 @@ dependencies = [ "mime", "mime_guess", "percent-encoding", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "tokio", "tokio-util", "tower-layer", @@ -5885,7 +6516,7 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.11", "tracing-attributes", "tracing-core", ] @@ -5898,7 +6529,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -6037,12 +6668,29 @@ dependencies = [ "webrtc-util", ] +[[package]] +name = "typed-builder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cba322cb9b7bc6ca048de49e83918223f35e7a86311267013afff257004870" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uint" version = "0.9.5" @@ -6144,6 +6792,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8-width" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" + [[package]] name = "utf8parse" version = "0.2.1" @@ -6262,7 +6916,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", "wasm-bindgen-shared", ] @@ -6296,7 +6950,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6366,7 +7020,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time 0.3.23", + "time 0.3.25", "unicode-segmentation", "url", ] @@ -6438,7 +7092,7 @@ dependencies = [ "smol_str", "stun", "thiserror", - "time 0.3.23", + "time 0.3.25", "tokio", "turn", "url", @@ -6510,6 +7164,30 @@ dependencies = [ "x509-parser 0.13.2", ] +[[package]] +name = "webrtc-example-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "env_logger 0.10.0", + "futures", + "libp2p-core", + "libp2p-identify", + "libp2p-identity", + "libp2p-ping", + "libp2p-relay", + "libp2p-swarm", + "libp2p-webrtc", + "multiaddr", + "rand 0.8.5", + "tokio", + "tokio-stream", + "tokio-util", + "tower-http", + "void", +] + [[package]] name = "webrtc-ice" version = "0.9.1" @@ -6650,8 +7328,7 @@ dependencies = [ name = "webrtc-websys-tests" version = "0.1.0" dependencies = [ - "chrono", - "console_log", + "console_log 1.0.0", "fern", "futures", "getrandom 0.2.10", @@ -6896,7 +7573,7 @@ dependencies = [ "oid-registry 0.4.0", "rusticata-macros", "thiserror", - "time 0.3.23", + "time 0.3.25", ] [[package]] @@ -6915,14 +7592,14 @@ dependencies = [ "ring", "rusticata-macros", "thiserror", - "time 0.3.23", + "time 0.3.25", ] [[package]] name = "x509-parser" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab0c2f54ae1d92f4fcb99c0b7ccf0b1e3451cbd395e5f115ccbdbcb18d4f634" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" dependencies = [ "asn1-rs 0.5.2", "data-encoding", @@ -6932,7 +7609,22 @@ dependencies = [ "oid-registry 0.6.1", "rusticata-macros", "thiserror", - "time 0.3.23", + "time 0.3.25", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", ] [[package]] @@ -6949,13 +7641,19 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "yansi" +version = "1.0.0-rc" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee746ad3851dd3bc40e4a028ab3b00b99278d929e48957bcb2d111874a7e43e" + [[package]] name = "yasna" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time 0.3.23", + "time 0.3.25", ] [[package]] @@ -6975,5 +7673,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] diff --git a/Cargo.toml b/Cargo.toml index e5ecd5e2c3c..0051e7f84f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ members = [ "core", "examples/autonat", + "examples/browser-webrtc/client", + "examples/browser-webrtc/server", "examples/chat-example", "examples/dcutr", "examples/distributed-key-value-store", @@ -52,13 +54,13 @@ members = [ "transports/uds", "transports/wasm-ext", "transports/webrtc", - "transports/webrtc-websys", "transports/webrtc-utils", + "transports/webrtc-websys", "transports/websocket", "transports/webtransport-websys", - "wasm-tests/webtransport-tests", "wasm-tests/webrtc", "wasm-tests/webrtc/server", + "wasm-tests/webtransport-tests", ] resolver = "2" @@ -99,17 +101,17 @@ libp2p-tls = { version = "0.2.0", path = "transports/tls" } libp2p-uds = { version = "0.39.0", path = "transports/uds" } libp2p-wasm-ext = { version = "0.40.0", path = "transports/wasm-ext" } libp2p-webrtc = { version = "0.6.0-alpha", path = "transports/webrtc" } -libp2p-webrtc-websys = { version = "0.1.0-alpha", path = "transports/webrtc-websys" } libp2p-webrtc-utils = { version = "0.1.0", path = "transports/webrtc-utils" } +libp2p-webrtc-websys = { version = "0.1.0-alpha", path = "transports/webrtc-websys" } 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" } +multiaddr = "0.18.0" +multihash = "0.19.0" multistream-select = { version = "0.13.0", path = "misc/multistream-select" } quick-protobuf-codec = { version = "0.2.0", path = "misc/quick-protobuf-codec" } quickcheck = { package = "quickcheck-ext", path = "misc/quickcheck-ext" } rw-stream-sink = { version = "0.4.0", path = "misc/rw-stream-sink" } -multiaddr = "0.18.0" -multihash = "0.19.0" webrtc-websys-test-server = { version = "0.1.0", path = "wasm-tests/webrtc/server" } [patch.crates-io] diff --git a/examples/browser-webrtc/README.md b/examples/browser-webrtc/README.md new file mode 100644 index 00000000000..8672a684cb3 --- /dev/null +++ b/examples/browser-webrtc/README.md @@ -0,0 +1,22 @@ +# Rust-Libp2p Browser-Server WebRTC Example + +This example demonstrates how to use the `rust-libp2p` library in a browser. +It uses [Leptos](https://leptos.dev/) WebAssembly framework to display the pings from the server. + +## Running the example + +First, start the `/server`: + +```sh +cd server +cargo run +``` + +Then, start the Leptos `/client`: + +```sh +cd client +trunk serve --open +``` + +This will open the browser where you will see the browser pinging the server. Open the server console logs to see the server pinging the browser. diff --git a/examples/browser-webrtc/client/Cargo.toml b/examples/browser-webrtc/client/Cargo.toml new file mode 100644 index 00000000000..235b290c845 --- /dev/null +++ b/examples/browser-webrtc/client/Cargo.toml @@ -0,0 +1,29 @@ +[package] +authors = ["Doug Anderson "] +edition = "2021" +name = "browser-webrtc-example-client" +rust-version.workspace = true +version = "0.1.0" + +[dependencies] +async-channel = "1.9.0" +cfg-if = "0.1" +chrono = { version = "0.4.26", features = ["wasmbind"] } +console_log = { version = "0.2", optional = true } +fern = { version = "0.6.2", features = ["colored"], optional = true } +futures = "0.3.28" +leptos = { version = "0.4.8", features = ["csr", "nightly"] } +libp2p = { path = "../../../libp2p", features = [ + "ed25519", + "macros", + "ping", + "wasm-bindgen", +] } +libp2p-webrtc-websys = { workspace = true } +log = "0.4" +wasm-bindgen = "0.2.87" +wasm-bindgen-futures = "0.4.37" +web-sys = { version = "0.3.64", features = ["Response", "Window"] } + +[features] +default = ["console_log", "fern"] diff --git a/examples/browser-webrtc/client/README.md b/examples/browser-webrtc/client/README.md new file mode 100644 index 00000000000..98439bcc71f --- /dev/null +++ b/examples/browser-webrtc/client/README.md @@ -0,0 +1,5 @@ +# Use rust-libp2p in the Browser + +## Run Leptos App + +From this directory, run `trunk serve --open` diff --git a/examples/browser-webrtc/client/index.html b/examples/browser-webrtc/client/index.html new file mode 100644 index 00000000000..4d9c7fd0435 --- /dev/null +++ b/examples/browser-webrtc/client/index.html @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/browser-webrtc/client/src/lib.rs b/examples/browser-webrtc/client/src/lib.rs new file mode 100644 index 00000000000..3eccc7ad143 --- /dev/null +++ b/examples/browser-webrtc/client/src/lib.rs @@ -0,0 +1,118 @@ +use async_channel::bounded; +use leptos::*; +use pinger::start_pinger; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::spawn_local; +use wasm_bindgen_futures::JsFuture; +use web_sys::{window, Response}; + +pub mod pinger; + +// The PORT that the server serves their Multiaddr +pub const PORT: u16 = 4455; + +/// Our main Leptos App component +#[component] +pub fn App(cx: Scope) -> impl IntoView { + // create a mpsc channel to get pings to from the pinger + let (sendr, recvr) = bounded::(2); + + // create Leptos signal to update the Pings diplayed + let (number_of_pings, set_number_of_pings) = create_signal(cx, Vec::new()); + + // start the pinger, pass in our sender + spawn_local(async move { + match start_pinger(sendr).await { + Ok(_) => log::info!("Pinger finished"), + Err(e) => log::error!("Pinger error: {:?}", e), + }; + }); + + // update number of pings signal each time out receiver receives an update + spawn_local(async move { + loop { + match recvr.recv().await { + Ok(rtt) => { + // set rtt and date time stamp + let now = chrono::Utc::now().timestamp(); + log::info!("[{now:?}] Got RTT: {rtt:?} ms"); + set_number_of_pings.update(move |pings| pings.insert(0, (now, rtt))); + } + Err(e) => log::error!("Pinger channel closed: {:?}", e), + } + } + }); + + // Build our DOM HTML + view! { cx, +

"Rust Libp2p WebRTC Demo"

+

"Pinging every 15 seconds. Open Browser console for more logging details."

+
    + + { + chrono::NaiveDateTime::from_timestamp_opt(stamp, 0) + .expect("timestamp is valid") + .format("%Y-%m-%d %H:%M:%S") + .to_string() + }" in " + {rtt} "ms" + + } + } + /> +
+ } +} + +/// Helper that returns the multiaddress of echo-server +/// +/// It fetches the multiaddress via HTTP request to +/// 127.0.0.1:4455. +pub async fn fetch_server_addr() -> String { + let url = format!("http://127.0.0.1:{}/", PORT); + let window = window().expect("no global `window` exists"); + + let value = match JsFuture::from(window.fetch_with_str(&url)).await { + Ok(value) => value, + Err(err) => { + log::error!("fetch failed: {:?}", err); + return "".to_string(); + } + }; + let resp = match value.dyn_into::() { + Ok(resp) => resp, + Err(err) => { + log::error!("fetch response failed: {:?}", err); + return "".to_string(); + } + }; + + let text = match resp.text() { + Ok(text) => text, + Err(err) => { + log::error!("fetch text failed: {:?}", err); + return "".to_string(); + } + }; + let text = match JsFuture::from(text).await { + Ok(text) => text, + Err(err) => { + log::error!("convert future failed: {:?}", err); + return "".to_string(); + } + }; + + match text.as_string().filter(|s| !s.is_empty()) { + Some(text) => text, + None => { + log::error!("fetch text is empty"); + "".to_string() + } + } +} diff --git a/examples/browser-webrtc/client/src/main.rs b/examples/browser-webrtc/client/src/main.rs new file mode 100644 index 00000000000..1682545bc34 --- /dev/null +++ b/examples/browser-webrtc/client/src/main.rs @@ -0,0 +1,43 @@ +use browser_webrtc_example_client::App; +use leptos::*; + +fn main() { + match init_log() { + Ok(_) => log::info!("Logging initialized"), + Err(e) => log::error!("Error initializing logging: {:?}", e), + } + leptos::mount_to_body(|cx| view! { cx, }) +} + +// Add Logging to the Browser console +fn init_log() -> Result<(), log::SetLoggerError> { + use fern::colors::{Color, ColoredLevelConfig}; + + let colors_line = ColoredLevelConfig::new() + .error(Color::Red) + .warn(Color::Yellow) + // we actually don't need to specify the color for debug and info, they are white by default + .info(Color::Green) + .debug(Color::BrightBlack) + // depending on the terminals color scheme, this is the same as the background color + .trace(Color::White); + + let colors_level = colors_line.info(Color::Green); + + fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "{color_line}[{level} {target}{color_line}] {message}\x1B[0m", + color_line = format_args!( + "\x1B[{}m", + colors_line.get_color(&record.level()).to_fg_str() + ), + target = record.target(), + level = colors_level.color(record.level()), + message = message, + )) + }) + .level(log::LevelFilter::Trace) + .chain(fern::Output::call(console_log::log)) + .apply() +} diff --git a/examples/browser-webrtc/client/src/pinger.rs b/examples/browser-webrtc/client/src/pinger.rs new file mode 100644 index 00000000000..6b311728be2 --- /dev/null +++ b/examples/browser-webrtc/client/src/pinger.rs @@ -0,0 +1,79 @@ +use super::fetch_server_addr; +use async_channel::Sender; +use futures::StreamExt; +use libp2p::core::Multiaddr; +use libp2p::identity; +use libp2p::identity::PeerId; +use libp2p::ping; +use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmBuilder, SwarmEvent}; +use libp2p::{multiaddr, swarm}; +use std::convert::From; + +pub async fn start_pinger(sendr: Sender) -> Result<(), PingerError> { + let addr_fut = fetch_server_addr(); + + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + + let mut swarm = SwarmBuilder::with_wasm_executor( + libp2p_webrtc_websys::Transport::new(libp2p_webrtc_websys::Config::new(&local_key)).boxed(), + Behaviour { + ping: ping::Behaviour::new(ping::Config::new()), + keep_alive: keep_alive::Behaviour, + }, + local_peer_id, + ) + .build(); + + log::info!("Running pinger with peer_id: {}", swarm.local_peer_id()); + + let addr = addr_fut.await; + + log::info!("Dialing {}", addr); + + swarm.dial(addr.parse::()?)?; + + loop { + match swarm.next().await.unwrap() { + SwarmEvent::Behaviour(BehaviourEvent::Ping(ping::Event { result: Err(e), .. })) => { + log::error!("Ping failed: {:?}", e); + let _result = sendr.send(-1.).await; + } + SwarmEvent::Behaviour(BehaviourEvent::Ping(ping::Event { + peer, + result: Ok(rtt), + .. + })) => { + log::info!("Ping successful: {rtt:?}, {peer}"); + let _result = sendr.send(rtt.as_micros() as f32 / 1000.).await; + log::debug!("RTT Sent"); + } + evt => log::info!("Swarm event: {:?}", evt), + } + } +} + +#[derive(NetworkBehaviour)] +struct Behaviour { + ping: ping::Behaviour, + keep_alive: keep_alive::Behaviour, +} + +#[derive(Debug)] +pub enum PingerError { + AddrParse(std::net::AddrParseError), + MultiaddrParse(multiaddr::Error), + Dial(swarm::DialError), +} + +impl From for PingerError { + fn from(err: libp2p::multiaddr::Error) -> Self { + PingerError::MultiaddrParse(err) + } +} + +impl From for PingerError { + fn from(err: libp2p::swarm::DialError) -> Self { + PingerError::Dial(err) + } +} diff --git a/examples/browser-webrtc/server/.cargo/config.toml b/examples/browser-webrtc/server/.cargo/config.toml new file mode 100644 index 00000000000..727d5e25b25 --- /dev/null +++ b/examples/browser-webrtc/server/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +RUST_LOG = "debug,libp2p_webrtc,webrtc_sctp=off,webrtc_mdns=off,webrtc_ice=off" diff --git a/examples/browser-webrtc/server/Cargo.toml b/examples/browser-webrtc/server/Cargo.toml new file mode 100644 index 00000000000..8c6ebef2b75 --- /dev/null +++ b/examples/browser-webrtc/server/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = ["Doug Anderson "] +edition = "2021" +license = "MIT" +name = "webrtc-example-server" +version = "0.1.0" + +[[bin]] +name = "webrtc-example-server-bin" +path = "src/bin/main.rs" + +[dependencies] +anyhow = "1.0.72" +axum = "0.6.19" +env_logger = "0.10" +futures = "0.3.28" +libp2p-core = { workspace = true, features = [] } +libp2p-identify = { workspace = true } +libp2p-identity = { workspace = true, features = ["ed25519", "peerid"] } +libp2p-ping = { workspace = true } +libp2p-relay = { workspace = true } +libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } +libp2p-webrtc = { workspace = true, features = ["tokio"] } +multiaddr = { workspace = true } +rand = "0.8" +tokio = { version = "1.29", features = ["macros", "net", "rt", "signal"] } +tokio-stream = "0.1" +tokio-util = { version = "0.7", features = ["compat"] } +tower-http = { version = "0.4.0", features = ["cors"] } # axum middleware +void = "1" diff --git a/examples/browser-webrtc/server/README.md b/examples/browser-webrtc/server/README.md new file mode 100644 index 00000000000..03069cd8145 --- /dev/null +++ b/examples/browser-webrtc/server/README.md @@ -0,0 +1,8 @@ +# Websys RTC Test Server + +- Runs a Rust-Libp2p WebRTC Node +- Serves the multiaddr for testing on http://localhost:3000/ + +# Run + +From this directory, run `cargo run` diff --git a/examples/browser-webrtc/server/src/bin/main.rs b/examples/browser-webrtc/server/src/bin/main.rs new file mode 100644 index 00000000000..3e273c3e71f --- /dev/null +++ b/examples/browser-webrtc/server/src/bin/main.rs @@ -0,0 +1,15 @@ +#[tokio::main] +async fn main() -> anyhow::Result<()> { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format_timestamp_millis() + .init(); + + let remote = std::env::args() + .nth(1) + .map(|addr| addr.parse()) + .transpose()?; + + webrtc_example_server::start(remote).await?; + + Ok(()) +} diff --git a/examples/browser-webrtc/server/src/lib.rs b/examples/browser-webrtc/server/src/lib.rs new file mode 100644 index 00000000000..2c693c4aa76 --- /dev/null +++ b/examples/browser-webrtc/server/src/lib.rs @@ -0,0 +1,135 @@ +//! Builds the server, exported so that bin/main.rs can run it +use anyhow::Result; +use axum::{http::Method, routing::get, Router}; +use futures::StreamExt; +use libp2p_core::muxing::StreamMuxerBox; +use libp2p_core::Transport; +use libp2p_identity as identity; +use libp2p_ping as ping; +use libp2p_relay as relay; +use libp2p_swarm::{keep_alive, NetworkBehaviour, SwarmBuilder, SwarmEvent}; +use libp2p_webrtc as webrtc; +use multiaddr::{Multiaddr, Protocol}; +use rand::thread_rng; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::time::Duration; +use tower_http::cors::{Any, CorsLayer}; +use void::Void; + +pub const PORT: u16 = 4455; + +pub async fn start(remote: Option) -> Result<()> { + let id_keys = identity::Keypair::generate_ed25519(); + let local_peer_id = id_keys.public().to_peer_id(); + let transport = webrtc::tokio::Transport::new( + id_keys, + webrtc::tokio::Certificate::generate(&mut thread_rng())?, + ) + .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) + .boxed(); + + let behaviour = Behaviour { + relay: relay::Behaviour::new(local_peer_id, Default::default()), + ping: ping::Behaviour::new(ping::Config::new()), + keep_alive: keep_alive::Behaviour, + }; + + let mut swarm = SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build(); + + let address_webrtc = Multiaddr::from(Ipv6Addr::LOCALHOST) + .with(Protocol::Udp(0)) + .with(Protocol::WebRTCDirect); + + swarm.listen_on(address_webrtc.clone())?; + + // Dial the peer identified by the multi-address given as the second + // command-line argument, if any. + if let Some(addr) = remote { + println!("Dialing {addr}"); + swarm.dial(addr)?; + } + + loop { + tokio::select! { + evt = swarm.select_next_some() => { + match evt { + SwarmEvent::NewListenAddr { address, .. } => { + + let addr = address + .with(Protocol::P2p(*swarm.local_peer_id())) + .clone() + .to_string(); + + eprintln!("Listening on {}", addr); + + tokio::spawn(async move { + + let app = Router::new().route("/", get(|| async { addr })) + .layer( + // allow cors + CorsLayer::new() + .allow_origin(Any) + .allow_methods([Method::GET]), + ); + + axum::Server::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), PORT)) + .serve(app.into_make_service()) + .await + .unwrap(); + }); + + eprintln!("Server spawned"); + } + SwarmEvent::Behaviour(Event::Ping(ping::Event { + peer, + result: Ok(rtt), + .. + })) => { + let id = peer.to_string().to_owned(); + eprintln!("🏓 Pinged {id} ({rtt:?})") + } + evt => { + eprintln!("SwarmEvent: {:?}", evt); + }, + } + }, + _ = tokio::signal::ctrl_c() => { + break; + } + } + } + Ok(()) +} + +#[derive(NetworkBehaviour)] +#[behaviour(to_swarm = "Event", prelude = "libp2p_swarm::derive_prelude")] +struct Behaviour { + ping: ping::Behaviour, + keep_alive: keep_alive::Behaviour, + relay: relay::Behaviour, +} + +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +enum Event { + Ping(ping::Event), + Relay(relay::Event), +} + +impl From for Event { + fn from(event: ping::Event) -> Self { + Event::Ping(event) + } +} + +impl From for Event { + fn from(event: Void) -> Self { + void::unreachable(event) + } +} + +impl From for Event { + fn from(event: relay::Event) -> Self { + Event::Relay(event) + } +} From 73d392438de583dea32c92c1f6cb605b878b2940 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 9 Aug 2023 16:17:24 -0300 Subject: [PATCH 073/235] audit dependencies, rm unused --- transports/webrtc-websys/Cargo.toml | 70 ++++++++++----------- transports/webrtc-websys/src/fingerprint.rs | 1 + 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index 57d5651ec22..7270efbae8c 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -1,68 +1,66 @@ [package] -name = "libp2p-webrtc-websys" -version = "0.1.0" authors = ["Doug Anderson "] +categories = [ + "asynchronous", + "browser", + "libp2p", + "network", + "network-programming", + "networking", + "p2p", + "peer", + "peer-to-peer", + "wasm", + "web", + "webassembly", + "webrtc", +] description = "WebRTC for libp2p under WASM environment" -repository = "https://github.com/libp2p/rust-libp2p" -license = "MIT" edition = "2021" +keywords = ["libp2p", "networking", "peer-to-peer"] +license = "MIT" +name = "libp2p-webrtc-websys" +repository = "https://github.com/libp2p/rust-libp2p" rust-version = { workspace = true } -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] +version = "0.1.0-alpha" [dependencies] -async-trait = "0.1" asynchronous-codec = "0.6.2" bytes = "1" futures = "0.3" futures-timer = "3" +getrandom = { version = "0.2.9", features = ["js"] } hex = "0.4" +js-sys = { version = "0.3" } libp2p-core = { workspace = true } -libp2p-noise = { workspace = true } libp2p-identity = { workspace = true } +libp2p-noise = { workspace = true } libp2p-webrtc-utils = { workspace = true } -sha2 = "0.10.7" -multihash = { workspace = true } +log = "0.4.19" quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } -rcgen = "0.10.0" +send_wrapper = { version = "0.6.0", features = ["futures"] } serde = { version = "1.0", features = ["derive"] } +sha2 = "0.10.7" thiserror = "1" tinytemplate = "1.2" -log = "0.4.19" - -js-sys = { version = "0.3" } -getrandom = { version = "0.2.9", features = ["js"] } -regex = { version = "1.9" } -send_wrapper = { version = "0.6.0", features = ["futures"] } wasm-bindgen = { version = "0.2.87" } wasm-bindgen-futures = { version = "0.4.37" } web-sys = { version = "0.3.64", features = [ "MessageEvent", - "RtcPeerConnection", - "RtcSignalingState", - "RtcSdpType", - "RtcSessionDescription", - "RtcSessionDescriptionInit", - "RtcPeerConnectionIceEvent", - "RtcIceCandidate", - "RtcDataChannel", - "RtcDataChannelEvent", "RtcCertificate", "RtcConfiguration", + "RtcDataChannel", + "RtcDataChannelEvent", "RtcDataChannelInit", - "RtcDataChannelType", "RtcDataChannelState", - "RtcCertificateExpiration", - "RtcIceCandidate", # so we can set ufrag - "RtcIceCandidateInit", + "RtcDataChannelType", + "RtcPeerConnection", + "RtcPeerConnectionIceEvent", + "RtcSdpType", + "RtcSessionDescription", + "RtcSessionDescriptionInit", ] } [dev-dependencies] -anyhow = "1.0" hex-literal = "0.4" -libp2p-swarm = { workspace = true, features = ["macros"] } -libp2p-ping = { workspace = true } -unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } -void = "1" -quickcheck = "1.0.3" diff --git a/transports/webrtc-websys/src/fingerprint.rs b/transports/webrtc-websys/src/fingerprint.rs index 01ab4151d92..1eaeafe4a8c 100644 --- a/transports/webrtc-websys/src/fingerprint.rs +++ b/transports/webrtc-websys/src/fingerprint.rs @@ -18,6 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use libp2p_core::multihash; use sha2::Digest as _; use std::fmt; From ec5cb3815a45ba0f66a44576eeca3c9a4c62abf3 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 9 Aug 2023 16:20:37 -0300 Subject: [PATCH 074/235] correct naming: webrtc-direct --- interop-tests/src/arch.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs index 43e5b7401cb..5284a7516ce 100644 --- a/interop-tests/src/arch.rs +++ b/interop-tests/src/arch.rs @@ -167,7 +167,6 @@ pub(crate) mod wasm { use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; use libp2p::PeerId; use libp2p_webrtc_websys as webrtc_websys; - use std::net::IpAddr; use std::time::Duration; use crate::{BlpopRequest, Transport}; @@ -202,7 +201,7 @@ pub(crate) mod wasm { webrtc_websys::Transport::new(webrtc_websys::Config::new(&local_key)).boxed(), format!("/ip4/{ip}/udp/0/webrtc-direct"), )), - _ => bail!("Only webtransport and webrtc are supported with wasm"), + _ => bail!("Only webtransport and webrtc-direct are supported with wasm"), } } From a34bc3bfbdd729bb49a23354a345e4a5c483b06d Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 9 Aug 2023 16:21:42 -0300 Subject: [PATCH 075/235] update readme dialer instr --- interop-tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interop-tests/README.md b/interop-tests/README.md index 1a909655a3d..4366a1d31ab 100644 --- a/interop-tests/README.md +++ b/interop-tests/README.md @@ -26,7 +26,7 @@ Firefox is not yet supported as it doesn't support all required features yet 1. Build the wasm package: `wasm-pack build --target web` 2. Run the webtransport dialer: `redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webtransport is_dialer=true cargo run --bin wasm_ping` -3. Run the webrtc-direct dialer: `RUST_LOG=debug,hyper=off redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webrtc-direct is_dialer=true cargo run --bin wasm_ping` +3. Run the webrtc-direct dialer: `RUST_LOG=debug,hyper=off redis_addr="127.0.0.1:6379" ip="0.0.0.0" transport=webrtc-direct is_dialer=true cargo run --bin wasm_ping` with the webrtc-direct listener `RUST_LOG=debug,webrtc=off,webrtc_sctp=off redis_addr="127.0.0.1:6379" ip="::1" transport=webrtc-direct is_dialer="false" cargo run --bin native_ping` # Running all interop tests locally with Compose From 06824e80f9e833e148dcba826ec3a4f717e06ab8 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 9 Aug 2023 16:23:06 -0300 Subject: [PATCH 076/235] revert chromedriver condition for windows --- interop-tests/src/bin/wasm_ping.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/interop-tests/src/bin/wasm_ping.rs b/interop-tests/src/bin/wasm_ping.rs index eafcf5946df..517cdc9acf1 100644 --- a/interop-tests/src/bin/wasm_ping.rs +++ b/interop-tests/src/bin/wasm_ping.rs @@ -1,3 +1,4 @@ +#![allow(non_upper_case_globals)] use std::process::Stdio; use std::time::Duration; @@ -104,14 +105,7 @@ async fn open_in_browser() -> Result<(Child, WebDriver)> { // currently only the chromedriver is supported as firefox doesn't // have support yet for the certhashes - // windows needs to append `.cmd` to the command - let chromedriver = if cfg!(windows) { - "chromedriver.cmd" - } else { - "chromedriver" - }; - - let mut chrome = tokio::process::Command::new(chromedriver) + let mut chrome = tokio::process::Command::new("chromedriver") .arg("--port=45782") .stdout(Stdio::piped()) .spawn()?; From 66852d8d1c0604c536c1670c2aef6be28a9f76de Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 11 Aug 2023 09:49:15 -0300 Subject: [PATCH 077/235] mv ./stream/mod.rs to ./stream.rs --- transports/webrtc-utils/src/{stream/mod.rs => stream.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename transports/webrtc-utils/src/{stream/mod.rs => stream.rs} (100%) diff --git a/transports/webrtc-utils/src/stream/mod.rs b/transports/webrtc-utils/src/stream.rs similarity index 100% rename from transports/webrtc-utils/src/stream/mod.rs rename to transports/webrtc-utils/src/stream.rs From 56baebf193cea524297e75a70e16dced745ef484 Mon Sep 17 00:00:00 2001 From: Doug A Date: Tue, 15 Aug 2023 14:11:03 -0300 Subject: [PATCH 078/235] test server formatting --- wasm-tests/webrtc/server/Cargo.toml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm-tests/webrtc/server/Cargo.toml b/wasm-tests/webrtc/server/Cargo.toml index 79f26bb0cc1..d79ac93ebb3 100644 --- a/wasm-tests/webrtc/server/Cargo.toml +++ b/wasm-tests/webrtc/server/Cargo.toml @@ -1,28 +1,28 @@ [package] -name = "webrtc-websys-test-server" -version = "0.1.0" edition = "2021" license = "MIT" +name = "webrtc-websys-test-server" +version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axum = "0.6.19" anyhow = "1.0.72" +axum = "0.6.19" +env_logger = "0.10" futures = "0.3.28" -rand = "0.8" -void = "1" -libp2p-identity = { workspace = true, features = ["peerid", "ed25519"] } -libp2p-identify = { workspace = true } libp2p-core = { workspace = true, features = [] } -libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } -multiaddr = { workspace = true } +libp2p-identify = { workspace = true } +libp2p-identity = { workspace = true, features = ["ed25519", "peerid"] } libp2p-ping = { workspace = true } libp2p-relay = { workspace = true } -tokio = { version = "1.29", features = ["rt", "macros", "signal", "net"] } -tokio-util = { version = "0.7", features = ["compat"] } -tokio-stream = "0.1" +libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } libp2p-webrtc = { workspace = true, features = ["tokio"] } -webrtc-websys-tests = { path = "../" } # for PORT import +multiaddr = { workspace = true } +rand = "0.8" +tokio = { version = "1.29", features = ["macros", "net", "rt", "signal"] } +tokio-stream = "0.1" +tokio-util = { version = "0.7", features = ["compat"] } tower-http = { version = "0.4.0", features = ["cors"] } # axum middleware -env_logger = "0.10" +void = "1" +webrtc-websys-tests = { path = "../" } # for PORT import From 254d67b329f57cc7abaa309a4cf43034c25506b0 Mon Sep 17 00:00:00 2001 From: Doug A Date: Tue, 15 Aug 2023 14:11:22 -0300 Subject: [PATCH 079/235] logging --- wasm-tests/webrtc/Cargo.toml | 17 ++++++++--------- wasm-tests/webrtc/src/lib.rs | 7 +++---- wasm-tests/webrtc/src/logging.rs | 7 +++++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/wasm-tests/webrtc/Cargo.toml b/wasm-tests/webrtc/Cargo.toml index 0ebafe23080..52f108c540e 100644 --- a/wasm-tests/webrtc/Cargo.toml +++ b/wasm-tests/webrtc/Cargo.toml @@ -1,23 +1,22 @@ [package] -name = "webrtc-websys-tests" -version = "0.1.0" edition = "2021" license = "MIT" +name = "webrtc-websys-tests" publish = false +version = "0.1.0" [dependencies] +console_log = { version = "1.0.0", features = ["wasm-bindgen"] } +fern = { version = "0.6", features = ["colored"] } futures = "0.3.28" getrandom = { version = "0.2.9", features = ["js"] } +libp2p-core = { workspace = true } +libp2p-identity = { workspace = true } +libp2p-webrtc-websys = { workspace = true } +log = "0.4.14" multiaddr = { workspace = true } multihash = { workspace = true } 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"] } -libp2p-identity = { workspace = true } -libp2p-webrtc-websys = { workspace = true } -libp2p-core = { workspace = true } -console_log = { version = "1.0.0", features = ["wasm-bindgen"] } -log = "0.4.14" -fern = { version = "0.6", features = ["colored"] } -chrono = { version = "0.4.26", features = ["wasm-bindgen", "wasmbind"] } diff --git a/wasm-tests/webrtc/src/lib.rs b/wasm-tests/webrtc/src/lib.rs index 37cb151a79b..4d5f9261dd4 100644 --- a/wasm-tests/webrtc/src/lib.rs +++ b/wasm-tests/webrtc/src/lib.rs @@ -1,14 +1,13 @@ use libp2p_core::transport::Transport; // So we can use the Traits that come with it use libp2p_identity::Keypair; -use libp2p_webrtc_websys::{Config, Connection, Transport as WebRTCTransport}; // So we can dial the server +use libp2p_webrtc_websys::{Config, Transport as WebRTCTransport}; // So we can dial the server use multiaddr::Multiaddr; -use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; use web_sys::{window, Response}; -mod logging; +pub mod logging; wasm_bindgen_test_configure!(run_in_browser); @@ -22,7 +21,7 @@ async fn dial_webrtc_server() { let keypair = Keypair::generate_ed25519(); let mut transport = WebRTCTransport::new(Config::new(&keypair)); - let connection = match transport.dial(addr) { + let _connection = match transport.dial(addr) { Ok(fut) => fut.await.expect("dial failed"), Err(e) => panic!("dial failed: {:?}", e), }; diff --git a/wasm-tests/webrtc/src/logging.rs b/wasm-tests/webrtc/src/logging.rs index 84dd6705a3c..55df5f884d1 100644 --- a/wasm-tests/webrtc/src/logging.rs +++ b/wasm-tests/webrtc/src/logging.rs @@ -13,8 +13,11 @@ pub fn set_up_logging() { fern::Dispatch::new() .format(move |out, message, record| { out.finish(format_args!( - "[{date} {level} {target}] {message}\x1B[0m", - date = chrono::Local::now().format("%T%.3f"), + "[{stamp} {level} {target}] {message}\x1B[0m", + stamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(), target = record.target(), level = colors_level.color(record.level()), message = message, From ac238609bab9bd6ef2654daf7d047bed6644d51b Mon Sep 17 00:00:00 2001 From: Doug A Date: Tue, 15 Aug 2023 14:14:31 -0300 Subject: [PATCH 080/235] stream identifier for web-sys --- transports/webrtc-websys/src/stream/drop_listener.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/transports/webrtc-websys/src/stream/drop_listener.rs b/transports/webrtc-websys/src/stream/drop_listener.rs index c0e360110eb..db0d3cf5ad5 100644 --- a/transports/webrtc-websys/src/stream/drop_listener.rs +++ b/transports/webrtc-websys/src/stream/drop_listener.rs @@ -35,8 +35,7 @@ pub(crate) struct DropListener { impl DropListener { pub(crate) fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { - // Note: Added Pin::new() to the stream to make it compatible with the new futures version. - let substream_id = Pin::new(&stream).get_ref().stream_identifier(); + let substream_id = stream.stream_identifier(); Self { state: State::Idle { From 68d05644504b51ee54fc0e9c1deb36ec82bf4b85 Mon Sep 17 00:00:00 2001 From: Doug A Date: Tue, 15 Aug 2023 16:23:18 -0300 Subject: [PATCH 081/235] rm callback futures module --- transports/webrtc-websys/src/cbfutures.rs | 158 ---------------------- 1 file changed, 158 deletions(-) delete mode 100644 transports/webrtc-websys/src/cbfutures.rs diff --git a/transports/webrtc-websys/src/cbfutures.rs b/transports/webrtc-websys/src/cbfutures.rs deleted file mode 100644 index 1035e77860e..00000000000 --- a/transports/webrtc-websys/src/cbfutures.rs +++ /dev/null @@ -1,158 +0,0 @@ -use futures::FutureExt; -use std::cell::{Cell, RefCell}; -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{ready, Context, Poll, Waker}; - -pub struct FusedCbFuture(Option>); - -impl FusedCbFuture { - pub(crate) fn new() -> Self { - Self(None) - } - - pub(crate) fn maybe_init(&mut self, init: F) -> &mut Self - where - F: FnOnce() -> CbFuture, - { - if self.0.is_none() { - self.0 = Some(init()); - } - - self - } - - /// Checks if future is already running - pub(crate) fn is_active(&self) -> bool { - self.0.is_some() - } -} - -impl Future for FusedCbFuture { - type Output = T; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let val = ready!(self - .0 - .as_mut() - .expect("FusedCbFuture not initialized") - .poll_unpin(cx)); - - // Future finished, drop it - self.0.take(); - - Poll::Ready(val) - } -} - -#[derive(Clone, Debug)] -pub struct CbFuture(Rc>); - -struct CallbackFutureInner { - waker: Cell>, - result: RefCell>, -} - -impl std::fmt::Debug for CallbackFutureInner { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CallbackFutureInner").finish() - } -} - -impl Default for CallbackFutureInner { - fn default() -> Self { - Self { - waker: Cell::new(None), - result: RefCell::new(None), - } - } -} - -impl CbFuture { - /// New Callback Future - pub(crate) fn new() -> Self { - Self(Rc::new(CallbackFutureInner::::default())) - } - - // call this from your callback - pub(crate) fn publish(&self, result: T) { - *self.0.result.borrow_mut() = Some(result); - if let Some(w) = self.0.waker.take() { - w.wake() - }; - } - - /// Checks if future is already running - pub(crate) fn is_active(&self) -> bool { - // check if self.0.result is_some() without taking it out - // (i.e. without calling self.0.result.take()) - self.0.result.borrow().is_some() - } -} - -impl Future for CbFuture { - type Output = T; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.0.result.take() { - Some(x) => Poll::Ready(x), - None => { - self.0.waker.set(Some(cx.waker().clone())); - Poll::Pending - } - } - } -} - -/// Callback Stream -/// Implement futures::Stream for a callback function -#[derive(Clone, Debug)] -pub struct CbStream(Rc>); - -struct CallbackStreamInner { - waker: Cell>, - result: Cell>, -} - -impl std::fmt::Debug for CallbackStreamInner { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CallbackStreamInner").finish() - } -} - -impl Default for CallbackStreamInner { - fn default() -> Self { - Self { - waker: Cell::new(None), - result: Cell::new(None), - } - } -} - -impl CbStream { - /// New Callback Stream - pub(crate) fn new() -> Self { - Self(Rc::new(CallbackStreamInner::::default())) - } - - // call this from your callback - pub(crate) fn publish(&self, result: T) { - self.0.result.set(Some(result)); - if let Some(w) = self.0.waker.take() { - w.wake() - }; - } -} - -impl futures::Stream for CbStream { - type Item = T; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.0.result.take() { - Some(x) => Poll::Ready(Some(x)), - None => { - self.0.waker.set(Some(cx.waker().clone())); - Poll::Pending - } - } - } -} From 080c27ebf764937274300421ee9a62d89d0d77e1 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 16 Aug 2023 07:22:22 -0300 Subject: [PATCH 082/235] rm unused dep --- examples/browser-webrtc/server/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/browser-webrtc/server/src/lib.rs b/examples/browser-webrtc/server/src/lib.rs index 2c693c4aa76..c6e86b9e36c 100644 --- a/examples/browser-webrtc/server/src/lib.rs +++ b/examples/browser-webrtc/server/src/lib.rs @@ -12,7 +12,6 @@ use libp2p_webrtc as webrtc; use multiaddr::{Multiaddr, Protocol}; use rand::thread_rng; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -use std::time::Duration; use tower_http::cors::{Any, CorsLayer}; use void::Void; From 305dadfc071b670a667e936e7d60cf3129fde8aa Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 16 Aug 2023 09:56:41 -0300 Subject: [PATCH 083/235] mv sdp noise fngerprnt err to libp2p-webrtc-utils --- Cargo.lock | 21 +- transports/webrtc-utils/Cargo.toml | 29 +- .../src/tokio => webrtc-utils/src}/error.rs | 1 + .../src/fingerprint.rs | 17 ++ transports/webrtc-utils/src/lib.rs | 6 + .../src/upgrade => webrtc-utils/src}/noise.rs | 16 +- .../src/tokio => webrtc-utils/src}/sdp.rs | 143 +++++----- transports/webrtc-utils/src/sdp/tokio.rs | 67 +++++ transports/webrtc-utils/src/sdp/wasm.rs | 75 +++++ transports/webrtc-websys/Cargo.toml | 7 +- transports/webrtc-websys/src/error.rs | 6 +- transports/webrtc-websys/src/lib.rs | 3 - transports/webrtc-websys/src/sdp.rs | 256 ------------------ transports/webrtc-websys/src/transport.rs | 2 +- transports/webrtc-websys/src/upgrade.rs | 15 +- transports/webrtc/Cargo.toml | 22 +- transports/webrtc/src/lib.rs | 2 - transports/webrtc/src/tokio/certificate.rs | 2 +- transports/webrtc/src/tokio/connection.rs | 3 +- transports/webrtc/src/tokio/fingerprint.rs | 110 -------- transports/webrtc/src/tokio/mod.rs | 6 +- transports/webrtc/src/tokio/stream.rs | 10 +- .../webrtc/src/tokio/stream/drop_listener.rs | 5 +- .../webrtc/src/tokio/stream/framed_dc.rs | 4 +- transports/webrtc/src/tokio/transport.rs | 20 +- transports/webrtc/src/tokio/upgrade.rs | 15 +- transports/webrtc/src/tokio/upgrade/noise.rs | 108 -------- 27 files changed, 343 insertions(+), 628 deletions(-) rename transports/{webrtc/src/tokio => webrtc-utils/src}/error.rs (98%) rename transports/{webrtc-websys => webrtc-utils}/src/fingerprint.rs (89%) rename transports/{webrtc-websys/src/upgrade => webrtc-utils/src}/noise.rs (92%) rename transports/{webrtc/src/tokio => webrtc-utils/src}/sdp.rs (71%) create mode 100644 transports/webrtc-utils/src/sdp/tokio.rs create mode 100644 transports/webrtc-utils/src/sdp/wasm.rs delete mode 100644 transports/webrtc-websys/src/sdp.rs delete mode 100644 transports/webrtc/src/tokio/fingerprint.rs delete mode 100644 transports/webrtc/src/tokio/upgrade/noise.rs diff --git a/Cargo.lock b/Cargo.lock index d9eef72f081..dbd067ea33d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3779,7 +3779,6 @@ dependencies = [ "if-watch", "libp2p-core", "libp2p-identity", - "libp2p-noise", "libp2p-ping", "libp2p-swarm", "libp2p-webrtc-utils", @@ -3791,7 +3790,6 @@ dependencies = [ "rand 0.8.5", "rcgen 0.10.0", "serde", - "sha2 0.10.7", "stun", "thiserror", "tinytemplate", @@ -3807,8 +3805,23 @@ name = "libp2p-webrtc-utils" version = "0.1.0" dependencies = [ "bytes", + "futures", + "hex", + "hex-literal", + "js-sys", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "log", "quick-protobuf", "quick-protobuf-codec", + "serde", + "sha2 0.10.7", + "thiserror", + "tinytemplate", + "wasm-bindgen", + "web-sys", + "webrtc", ] [[package]] @@ -3820,21 +3833,17 @@ dependencies = [ "futures", "futures-timer", "getrandom 0.2.10", - "hex", "hex-literal", "js-sys", "libp2p-core", "libp2p-identity", - "libp2p-noise", "libp2p-webrtc-utils", "log", "quick-protobuf", "quick-protobuf-codec", "send_wrapper 0.6.0", "serde", - "sha2 0.10.7", "thiserror", - "tinytemplate", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/transports/webrtc-utils/Cargo.toml b/transports/webrtc-utils/Cargo.toml index 2404d48d806..19c3b535416 100644 --- a/transports/webrtc-utils/Cargo.toml +++ b/transports/webrtc-utils/Cargo.toml @@ -1,12 +1,37 @@ [package] -name = "libp2p-webrtc-utils" -version = "0.1.0" edition = "2021" +name = "libp2p-webrtc-utils" rust-version.workspace = true +version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bytes = "1" +futures = "0.3" +hex = "0.4" +js-sys = { version = "0.3", optional = true } +libp2p-core = { workspace = true } +libp2p-identity = { workspace = true } +libp2p-noise = { workspace = true } +log = "0.4.19" quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } +serde = { version = "1.0", features = ["derive"] } +sha2 = "0.10.7" +thiserror = "1" +tinytemplate = "1.2" +wasm-bindgen = { version = "0.2.87", optional = true } +web-sys = { version = "0.3.64", optional = true, features = [ + "RtcSdpType", + "RtcSessionDescription", + "RtcSessionDescriptionInit", +] } +webrtc = { version = "0.8.0", optional = true } + +[features] +tokio = ["dep:webrtc"] +wasm-bindgen = ["dep:js-sys", "dep:wasm-bindgen", "dep:web-sys"] + +[dev-dependencies] +hex-literal = "0.4" diff --git a/transports/webrtc/src/tokio/error.rs b/transports/webrtc-utils/src/error.rs similarity index 98% rename from transports/webrtc/src/tokio/error.rs rename to transports/webrtc-utils/src/error.rs index 1b274686ef3..da896e90476 100644 --- a/transports/webrtc/src/tokio/error.rs +++ b/transports/webrtc-utils/src/error.rs @@ -24,6 +24,7 @@ use thiserror::Error; /// Error in WebRTC. #[derive(Error, Debug)] pub enum Error { + #[cfg(feature = "tokio")] #[error(transparent)] WebRTC(#[from] webrtc::Error), #[error("IO error")] diff --git a/transports/webrtc-websys/src/fingerprint.rs b/transports/webrtc-utils/src/fingerprint.rs similarity index 89% rename from transports/webrtc-websys/src/fingerprint.rs rename to transports/webrtc-utils/src/fingerprint.rs index 1eaeafe4a8c..20febea0fc1 100644 --- a/transports/webrtc-websys/src/fingerprint.rs +++ b/transports/webrtc-utils/src/fingerprint.rs @@ -22,6 +22,10 @@ use libp2p_core::multihash; use sha2::Digest as _; use std::fmt; +// feat tokio +#[cfg(feature = "tokio")] +use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; + const SHA256: &str = "sha-256"; const MULTIHASH_SHA256_CODE: u64 = 0x12; @@ -44,6 +48,19 @@ impl Fingerprint { Fingerprint(sha2::Sha256::digest(bytes).into()) } + /// Converts [`RTCDtlsFingerprint`] to [`Fingerprint`]. + #[cfg(feature = "tokio")] + pub fn try_from_rtc_dtls(fp: &RTCDtlsFingerprint) -> Option { + if fp.algorithm != SHA256 { + return None; + } + + let mut buf = [0; 32]; + hex::decode_to_slice(fp.value.replace(':', ""), &mut buf).ok()?; + + Some(Self(buf)) + } + /// Converts [`Multihash`](multihash::Multihash) to [`Fingerprint`]. pub fn try_from_multihash(hash: Multihash) -> Option { if hash.code() != MULTIHASH_SHA256_CODE { diff --git a/transports/webrtc-utils/src/lib.rs b/transports/webrtc-utils/src/lib.rs index bbd121a6064..fe2cbf274d6 100644 --- a/transports/webrtc-utils/src/lib.rs +++ b/transports/webrtc-utils/src/lib.rs @@ -6,4 +6,10 @@ pub mod proto { pub use self::webrtc::pb::{mod_Message::Flag, Message}; } +pub mod fingerprint; +pub mod noise; +pub mod sdp; pub mod stream; +pub mod error; + +pub use error::Error; diff --git a/transports/webrtc-websys/src/upgrade/noise.rs b/transports/webrtc-utils/src/noise.rs similarity index 92% rename from transports/webrtc-websys/src/upgrade/noise.rs rename to transports/webrtc-utils/src/noise.rs index f2907b778b4..33a99ca5fb9 100644 --- a/transports/webrtc-websys/src/upgrade/noise.rs +++ b/transports/webrtc-utils/src/noise.rs @@ -19,15 +19,15 @@ // DEALINGS IN THE SOFTWARE. use super::Error; -use super::Fingerprint; +use crate::fingerprint::Fingerprint; use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use libp2p_identity as identity; use libp2p_identity::PeerId; use libp2p_noise as noise; -// TODO: Can the browser handle inbound connections? -pub(crate) async fn inbound( +/// Handle inbound connections from other browsers +pub async fn inbound( id_keys: identity::Keypair, stream: T, client_fingerprint: Fingerprint, @@ -46,7 +46,8 @@ where Ok(peer_id) } -pub(crate) async fn outbound( +/// Handle outbound connections +pub async fn outbound( id_keys: identity::Keypair, stream: T, remote_fingerprint: Fingerprint, @@ -65,7 +66,7 @@ where // noise.upgrade_inbound has into_responder(), so that's the one we need let (peer_id, mut channel) = match noise.upgrade_inbound(stream, info).await { Ok((peer_id, channel)) => (peer_id, channel), - Err(e) => return Err(Error::Noise(e)), + Err(e) => return Err(Error::Authentication(e)), }; log::trace!("Outbound noise upgrade peer_id {:?}", peer_id); @@ -74,10 +75,7 @@ where Ok(peer_id) } -pub(crate) fn noise_prologue( - local_fingerprint: Fingerprint, - remote_fingerprint: Fingerprint, -) -> Vec { +fn noise_prologue(local_fingerprint: Fingerprint, remote_fingerprint: Fingerprint) -> Vec { let local = local_fingerprint.to_multihash().to_bytes(); let remote = remote_fingerprint.to_multihash().to_bytes(); const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; diff --git a/transports/webrtc/src/tokio/sdp.rs b/transports/webrtc-utils/src/sdp.rs similarity index 71% rename from transports/webrtc/src/tokio/sdp.rs rename to transports/webrtc-utils/src/sdp.rs index d2f424e5d4e..22d6836bc3b 100644 --- a/transports/webrtc/src/tokio/sdp.rs +++ b/transports/webrtc-utils/src/sdp.rs @@ -1,3 +1,4 @@ +// Copyright 2023 Doug Anderson // Copyright 2022 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -17,42 +18,15 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#[cfg(feature = "wasm-bindgen")] +pub mod wasm; +#[cfg(feature = "tokio")] +pub mod tokio; +use crate::fingerprint::Fingerprint; use serde::Serialize; -use tinytemplate::TinyTemplate; -use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; - use std::net::{IpAddr, SocketAddr}; - -use crate::tokio::fingerprint::Fingerprint; - -/// Creates the SDP answer used by the client. -pub(crate) fn answer( - addr: SocketAddr, - server_fingerprint: &Fingerprint, - client_ufrag: &str, -) -> RTCSessionDescription { - RTCSessionDescription::answer(render_description( - SERVER_SESSION_DESCRIPTION, - addr, - server_fingerprint, - client_ufrag, - )) - .unwrap() -} - -/// Creates the SDP offer used by the server. -/// -/// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. -pub(crate) fn offer(addr: SocketAddr, client_ufrag: &str) -> RTCSessionDescription { - RTCSessionDescription::offer(render_description( - CLIENT_SESSION_DESCRIPTION, - addr, - &Fingerprint::FF, - client_ufrag, - )) - .unwrap() -} +use tinytemplate::TinyTemplate; // An SDP message that constitutes the offer. // @@ -126,22 +100,7 @@ pub(crate) fn offer(addr: SocketAddr, client_ufrag: &str) -> RTCSessionDescripti // a=max-message-size: // // The maximum SCTP user message size (in bytes). (RFC8841) -const CLIENT_SESSION_DESCRIPTION: &str = "v=0 -o=- 0 0 IN {ip_version} {target_ip} -s=- -c=IN {ip_version} {target_ip} -t=0 0 -m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel -a=mid:0 -a=ice-options:ice2 -a=ice-ufrag:{ufrag} -a=ice-pwd:{pwd} -a=fingerprint:{fingerprint_algorithm} {fingerprint_value} -a=setup:actpass -a=sctp-port:5000 -a=max-message-size:16384 -"; // See [`CLIENT_SESSION_DESCRIPTION`]. // @@ -182,25 +141,41 @@ a=max-message-size:16384 // a=end-of-candidates // // Indicate that no more candidates will ever be sent (RFC8838). -const SERVER_SESSION_DESCRIPTION: &str = "v=0 -o=- 0 0 IN {ip_version} {target_ip} -s=- -t=0 0 -a=ice-lite -m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel -c=IN {ip_version} {target_ip} -a=mid:0 -a=ice-options:ice2 -a=ice-ufrag:{ufrag} -a=ice-pwd:{pwd} -a=fingerprint:{fingerprint_algorithm} {fingerprint_value} +// const SERVER_SESSION_DESCRIPTION: &str = "v=0 +// o=- 0 0 IN {ip_version} {target_ip} +// s=- +// t=0 0 +// a=ice-lite +// m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +// c=IN {ip_version} {target_ip} +// a=mid:0 +// a=ice-options:ice2 +// a=ice-ufrag:{ufrag} +// a=ice-pwd:{pwd} +// a=fingerprint:{fingerprint_algorithm} {fingerprint_value} -a=setup:passive -a=sctp-port:5000 -a=max-message-size:16384 -a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host -a=end-of-candidates -"; +// a=setup:passive +// a=sctp-port:5000 +// a=max-message-size:16384 +// a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host +// a=end-of-candidates"; + +// Update to this: +// v=0 +// o=- 0 0 IN ${ipVersion} ${host} +// s=- +// c=IN ${ipVersion} ${host} +// t=0 0 +// a=ice-lite +// m=application ${port} UDP/DTLS/SCTP webrtc-datachannel +// a=mid:0 +// a=setup:passive +// a=ice-ufrag:${ufrag} +// a=ice-pwd:${ufrag} +// a=fingerprint:${CERTFP} +// a=sctp-port:5000 +// a=max-message-size:100000 +// a=candidate:1467250027 1 UDP 1467250027 ${host} ${port} typ host\r\n /// Indicates the IP version used in WebRTC: `IP4` or `IP6`. #[derive(Serialize)] @@ -250,3 +225,39 @@ fn render_description( }; tt.render("description", &context).unwrap() } + +/// Get Fingerprint from SDP +/// Gets the fingerprint from matching between the angle brackets: a=fingerprint: +pub fn fingerprint(sdp: &str) -> Option { + // split the sdp by new lines / carriage returns + let lines = sdp.split("\r\n"); + + // iterate through the lines to find the one starting with a=fingerprint: + // get the value after the first space + // return the value as a Fingerprint + for line in lines { + if line.starts_with("a=fingerprint:") { + let fingerprint = line.split(' ').nth(1).unwrap(); + let bytes = hex::decode(fingerprint.replace(':', "")).unwrap(); + let arr: [u8; 32] = bytes.as_slice().try_into().unwrap(); + return Some(Fingerprint::raw(arr)); + } + } + None +} + +#[cfg(test)] +mod sdp_tests { + use super::*; + + #[test] + fn test_fingerprint() { + let sdp: &str = "v=0\no=- 0 0 IN IP6 ::1\ns=-\nc=IN IP6 ::1\nt=0 0\na=ice-lite\nm=application 61885 UDP/DTLS/SCTP webrtc-datachannel\na=mid:0\na=setup:passive\na=ice-ufrag:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\na=ice-pwd:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\na=fingerprint:sha-256 A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89\na=sctp-port:5000\na=max-message-size:16384\na=candidate:1467250027 1 UDP 1467250027 ::1 61885 typ host\n"; + let fingerprint = match fingerprint(sdp) { + Some(fingerprint) => fingerprint, + None => panic!("No fingerprint found"), + }; + assert_eq!(fingerprint.algorithm(), "sha-256"); + assert_eq!(fingerprint.to_sdp_format(), "A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89"); + } +} diff --git a/transports/webrtc-utils/src/sdp/tokio.rs b/transports/webrtc-utils/src/sdp/tokio.rs new file mode 100644 index 00000000000..c01e9a7fda0 --- /dev/null +++ b/transports/webrtc-utils/src/sdp/tokio.rs @@ -0,0 +1,67 @@ +use super::*; +use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; + +/// Creates the SDP answer used by the client. +pub fn answer( + addr: SocketAddr, + server_fingerprint: &Fingerprint, + client_ufrag: &str, +) -> RTCSessionDescription { + RTCSessionDescription::answer(render_description( + SERVER_SESSION_DESCRIPTION, + addr, + server_fingerprint, + client_ufrag, + )) + .unwrap() +} + +/// Creates the SDP offer used by the server. +/// +/// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. +pub fn offer(addr: SocketAddr, client_ufrag: &str) -> RTCSessionDescription { + RTCSessionDescription::offer(render_description( + CLIENT_SESSION_DESCRIPTION, + addr, + &Fingerprint::FF, + client_ufrag, + )) + .unwrap() +} + +const CLIENT_SESSION_DESCRIPTION: &str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +c=IN {ip_version} {target_ip} +t=0 0 + +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +a=mid:0 +a=ice-options:ice2 +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} +a=setup:actpass +a=sctp-port:5000 +a=max-message-size:16384 +"; + +const SERVER_SESSION_DESCRIPTION: &str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +t=0 0 +a=ice-lite +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +c=IN {ip_version} {target_ip} +a=mid:0 +a=ice-options:ice2 +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} + +a=setup:passive +a=sctp-port:5000 +a=max-message-size:16384 +a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host +a=end-of-candidates +"; diff --git a/transports/webrtc-utils/src/sdp/wasm.rs b/transports/webrtc-utils/src/sdp/wasm.rs new file mode 100644 index 00000000000..71e0d632573 --- /dev/null +++ b/transports/webrtc-utils/src/sdp/wasm.rs @@ -0,0 +1,75 @@ +use super::*; + +use js_sys::Reflect; +use wasm_bindgen::JsValue; +use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; + +/// Creates the SDP answer used by the client. +pub fn answer( + addr: SocketAddr, + server_fingerprint: &Fingerprint, + client_ufrag: &str, +) -> RtcSessionDescriptionInit { + let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); + answer_obj.sdp(&render_description( + SESSION_DESCRIPTION, + addr, + server_fingerprint, + client_ufrag, + )); + answer_obj +} + +/// Creates the munged SDP offer from the Browser's given SDP offer +/// +/// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. +pub fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescriptionInit { + //JsValue to String + let offer = Reflect::get(&offer, &JsValue::from_str("sdp")).unwrap(); + let offer = offer.as_string().unwrap(); + + let lines = offer.split("\r\n"); + + // find line and replace a=ice-ufrag: with "\r\na=ice-ufrag:{client_ufrag}\r\n" + // find line andreplace a=ice-pwd: with "\r\na=ice-ufrag:{client_ufrag}\r\n" + + let mut munged_offer_sdp = String::new(); + + for line in lines { + if line.starts_with("a=ice-ufrag:") { + munged_offer_sdp.push_str(&format!("a=ice-ufrag:{}\r\n", client_ufrag)); + } else if line.starts_with("a=ice-pwd:") { + munged_offer_sdp.push_str(&format!("a=ice-pwd:{}\r\n", client_ufrag)); + } else if !line.is_empty() { + munged_offer_sdp.push_str(&format!("{}\r\n", line)); + } + } + + // remove any double \r\n + let munged_offer_sdp = munged_offer_sdp.replace("\r\n\r\n", "\r\n"); + + log::trace!("munged_offer_sdp: {}", munged_offer_sdp); + + // setLocalDescription + let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); + offer_obj.sdp(&munged_offer_sdp); + + offer_obj +} + +const SESSION_DESCRIPTION: &str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +c=IN {ip_version} {target_ip} +t=0 0 +a=ice-lite +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +a=mid:0 +a=setup:passive +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} +a=sctp-port:5000 +a=max-message-size:16384 +a=candidate:1467250027 1 UDP 1467250027 {target_ip} {target_port} typ host +"; diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index 7270efbae8c..d033c0fb17c 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -30,20 +30,16 @@ bytes = "1" futures = "0.3" futures-timer = "3" getrandom = { version = "0.2.9", features = ["js"] } -hex = "0.4" js-sys = { version = "0.3" } libp2p-core = { workspace = true } libp2p-identity = { workspace = true } -libp2p-noise = { workspace = true } -libp2p-webrtc-utils = { workspace = true } +libp2p-webrtc-utils = { workspace = true, features = ["wasm-bindgen"] } log = "0.4.19" quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } send_wrapper = { version = "0.6.0", features = ["futures"] } serde = { version = "1.0", features = ["derive"] } -sha2 = "0.10.7" thiserror = "1" -tinytemplate = "1.2" wasm-bindgen = { version = "0.2.87" } wasm-bindgen-futures = { version = "0.4.37" } web-sys = { version = "0.3.64", features = [ @@ -57,7 +53,6 @@ web-sys = { version = "0.3.64", features = [ "RtcDataChannelType", "RtcPeerConnection", "RtcPeerConnectionIceEvent", - "RtcSdpType", "RtcSessionDescription", "RtcSessionDescriptionInit", ] } diff --git a/transports/webrtc-websys/src/error.rs b/transports/webrtc-websys/src/error.rs index f4b0079ba98..5421eeb6bde 100644 --- a/transports/webrtc-websys/src/error.rs +++ b/transports/webrtc-websys/src/error.rs @@ -7,9 +7,9 @@ pub enum Error { #[error("Invalid multiaddr: {0}")] InvalidMultiaddr(&'static str), - /// Noise authentication failed - #[error("Noise authentication failed")] - Noise(#[from] libp2p_noise::Error), + /// Noise upgrade authentication failed + #[error("WebRTC Utilities failure")] + Utils(#[from] libp2p_webrtc_utils::Error), #[error("JavaScript error: {0}")] JsError(String), diff --git a/transports/webrtc-websys/src/lib.rs b/transports/webrtc-websys/src/lib.rs index 29888e10ec3..e3538f87a72 100644 --- a/transports/webrtc-websys/src/lib.rs +++ b/transports/webrtc-websys/src/lib.rs @@ -1,8 +1,5 @@ -mod cbfutures; mod connection; mod error; -pub(crate) mod fingerprint; -mod sdp; mod stream; mod transport; mod upgrade; diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc-websys/src/sdp.rs deleted file mode 100644 index 0a41b1ae7b8..00000000000 --- a/transports/webrtc-websys/src/sdp.rs +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::fingerprint::Fingerprint; -use js_sys::Reflect; -use serde::Serialize; -use std::net::{IpAddr, SocketAddr}; -use tinytemplate::TinyTemplate; -use wasm_bindgen::JsValue; -use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; - -/// Creates the SDP answer used by the client. -pub(crate) fn answer( - addr: SocketAddr, - server_fingerprint: &Fingerprint, - client_ufrag: &str, -) -> RtcSessionDescriptionInit { - let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); - answer_obj.sdp(&render_description( - SESSION_DESCRIPTION, - addr, - server_fingerprint, - client_ufrag, - )); - answer_obj -} - -/// Creates the SDP offer. -/// -/// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. -pub(crate) fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescriptionInit { - //JsValue to String - let offer = Reflect::get(&offer, &JsValue::from_str("sdp")).unwrap(); - let offer = offer.as_string().unwrap(); - - let lines = offer.split("\r\n"); - - // find line and replace a=ice-ufrag: with "\r\na=ice-ufrag:{client_ufrag}\r\n" - // find line andreplace a=ice-pwd: with "\r\na=ice-ufrag:{client_ufrag}\r\n" - - let mut munged_offer_sdp = String::new(); - - for line in lines { - if line.starts_with("a=ice-ufrag:") { - munged_offer_sdp.push_str(&format!("a=ice-ufrag:{}\r\n", client_ufrag)); - } else if line.starts_with("a=ice-pwd:") { - munged_offer_sdp.push_str(&format!("a=ice-pwd:{}\r\n", client_ufrag)); - } else if !line.is_empty() { - munged_offer_sdp.push_str(&format!("{}\r\n", line)); - } - } - - // remove any double \r\n - let munged_offer_sdp = munged_offer_sdp.replace("\r\n\r\n", "\r\n"); - - log::trace!("munged_offer_sdp: {}", munged_offer_sdp); - - // setLocalDescription - let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); - offer_obj.sdp(&munged_offer_sdp); - - offer_obj -} - -// See [`CLIENT_SESSION_DESCRIPTION`]. -// -// a=ice-lite -// -// A lite implementation is only appropriate for devices that will *always* be connected to -// the public Internet and have a public IP address at which it can receive packets from any -// correspondent. ICE will not function when a lite implementation is placed behind a NAT -// (RFC8445). -// -// a=tls-id: -// -// "TLS ID" uniquely identifies a TLS association. -// The ICE protocol uses a "TLS ID" system to indicate whether a fresh DTLS connection -// must be reopened in case of ICE renegotiation. Considering that ICE renegotiations -// never happen in our use case, we can simply put a random value and not care about -// it. Note however that the TLS ID in the answer must be present if and only if the -// offer contains one. (RFC8842) -// TODO: is it true that renegotiations never happen? what about a connection closing? -// "tls-id" attribute MUST be present in the initial offer and respective answer (RFC8839). -// XXX: but right now browsers don't send it. -// -// a=setup:passive -// -// "passive" indicates that the remote DTLS server will only listen for incoming -// connections. (RFC5763) -// The answerer (server) MUST not be located behind a NAT (RFC6135). -// -// The answerer MUST use either a setup attribute value of setup:active or setup:passive. -// Note that if the answerer uses setup:passive, then the DTLS handshake will not begin until -// the answerer is received, which adds additional latency. setup:active allows the answer and -// the DTLS handshake to occur in parallel. Thus, setup:active is RECOMMENDED. -// -// a=candidate: -// -// A transport address for a candidate that can be used for connectivity checks (RFC8839). -// -// a=end-of-candidates -// -// Indicate that no more candidates will ever be sent (RFC8838). -// const SERVER_SESSION_DESCRIPTION: &str = "v=0 -// o=- 0 0 IN {ip_version} {target_ip} -// s=- -// t=0 0 -// a=ice-lite -// m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel -// c=IN {ip_version} {target_ip} -// a=mid:0 -// a=ice-options:ice2 -// a=ice-ufrag:{ufrag} -// a=ice-pwd:{pwd} -// a=fingerprint:{fingerprint_algorithm} {fingerprint_value} - -// a=setup:passive -// a=sctp-port:5000 -// a=max-message-size:16384 -// a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host -// a=end-of-candidates"; - -// Update to this: -// v=0 -// o=- 0 0 IN ${ipVersion} ${host} -// s=- -// c=IN ${ipVersion} ${host} -// t=0 0 -// a=ice-lite -// m=application ${port} UDP/DTLS/SCTP webrtc-datachannel -// a=mid:0 -// a=setup:passive -// a=ice-ufrag:${ufrag} -// a=ice-pwd:${ufrag} -// a=fingerprint:${CERTFP} -// a=sctp-port:5000 -// a=max-message-size:100000 -// a=candidate:1467250027 1 UDP 1467250027 ${host} ${port} typ host\r\n -const SESSION_DESCRIPTION: &str = "v=0 -o=- 0 0 IN {ip_version} {target_ip} -s=- -c=IN {ip_version} {target_ip} -t=0 0 -a=ice-lite -m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel -a=mid:0 -a=setup:passive -a=ice-ufrag:{ufrag} -a=ice-pwd:{pwd} -a=fingerprint:{fingerprint_algorithm} {fingerprint_value} -a=sctp-port:5000 -a=max-message-size:16384 -a=candidate:1467250027 1 UDP 1467250027 {target_ip} {target_port} typ host -"; - -/// Indicates the IP version used in WebRTC: `IP4` or `IP6`. -#[derive(Serialize)] -enum IpVersion { - IP4, - IP6, -} - -/// Context passed to the templating engine, which replaces the above placeholders (e.g. -/// `{IP_VERSION}`) with real values. -#[derive(Serialize)] -struct DescriptionContext { - pub(crate) ip_version: IpVersion, - pub(crate) target_ip: IpAddr, - pub(crate) target_port: u16, - pub(crate) fingerprint_algorithm: String, - pub(crate) fingerprint_value: String, - pub(crate) ufrag: String, - pub(crate) pwd: String, -} - -/// Renders a [`TinyTemplate`] description using the provided arguments. -fn render_description( - description: &str, - addr: SocketAddr, - fingerprint: &Fingerprint, - ufrag: &str, -) -> String { - let mut tt = TinyTemplate::new(); - tt.add_template("description", description).unwrap(); - - let context = DescriptionContext { - ip_version: { - if addr.is_ipv4() { - IpVersion::IP4 - } else { - IpVersion::IP6 - } - }, - target_ip: addr.ip(), - target_port: addr.port(), - fingerprint_algorithm: fingerprint.algorithm(), - fingerprint_value: fingerprint.to_sdp_format(), - // NOTE: ufrag is equal to pwd. - ufrag: ufrag.to_owned(), - pwd: ufrag.to_owned(), - }; - tt.render("description", &context).unwrap() -} - -/// Get Fingerprint from SDP -/// Gets the fingerprint from matching between the angle brackets: a=fingerprint: -pub fn fingerprint(sdp: &str) -> Option { - // split the sdp by new lines / carriage returns - let lines = sdp.split("\r\n"); - - // iterate through the lines to find the one starting with a=fingerprint: - // get the value after the first space - // return the value as a Fingerprint - for line in lines { - if line.starts_with("a=fingerprint:") { - let fingerprint = line.split(' ').nth(1).unwrap(); - let bytes = hex::decode(fingerprint.replace(':', "")).unwrap(); - let arr: [u8; 32] = bytes.as_slice().try_into().unwrap(); - return Some(Fingerprint::raw(arr)); - } - } - None -} - -#[cfg(test)] -mod sdp_tests { - use super::*; - - #[test] - fn test_fingerprint() { - let sdp: &str = "v=0\no=- 0 0 IN IP6 ::1\ns=-\nc=IN IP6 ::1\nt=0 0\na=ice-lite\nm=application 61885 UDP/DTLS/SCTP webrtc-datachannel\na=mid:0\na=setup:passive\na=ice-ufrag:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\na=ice-pwd:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\na=fingerprint:sha-256 A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89\na=sctp-port:5000\na=max-message-size:16384\na=candidate:1467250027 1 UDP 1467250027 ::1 61885 typ host\n"; - let fingerprint = match fingerprint(sdp) { - Some(fingerprint) => fingerprint, - None => panic!("No fingerprint found"), - }; - assert_eq!(fingerprint.algorithm(), "sha-256"); - assert_eq!(fingerprint.to_sdp_format(), "A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89"); - } -} diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs index 34d8b4f3e6d..ecba15cf67a 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc-websys/src/transport.rs @@ -1,4 +1,3 @@ -use super::fingerprint::Fingerprint; use super::upgrade; use super::Connection; use super::Error; @@ -7,6 +6,7 @@ use libp2p_core::multiaddr::{Multiaddr, Protocol}; use libp2p_core::muxing::StreamMuxerBox; use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; use libp2p_identity::{Keypair, PeerId}; +use libp2p_webrtc_utils::fingerprint::Fingerprint; use std::future::Future; use std::net::{IpAddr, SocketAddr}; use std::pin::Pin; diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index 92bdc9a8836..b2cb3ee0f96 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -1,13 +1,12 @@ -pub(crate) mod noise; - -use crate::stream::RtcDataChannelBuilder; - -pub(crate) use super::fingerprint::Fingerprint; use super::stream::Stream; use super::Error; -use super::{sdp, Connection}; +use crate::stream::RtcDataChannelBuilder; +use crate::Connection; use js_sys::{Object, Reflect}; use libp2p_identity::{Keypair, PeerId}; +use libp2p_webrtc_utils::fingerprint::Fingerprint; +use libp2p_webrtc_utils::noise; +use libp2p_webrtc_utils::sdp; use send_wrapper::SendWrapper; use std::net::SocketAddr; use wasm_bindgen_futures::JsFuture; @@ -67,7 +66,7 @@ async fn outbound_inner( * OFFER */ let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send - let offer_obj = sdp::offer(offer, &ufrag); + let offer_obj = sdp::wasm::offer(offer, &ufrag); log::trace!("Offer SDP: {:?}", offer_obj); let sld_promise = peer_connection.set_local_description(&offer_obj); JsFuture::from(sld_promise) @@ -78,7 +77,7 @@ async fn outbound_inner( * ANSWER */ // TODO: Update SDP Answer format for Browser WebRTC - let answer_obj = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); + let answer_obj = sdp::wasm::answer(sock_addr, &remote_fingerprint, &ufrag); log::trace!("Answer SDP: {:?}", answer_obj); let srd_promise = peer_connection.set_remote_description(&answer_obj); JsFuture::from(srd_promise) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 31a9c0fde98..9beca4f5c4a 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "libp2p-webrtc" -version = "0.6.0-alpha" authors = ["Parity Technologies "] +categories = ["asynchronous", "network-programming"] description = "WebRTC transport for libp2p" -repository = "https://github.com/libp2p/rust-libp2p" -license = "MIT" edition = "2021" +keywords = ["libp2p", "networking", "peer-to-peer"] +license = "MIT" +name = "libp2p-webrtc" +repository = "https://github.com/libp2p/rust-libp2p" rust-version = { workspace = true } -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] +version = "0.6.0-alpha" [dependencies] async-trait = "0.1" @@ -19,11 +19,9 @@ futures-timer = "3" hex = "0.4" if-watch = "3.0" libp2p-core = { workspace = true } -libp2p-noise = { workspace = true } libp2p-identity = { workspace = true } -libp2p-webrtc-utils = { workspace = true } +libp2p-webrtc-utils = { workspace = true, features = ["tokio"] } log = "0.4" -sha2 = "0.10.7" multihash = { workspace = true } quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } @@ -38,19 +36,19 @@ tokio-util = { version = "0.7", features = ["compat"], optional = true } webrtc = { version = "0.8.0", optional = true } [features] -tokio = ["dep:tokio", "dep:tokio-util", "dep:webrtc", "if-watch/tokio"] pem = ["webrtc?/pem"] +tokio = ["dep:tokio", "dep:tokio-util", "dep:webrtc", "if-watch/tokio"] [dev-dependencies] anyhow = "1.0" env_logger = "0.10" hex-literal = "0.4" -libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } libp2p-ping = { workspace = true } +libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } +quickcheck = "1.0.3" tokio = { version = "1.29", features = ["full"] } unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } void = "1" -quickcheck = "1.0.3" [[test]] name = "smoke" diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 49bdd8993a4..f71203cc2f7 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -81,5 +81,3 @@ //! certificate verification off. #[cfg(feature = "tokio")] pub mod tokio; - -use libp2p_webrtc_utils as utils; diff --git a/transports/webrtc/src/tokio/certificate.rs b/transports/webrtc/src/tokio/certificate.rs index 748cfdb6ffd..a6a105a21d8 100644 --- a/transports/webrtc/src/tokio/certificate.rs +++ b/transports/webrtc/src/tokio/certificate.rs @@ -21,7 +21,7 @@ use rand::{distributions::DistString, CryptoRng, Rng}; use webrtc::peer_connection::certificate::RTCCertificate; -use crate::tokio::fingerprint::Fingerprint; +use libp2p_webrtc_utils::fingerprint::Fingerprint; #[derive(Debug, Clone, PartialEq)] pub struct Certificate { diff --git a/transports/webrtc/src/tokio/connection.rs b/transports/webrtc/src/tokio/connection.rs index 830ed4144ad..75c5c6ff6fd 100644 --- a/transports/webrtc/src/tokio/connection.rs +++ b/transports/webrtc/src/tokio/connection.rs @@ -40,7 +40,8 @@ use std::{ task::{Context, Poll}, }; -use crate::tokio::{error::Error, stream, stream::Substream}; +use super::Error; +use crate::tokio::{stream, stream::Substream}; /// Maximum number of unprocessed data channels. /// See [`Connection::poll_inbound`]. diff --git a/transports/webrtc/src/tokio/fingerprint.rs b/transports/webrtc/src/tokio/fingerprint.rs deleted file mode 100644 index c3d58d64e72..00000000000 --- a/transports/webrtc/src/tokio/fingerprint.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use sha2::Digest as _; -use std::fmt; -use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; - -const SHA256: &str = "sha-256"; -const MULTIHASH_SHA256_CODE: u64 = 0x12; - -type Multihash = multihash::Multihash<64>; - -/// A certificate fingerprint that is assumed to be created using the SHA256 hash algorithm. -#[derive(Eq, PartialEq, Copy, Clone)] -pub struct Fingerprint([u8; 32]); - -impl Fingerprint { - pub(crate) const FF: Fingerprint = Fingerprint([0xFF; 32]); - - #[cfg(test)] - pub fn raw(bytes: [u8; 32]) -> Self { - Self(bytes) - } - - /// Creates a fingerprint from a raw certificate. - pub fn from_certificate(bytes: &[u8]) -> Self { - Fingerprint(sha2::Sha256::digest(bytes).into()) - } - - /// Converts [`RTCDtlsFingerprint`] to [`Fingerprint`]. - pub fn try_from_rtc_dtls(fp: &RTCDtlsFingerprint) -> Option { - if fp.algorithm != SHA256 { - return None; - } - - let mut buf = [0; 32]; - hex::decode_to_slice(fp.value.replace(':', ""), &mut buf).ok()?; - - Some(Self(buf)) - } - - /// Converts [`Multihash`](multihash::Multihash) to [`Fingerprint`]. - pub fn try_from_multihash(hash: Multihash) -> Option { - if hash.code() != MULTIHASH_SHA256_CODE { - // Only support SHA256 for now. - return None; - } - - let bytes = hash.digest().try_into().ok()?; - - Some(Self(bytes)) - } - - /// Converts this fingerprint to [`Multihash`](multihash::Multihash). - pub fn to_multihash(self) -> Multihash { - Multihash::wrap(MULTIHASH_SHA256_CODE, &self.0).expect("fingerprint's len to be 32 bytes") - } - - /// Formats this fingerprint as uppercase hex, separated by colons (`:`). - /// - /// This is the format described in . - pub fn to_sdp_format(self) -> String { - self.0.map(|byte| format!("{byte:02X}")).join(":") - } - - /// Returns the algorithm used (e.g. "sha-256"). - /// See - pub fn algorithm(&self) -> String { - SHA256.to_owned() - } -} - -impl fmt::Debug for Fingerprint { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&hex::encode(self.0)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn sdp_format() { - let fp = Fingerprint::raw(hex_literal::hex!( - "7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC" - )); - - let sdp_format = fp.to_sdp_format(); - - assert_eq!(sdp_format, "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC") - } -} diff --git a/transports/webrtc/src/tokio/mod.rs b/transports/webrtc/src/tokio/mod.rs index 4f2c0dd9116..a82f2098ffe 100644 --- a/transports/webrtc/src/tokio/mod.rs +++ b/transports/webrtc/src/tokio/mod.rs @@ -20,10 +20,7 @@ pub mod certificate; mod connection; -mod error; -mod fingerprint; mod req_res_chan; -mod sdp; mod stream; mod transport; mod udp_mux; @@ -31,6 +28,5 @@ mod upgrade; pub use certificate::Certificate; pub use connection::Connection; -pub use error::Error; -pub use fingerprint::Fingerprint; +pub use libp2p_webrtc_utils::error::Error; pub use transport::Transport; diff --git a/transports/webrtc/src/tokio/stream.rs b/transports/webrtc/src/tokio/stream.rs index 3839dd60620..3017d58d360 100644 --- a/transports/webrtc/src/tokio/stream.rs +++ b/transports/webrtc/src/tokio/stream.rs @@ -32,8 +32,8 @@ use std::{ }; use crate::tokio::stream::{drop_listener::GracefullyClosed, framed_dc::FramedDc}; -use crate::utils::proto::{Flag, Message}; -use crate::utils::stream::{ +use libp2p_webrtc_utils::proto::{Flag, Message}; +use libp2p_webrtc_utils::stream::{ state::{Closing, State}, MAX_DATA_LEN, }; @@ -238,7 +238,7 @@ fn io_poll_next( #[cfg(test)] mod tests { - use crate::utils::stream::{MAX_MSG_LEN, PROTO_OVERHEAD, VARINT_LEN}; + use libp2p_webrtc_utils::stream::{MAX_MSG_LEN, PROTO_OVERHEAD, VARINT_LEN}; use super::*; use asynchronous_codec::Encoder; @@ -251,8 +251,8 @@ mod tests { // Largest possible message. let message = [0; MAX_DATA_LEN]; - let protobuf = crate::utils::proto::Message { - flag: Some(crate::utils::proto::Flag::FIN), + let protobuf = libp2p_webrtc_utils::proto::Message { + flag: Some(libp2p_webrtc_utils::proto::Flag::FIN), message: Some(message.to_vec()), }; diff --git a/transports/webrtc/src/tokio/stream/drop_listener.rs b/transports/webrtc/src/tokio/stream/drop_listener.rs index 9b35a3db054..da5b6f852c4 100644 --- a/transports/webrtc/src/tokio/stream/drop_listener.rs +++ b/transports/webrtc/src/tokio/stream/drop_listener.rs @@ -18,6 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use crate::tokio::stream::framed_dc::FramedDc; + use futures::channel::oneshot; use futures::channel::oneshot::Canceled; use futures::{FutureExt, SinkExt}; @@ -27,8 +29,7 @@ use std::io; use std::pin::Pin; use std::task::{Context, Poll}; -use crate::tokio::stream::framed_dc::FramedDc; -use crate::utils::proto::{Flag, Message}; +use libp2p_webrtc_utils::proto::{Flag, Message}; #[must_use] pub(crate) struct DropListener { diff --git a/transports/webrtc/src/tokio/stream/framed_dc.rs b/transports/webrtc/src/tokio/stream/framed_dc.rs index 242890740a1..ee0e8baad50 100644 --- a/transports/webrtc/src/tokio/stream/framed_dc.rs +++ b/transports/webrtc/src/tokio/stream/framed_dc.rs @@ -25,8 +25,8 @@ use webrtc::data::data_channel::{DataChannel, PollDataChannel}; use std::sync::Arc; -use crate::utils::proto::Message; -use crate::utils::stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; +use libp2p_webrtc_utils::proto::Message; +use libp2p_webrtc_utils::stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; pub(crate) type FramedDc = Framed, quick_protobuf_codec::Codec>; pub(crate) fn new(data_channel: Arc) -> FramedDc { diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index faac75b24d5..e58d9db1d5a 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -18,6 +18,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use super::Error; +use crate::tokio::{ + certificate::Certificate, + connection::Connection, + udp_mux::{UDPMuxEvent, UDPMuxNewAddr}, + upgrade, +}; use futures::{future::BoxFuture, prelude::*, stream::SelectAll, stream::Stream}; use if_watch::{tokio::IfWatcher, IfEvent}; use libp2p_core::{ @@ -26,8 +33,7 @@ use libp2p_core::{ }; use libp2p_identity as identity; use libp2p_identity::PeerId; -use webrtc::peer_connection::configuration::RTCConfiguration; - +use libp2p_webrtc_utils::fingerprint::Fingerprint; use std::net::IpAddr; use std::{ io, @@ -35,15 +41,7 @@ use std::{ pin::Pin, task::{Context, Poll, Waker}, }; - -use crate::tokio::{ - certificate::Certificate, - connection::Connection, - error::Error, - fingerprint::Fingerprint, - udp_mux::{UDPMuxEvent, UDPMuxNewAddr}, - upgrade, -}; +use webrtc::peer_connection::configuration::RTCConfiguration; /// A WebRTC transport with direct p2p communication (without a STUN server). pub struct Transport { diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index aa0d6fc2c53..b176440df61 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -17,16 +17,17 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. - -mod noise; - +use super::Error; +use crate::tokio::{stream::Substream, Connection}; use futures::channel::oneshot; use futures::future::Either; use futures_timer::Delay; use libp2p_identity as identity; use libp2p_identity::PeerId; +use libp2p_webrtc_utils::{fingerprint::Fingerprint, noise, sdp}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; +use std::{net::SocketAddr, sync::Arc, time::Duration}; use webrtc::api::setting_engine::SettingEngine; use webrtc::api::APIBuilder; use webrtc::data::data_channel::DataChannel; @@ -38,10 +39,6 @@ use webrtc::ice::udp_network::UDPNetwork; use webrtc::peer_connection::configuration::RTCConfiguration; use webrtc::peer_connection::RTCPeerConnection; -use std::{net::SocketAddr, sync::Arc, time::Duration}; - -use crate::tokio::{error::Error, fingerprint::Fingerprint, sdp, stream::Substream, Connection}; - /// Creates a new outbound WebRTC connection. pub(crate) async fn outbound( addr: SocketAddr, @@ -59,7 +56,7 @@ pub(crate) async fn outbound( log::debug!("created SDP offer for outbound connection: {:?}", offer.sdp); peer_connection.set_local_description(offer).await?; - let answer = sdp::answer(addr, &server_fingerprint, &ufrag); + let answer = sdp::tokio::answer(addr, &server_fingerprint, &ufrag); log::debug!( "calculated SDP answer for outbound connection: {:?}", answer @@ -91,7 +88,7 @@ pub(crate) async fn inbound( let peer_connection = new_inbound_connection(addr, config, udp_mux, &remote_ufrag).await?; - let offer = sdp::offer(addr, &remote_ufrag); + let offer = sdp::tokio::offer(addr, &remote_ufrag); log::debug!("calculated SDP offer for inbound connection: {:?}", offer); peer_connection.set_remote_description(offer).await?; diff --git a/transports/webrtc/src/tokio/upgrade/noise.rs b/transports/webrtc/src/tokio/upgrade/noise.rs deleted file mode 100644 index 34e3526a2fe..00000000000 --- a/transports/webrtc/src/tokio/upgrade/noise.rs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; -use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; -use libp2p_identity as identity; -use libp2p_identity::PeerId; -use libp2p_noise as noise; - -use crate::tokio::fingerprint::Fingerprint; -use crate::tokio::Error; - -pub(crate) async fn inbound( - id_keys: identity::Keypair, - stream: T, - client_fingerprint: Fingerprint, - server_fingerprint: Fingerprint, -) -> Result -where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - let noise = noise::Config::new(&id_keys) - .unwrap() - .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); - let info = noise.protocol_info().next().unwrap(); - // Note the roles are reversed because it allows the server (webrtc connection responder) to - // send application data 0.5 RTT earlier. - let (peer_id, mut channel) = noise.upgrade_outbound(stream, info).await?; - - channel.close().await?; - - Ok(peer_id) -} - -pub(crate) async fn outbound( - id_keys: identity::Keypair, - stream: T, - server_fingerprint: Fingerprint, - client_fingerprint: Fingerprint, -) -> Result -where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - let noise = noise::Config::new(&id_keys) - .unwrap() - .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); - let info = noise.protocol_info().next().unwrap(); - // Note the roles are reversed because it allows the server (webrtc connection responder) to - // send application data 0.5 RTT earlier. - let (peer_id, mut channel) = noise.upgrade_inbound(stream, info).await?; - - channel.close().await?; - - Ok(peer_id) -} - -pub(crate) fn noise_prologue( - client_fingerprint: Fingerprint, - server_fingerprint: Fingerprint, -) -> Vec { - let client = client_fingerprint.to_multihash().to_bytes(); - let server = server_fingerprint.to_multihash().to_bytes(); - const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; - let mut out = Vec::with_capacity(PREFIX.len() + client.len() + server.len()); - out.extend_from_slice(PREFIX); - out.extend_from_slice(&client); - out.extend_from_slice(&server); - out -} - -#[cfg(test)] -mod tests { - use super::*; - use hex_literal::hex; - - #[test] - fn noise_prologue_tests() { - let a = Fingerprint::raw(hex!( - "3e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870" - )); - let b = Fingerprint::raw(hex!( - "30fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99" - )); - - let prologue1 = noise_prologue(a, b); - let prologue2 = noise_prologue(b, a); - - assert_eq!(hex::encode(prologue1), "6c69627032702d7765627274632d6e6f6973653a12203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); - assert_eq!(hex::encode(prologue2), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); - } -} From de6098d5a2e77ea29af7253bf3bee6e294ce7084 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 16 Aug 2023 19:19:57 -0300 Subject: [PATCH 084/235] bump deps --- Cargo.lock | 7 +++--- interop-tests/Cargo.toml | 41 ++++++++++++++++++++++++++---------- transports/webrtc/Cargo.toml | 4 ++-- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dbd067ea33d..10e1d8324ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6379,11 +6379,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", @@ -6392,7 +6391,7 @@ dependencies = [ "parking_lot", "pin-project-lite 0.2.11", "signal-hook-registry", - "socket2 0.4.9", + "socket2 0.5.3", "tokio-macros", "windows-sys", ] diff --git a/interop-tests/Cargo.toml b/interop-tests/Cargo.toml index 85e77119a7a..b876d461495 100644 --- a/interop-tests/Cargo.toml +++ b/interop-tests/Cargo.toml @@ -1,9 +1,9 @@ [package] edition = "2021" +license = "MIT" name = "interop-tests" -version = "0.1.0" publish = false -license = "MIT" +version = "0.1.0" [lib] crate-type = ["cdylib", "rlib"] @@ -14,17 +14,30 @@ either = "1.9.0" env_logger = "0.10.0" futures = "0.3.28" log = "0.4" -serde = { version = "1", features = ["derive"] } rand = "0.8.5" +serde = { version = "1", features = ["derive"] } [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 = { path = "../libp2p", features = [ + "dns", + "macros", + "noise", + "ping", + "rsa", + "tcp", + "tls", + "tokio", + "websocket", + "yamux", +] } +libp2p-mplex = { path = "../muxers/mplex" } libp2p-quic = { workspace = true, features = ["tokio"] } libp2p-webrtc = { workspace = true, features = ["tokio"] } -libp2p-mplex = { path = "../muxers/mplex" } mime_guess = "2.0" -redis = { version = "0.23.0", default-features = false, features = ["tokio-comp"] } +redis = { version = "0.23.0", default-features = false, features = [ + "tokio-comp", +] } rust-embed = "6.8" serde_json = "1" thirtyfour = "=0.32.0-rc.8" # https://github.com/stevepryde/thirtyfour/issues/169 @@ -34,12 +47,18 @@ 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"] } +console_error_panic_hook = { version = "0.1.7" } +futures-timer = "3.0.2" +instant = "0.1.12" +libp2p = { path = "../libp2p", features = [ + "identify", + "macros", + "ping", + "wasm-bindgen", + "webtransport-websys", +] } libp2p-webrtc-websys = { workspace = true } +reqwest = { version = "0.11", features = ["json"] } 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/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 9beca4f5c4a..b0a96a036b3 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -31,7 +31,7 @@ serde = { version = "1.0", features = ["derive"] } stun = "0.4" thiserror = "1" tinytemplate = "1.2" -tokio = { version = "1.29", features = ["net"], optional = true } +tokio = { version = "1.31", features = ["net"], optional = true } tokio-util = { version = "0.7", features = ["compat"], optional = true } webrtc = { version = "0.8.0", optional = true } @@ -46,7 +46,7 @@ hex-literal = "0.4" libp2p-ping = { workspace = true } libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } quickcheck = "1.0.3" -tokio = { version = "1.29", features = ["full"] } +tokio = { version = "1.31", features = ["full"] } unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } void = "1" From e00166a3e3a4abb33c17e2968e5fdf647a100d62 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 16 Aug 2023 19:20:07 -0300 Subject: [PATCH 085/235] update ROADMAP --- ROADMAP.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index cb523b5f7cd..17d5610da15 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,7 +11,7 @@ roadmap](https://github.com/libp2p/specs/blob/master/ROADMAP.md). ## QUIC - evaluate and move to quinn | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|---------------------------------------------------|--------------|------------| +| ------------ | ------ | ----------------- | ------------------------------------------------- | ------------ | ---------- | | Connectivity | todo | Q3/2023 | https://github.com/libp2p/rust-libp2p/issues/2883 | | | We added alpha support for QUIC in Q4/2022 wrapping `quinn-proto`. Evaluate using `quinn` directly, replacing the wrapper. @@ -19,7 +19,7 @@ We added alpha support for QUIC in Q4/2022 wrapping `quinn-proto`. Evaluate usin ## Attempt to switch from webrtc-rs to str0m | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|---------------------------------------------------|--------------|------------| +| ------------ | ------ | ----------------- | ------------------------------------------------- | ------------ | ---------- | | Connectivity | todo | | https://github.com/libp2p/rust-libp2p/issues/3659 | | | Reduce maintenance burden and reduce dependency footprint. @@ -27,7 +27,7 @@ Reduce maintenance burden and reduce dependency footprint. ## Address pipeline | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|----------|--------------|------------| +| ------------ | ------ | ----------------- | -------- | ------------ | ---------- | | Connectivity | todo | Q4/2023 | | AutoNATv2 | AutoNATv2 | Be smart on address prioritization. go-libp2p made a lot of progress here. Lots to learn. See https://github.com/libp2p/go-libp2p/issues/2229 and https://github.com/libp2p/rust-libp2p/issues/1896#issuecomment-1537774383. @@ -35,7 +35,7 @@ Be smart on address prioritization. go-libp2p made a lot of progress here. Lots ## AutoNATv2 | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|----------|------------------|------------------| +| ------------ | ------ | ----------------- | -------- | ---------------- | ---------------- | | Connectivity | todo | Q4/2023 | | Address pipeline | Address pipeline | Implement the new AutoNAT v2 specification. See https://github.com/libp2p/specs/pull/538. @@ -43,7 +43,7 @@ Implement the new AutoNAT v2 specification. See https://github.com/libp2p/specs/ ## Optimize Hole punching | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|----------|--------------|------------| +| ------------ | ------ | ----------------- | -------- | ------------ | ---------- | | Optimization | todo | | | | | We released hole punching support with [rust-libp2p @@ -58,7 +58,7 @@ hole punching stack. ## Improved Wasm support | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|----------------------|--------|-------------------|---------------------------------------------------|--------------|----------------------------------------------| +| -------------------- | ------ | ----------------- | ------------------------------------------------- | ------------ | -------------------------------------------- | | Developer ergonomics | todo | | https://github.com/libp2p/rust-libp2p/issues/2617 | | [WebRTC](#webrtc-support-browser-to-browser) | The project supports Wasm already today, though the developer experience is cumbersome at best. @@ -69,9 +69,9 @@ argue that that demand follows this roadmap item and not the other way round.) ## WebRTC in the browser via WASM -| Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|--------------------------------------------|-------------------------------------------------------------------------------------------|------------| -| Connectivity | todo | | https://github.com/libp2p/specs/issues/475 | [Improved WASM support](#improved-wasm-support), https://github.com/libp2p/specs/pull/497 | https://github.com/libp2p/rust-libp2p/pull/4248 | +| Category | Status | Target Completion | Tracking | Dependencies | Dependents | +| ------------ | ----------- | ----------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------- | ----------------------------------------------- | +| Connectivity | In progress | | https://github.com/libp2p/specs/issues/475 | [Improved WASM support](#improved-wasm-support), https://github.com/libp2p/specs/pull/497 | https://github.com/libp2p/rust-libp2p/pull/4248 | Use the browser's WebRTC stack to support [`/webrtc`](https://github.com/libp2p/specs/blob/master/webrtc/webrtc.md) and @@ -82,7 +82,7 @@ enabling users to use rust-libp2p on both the client (browser) and server side. ## WebTransport | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|-----------------------------|--------|-------------------|---------------------------------------------------|------------------------------------|------------| +| --------------------------- | ------ | ----------------- | ------------------------------------------------- | ---------------------------------- | ---------- | | Connectivity / optimization | todo | | https://github.com/libp2p/rust-libp2p/issues/2993 | [QUIC](#experimental-quic-support) | | A WebTransport implementation in rust-libp2p will enable browsers to connect to rust-libp2p nodes @@ -93,7 +93,7 @@ more performant. It is dependent on QUIC support in rust-libp2p. Given that we w ## Automate port-forwarding e.g. via UPnP | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|---------------------------------------------------|--------------|------------| +| ------------ | ------ | ----------------- | ------------------------------------------------- | ------------ | ---------- | | Connectivity | todo | | https://github.com/libp2p/rust-libp2p/issues/3903 | | | Leverage protocols like UPnP to configure port-forwarding on ones router when behind NAT and/or @@ -105,7 +105,7 @@ become publicly reachable when behind a firewall and/or NAT. ### Alpha QUIC support | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|---------------------------------------------------|------------------------------------------------|------------| +| ------------ | ------ | ----------------- | ------------------------------------------------- | ---------------------------------------------- | ---------- | | Connectivity | Done | Q4/2022 | https://github.com/libp2p/rust-libp2p/issues/2883 | https://github.com/libp2p/test-plans/issues/53 | | QUIC has been on the roadmap for a long time. It enables various performance improvements as well as @@ -115,7 +115,7 @@ https://github.com/libp2p/rust-libp2p/pull/2289. ### WebRTC support (browser-to-server) | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|------------------------------------------|-----------------------------------------------|-------------------------------------------------------------------| +| ------------ | ------ | ----------------- | ---------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------- | | Connectivity | Done | Q4/2022 | https://github.com/libp2p/specs/pull/412 | https://github.com/libp2p/test-plans/pull/100 | [WebRTC (browser-to-browser)](#webrtc-support-browser-to-browser) | We are currently implementing WebRTC for **browser-to-server** connectivity in @@ -128,9 +128,9 @@ stack. Though that should only happen after improved Wasm support, see below. ### Kademlia efficient querying -| Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|-------------|-------------------|-------------------------------------------------|--------------|------------| -| Optimization | done | Q1/2023 | https://github.com/libp2p/rust-libp2p/pull/2712 | | | +| Category | Status | Target Completion | Tracking | Dependencies | Dependents | +| ------------ | ------ | ----------------- | ----------------------------------------------- | ------------ | ---------- | +| Optimization | done | Q1/2023 | https://github.com/libp2p/rust-libp2p/pull/2712 | | | Users of rust-libp2p like [iroh](https://github.com/n0-computer/iroh) need this for low latency usage of `libp2p-kad`. The rust-libp2p maintainers can pick this up unless iroh folks finish the @@ -139,7 +139,7 @@ work before that. ### Generic connection management | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|----------------------|--------|-------------------|---------------------------------------------------|--------------|------------| +| -------------------- | ------ | ----------------- | ------------------------------------------------- | ------------ | ---------- | | Developer Ergonomics | done | Q1/2023 | https://github.com/libp2p/rust-libp2p/issues/2824 | | | Today connection management functionality in rust-libp2p is limited. Building abstractions on top is @@ -149,7 +149,7 @@ management generic allows users to build advanced and efficient abstractions on ### Cross Behaviour communication | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|----------------------|--------|-------------------|---------------------------------------------------|---------------------------------------------------|-----------------------------------------------| +| -------------------- | ------ | ----------------- | ------------------------------------------------- | ------------------------------------------------- | --------------------------------------------- | | Developer ergonomics | Done | Q1/2023 | https://github.com/libp2p/rust-libp2p/issues/2680 | https://github.com/libp2p/rust-libp2p/issues/2832 | [Kademlia client mode](#kademlia-client-mode) | Today `NetworkBehaviour` implementations like Kademlia, GossipSub or Circuit Relay v2 can not @@ -166,7 +166,7 @@ would deserve its own roadmap item. ## QUIC - implement hole punching | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|---------------------------------------------------|--------------|------------| +| ------------ | ------ | ----------------- | ------------------------------------------------- | ------------ | ---------- | | Connectivity | done | Q3/2023 | https://github.com/libp2p/rust-libp2p/issues/2883 | | | Add hole punching support for QUIC. See also [DCUtR specification on usage with @@ -175,7 +175,7 @@ QUIC](https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#the-protocol). ## Kademlia client mode | Category | Status | Target Completion | Tracking | Dependencies | Dependents | -|--------------|--------|-------------------|---------------------------------------------------|-----------------------------------------------------------------|------------| +| ------------ | ------ | ----------------- | ------------------------------------------------- | --------------------------------------------------------------- | ---------- | | Optimization | Done | Q2/2023 | https://github.com/libp2p/rust-libp2p/issues/2032 | [Cross behaviour communication](#cross-behaviour-communication) | | Kademlia client mode will enhance routing table health and thus have a positive impact on all From 1618c492deb0886610a47955c0f8fd80605f1e86 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 11:37:38 -0300 Subject: [PATCH 086/235] asynchronous-codec = "0.6" --- transports/webrtc-websys/Cargo.toml | 2 +- transports/webrtc/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index d033c0fb17c..cac0a057725 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -25,7 +25,7 @@ rust-version = { workspace = true } version = "0.1.0-alpha" [dependencies] -asynchronous-codec = "0.6.2" +asynchronous-codec = "0.6" bytes = "1" futures = "0.3" futures-timer = "3" diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index b0a96a036b3..1545c27a916 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -12,7 +12,7 @@ version = "0.6.0-alpha" [dependencies] async-trait = "0.1" -asynchronous-codec = "0.6.2" +asynchronous-codec = "0.6" bytes = "1" futures = "0.3" futures-timer = "3" From 358b86c4715c3e868b894facf36187da3697f900 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 11:39:09 -0300 Subject: [PATCH 087/235] commit lock file --- Cargo.lock | 321 ++++++++++++++++++++++++++--------------------------- 1 file changed, 159 insertions(+), 162 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10e1d8324ae..c7d8253c397 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,9 +152,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] @@ -227,9 +227,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys", @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arbitrary" @@ -422,9 +422,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ "event-listener", ] @@ -467,7 +467,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -492,7 +492,7 @@ dependencies = [ "log", "memchr", "once_cell", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "pin-utils", "slab", "wasm-bindgen-futures", @@ -521,13 +521,13 @@ checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-trait" -version = "0.1.72" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -540,7 +540,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", ] [[package]] @@ -558,7 +558,7 @@ dependencies = [ "attribute-derive-macro", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -574,7 +574,7 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -613,7 +613,7 @@ dependencies = [ "memchr", "mime", "percent-encoding", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "rustversion", "serde", "serde_json", @@ -726,9 +726,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "blake2" @@ -999,9 +999,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.19" +version = "4.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +checksum = "b417ae4361bca3f5de378294fc7472d3c4ed86a5ef9f49e93ae722f432aae8d2" dependencies = [ "clap_builder", "clap_derive", @@ -1010,9 +1010,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.19" +version = "4.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +checksum = "9c90dc0f0e42c64bff177ca9d7be6fcc9ddb0f26a6e062174a61c84dd6c644d4" dependencies = [ "anstream", "anstyle", @@ -1029,7 +1029,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -1070,7 +1070,7 @@ dependencies = [ "bytes", "futures-core", "memchr", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "tokio", "tokio-util", ] @@ -1136,9 +1136,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "const_format" @@ -1421,18 +1421,31 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4ba9852b42210c7538b75484f9daa0655e9a3ac04f693747bb0f02cf3cfe16" +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" dependencies = [ "cfg-if 1.0.0", + "cpufeatures", + "curve25519-dalek-derive", "fiat-crypto", - "packed_simd_2", "platforms", + "rustc_version", "subtle", "zeroize", ] +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "darling" version = "0.14.4" @@ -1520,9 +1533,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "pem-rfc7468 0.7.0", @@ -1571,7 +1584,7 @@ checksum = "5bc1955a640c4464859ae700fbe48e666da6fdce99ce5fe1acd08dd295889d10" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -1634,7 +1647,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -1684,7 +1697,7 @@ version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ - "der 0.7.7", + "der 0.7.8", "digest 0.10.7", "elliptic-curve 0.13.5", "rfc6979 0.4.0", @@ -1806,7 +1819,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -1954,9 +1967,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", "miniz_oxide", @@ -2052,7 +2065,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "waker-fn", ] @@ -2064,7 +2077,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -2134,7 +2147,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "pin-utils", "slab", ] @@ -2437,7 +2450,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", ] [[package]] @@ -2454,9 +2467,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -2480,7 +2493,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "socket2 0.4.9", "tokio", "tower-service", @@ -2772,7 +2785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.7", + "rustix 0.38.8", "windows-sys", ] @@ -2922,7 +2935,7 @@ dependencies = [ "quote", "rstml", "serde", - "syn 2.0.28", + "syn 2.0.29", "walkdir", ] @@ -2944,7 +2957,7 @@ dependencies = [ "quote", "rstml", "server_fn_macro", - "syn 2.0.28", + "syn 2.0.29", "tracing", "uuid", ] @@ -2995,12 +3008,6 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" -[[package]] -name = "libm" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" - [[package]] name = "libp2p" version = "0.52.2" @@ -3679,7 +3686,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -3982,9 +3989,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" dependencies = [ "value-bag", ] @@ -4415,7 +4422,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -4486,16 +4493,6 @@ dependencies = [ "sha2 0.10.7", ] -[[package]] -name = "packed_simd_2" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" -dependencies = [ - "cfg-if 1.0.0", - "libm", -] - [[package]] name = "pad-adapter" version = "0.1.1" @@ -4606,7 +4603,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -4637,7 +4634,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -4648,9 +4645,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -4684,7 +4681,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.7", + "der 0.7.8", "spki 0.7.2", ] @@ -4740,7 +4737,7 @@ dependencies = [ "concurrent-queue", "libc", "log", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "windows-sys", ] @@ -4792,7 +4789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -4847,7 +4844,7 @@ checksum = "70550716265d1ec349c41f70dd4f964b4fd88394efe4405f0c1da679c4799a07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -4867,7 +4864,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", "version_check", "yansi", ] @@ -4886,13 +4883,13 @@ dependencies = [ [[package]] name = "prometheus-client-derive-encode" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b6a5217beb0ad503ee7fa752d451c905113d70721b937126158f3106a48cc1" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] @@ -4970,9 +4967,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -4986,7 +4983,7 @@ dependencies = [ "derive-where", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -5109,9 +5106,9 @@ dependencies = [ [[package]] name = "redis" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff5d95dd18a4d76650f0c2607ed8ebdbf63baf9cb934e1c233cd220c694db1d7" +checksum = "ffd6543a7bc6428396845f6854ccf3d1ae8823816592e2cbe74f20f50f209d02" dependencies = [ "async-trait", "bytes", @@ -5119,7 +5116,7 @@ dependencies = [ "futures-util", "itoa", "percent-encoding", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "ryu", "tokio", "tokio-util", @@ -5228,7 +5225,7 @@ dependencies = [ "native-tls", "once_cell", "percent-encoding", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "serde", "serde_json", "serde_urlencoded", @@ -5332,14 +5329,14 @@ dependencies = [ [[package]] name = "rstml" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6522514806fbc2fc4c3d54ee9cc01e928fa00e1c988af4c730a64f57637ad7cf" +checksum = "fe542870b8f59dd45ad11d382e5339c9a1047cde059be136a7016095bbdefa77" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.28", + "syn 2.0.29", "syn_derive", "thiserror", ] @@ -5404,7 +5401,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.28", + "syn 2.0.29", "walkdir", ] @@ -5474,11 +5471,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.7" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys 0.4.5", @@ -5545,9 +5542,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.2" +version = "0.101.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" +checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" dependencies = [ "ring", "untrusted", @@ -5667,7 +5664,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", - "der 0.7.7", + "der 0.7.8", "generic-array", "pkcs8 0.10.2", "subtle", @@ -5752,14 +5749,14 @@ checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "indexmap 2.0.0", "itoa", @@ -5796,7 +5793,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -5830,7 +5827,7 @@ dependencies = [ "serde_json", "serde_qs", "server_fn_macro_default", - "syn 2.0.28", + "syn 2.0.29", "thiserror", "xxhash-rust", ] @@ -5846,7 +5843,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.28", + "syn 2.0.29", "xxhash-rust", ] @@ -5857,7 +5854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35ee7b18c66e7a30b1855096cee24d540925825ce91193f42fae322033b109c1" dependencies = [ "server_fn_macro", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -6019,14 +6016,14 @@ dependencies = [ [[package]] name = "snow" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733" +checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155" dependencies = [ "aes-gcm 0.9.2", "blake2", "chacha20poly1305", - "curve25519-dalek 4.0.0-rc.1", + "curve25519-dalek 4.0.0", "rand_core 0.6.4", "ring", "rustc_version", @@ -6092,7 +6089,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der 0.7.7", + "der 0.7.8", ] [[package]] @@ -6163,9 +6160,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -6181,7 +6178,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -6232,7 +6229,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand 2.0.0", "redox_syscall", - "rustix 0.38.7", + "rustix 0.38.8", "windows-sys", ] @@ -6285,22 +6282,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -6389,7 +6386,7 @@ dependencies = [ "mio", "num_cpus", "parking_lot", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "signal-hook-registry", "socket2 0.5.3", "tokio-macros", @@ -6404,7 +6401,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -6435,7 +6432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "tokio", ] @@ -6449,7 +6446,7 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "tokio", "tracing", ] @@ -6472,7 +6469,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "tokio", "tower-layer", "tower-service", @@ -6485,7 +6482,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "bytes", "futures-core", "futures-util", @@ -6496,7 +6493,7 @@ dependencies = [ "mime", "mime_guess", "percent-encoding", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "tokio", "tokio-util", "tower-layer", @@ -6524,7 +6521,7 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.11", + "pin-project-lite 0.2.12", "tracing-attributes", "tracing-core", ] @@ -6537,7 +6534,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -6644,9 +6641,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "trybuild" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84e0202ea606ba5ebee8507ab2bfbe89b98551ed9b8f0be198109275cff284b" +checksum = "6df60d81823ed9c520ee897489573da4b1d79ffbe006b8134f46de1a1aa03555" dependencies = [ "basic-toml", "glob", @@ -6924,7 +6921,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", "wasm-bindgen-shared", ] @@ -6958,7 +6955,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7132,9 +7129,9 @@ dependencies = [ [[package]] name = "webrtc-dtls" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942be5bd85f072c3128396f6e5a9bfb93ca8c1939ded735d177b7bcba9a13d05" +checksum = "c4a00f4242f2db33307347bd5be53263c52a0331c96c14292118c9a6bb48d267" dependencies = [ "aes 0.6.0", "aes-gcm 0.10.2", @@ -7149,13 +7146,12 @@ dependencies = [ "hkdf", "hmac 0.12.1", "log", - "oid-registry 0.6.1", "p256 0.11.1", "p384", "pem", "rand 0.8.5", "rand_core 0.6.4", - "rcgen 0.9.3", + "rcgen 0.10.0", "ring", "rustls 0.19.1", "sec1 0.3.0", @@ -7168,7 +7164,7 @@ dependencies = [ "tokio", "webpki 0.21.4", "webrtc-util", - "x25519-dalek 2.0.0-pre.1", + "x25519-dalek 2.0.0", "x509-parser 0.13.2", ] @@ -7440,24 +7436,24 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "27f51fb4c64f8b770a823c043c7fad036323e1c48f55287b7bbb7987b2fcdf3b" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", + "windows_aarch64_msvc 0.48.3", + "windows_i686_gnu 0.48.3", + "windows_i686_msvc 0.48.3", + "windows_x86_64_gnu 0.48.3", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.48.0", + "windows_x86_64_msvc 0.48.3", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "fde1bb55ae4ce76a597a8566d82c57432bc69c039449d61572a7a353da28f68c" [[package]] name = "windows_aarch64_msvc" @@ -7467,9 +7463,9 @@ checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "1513e8d48365a78adad7322fd6b5e4c4e99d92a69db8df2d435b25b1f1f286d4" [[package]] name = "windows_i686_gnu" @@ -7479,9 +7475,9 @@ checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "60587c0265d2b842298f5858e1a5d79d146f9ee0c37be5782e92a6eb5e1d7a83" [[package]] name = "windows_i686_msvc" @@ -7491,9 +7487,9 @@ checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "224fe0e0ffff5d2ea6a29f82026c8f43870038a0ffc247aa95a52b47df381ac4" [[package]] name = "windows_x86_64_gnu" @@ -7503,15 +7499,15 @@ checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "62fc52a0f50a088de499712cbc012df7ebd94e2d6eb948435449d76a6287e7ad" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "2093925509d91ea3d69bcd20238f4c2ecdb1a29d3c281d026a09705d0dd35f3d" [[package]] name = "windows_x86_64_msvc" @@ -7521,9 +7517,9 @@ checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "b6ade45bc8bf02ae2aa34a9d54ba660a1a58204da34ba793c00d83ca3730b5f1" [[package]] name = "winreg" @@ -7557,12 +7553,13 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "2.0.0-pre.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ - "curve25519-dalek 3.2.0", + "curve25519-dalek 4.0.0", "rand_core 0.6.4", + "serde", "zeroize", ] @@ -7651,9 +7648,9 @@ dependencies = [ [[package]] name = "yansi" -version = "1.0.0-rc" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee746ad3851dd3bc40e4a028ab3b00b99278d929e48957bcb2d111874a7e43e" +checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" [[package]] name = "yasna" @@ -7681,5 +7678,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] From 7b8db4b7e32692af890aff2034c8fa4db429b95f Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 11:54:32 -0300 Subject: [PATCH 088/235] merge yamux from main --- Cargo.lock | 7 ++-- Cargo.toml | 2 +- muxers/yamux/Cargo.toml | 4 +- muxers/yamux/src/lib.rs | 85 +++++++++++++++-------------------------- 4 files changed, 37 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7d8253c397..6a920d1b1bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3900,7 +3900,7 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.44.0" +version = "0.44.1" dependencies = [ "async-std", "futures", @@ -7634,14 +7634,15 @@ dependencies = [ [[package]] name = "yamux" -version = "0.10.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" +checksum = "0329ef377816896f014435162bb3711ea7a07729c23d0960e6f8048b21b8fe91" dependencies = [ "futures", "log", "nohash-hasher", "parking_lot", + "pin-project", "rand 0.8.5", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index 0051e7f84f1..4f7c23e65ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ libp2p-webrtc-utils = { version = "0.1.0", path = "transports/webrtc-utils" } libp2p-webrtc-websys = { version = "0.1.0-alpha", path = "transports/webrtc-websys" } 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" } +libp2p-yamux = { version = "0.44.1", path = "muxers/yamux" } multiaddr = "0.18.0" multihash = "0.19.0" multistream-select = { version = "0.13.0", path = "misc/multistream-select" } diff --git a/muxers/yamux/Cargo.toml b/muxers/yamux/Cargo.toml index 50a9e97d1d0..11b94ac9738 100644 --- a/muxers/yamux/Cargo.toml +++ b/muxers/yamux/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-yamux" edition = "2021" rust-version = { workspace = true } description = "Yamux multiplexing protocol for libp2p" -version = "0.44.0" +version = "0.44.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -14,7 +14,7 @@ categories = ["network-programming", "asynchronous"] futures = "0.3.28" libp2p-core = { workspace = true } thiserror = "1.0" -yamux = "0.10.0" +yamux = "0.12" log = "0.4" [dev-dependencies] diff --git a/muxers/yamux/src/lib.rs b/muxers/yamux/src/lib.rs index b24c976ebf2..12e5dd8c1ff 100644 --- a/muxers/yamux/src/lib.rs +++ b/muxers/yamux/src/lib.rs @@ -22,14 +22,14 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use futures::{future, prelude::*, ready, stream::BoxStream}; +use futures::{future, prelude::*, ready}; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; use libp2p_core::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use std::collections::VecDeque; use std::io::{IoSlice, IoSliceMut}; use std::task::Waker; use std::{ - fmt, io, iter, mem, + io, iter, pin::Pin, task::{Context, Poll}, }; @@ -37,14 +37,12 @@ use thiserror::Error; use yamux::ConnectionError; /// A Yamux connection. +#[derive(Debug)] pub struct Muxer { - /// The [`futures::stream::Stream`] of incoming substreams. - incoming: BoxStream<'static, Result>, - /// Handle to control the connection. - control: yamux::Control, + connection: yamux::Connection, /// Temporarily buffers inbound streams in case our node is performing backpressure on the remote. /// - /// The only way how yamux can make progress is by driving the stream. However, the + /// The only way how yamux can make progress is by calling [`yamux::Connection::poll_next_inbound`]. However, the /// [`StreamMuxer`] interface is designed to allow a caller to selectively make progress via /// [`StreamMuxer::poll_inbound`] and [`StreamMuxer::poll_outbound`] whilst the more general /// [`StreamMuxer::poll`] is designed to make progress on existing streams etc. @@ -54,17 +52,13 @@ pub struct Muxer { inbound_stream_buffer: VecDeque, /// Waker to be called when new inbound streams are available. inbound_stream_waker: Option, - - _phantom: std::marker::PhantomData, } -const MAX_BUFFERED_INBOUND_STREAMS: usize = 25; - -impl fmt::Debug for Muxer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Yamux") - } -} +/// How many streams to buffer before we start resetting them. +/// +/// This is equal to the ACK BACKLOG in `rust-yamux`. +/// Thus, for peers running on a recent version of `rust-libp2p`, we should never need to reset streams because they'll voluntarily stop opening them once they hit the ACK backlog. +const MAX_BUFFERED_INBOUND_STREAMS: usize = 256; impl Muxer where @@ -72,22 +66,17 @@ where { /// Create a new Yamux connection. fn new(io: C, cfg: yamux::Config, mode: yamux::Mode) -> Self { - let conn = yamux::Connection::new(io, cfg, mode); - let ctrl = conn.control(); - - Self { - incoming: yamux::into_stream(conn).err_into().boxed(), - control: ctrl, + Muxer { + connection: yamux::Connection::new(io, cfg, mode), inbound_stream_buffer: VecDeque::default(), inbound_stream_waker: None, - _phantom: Default::default(), } } } impl StreamMuxer for Muxer where - C: AsyncRead + AsyncWrite + Send + Unpin + 'static, + C: AsyncRead + AsyncWrite + Unpin + 'static, { type Substream = Stream; type Error = Error; @@ -112,10 +101,15 @@ where mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - Pin::new(&mut self.control) - .poll_open_stream(cx) - .map_ok(Stream) - .map_err(Error) + let stream = ready!(self.connection.poll_new_outbound(cx).map_err(Error)?); + + Poll::Ready(Ok(Stream(stream))) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + ready!(self.connection.poll_close(cx).map_err(Error)?); + + Poll::Ready(Ok(())) } fn poll( @@ -141,23 +135,6 @@ where cx.waker().wake_by_ref(); Poll::Pending } - - fn poll_close(mut self: Pin<&mut Self>, c: &mut Context<'_>) -> Poll> { - if let Poll::Ready(()) = Pin::new(&mut self.control).poll_close(c).map_err(Error)? { - return Poll::Ready(Ok(())); - } - - while let Poll::Ready(maybe_inbound_stream) = - self.incoming.poll_next_unpin(c).map_err(Error)? - { - match maybe_inbound_stream { - Some(inbound_stream) => mem::drop(inbound_stream), - None => return Poll::Ready(Ok(())), - } - } - - Poll::Pending - } } /// A stream produced by the yamux multiplexer. @@ -210,18 +187,16 @@ impl AsyncWrite for Stream { impl Muxer where - C: AsyncRead + AsyncWrite + Send + Unpin + 'static, + C: AsyncRead + AsyncWrite + Unpin + 'static, { fn poll_inner(&mut self, cx: &mut Context<'_>) -> Poll> { - self.incoming.poll_next_unpin(cx).map(|maybe_stream| { - let stream = maybe_stream - .transpose() - .map_err(Error)? - .map(Stream) - .ok_or(Error(ConnectionError::Closed))?; - - Ok(stream) - }) + let stream = ready!(self.connection.poll_next_inbound(cx)) + .transpose() + .map_err(Error)? + .map(Stream) + .ok_or(Error(ConnectionError::Closed))?; + + Poll::Ready(Ok(stream)) } } From 2e8c2e5ac41e758fc2b0d32b9dd5a4a1e685f10d Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 12:28:44 -0300 Subject: [PATCH 089/235] fix reqwest version --- interop-tests/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interop-tests/Cargo.toml b/interop-tests/Cargo.toml index 7e443be77dc..c58ed21f19c 100644 --- a/interop-tests/Cargo.toml +++ b/interop-tests/Cargo.toml @@ -58,7 +58,7 @@ libp2p = { path = "../libp2p", features = [ "webtransport-websys", ] } libp2p-webrtc-websys = { workspace = true } -reqwest = { version = "0.11", features = ["identify", "json"] } +reqwest = { version = "0.11", features = ["json"] } wasm-bindgen = { version = "0.2" } wasm-bindgen-futures = { version = "0.4" } wasm-logger = { version = "0.2.0" } From da71eb3ffd8d58c73d8b49be2183b89db3793880 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 12:54:27 -0300 Subject: [PATCH 090/235] fix merge error --- Cargo.lock | 116 +++++++++++++++++++++++++++++++++------ interop-tests/Cargo.toml | 1 + 2 files changed, 99 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a920d1b1bb..76da67726c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -924,7 +924,6 @@ dependencies = [ "env_logger 0.10.0", "futures", "libp2p", - "libp2p-quic", ] [[package]] @@ -1516,7 +1515,6 @@ dependencies = [ "futures", "futures-timer", "libp2p", - "libp2p-quic", "log", ] @@ -2693,7 +2691,6 @@ dependencies = [ "instant", "libp2p", "libp2p-mplex", - "libp2p-quic", "libp2p-webrtc", "libp2p-webrtc-websys", "log", @@ -3010,7 +3007,7 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libp2p" -version = "0.52.2" +version = "0.52.3" dependencies = [ "async-std", "async-trait", @@ -3035,11 +3032,13 @@ dependencies = [ "libp2p-identity", "libp2p-kad", "libp2p-mdns", + "libp2p-memory-connection-limits", "libp2p-metrics", "libp2p-noise", "libp2p-ping", "libp2p-plaintext", "libp2p-pnet", + "libp2p-quic", "libp2p-relay", "libp2p-rendezvous", "libp2p-request-response", @@ -3091,7 +3090,7 @@ dependencies = [ [[package]] name = "libp2p-connection-limits" -version = "0.2.0" +version = "0.2.1" dependencies = [ "async-std", "libp2p-core", @@ -3221,7 +3220,7 @@ dependencies = [ [[package]] name = "libp2p-gossipsub" -version = "0.45.0" +version = "0.45.1" dependencies = [ "async-std", "asynchronous-codec", @@ -3310,7 +3309,7 @@ dependencies = [ [[package]] name = "libp2p-kad" -version = "0.44.3" +version = "0.44.4" dependencies = [ "arrayvec", "async-std", @@ -3368,6 +3367,24 @@ dependencies = [ "void", ] +[[package]] +name = "libp2p-memory-connection-limits" +version = "0.1.0" +dependencies = [ + "async-std", + "libp2p-core", + "libp2p-identify", + "libp2p-identity", + "libp2p-swarm", + "libp2p-swarm-derive", + "libp2p-swarm-test", + "log", + "memory-stats", + "rand 0.8.5", + "sysinfo", + "void", +] + [[package]] name = "libp2p-metrics" version = "0.13.1" @@ -3537,7 +3554,7 @@ dependencies = [ [[package]] name = "libp2p-quic" -version = "0.8.0-alpha" +version = "0.9.2" dependencies = [ "async-std", "bytes", @@ -3555,9 +3572,10 @@ dependencies = [ "log", "parking_lot", "quickcheck", - "quinn-proto", + "quinn", "rand 0.8.5", "rustls 0.21.6", + "socket2 0.5.3", "thiserror", "tokio", ] @@ -3647,7 +3665,7 @@ dependencies = [ [[package]] name = "libp2p-swarm" -version = "0.43.2" +version = "0.43.3" dependencies = [ "async-std", "either", @@ -3793,7 +3811,6 @@ dependencies = [ "multihash", "quick-protobuf", "quick-protobuf-codec", - "quickcheck", "rand 0.8.5", "rcgen 0.10.0", "serde", @@ -3875,7 +3892,7 @@ dependencies = [ "rw-stream-sink", "soketto", "url", - "webpki-roots 0.24.0", + "webpki-roots 0.25.2", ] [[package]] @@ -4074,6 +4091,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memory-stats" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f79cf9964c5c9545493acda1263f1912f8d2c56c8a2ffee2606cb960acaacc" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "metrics-example" version = "0.1.0" @@ -4304,6 +4331,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -4948,6 +4984,26 @@ dependencies = [ "pin-project-lite 0.1.12", ] +[[package]] +name = "quinn" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" +dependencies = [ + "async-io", + "async-std", + "bytes", + "futures-io", + "pin-project-lite 0.2.12", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.21.6", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "quinn-proto" version = "0.10.2" @@ -4965,6 +5021,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "quinn-udp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df19e284d93757a9fb91d63672f7741b129246a669db09d1c0063071debc0c0" +dependencies = [ + "bytes", + "libc", + "socket2 0.5.3", + "tracing", + "windows-sys", +] + [[package]] name = "quote" version = "1.0.33" @@ -5186,7 +5255,6 @@ dependencies = [ "env_logger 0.10.0", "futures", "libp2p", - "libp2p-quic", ] [[package]] @@ -6199,6 +6267,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "sysinfo" +version = "0.29.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d10ed79c22663a35a255d289a7fdcb43559fc77ff15df5ce6c341809e7867528" +dependencies = [ + "cfg-if 1.0.0", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -7061,12 +7144,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.24.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" -dependencies = [ - "rustls-webpki", -] +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "webrtc" diff --git a/interop-tests/Cargo.toml b/interop-tests/Cargo.toml index c58ed21f19c..9891e9ce20c 100644 --- a/interop-tests/Cargo.toml +++ b/interop-tests/Cargo.toml @@ -33,6 +33,7 @@ libp2p = { path = "../libp2p", features = [ "websocket", "yamux", ] } +libp2p-mplex = { path = "../muxers/mplex" } libp2p-webrtc = { workspace = true, features = ["tokio"] } mime_guess = "2.0" redis = { version = "0.23.0", default-features = false, features = [ From 2913fbe2608e2c8aae3652d4a5c93f4dcbaeaa47 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 13:03:47 -0300 Subject: [PATCH 091/235] fix licences --- examples/browser-webrtc/client/Cargo.toml | 1 + transports/webrtc-utils/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/browser-webrtc/client/Cargo.toml b/examples/browser-webrtc/client/Cargo.toml index 235b290c845..c41ad8104d4 100644 --- a/examples/browser-webrtc/client/Cargo.toml +++ b/examples/browser-webrtc/client/Cargo.toml @@ -1,6 +1,7 @@ [package] authors = ["Doug Anderson "] edition = "2021" +license = "MIT" name = "browser-webrtc-example-client" rust-version.workspace = true version = "0.1.0" diff --git a/transports/webrtc-utils/Cargo.toml b/transports/webrtc-utils/Cargo.toml index 19c3b535416..b7a332c2f8e 100644 --- a/transports/webrtc-utils/Cargo.toml +++ b/transports/webrtc-utils/Cargo.toml @@ -1,5 +1,6 @@ [package] edition = "2021" +license = "MIT" name = "libp2p-webrtc-utils" rust-version.workspace = true version = "0.1.0" From 01dd0ee1e76a426e53dd0fd7d6a6673bad764f06 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 14:18:15 -0300 Subject: [PATCH 092/235] rm chrono dep --- examples/browser-webrtc/client/Cargo.toml | 8 ++++++-- examples/browser-webrtc/client/src/lib.rs | 15 ++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/examples/browser-webrtc/client/Cargo.toml b/examples/browser-webrtc/client/Cargo.toml index c41ad8104d4..ccb66d475bc 100644 --- a/examples/browser-webrtc/client/Cargo.toml +++ b/examples/browser-webrtc/client/Cargo.toml @@ -9,7 +9,6 @@ version = "0.1.0" [dependencies] async-channel = "1.9.0" cfg-if = "0.1" -chrono = { version = "0.4.26", features = ["wasmbind"] } console_log = { version = "0.2", optional = true } fern = { version = "0.6.2", features = ["colored"], optional = true } futures = "0.3.28" @@ -24,7 +23,12 @@ libp2p-webrtc-websys = { workspace = true } log = "0.4" wasm-bindgen = "0.2.87" wasm-bindgen-futures = "0.4.37" -web-sys = { version = "0.3.64", features = ["Response", "Window"] } +web-sys = { version = "0.3.64", features = [ + "Response", + "Window", + 'Performance', + 'PerformanceTiming', +] } [features] default = ["console_log", "fern"] diff --git a/examples/browser-webrtc/client/src/lib.rs b/examples/browser-webrtc/client/src/lib.rs index 3eccc7ad143..d3d62643238 100644 --- a/examples/browser-webrtc/client/src/lib.rs +++ b/examples/browser-webrtc/client/src/lib.rs @@ -30,11 +30,17 @@ pub fn App(cx: Scope) -> impl IntoView { // update number of pings signal each time out receiver receives an update spawn_local(async move { + let window = web_sys::window().expect("should have a window in this context"); + let performance = window + .performance() + .expect("performance should be available"); + loop { match recvr.recv().await { Ok(rtt) => { // set rtt and date time stamp - let now = chrono::Utc::now().timestamp(); + // use leptos performance now + let now = performance.now() as u64; log::info!("[{now:?}] Got RTT: {rtt:?} ms"); set_number_of_pings.update(move |pings| pings.insert(0, (now, rtt))); } @@ -55,12 +61,7 @@ pub fn App(cx: Scope) -> impl IntoView { view=move |cx, (stamp, rtt)| { view! { cx,
  • - { - chrono::NaiveDateTime::from_timestamp_opt(stamp, 0) - .expect("timestamp is valid") - .format("%Y-%m-%d %H:%M:%S") - .to_string() - }" in " + {stamp}" in " {rtt} "ms"
  • } From 88420704212ce0f569e61e12c24c70de2764fb33 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 14:18:23 -0300 Subject: [PATCH 093/235] add trunk note --- examples/browser-webrtc/client/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/browser-webrtc/client/README.md b/examples/browser-webrtc/client/README.md index 98439bcc71f..dc95038f4bd 100644 --- a/examples/browser-webrtc/client/README.md +++ b/examples/browser-webrtc/client/README.md @@ -2,4 +2,6 @@ ## Run Leptos App +Ensure you have `trunk` [installed](https://trunkrs.dev/). + From this directory, run `trunk serve --open` From c825cc3e3279ac67941d0accb185c03b04c0fb83 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 14:18:38 -0300 Subject: [PATCH 094/235] fix test --- transports/webrtc-utils/src/fingerprint.rs | 26 +++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/transports/webrtc-utils/src/fingerprint.rs b/transports/webrtc-utils/src/fingerprint.rs index 20febea0fc1..8da2b66c82b 100644 --- a/transports/webrtc-utils/src/fingerprint.rs +++ b/transports/webrtc-utils/src/fingerprint.rs @@ -102,27 +102,23 @@ impl fmt::Debug for Fingerprint { mod tests { use super::*; + const SDP_FORMAT: &str = "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC"; + const REGULAR_FORMAT: [u8; 32] = + hex_literal::hex!("7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC"); + #[test] fn sdp_format() { - let fp = Fingerprint::raw(hex_literal::hex!( - "7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC" - )); + let fp = Fingerprint::raw(REGULAR_FORMAT); - let sdp_format = fp.to_sdp_format(); + let formatted = fp.to_sdp_format(); - assert_eq!(sdp_format, "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC") + assert_eq!(formatted, SDP_FORMAT) } #[test] - fn from_sdp_to_bytes() { - let sdp_format = "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC"; - let bytes = hex::decode(sdp_format.replace(":", "")).unwrap(); - let fp = Fingerprint::from_certificate(&bytes); - assert_eq!( - fp, - Fingerprint::raw(hex_literal::hex!( - "7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC" - )) - ); + fn from_sdp() { + let bytes = hex::decode(SDP_FORMAT.replace(':', "")).unwrap(); + let fp = Fingerprint::raw(bytes.as_slice().try_into().unwrap()); + assert_eq!(fp, Fingerprint::raw(REGULAR_FORMAT)); } } From a55ea6897a904427a523e7371a2f017161283da3 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 14:22:31 -0300 Subject: [PATCH 095/235] fix missing dep --- transports/webrtc/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 63a7fef769a..1545c27a916 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -45,6 +45,7 @@ env_logger = "0.10" hex-literal = "0.4" libp2p-ping = { workspace = true } libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } +quickcheck = "1.0.3" tokio = { version = "1.31", features = ["full"] } unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } void = "1" From 7116393526d7f78e57d5268062961f577f1012fe Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 14:24:23 -0300 Subject: [PATCH 096/235] commit lockfile --- Cargo.lock | 107 +++++++---------------------------------------------- 1 file changed, 14 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76da67726c4..15b74a55777 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,21 +165,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anes" version = "0.1.6" @@ -278,7 +263,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.25", + "time", ] [[package]] @@ -294,7 +279,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.25", + "time", ] [[package]] @@ -794,7 +779,6 @@ version = "0.1.0" dependencies = [ "async-channel", "cfg-if 0.1.10", - "chrono", "console_log 0.2.2", "fern", "futures", @@ -926,21 +910,6 @@ dependencies = [ "libp2p", ] -[[package]] -name = "chrono" -version = "0.4.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "time 0.1.45", - "wasm-bindgen", - "winapi", -] - [[package]] name = "ciborium" version = "0.2.1" @@ -1175,7 +1144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.25", + "time", "version_check", ] @@ -1892,7 +1861,7 @@ dependencies = [ "mime", "serde", "serde_json", - "time 0.3.25", + "time", "tokio", "url", "webdriver", @@ -2527,29 +2496,6 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "iana-time-zone" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows 0.48.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -2614,7 +2560,7 @@ dependencies = [ "smol", "system-configuration", "tokio", - "windows 0.34.0", + "windows", ] [[package]] @@ -3811,6 +3757,7 @@ dependencies = [ "multihash", "quick-protobuf", "quick-protobuf-codec", + "quickcheck", "rand 0.8.5", "rcgen 0.10.0", "serde", @@ -5156,7 +5103,7 @@ checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" dependencies = [ "pem", "ring", - "time 0.3.25", + "time", "yasna", ] @@ -5168,7 +5115,7 @@ checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ "pem", "ring", - "time 0.3.25", + "time", "x509-parser 0.14.0", "yasna", ] @@ -6393,17 +6340,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.25" @@ -6971,12 +6907,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -7108,7 +7038,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time 0.3.25", + "time", "unicode-segmentation", "url", ] @@ -7177,7 +7107,7 @@ dependencies = [ "smol_str", "stun", "thiserror", - "time 0.3.25", + "time", "tokio", "turn", "url", @@ -7496,15 +7426,6 @@ dependencies = [ "windows_x86_64_msvc 0.34.0", ] -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -7658,7 +7579,7 @@ dependencies = [ "oid-registry 0.4.0", "rusticata-macros", "thiserror", - "time 0.3.25", + "time", ] [[package]] @@ -7677,7 +7598,7 @@ dependencies = [ "ring", "rusticata-macros", "thiserror", - "time 0.3.25", + "time", ] [[package]] @@ -7694,7 +7615,7 @@ dependencies = [ "oid-registry 0.6.1", "rusticata-macros", "thiserror", - "time 0.3.25", + "time", ] [[package]] @@ -7739,7 +7660,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time 0.3.25", + "time", ] [[package]] From a37a72744a65357ead7c8c86de56bc8d69196358 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 14:39:10 -0300 Subject: [PATCH 097/235] fix formatting --- transports/webrtc-utils/src/lib.rs | 2 +- transports/webrtc-utils/src/sdp.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/transports/webrtc-utils/src/lib.rs b/transports/webrtc-utils/src/lib.rs index fe2cbf274d6..59440f30e6e 100644 --- a/transports/webrtc-utils/src/lib.rs +++ b/transports/webrtc-utils/src/lib.rs @@ -6,10 +6,10 @@ pub mod proto { pub use self::webrtc::pb::{mod_Message::Flag, Message}; } +pub mod error; pub mod fingerprint; pub mod noise; pub mod sdp; pub mod stream; -pub mod error; pub use error::Error; diff --git a/transports/webrtc-utils/src/sdp.rs b/transports/webrtc-utils/src/sdp.rs index 22d6836bc3b..6344e58e2d9 100644 --- a/transports/webrtc-utils/src/sdp.rs +++ b/transports/webrtc-utils/src/sdp.rs @@ -18,10 +18,10 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -#[cfg(feature = "wasm-bindgen")] -pub mod wasm; #[cfg(feature = "tokio")] pub mod tokio; +#[cfg(feature = "wasm-bindgen")] +pub mod wasm; use crate::fingerprint::Fingerprint; use serde::Serialize; @@ -101,7 +101,6 @@ use tinytemplate::TinyTemplate; // // The maximum SCTP user message size (in bytes). (RFC8841) - // See [`CLIENT_SESSION_DESCRIPTION`]. // // a=ice-lite From d10203ce7c92ac7dd5db759de6ea273f10ea5765 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 14:39:20 -0300 Subject: [PATCH 098/235] leptos uses nightly --- examples/browser-webrtc/client/rust-toolchain.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 examples/browser-webrtc/client/rust-toolchain.toml diff --git a/examples/browser-webrtc/client/rust-toolchain.toml b/examples/browser-webrtc/client/rust-toolchain.toml new file mode 100644 index 00000000000..b33505e5c7c --- /dev/null +++ b/examples/browser-webrtc/client/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file From 12fdffaa67b9020f729d95672260042b86ce5c6b Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 14:46:19 -0300 Subject: [PATCH 099/235] fix test --- transports/webrtc-utils/src/sdp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc-utils/src/sdp.rs b/transports/webrtc-utils/src/sdp.rs index 6344e58e2d9..b8fa596463d 100644 --- a/transports/webrtc-utils/src/sdp.rs +++ b/transports/webrtc-utils/src/sdp.rs @@ -251,7 +251,7 @@ mod sdp_tests { #[test] fn test_fingerprint() { - let sdp: &str = "v=0\no=- 0 0 IN IP6 ::1\ns=-\nc=IN IP6 ::1\nt=0 0\na=ice-lite\nm=application 61885 UDP/DTLS/SCTP webrtc-datachannel\na=mid:0\na=setup:passive\na=ice-ufrag:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\na=ice-pwd:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\na=fingerprint:sha-256 A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89\na=sctp-port:5000\na=max-message-size:16384\na=candidate:1467250027 1 UDP 1467250027 ::1 61885 typ host\n"; + let sdp: &str = "v=0\r\no=- 0 0 IN IP6 ::1\r\ns=-\r\nc=IN IP6 ::1\r\nt=0 0\r\na=ice-lite\r\nm=application 61885 UDP/DTLS/SCTP webrtc-datachannel\r\na=mid:0\r\na=setup:passive\r\na=ice-ufrag:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\r\na=ice-pwd:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\r\na=fingerprint:sha-256 A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89\r\na=sctp-port:5000\r\na=max-message-size:16384\r\na=candidate:1467250027 1 UDP 1467250027 ::1 61885 typ host\r\n"; let fingerprint = match fingerprint(sdp) { Some(fingerprint) => fingerprint, None => panic!("No fingerprint found"), From 97ed723ab359472dd364c06825119d2ebba926d0 Mon Sep 17 00:00:00 2001 From: Doug A Date: Thu, 17 Aug 2023 15:27:59 -0300 Subject: [PATCH 100/235] add rust-version.workspace --- examples/browser-webrtc/server/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/browser-webrtc/server/Cargo.toml b/examples/browser-webrtc/server/Cargo.toml index 8c6ebef2b75..60a85821826 100644 --- a/examples/browser-webrtc/server/Cargo.toml +++ b/examples/browser-webrtc/server/Cargo.toml @@ -3,6 +3,7 @@ authors = ["Doug Anderson "] edition = "2021" license = "MIT" name = "webrtc-example-server" +rust-version.workspace = true version = "0.1.0" [[bin]] From 99cc8da78ed7f7c38ebec23f4ce0a2a1163dbf7a Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 18 Aug 2023 09:38:02 -0300 Subject: [PATCH 101/235] switch Leptos example to rust stable channel --- examples/browser-webrtc/client/Cargo.toml | 2 +- examples/browser-webrtc/client/rust-toolchain.toml | 4 ++-- examples/browser-webrtc/client/src/lib.rs | 6 +++--- examples/browser-webrtc/client/src/pinger.rs | 5 ++--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/browser-webrtc/client/Cargo.toml b/examples/browser-webrtc/client/Cargo.toml index ccb66d475bc..c96aceaef47 100644 --- a/examples/browser-webrtc/client/Cargo.toml +++ b/examples/browser-webrtc/client/Cargo.toml @@ -12,7 +12,7 @@ cfg-if = "0.1" console_log = { version = "0.2", optional = true } fern = { version = "0.6.2", features = ["colored"], optional = true } futures = "0.3.28" -leptos = { version = "0.4.8", features = ["csr", "nightly"] } +leptos = { version = "0.4.8", features = ["csr"] } libp2p = { path = "../../../libp2p", features = [ "ed25519", "macros", diff --git a/examples/browser-webrtc/client/rust-toolchain.toml b/examples/browser-webrtc/client/rust-toolchain.toml index b33505e5c7c..292fe499e3b 100644 --- a/examples/browser-webrtc/client/rust-toolchain.toml +++ b/examples/browser-webrtc/client/rust-toolchain.toml @@ -1,2 +1,2 @@ -[toolchain] -channel = "nightly" \ No newline at end of file +[toolchain] +channel = "stable" diff --git a/examples/browser-webrtc/client/src/lib.rs b/examples/browser-webrtc/client/src/lib.rs index d3d62643238..e48147b3de1 100644 --- a/examples/browser-webrtc/client/src/lib.rs +++ b/examples/browser-webrtc/client/src/lib.rs @@ -55,13 +55,13 @@ pub fn App(cx: Scope) -> impl IntoView {

    "Pinging every 15 seconds. Open Browser console for more logging details."

      - {stamp}" in " + {stamp/1000}" seconds later in " {rtt} "ms" } diff --git a/examples/browser-webrtc/client/src/pinger.rs b/examples/browser-webrtc/client/src/pinger.rs index 6b311728be2..a4cae56b095 100644 --- a/examples/browser-webrtc/client/src/pinger.rs +++ b/examples/browser-webrtc/client/src/pinger.rs @@ -2,8 +2,7 @@ use super::fetch_server_addr; use async_channel::Sender; use futures::StreamExt; use libp2p::core::Multiaddr; -use libp2p::identity; -use libp2p::identity::PeerId; +use libp2p::identity::{Keypair, PeerId}; use libp2p::ping; use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmBuilder, SwarmEvent}; use libp2p::{multiaddr, swarm}; @@ -12,7 +11,7 @@ use std::convert::From; pub async fn start_pinger(sendr: Sender) -> Result<(), PingerError> { let addr_fut = fetch_server_addr(); - let local_key = identity::Keypair::generate_ed25519(); + let local_key = Keypair::generate_ed25519(); let local_peer_id = PeerId::from(local_key.public()); let mut swarm = SwarmBuilder::with_wasm_executor( From ef6ebee74b02e3db70202f3debe5f0fcb829b72b Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 18 Aug 2023 09:41:54 -0300 Subject: [PATCH 102/235] switch to Ipv6Addr::UNSPECIFIED for CI --- interop-tests/src/arch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs index 9f3484059f7..07ac589ff65 100644 --- a/interop-tests/src/arch.rs +++ b/interop-tests/src/arch.rs @@ -116,7 +116,7 @@ pub(crate) mod native { .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) .boxed(), // IPv4 does not pass tests locally, but IPv6 does. - Multiaddr::from(Ipv6Addr::LOCALHOST) + Multiaddr::from(Ipv6Addr::UNSPECIFIED) .with(Protocol::Udp(0)) .with(Protocol::WebRTCDirect) .to_string(), From b26046e12a685d5ef05386030eec6e3b516d1d77 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 18 Aug 2023 09:45:57 -0300 Subject: [PATCH 103/235] rust-version = { workspace = true } --- examples/browser-webrtc/client/Cargo.toml | 2 +- examples/browser-webrtc/server/Cargo.toml | 2 +- transports/webrtc-utils/Cargo.toml | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/browser-webrtc/client/Cargo.toml b/examples/browser-webrtc/client/Cargo.toml index c96aceaef47..01fb0834e64 100644 --- a/examples/browser-webrtc/client/Cargo.toml +++ b/examples/browser-webrtc/client/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Doug Anderson "] edition = "2021" license = "MIT" name = "browser-webrtc-example-client" -rust-version.workspace = true +rust-version = { workspace = true } version = "0.1.0" [dependencies] diff --git a/examples/browser-webrtc/server/Cargo.toml b/examples/browser-webrtc/server/Cargo.toml index 60a85821826..ca3e195c4fd 100644 --- a/examples/browser-webrtc/server/Cargo.toml +++ b/examples/browser-webrtc/server/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Doug Anderson "] edition = "2021" license = "MIT" name = "webrtc-example-server" -rust-version.workspace = true +rust-version = { workspace = true } version = "0.1.0" [[bin]] diff --git a/transports/webrtc-utils/Cargo.toml b/transports/webrtc-utils/Cargo.toml index b7a332c2f8e..21e5a331ed9 100644 --- a/transports/webrtc-utils/Cargo.toml +++ b/transports/webrtc-utils/Cargo.toml @@ -1,12 +1,14 @@ [package] +authors = ["Doug Anderson "] +categories = ["libp2p", "webrtc"] +description = "Utilities for WebRTC in libp2p" edition = "2021" license = "MIT" name = "libp2p-webrtc-utils" -rust-version.workspace = true +repository = "https://github.com/libp2p/rust-libp2p" +rust-version = { workspace = true } version = "0.1.0" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] bytes = "1" futures = "0.3" From 177756600830bf125bd06f3f42daa05c9ed753fd Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 18 Aug 2023 09:46:07 -0300 Subject: [PATCH 104/235] cleanup & docs --- transports/webrtc-websys/CHANGELOG.md | 3 +++ transports/webrtc-websys/Cargo.toml | 3 ++- transports/webrtc-websys/README.md | 9 +++++++ transports/webrtc-websys/src/connection.rs | 17 +++++++++++-- transports/webrtc-websys/src/error.rs | 1 - transports/webrtc-websys/src/lib.rs | 2 ++ transports/webrtc-websys/src/stream.rs | 29 +++++----------------- transports/webrtc-websys/src/transport.rs | 2 -- transports/webrtc-websys/src/upgrade.rs | 4 +++ 9 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 transports/webrtc-websys/CHANGELOG.md create mode 100644 transports/webrtc-websys/README.md diff --git a/transports/webrtc-websys/CHANGELOG.md b/transports/webrtc-websys/CHANGELOG.md new file mode 100644 index 00000000000..5ed2a535939 --- /dev/null +++ b/transports/webrtc-websys/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0-alpha + +- Initial alpha release. diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index cac0a057725..7a2930537be 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -52,10 +52,11 @@ web-sys = { version = "0.3.64", features = [ "RtcDataChannelState", "RtcDataChannelType", "RtcPeerConnection", - "RtcPeerConnectionIceEvent", "RtcSessionDescription", "RtcSessionDescriptionInit", ] } [dev-dependencies] hex-literal = "0.4" +libp2p-ping = { workspace = true } +libp2p-swarm = { workspace = true, features = ["wasm-bindgen"] } diff --git a/transports/webrtc-websys/README.md b/transports/webrtc-websys/README.md new file mode 100644 index 00000000000..b4b26b614f0 --- /dev/null +++ b/transports/webrtc-websys/README.md @@ -0,0 +1,9 @@ +# (rust) libp2p-webrtc-websys + +Browser Transport made available through `web-sys` bindings. + +## Usage + +Use with `with_wasm_executor` to enable the `wasm-bindgen` executor for the swarm. + +See the [browser-webrtc](../../examples/browser-webrtc) example for a full example. diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 46f99b9dad7..8366a01c518 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -1,5 +1,6 @@ //! Websys WebRTC Peer Connection //! +//! Creates and manages the [RtcPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) use crate::stream::RtcDataChannelBuilder; use super::{Error, Stream}; @@ -14,6 +15,7 @@ use std::task::{ready, Context, Poll}; use wasm_bindgen::prelude::*; use web_sys::{RtcDataChannel, RtcDataChannelEvent, RtcPeerConnection}; +/// A WebRTC Connection pub struct Connection { // Swarm needs all types to be Send. WASM is single-threaded // and it is safe to use SendWrapper. @@ -21,24 +23,32 @@ pub struct Connection { } impl Connection { - /// Create a new Connection + /// Create a new WebRTC Connection pub(crate) fn new(peer_connection: RtcPeerConnection) -> Self { Self { inner: SendWrapper::new(ConnectionInner::new(peer_connection)), } } } + +/// Inner Connection that is wrapped in [SendWrapper] struct ConnectionInner { + /// The [RtcPeerConnection] that is used for the WebRTC Connection peer_connection: RtcPeerConnection, + /// Whether the connection is closed closed: bool, + /// A channel that signals incoming data channels rx_ondatachannel: channel::mpsc::Receiver, /// A list of futures, which, once completed, signal that a [`WebRTCStream`] has been dropped. + /// Currently unimplemented, will be implemented in a future PR. drop_listeners: FuturesUnordered, + /// Currently unimplemented, will be implemented in a future PR. no_drop_listeners_waker: Option, } impl ConnectionInner { + /// Create a new inner WebRTC Connection fn new(peer_connection: RtcPeerConnection) -> Self { // An ondatachannel Future enables us to poll for incoming data channel events in poll_incoming let (mut tx_ondatachannel, rx_ondatachannel) = channel::mpsc::channel(4); // we may get more than one data channel opened on a single peer connection @@ -139,9 +149,10 @@ impl Drop for ConnectionInner { /// WebRTC native multiplexing /// Allows users to open substreams impl StreamMuxer for Connection { - type Substream = Stream; // A Substream of a WebRTC PeerConnection is a Data Channel + type Substream = Stream; // A Stream of a WebRTC PeerConnection is a Data Channel type Error = Error; + /// Polls for inbound connections by waiting for the ondatachannel callback to be triggered fn poll_inbound( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -159,6 +170,7 @@ impl StreamMuxer for Connection { self.inner.poll_create_data_channel(cx) } + /// Closes the Peer Connection. fn poll_close( mut self: Pin<&mut Self>, _cx: &mut Context<'_>, @@ -169,6 +181,7 @@ impl StreamMuxer for Connection { Poll::Ready(Ok(())) } + /// Polls the connection fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/transports/webrtc-websys/src/error.rs b/transports/webrtc-websys/src/error.rs index 5421eeb6bde..eb0241df960 100644 --- a/transports/webrtc-websys/src/error.rs +++ b/transports/webrtc-websys/src/error.rs @@ -45,7 +45,6 @@ impl std::convert::From for Error { } } -// impl From String impl From for Error { fn from(value: String) -> Self { Error::JsError(value) diff --git a/transports/webrtc-websys/src/lib.rs b/transports/webrtc-websys/src/lib.rs index e3538f87a72..3b1f842f45e 100644 --- a/transports/webrtc-websys/src/lib.rs +++ b/transports/webrtc-websys/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + mod connection; mod error; mod stream; diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 7a9af5f0ab4..39507fe766b 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -1,4 +1,4 @@ -//! The Substream over the Connection +//! The WebRTC [Stream] over the Connection use self::framed_dc::FramedDc; use bytes::Bytes; use futures::{AsyncRead, AsyncWrite, Sink, SinkExt, StreamExt}; @@ -19,15 +19,6 @@ mod poll_data_channel; pub(crate) use drop_listener::DropListener; -// Max message size that can be sent to the DataChannel -// const MAX_MESSAGE_SIZE: u32 = 16 * 1024; - -// How much can be buffered to the DataChannel at once -// const MAX_BUFFERED_AMOUNT: u32 = 16 * 1024 * 1024; - -// How long time we wait for the 'bufferedamountlow' event to be emitted -// const BUFFERED_AMOUNT_LOW_TIMEOUT: u32 = 30 * 1000; - /// The Browser Default is Blob, so we must set ours to Arraybuffer explicitly const ARRAY_BUFFER_BINARY_TYPE: RtcDataChannelType = RtcDataChannelType::Arraybuffer; @@ -68,8 +59,11 @@ impl RtcDataChannelBuilder { /// Substream over the Connection pub struct Stream { + /// Wrapper for the inner stream to make it Send inner: SendWrapper, + /// State of the Stream state: State, + /// Buffer for incoming data read_buffer: Bytes, } @@ -82,11 +76,6 @@ impl Stream { state: State::Open, } } - - /// Return the underlying RtcDataChannel - pub fn channel(&self) -> &RtcDataChannel { - self.inner.io.as_ref() - } } /// Inner Stream to make Sendable @@ -94,6 +83,7 @@ struct StreamInner { io: FramedDc, } +/// Inner Stream to make Sendable impl StreamInner { pub fn new(channel: RtcDataChannel) -> Self { Self { @@ -156,13 +146,6 @@ impl AsyncWrite for Stream { /// arranges for the current task (via cx.waker().wake_by_ref()) /// to receive a notification when the object becomes writable /// or is closed. - /// - /// In WebRTC DataChannels, we can always write to the channel - /// so we don't need to poll the channel for writability - /// as long as the channel is open - /// - /// So we need access to the channel or peer_connection, - /// and the State of the Channel (DataChannel.readyState) fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context, @@ -239,7 +222,7 @@ impl AsyncWrite for Stream { self.state.write_closed(); - // TODO: implement drop_notifier? + // TODO: implement drop_notifier? Drop notifier built into the Browser as 'onclose' event // let _ = self // .drop_notifier // .take() diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs index ecba15cf67a..1a2b5939e44 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc-websys/src/transport.rs @@ -73,10 +73,8 @@ impl libp2p_core::Transport for Transport { } let config = self.config.clone(); - // let mut connection = Connection::new(sock_addr, server_fingerprint, config.keypair.clone()); Ok(async move { - // let peer_id = connection.connect().await?; let (peer_id, connection) = upgrade::outbound(sock_addr, server_fingerprint, config.keypair.clone()).await?; diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index b2cb3ee0f96..811b7268a95 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -15,6 +15,8 @@ use web_sys::{RtcConfiguration, RtcPeerConnection}; const SHA2_256: u64 = 0x12; const SHA2_512: u64 = 0x13; +/// Upgrades an outbound WebRTC connection by creating the data channel +/// and conducting a Noise handshake pub(crate) async fn outbound( sock_addr: SocketAddr, remote_fingerprint: Fingerprint, @@ -24,6 +26,7 @@ pub(crate) async fn outbound( fut.await } +/// Inner outbound function that is wrapped in [SendWrapper] async fn outbound_inner( sock_addr: SocketAddr, remote_fingerprint: Fingerprint, @@ -110,6 +113,7 @@ async fn outbound_inner( Ok((peer_id, Connection::new(peer_connection))) } +/// Generates a random ufrag of the given length fn gen_ufrag(len: usize) -> String { let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; From f7ecb183127f6eb2c9aeb2838fa7bdc32ee096b7 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 18 Aug 2023 11:15:53 -0300 Subject: [PATCH 105/235] rename bin to webrtc-example-server --- .../server/src/bin/{main.rs => webrtc-example-server.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/browser-webrtc/server/src/bin/{main.rs => webrtc-example-server.rs} (100%) diff --git a/examples/browser-webrtc/server/src/bin/main.rs b/examples/browser-webrtc/server/src/bin/webrtc-example-server.rs similarity index 100% rename from examples/browser-webrtc/server/src/bin/main.rs rename to examples/browser-webrtc/server/src/bin/webrtc-example-server.rs From 40b24e8db2848c7ddb021ad933c1ea787454784e Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 18 Aug 2023 11:16:18 -0300 Subject: [PATCH 106/235] log, publish (false) and naming --- examples/browser-webrtc/server/Cargo.toml | 4 +++- examples/browser-webrtc/server/src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/browser-webrtc/server/Cargo.toml b/examples/browser-webrtc/server/Cargo.toml index ca3e195c4fd..86c40070cf9 100644 --- a/examples/browser-webrtc/server/Cargo.toml +++ b/examples/browser-webrtc/server/Cargo.toml @@ -3,12 +3,13 @@ authors = ["Doug Anderson "] edition = "2021" license = "MIT" name = "webrtc-example-server" +publish = false rust-version = { workspace = true } version = "0.1.0" [[bin]] name = "webrtc-example-server-bin" -path = "src/bin/main.rs" +path = "src/bin/webrtc-example-server.rs" [dependencies] anyhow = "1.0.72" @@ -22,6 +23,7 @@ libp2p-ping = { workspace = true } libp2p-relay = { workspace = true } libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } libp2p-webrtc = { workspace = true, features = ["tokio"] } +log = "0.4" multiaddr = { workspace = true } rand = "0.8" tokio = { version = "1.29", features = ["macros", "net", "rt", "signal"] } diff --git a/examples/browser-webrtc/server/src/lib.rs b/examples/browser-webrtc/server/src/lib.rs index c6e86b9e36c..f1e3c6c8014 100644 --- a/examples/browser-webrtc/server/src/lib.rs +++ b/examples/browser-webrtc/server/src/lib.rs @@ -44,7 +44,7 @@ pub async fn start(remote: Option) -> Result<()> { // Dial the peer identified by the multi-address given as the second // command-line argument, if any. if let Some(addr) = remote { - println!("Dialing {addr}"); + log::info!("Dialing {addr}"); swarm.dial(addr)?; } From 66bbf6c5638fa9b66f35ffb6e9fdfe64fdc37bd8 Mon Sep 17 00:00:00 2001 From: Doug A Date: Sat, 19 Aug 2023 11:34:45 -0300 Subject: [PATCH 107/235] refactor example/client to vanilla wasm --- examples/browser-webrtc/client/.gitignore | 6 + examples/browser-webrtc/client/Cargo.toml | 32 ++-- examples/browser-webrtc/client/LICENSE | 25 +++ examples/browser-webrtc/client/README.md | 20 ++- examples/browser-webrtc/client/index.html | 20 ++- .../browser-webrtc/client/rust-toolchain.toml | 2 - examples/browser-webrtc/client/src/error.rs | 35 ++++ examples/browser-webrtc/client/src/lib.rs | 159 +++++++----------- examples/browser-webrtc/client/src/main.rs | 43 ----- examples/browser-webrtc/client/src/pinger.rs | 84 ++++++--- examples/browser-webrtc/client/src/utils.rs | 10 ++ 11 files changed, 248 insertions(+), 188 deletions(-) create mode 100644 examples/browser-webrtc/client/.gitignore create mode 100644 examples/browser-webrtc/client/LICENSE delete mode 100644 examples/browser-webrtc/client/rust-toolchain.toml create mode 100644 examples/browser-webrtc/client/src/error.rs delete mode 100644 examples/browser-webrtc/client/src/main.rs create mode 100644 examples/browser-webrtc/client/src/utils.rs diff --git a/examples/browser-webrtc/client/.gitignore b/examples/browser-webrtc/client/.gitignore new file mode 100644 index 00000000000..4e301317e55 --- /dev/null +++ b/examples/browser-webrtc/client/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk +Cargo.lock +bin/ +pkg/ +wasm-pack.log diff --git a/examples/browser-webrtc/client/Cargo.toml b/examples/browser-webrtc/client/Cargo.toml index 01fb0834e64..19b25f1f2a3 100644 --- a/examples/browser-webrtc/client/Cargo.toml +++ b/examples/browser-webrtc/client/Cargo.toml @@ -6,13 +6,16 @@ name = "browser-webrtc-example-client" rust-version = { workspace = true } version = "0.1.0" +[lib] +crate-type = ["cdylib"] + +[features] +default = ["console_error_panic_hook"] + [dependencies] -async-channel = "1.9.0" -cfg-if = "0.1" -console_log = { version = "0.2", optional = true } -fern = { version = "0.6.2", features = ["colored"], optional = true } +console_log = { version = "1.0", features = ["wasm-bindgen"] } futures = "0.3.28" -leptos = { version = "0.4.8", features = ["csr"] } +js-sys = "0.3.64" libp2p = { path = "../../../libp2p", features = [ "ed25519", "macros", @@ -21,14 +24,15 @@ libp2p = { path = "../../../libp2p", features = [ ] } libp2p-webrtc-websys = { workspace = true } log = "0.4" -wasm-bindgen = "0.2.87" +wasm-bindgen = "0.2.84" wasm-bindgen-futures = "0.4.37" -web-sys = { version = "0.3.64", features = [ - "Response", - "Window", - 'Performance', - 'PerformanceTiming', -] } -[features] -default = ["console_log", "fern"] +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.7", optional = true } + +[dependencies.web-sys] +features = ['Document', 'Element', 'HtmlElement', 'Node', 'Response', 'Window'] +version = "0.3" diff --git a/examples/browser-webrtc/client/LICENSE b/examples/browser-webrtc/client/LICENSE new file mode 100644 index 00000000000..801421ee604 --- /dev/null +++ b/examples/browser-webrtc/client/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2023 Doug A + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/examples/browser-webrtc/client/README.md b/examples/browser-webrtc/client/README.md index dc95038f4bd..e51129648cc 100644 --- a/examples/browser-webrtc/client/README.md +++ b/examples/browser-webrtc/client/README.md @@ -1,7 +1,19 @@ -# Use rust-libp2p in the Browser +## Example Client Usage of `libp2p-webrtc-websys` -## Run Leptos App +A simple demo usage of `libp2p-webrtc-websys`. -Ensure you have `trunk` [installed](https://trunkrs.dev/). +## 🚴 Usage -From this directory, run `trunk serve --open` +### 🛠️ Build with `wasm-pack` + +``` +wasm-pack build --release --target web +``` + +### 🎁 Serve webpage + +The static site at `index.html` can be served with any static HTTP server, such as [`http-server`](https://github.com/http-party/http-server): + +``` +npx http-server +``` diff --git a/examples/browser-webrtc/client/index.html b/examples/browser-webrtc/client/index.html index 4d9c7fd0435..bbba2ba2cad 100644 --- a/examples/browser-webrtc/client/index.html +++ b/examples/browser-webrtc/client/index.html @@ -1,5 +1,19 @@ - - - + + + + + +
      +

      Rust Libp2p Demo!

      +
      + + + diff --git a/examples/browser-webrtc/client/rust-toolchain.toml b/examples/browser-webrtc/client/rust-toolchain.toml deleted file mode 100644 index 292fe499e3b..00000000000 --- a/examples/browser-webrtc/client/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "stable" diff --git a/examples/browser-webrtc/client/src/error.rs b/examples/browser-webrtc/client/src/error.rs new file mode 100644 index 00000000000..29cba3eeda0 --- /dev/null +++ b/examples/browser-webrtc/client/src/error.rs @@ -0,0 +1,35 @@ +use futures::channel; +use libp2p::ping::Failure; +use libp2p::{multiaddr, swarm}; + +#[derive(Debug)] +pub(crate) enum PingerError { + Ping(Failure), + MultiaddrParse(multiaddr::Error), + Dial(swarm::DialError), + Other(String), +} + +impl From for PingerError { + fn from(f: Failure) -> Self { + PingerError::Ping(f) + } +} + +impl From for PingerError { + fn from(err: multiaddr::Error) -> Self { + PingerError::MultiaddrParse(err) + } +} + +impl From for PingerError { + fn from(err: swarm::DialError) -> Self { + PingerError::Dial(err) + } +} + +impl From for PingerError { + fn from(err: channel::mpsc::SendError) -> Self { + PingerError::Other(format!("SendError: {:?}", err)) + } +} diff --git a/examples/browser-webrtc/client/src/lib.rs b/examples/browser-webrtc/client/src/lib.rs index e48147b3de1..b6ce642c863 100644 --- a/examples/browser-webrtc/client/src/lib.rs +++ b/examples/browser-webrtc/client/src/lib.rs @@ -1,119 +1,86 @@ -use async_channel::bounded; -use leptos::*; +mod error; +mod pinger; +mod utils; + +use crate::error::PingerError; +use futures::channel; +use futures::StreamExt; +use js_sys::Date; use pinger::start_pinger; -use wasm_bindgen::JsCast; +use wasm_bindgen::prelude::*; use wasm_bindgen_futures::spawn_local; -use wasm_bindgen_futures::JsFuture; -use web_sys::{window, Response}; -pub mod pinger; +#[wasm_bindgen] +extern "C" { + // Use `js_namespace` here to bind `console.log(..)` instead of just + // `log(..)` + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + +macro_rules! console_log { + // Note that this is using the `log` function imported above during + // `bare_bones` + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) +} + +#[wasm_bindgen(start)] +pub fn run() -> Result<(), JsValue> { + console_log!("Starting main()"); + + match console_log::init_with_level(log::Level::Info) { + Ok(_) => log::info!("Console logging initialized"), + Err(_) => log::info!("Console logging already initialized"), + }; -// The PORT that the server serves their Multiaddr -pub const PORT: u16 = 4455; + // Use `web_sys`'s global `window` function to get a handle on the global + // window object. + let window = web_sys::window().expect("no global `window` exists"); + let document = window.document().expect("should have a document on window"); + let body = document.body().expect("document should have a body"); + + // Manufacture the element we're gonna append + let val = document.create_element("p")?; + val.set_text_content(Some("Let's ping the WebRTC Server!")); + + body.append_child(&val)?; -/// Our main Leptos App component -#[component] -pub fn App(cx: Scope) -> impl IntoView { // create a mpsc channel to get pings to from the pinger - let (sendr, recvr) = bounded::(2); + let (sendr, mut recvr) = channel::mpsc::unbounded::>(); - // create Leptos signal to update the Pings diplayed - let (number_of_pings, set_number_of_pings) = create_signal(cx, Vec::new()); + log::info!("Spawn a pinger"); + console_log!("Spawn a pinger main()"); // start the pinger, pass in our sender spawn_local(async move { + log::info!("Spawning pinger"); match start_pinger(sendr).await { Ok(_) => log::info!("Pinger finished"), - Err(e) => log::error!("Pinger error: {:?}", e), + Err(e) => log::info!("Pinger error: {:?}", e), }; }); - // update number of pings signal each time out receiver receives an update + // loop on recvr await, appending to the DOM with date and RTT when we get it spawn_local(async move { - let window = web_sys::window().expect("should have a window in this context"); - let performance = window - .performance() - .expect("performance should be available"); - loop { - match recvr.recv().await { - Ok(rtt) => { - // set rtt and date time stamp - // use leptos performance now - let now = performance.now() as u64; - log::info!("[{now:?}] Got RTT: {rtt:?} ms"); - set_number_of_pings.update(move |pings| pings.insert(0, (now, rtt))); + match recvr.next().await { + Some(Ok(rtt)) => { + log::info!("Got RTT: {}", rtt); + let val = document + .create_element("p") + .expect("should create a p elem"); + val.set_text_content(Some(&format!( + "RTT: {}ms at {}", + rtt, + Date::new_0().to_string() + ))); + body.append_child(&val).expect("should append body elem"); } - Err(e) => log::error!("Pinger channel closed: {:?}", e), + Some(Err(e)) => log::info!("Error: {:?}", e), + None => log::info!("Recvr channel closed"), } } }); - // Build our DOM HTML - view! { cx, -

      "Rust Libp2p WebRTC Demo"

      -

      "Pinging every 15 seconds. Open Browser console for more logging details."

      -
        - - {stamp/1000}" seconds later in " - {rtt} "ms" - - } - } - /> -
      - } -} - -/// Helper that returns the multiaddress of echo-server -/// -/// It fetches the multiaddress via HTTP request to -/// 127.0.0.1:4455. -pub async fn fetch_server_addr() -> String { - let url = format!("http://127.0.0.1:{}/", PORT); - let window = window().expect("no global `window` exists"); - - let value = match JsFuture::from(window.fetch_with_str(&url)).await { - Ok(value) => value, - Err(err) => { - log::error!("fetch failed: {:?}", err); - return "".to_string(); - } - }; - let resp = match value.dyn_into::() { - Ok(resp) => resp, - Err(err) => { - log::error!("fetch response failed: {:?}", err); - return "".to_string(); - } - }; - - let text = match resp.text() { - Ok(text) => text, - Err(err) => { - log::error!("fetch text failed: {:?}", err); - return "".to_string(); - } - }; - let text = match JsFuture::from(text).await { - Ok(text) => text, - Err(err) => { - log::error!("convert future failed: {:?}", err); - return "".to_string(); - } - }; - - match text.as_string().filter(|s| !s.is_empty()) { - Some(text) => text, - None => { - log::error!("fetch text is empty"); - "".to_string() - } - } + Ok(()) } diff --git a/examples/browser-webrtc/client/src/main.rs b/examples/browser-webrtc/client/src/main.rs deleted file mode 100644 index 1682545bc34..00000000000 --- a/examples/browser-webrtc/client/src/main.rs +++ /dev/null @@ -1,43 +0,0 @@ -use browser_webrtc_example_client::App; -use leptos::*; - -fn main() { - match init_log() { - Ok(_) => log::info!("Logging initialized"), - Err(e) => log::error!("Error initializing logging: {:?}", e), - } - leptos::mount_to_body(|cx| view! { cx, }) -} - -// Add Logging to the Browser console -fn init_log() -> Result<(), log::SetLoggerError> { - use fern::colors::{Color, ColoredLevelConfig}; - - let colors_line = ColoredLevelConfig::new() - .error(Color::Red) - .warn(Color::Yellow) - // we actually don't need to specify the color for debug and info, they are white by default - .info(Color::Green) - .debug(Color::BrightBlack) - // depending on the terminals color scheme, this is the same as the background color - .trace(Color::White); - - let colors_level = colors_line.info(Color::Green); - - fern::Dispatch::new() - .format(move |out, message, record| { - out.finish(format_args!( - "{color_line}[{level} {target}{color_line}] {message}\x1B[0m", - color_line = format_args!( - "\x1B[{}m", - colors_line.get_color(&record.level()).to_fg_str() - ), - target = record.target(), - level = colors_level.color(record.level()), - message = message, - )) - }) - .level(log::LevelFilter::Trace) - .chain(fern::Output::call(console_log::log)) - .apply() -} diff --git a/examples/browser-webrtc/client/src/pinger.rs b/examples/browser-webrtc/client/src/pinger.rs index a4cae56b095..816299d707c 100644 --- a/examples/browser-webrtc/client/src/pinger.rs +++ b/examples/browser-webrtc/client/src/pinger.rs @@ -1,16 +1,23 @@ -use super::fetch_server_addr; -use async_channel::Sender; -use futures::StreamExt; +use crate::error::PingerError; +use futures::{channel, SinkExt, StreamExt}; use libp2p::core::Multiaddr; use libp2p::identity::{Keypair, PeerId}; use libp2p::ping; use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmBuilder, SwarmEvent}; -use libp2p::{multiaddr, swarm}; use std::convert::From; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::JsFuture; -pub async fn start_pinger(sendr: Sender) -> Result<(), PingerError> { - let addr_fut = fetch_server_addr(); +// The PORT that the server serves their Multiaddr +pub const PORT: u16 = 4455; +pub(crate) async fn start_pinger( + mut sendr: channel::mpsc::UnboundedSender>, +) -> Result<(), PingerError> { + log::info!("start_pinger"); + let addr = fetch_server_addr().await; + + log::trace!("Got addr {} from server", addr); let local_key = Keypair::generate_ed25519(); let local_peer_id = PeerId::from(local_key.public()); @@ -26,8 +33,6 @@ pub async fn start_pinger(sendr: Sender) -> Result<(), PingerError> { log::info!("Running pinger with peer_id: {}", swarm.local_peer_id()); - let addr = addr_fut.await; - log::info!("Dialing {}", addr); swarm.dial(addr.parse::()?)?; @@ -36,16 +41,15 @@ pub async fn start_pinger(sendr: Sender) -> Result<(), PingerError> { match swarm.next().await.unwrap() { SwarmEvent::Behaviour(BehaviourEvent::Ping(ping::Event { result: Err(e), .. })) => { log::error!("Ping failed: {:?}", e); - let _result = sendr.send(-1.).await; + sendr.send(Err(e.into())).await?; } SwarmEvent::Behaviour(BehaviourEvent::Ping(ping::Event { peer, result: Ok(rtt), .. })) => { - log::info!("Ping successful: {rtt:?}, {peer}"); - let _result = sendr.send(rtt.as_micros() as f32 / 1000.).await; - log::debug!("RTT Sent"); + log::info!("Ping successful: RTT: {rtt:?}, from {peer}"); + sendr.send(Ok(rtt.as_secs_f32())).await?; } evt => log::info!("Swarm event: {:?}", evt), } @@ -58,21 +62,49 @@ struct Behaviour { keep_alive: keep_alive::Behaviour, } -#[derive(Debug)] -pub enum PingerError { - AddrParse(std::net::AddrParseError), - MultiaddrParse(multiaddr::Error), - Dial(swarm::DialError), -} +/// Helper that returns the multiaddress of echo-server +/// +/// It fetches the multiaddress via HTTP request to +/// 127.0.0.1:4455. +pub async fn fetch_server_addr() -> String { + let url = format!("http://127.0.0.1:{}/", PORT); + let window = web_sys::window().expect("no global `window` exists"); -impl From for PingerError { - fn from(err: libp2p::multiaddr::Error) -> Self { - PingerError::MultiaddrParse(err) - } -} + let value = match JsFuture::from(window.fetch_with_str(&url)).await { + Ok(value) => value, + Err(err) => { + log::error!("fetch failed: {:?}", err); + return "".to_string(); + } + }; + let resp = match value.dyn_into::() { + Ok(resp) => resp, + Err(err) => { + log::error!("fetch response failed: {:?}", err); + return "".to_string(); + } + }; -impl From for PingerError { - fn from(err: libp2p::swarm::DialError) -> Self { - PingerError::Dial(err) + let text = match resp.text() { + Ok(text) => text, + Err(err) => { + log::error!("fetch text failed: {:?}", err); + return "".to_string(); + } + }; + let text = match JsFuture::from(text).await { + Ok(text) => text, + Err(err) => { + log::error!("convert future failed: {:?}", err); + return "".to_string(); + } + }; + + match text.as_string().filter(|s| !s.is_empty()) { + Some(text) => text, + None => { + log::error!("fetch text is empty"); + "".to_string() + } } } diff --git a/examples/browser-webrtc/client/src/utils.rs b/examples/browser-webrtc/client/src/utils.rs new file mode 100644 index 00000000000..b1d7929dc9c --- /dev/null +++ b/examples/browser-webrtc/client/src/utils.rs @@ -0,0 +1,10 @@ +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} From 6e0b93b8e8a18a577a521d9af9b615b1f8bf83e4 Mon Sep 17 00:00:00 2001 From: Doug A Date: Sat, 19 Aug 2023 12:03:24 -0300 Subject: [PATCH 108/235] use bounded channel --- examples/browser-webrtc/client/src/lib.rs | 6 +----- examples/browser-webrtc/client/src/pinger.rs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/browser-webrtc/client/src/lib.rs b/examples/browser-webrtc/client/src/lib.rs index b6ce642c863..c47cbd118a9 100644 --- a/examples/browser-webrtc/client/src/lib.rs +++ b/examples/browser-webrtc/client/src/lib.rs @@ -26,8 +26,6 @@ macro_rules! console_log { #[wasm_bindgen(start)] pub fn run() -> Result<(), JsValue> { - console_log!("Starting main()"); - match console_log::init_with_level(log::Level::Info) { Ok(_) => log::info!("Console logging initialized"), Err(_) => log::info!("Console logging already initialized"), @@ -46,14 +44,12 @@ pub fn run() -> Result<(), JsValue> { body.append_child(&val)?; // create a mpsc channel to get pings to from the pinger - let (sendr, mut recvr) = channel::mpsc::unbounded::>(); + let (sendr, mut recvr) = channel::mpsc::channel::>(2); log::info!("Spawn a pinger"); - console_log!("Spawn a pinger main()"); // start the pinger, pass in our sender spawn_local(async move { - log::info!("Spawning pinger"); match start_pinger(sendr).await { Ok(_) => log::info!("Pinger finished"), Err(e) => log::info!("Pinger error: {:?}", e), diff --git a/examples/browser-webrtc/client/src/pinger.rs b/examples/browser-webrtc/client/src/pinger.rs index 816299d707c..d62a645dca1 100644 --- a/examples/browser-webrtc/client/src/pinger.rs +++ b/examples/browser-webrtc/client/src/pinger.rs @@ -12,7 +12,7 @@ use wasm_bindgen_futures::JsFuture; pub const PORT: u16 = 4455; pub(crate) async fn start_pinger( - mut sendr: channel::mpsc::UnboundedSender>, + mut sendr: channel::mpsc::Sender>, ) -> Result<(), PingerError> { log::info!("start_pinger"); let addr = fetch_server_addr().await; From 7bb0884c939bc726efc2b314c92c6130527d5290 Mon Sep 17 00:00:00 2001 From: Doug A Date: Sat, 19 Aug 2023 12:03:36 -0300 Subject: [PATCH 109/235] cleanup --- examples/browser-webrtc/client/index.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/browser-webrtc/client/index.html b/examples/browser-webrtc/client/index.html index bbba2ba2cad..e30edc4e8c2 100644 --- a/examples/browser-webrtc/client/index.html +++ b/examples/browser-webrtc/client/index.html @@ -1,6 +1,11 @@ + @@ -10,7 +15,6 @@

      Rust Libp2p Demo!

      From 311062bce187c96cb093caead716c184ce0620ce Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 13:46:53 +1000 Subject: [PATCH 182/235] Remove `stream_identifier` hack It is only used for logging so we can do without for now. --- .../webrtc-websys/src/stream/drop_listener.rs | 42 +++++++------------ .../src/stream/poll_data_channel.rs | 20 --------- 2 files changed, 14 insertions(+), 48 deletions(-) diff --git a/transports/webrtc-websys/src/stream/drop_listener.rs b/transports/webrtc-websys/src/stream/drop_listener.rs index 0caed467b12..ea633bdff3b 100644 --- a/transports/webrtc-websys/src/stream/drop_listener.rs +++ b/transports/webrtc-websys/src/stream/drop_listener.rs @@ -35,14 +35,8 @@ pub(crate) struct DropListener { impl DropListener { pub(crate) fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { - let substream_id = stream.stream_identifier(); - Self { - state: State::Idle { - stream, - receiver, - substream_id, - }, + state: State::Idle { stream, receiver }, } } } @@ -52,7 +46,6 @@ enum State { Idle { stream: FramedDc, receiver: oneshot::Receiver, - substream_id: u16, }, /// The stream got dropped and we are sending a reset flag. SendingReset { @@ -75,28 +68,21 @@ impl Future for DropListener { match std::mem::replace(state, State::Poisoned) { State::Idle { stream, - substream_id, mut receiver, - } => { - match receiver.poll_unpin(cx) { - Poll::Ready(Ok(GracefullyClosed {})) => { - return Poll::Ready(Ok(())); - } - Poll::Ready(Err(Canceled)) => { - log::info!("Stream {substream_id} dropped without graceful close, sending Reset"); - *state = State::SendingReset { stream }; - continue; - } - Poll::Pending => { - *state = State::Idle { - stream, - substream_id, - receiver, - }; - return Poll::Pending; - } + } => match receiver.poll_unpin(cx) { + Poll::Ready(Ok(GracefullyClosed {})) => { + return Poll::Ready(Ok(())); } - } + Poll::Ready(Err(Canceled)) => { + log::info!("Stream dropped without graceful close, sending Reset"); + *state = State::SendingReset { stream }; + continue; + } + Poll::Pending => { + *state = State::Idle { stream, receiver }; + return Poll::Pending; + } + }, State::SendingReset { mut stream } => match stream.poll_ready_unpin(cx)? { Poll::Ready(()) => { stream.start_send_unpin(Message { diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index 8c43caf21bc..edbf9104eff 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -121,11 +121,6 @@ impl DataChannel { self.inner.ready_state() } - /// Returns the [RtcDataChannel] label - fn label(&self) -> String { - self.inner.label() - } - /// Send data over this [RtcDataChannel] fn send(&self, data: &[u8]) -> Result<(), JsValue> { self.inner.send_with_u8_array(data) @@ -175,21 +170,6 @@ impl PollDataChannel { self.data_channel.borrow().send(data)?; Ok(()) } - - /// StreamIdentifier returns the Stream identifier associated to the stream. - pub(crate) fn stream_identifier(&self) -> u16 { - // self.data_channel.id() // not released (yet), see https://github.com/rustwasm/wasm-bindgen/issues/3547 - - // temp workaround: use label, though it is "" so it's not unique - // TODO: After the above PR is released, use the label instead of the stream id - let label = self.data_channel.borrow().label(); - let b = label.as_bytes(); - let mut stream_id: u16 = 0; - b.iter().enumerate().for_each(|(i, &b)| { - stream_id += (b as u16) << (8 * i); - }); - stream_id - } } impl AsyncRead for PollDataChannel { From 0180da72f95518350bfbcbece4f877d10df086af Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 13:54:04 +1000 Subject: [PATCH 183/235] Simplify `PollDataChannel` implementation --- transports/webrtc-websys/src/stream.rs | 62 ++-- .../webrtc-websys/src/stream/data_channel.rs | 214 +++++++++++++ .../webrtc-websys/src/stream/framed_dc.rs | 11 +- .../src/stream/poll_data_channel.rs | 280 ------------------ 4 files changed, 242 insertions(+), 325 deletions(-) create mode 100644 transports/webrtc-websys/src/stream/data_channel.rs delete mode 100644 transports/webrtc-websys/src/stream/poll_data_channel.rs diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 3206792caa2..5038008040b 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -1,27 +1,27 @@ //! The WebRTC [Stream] over the Connection -use self::framed_dc::FramedDc; -use self::poll_data_channel::DataChannel; +use std::io; +use std::pin::Pin; +use std::task::{ready, Context, Poll}; + use bytes::Bytes; use futures::channel::oneshot; use futures::{AsyncRead, AsyncWrite, Sink, SinkExt, StreamExt}; +use send_wrapper::SendWrapper; +use web_sys::{RtcDataChannel, RtcDataChannelInit, RtcDataChannelType, RtcPeerConnection}; + +pub(crate) use drop_listener::{DropListener, GracefullyClosed}; use libp2p_webrtc_utils::proto::{Flag, Message}; use libp2p_webrtc_utils::stream::{ state::{Closing, State}, MAX_DATA_LEN, }; -use send_wrapper::SendWrapper; -use std::cell::RefCell; -use std::io; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{ready, Context, Poll}; -use web_sys::{RtcDataChannel, RtcDataChannelInit, RtcDataChannelType, RtcPeerConnection}; +use self::data_channel::DataChannel; +use self::framed_dc::FramedDc; + +mod data_channel; mod drop_listener; mod framed_dc; -mod poll_data_channel; - -pub(crate) use drop_listener::{DropListener, GracefullyClosed}; /// The Browser Default is Blob, so we must set ours to Arraybuffer explicitly const ARRAY_BUFFER_BINARY_TYPE: RtcDataChannelType = RtcDataChannelType::Arraybuffer; @@ -64,7 +64,7 @@ impl RtcDataChannelBuilder { /// Stream over the Connection pub struct Stream { /// Wrapper for the inner stream to make it Send - inner: SendWrapper, + inner: SendWrapper, /// State of the Stream state: State, /// Buffer for incoming data @@ -77,12 +77,12 @@ impl Stream { /// Create a new WebRTC Stream pub(crate) fn new(channel: RtcDataChannel) -> (Self, DropListener) { let (sender, receiver) = oneshot::channel(); - let wrapped_dc = Rc::new(RefCell::new(DataChannel::new(channel))); + let channel = DataChannel::new(channel); - let drop_listener = DropListener::new(framed_dc::new(wrapped_dc.clone()), receiver); + let drop_listener = DropListener::new(framed_dc::new(channel.clone()), receiver); let stream = Self { - inner: SendWrapper::new(StreamInner::new(wrapped_dc)), + inner: SendWrapper::new(framed_dc::new(channel)), read_buffer: Bytes::new(), state: State::Open, drop_notifier: Some(sender), @@ -91,20 +91,6 @@ impl Stream { } } -/// Inner Stream to make Sendable -struct StreamInner { - io: FramedDc, -} - -/// Inner Stream to make Sendable -impl StreamInner { - fn new(wrapped_dc: Rc>) -> Self { - Self { - io: framed_dc::new(wrapped_dc), - } - } -} - impl AsyncRead for Stream { fn poll_read( mut self: Pin<&mut Self>, @@ -130,7 +116,7 @@ impl AsyncRead for Stream { .. } = &mut *self; - match ready!(io_poll_next(&mut inner.io, cx))? { + match ready!(io_poll_next(&mut *inner, cx))? { Some((flag, message)) => { if let Some(flag) = flag { state.handle_inbound_flag(flag, read_buffer); @@ -168,7 +154,7 @@ impl AsyncWrite for Stream { .. } = &mut *self; - match io_poll_next(&mut inner.io, cx)? { + match io_poll_next(&mut *inner, cx)? { Poll::Ready(Some((Some(flag), message))) => { // Read side is closed. Discard any incoming messages. drop(message); @@ -182,11 +168,11 @@ impl AsyncWrite for Stream { self.state.write_barrier()?; - ready!(self.inner.io.poll_ready_unpin(cx))?; + ready!(self.inner.poll_ready_unpin(cx))?; let n = usize::min(buf.len(), MAX_DATA_LEN); - Pin::new(&mut self.inner.io).start_send(Message { + Pin::new(&mut *self.inner).start_send(Message { flag: None, message: Some(buf[0..n].into()), })?; @@ -195,7 +181,7 @@ impl AsyncWrite for Stream { } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - self.inner.io.poll_flush_unpin(cx).map_err(Into::into) + self.inner.poll_flush_unpin(cx).map_err(Into::into) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { @@ -204,11 +190,11 @@ impl AsyncWrite for Stream { loop { match self.state.close_write_barrier()? { Some(Closing::Requested) => { - ready!(self.inner.io.poll_ready_unpin(cx))?; + ready!(self.inner.poll_ready_unpin(cx))?; log::debug!("Sending FIN flag"); - self.inner.io.start_send_unpin(Message { + self.inner.start_send_unpin(Message { flag: Some(Flag::FIN), message: None, })?; @@ -217,7 +203,7 @@ impl AsyncWrite for Stream { continue; } Some(Closing::MessageSent) => { - ready!(self.inner.io.poll_flush_unpin(cx))?; + ready!(self.inner.poll_flush_unpin(cx))?; self.state.write_closed(); diff --git a/transports/webrtc-websys/src/stream/data_channel.rs b/transports/webrtc-websys/src/stream/data_channel.rs new file mode 100644 index 00000000000..d7f9266b5dd --- /dev/null +++ b/transports/webrtc-websys/src/stream/data_channel.rs @@ -0,0 +1,214 @@ +use std::cmp::min; +use std::io; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; + +use bytes::BytesMut; +use futures::task::AtomicWaker; +use futures::{AsyncRead, AsyncWrite}; +use wasm_bindgen::{prelude::*, JsCast}; +use web_sys::{MessageEvent, RtcDataChannel, RtcDataChannelState}; + +/// WebRTC data channels only support backpressure for reading in a limited way. +/// We have to check the `bufferedAmount` property and compare it to chosen constant. +/// Once we exceed the constant, we pause sending of data until we receive the `bufferedAmountLow` event. +/// +/// As per spec, we limit the maximum amount to 16KB, see . +const MAX_BUFFER: usize = 16 * 1024; + +/// [`DataChannel`] is a wrapper around around [`RtcDataChannel`] which implements [`AsyncRead`] and [`AsyncWrite`]. + +#[derive(Debug, Clone)] +pub(crate) struct DataChannel { + /// The [RtcDataChannel] being wrapped. + inner: RtcDataChannel, + + new_data_waker: Arc, + read_buffer: Arc>, + + /// Waker for when we are waiting for the DC to be opened. + open_waker: Arc, + + /// Waker for when we are waiting to write (again) to the DC because we previously exceeded the `MAX_BUFFERED_AMOUNT` threshold. + write_waker: Arc, + + /// Waker for when we are waiting for the DC to be closed. + close_waker: Arc, +} + +impl DataChannel { + pub(crate) fn new(inner: RtcDataChannel) -> Self { + let open_waker = Arc::new(AtomicWaker::new()); + inner.set_onopen(Some( + Closure::once_into_js({ + let open_waker = open_waker.clone(); + + move || { + log::trace!("DataChannel opened"); + open_waker.wake(); + } + }) + .unchecked_ref(), + )); + + let write_waker = Arc::new(AtomicWaker::new()); + inner.set_buffered_amount_low_threshold(0); + inner.set_onbufferedamountlow(Some( + Closure::once_into_js({ + let write_waker = write_waker.clone(); + + move || { + log::trace!("DataChannel available for writing (again)"); + write_waker.wake(); + } + }) + .unchecked_ref(), + )); + + let close_waker = Arc::new(AtomicWaker::new()); + inner.set_onclose(Some( + Closure::once_into_js({ + let close_waker = close_waker.clone(); + + move || { + log::trace!("DataChannel closed"); + close_waker.wake(); + } + }) + .unchecked_ref(), + )); + + let new_data_waker = Arc::new(AtomicWaker::new()); + let read_buffer = Arc::new(Mutex::new(BytesMut::new())); // We purposely don't use `with_capacity` so we don't eagerly allocate `MAX_READ_BUFFER` per stream. + + let onmessage_callback = Closure::::new({ + let new_data_waker = new_data_waker.clone(); + let read_buffer = read_buffer.clone(); + + move |ev: MessageEvent| { + let data = js_sys::Uint8Array::new(&ev.data()); + + let mut read_buffer = read_buffer.lock().unwrap(); + + if read_buffer.len() + data.length() as usize >= MAX_BUFFER { + log::warn!( + "Remote is overloading us with messages, dropping {} bytes of data", + data.length() + ); + return; + } + + read_buffer.copy_from_slice(&data.to_vec()); + new_data_waker.wake(); + } + }) + .into_js_value(); + inner.set_onmessage(Some(onmessage_callback.unchecked_ref())); + + Self { + inner, + new_data_waker, + read_buffer, + open_waker, + write_waker, + close_waker, + } + } + + /// Returns the [RtcDataChannelState] of the [RtcDataChannel] + fn ready_state(&self) -> RtcDataChannelState { + self.inner.ready_state() + } + + /// Returns the current [RtcDataChannel] BufferedAmount + fn buffered_amount(&self) -> usize { + self.inner.buffered_amount() as usize + } +} + +impl AsyncRead for DataChannel { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + match self.ready_state() { + RtcDataChannelState::Connecting => { + self.open_waker.register(cx.waker()); + return Poll::Pending; + } + RtcDataChannelState::Closing | RtcDataChannelState::Closed => { + return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())) + } + RtcDataChannelState::Open | RtcDataChannelState::__Nonexhaustive => {} + } + + let mut read_buffer = self.read_buffer.lock().unwrap(); + + if read_buffer.is_empty() { + self.new_data_waker.register(cx.waker()); + return Poll::Pending; + } + + // Ensure that we: + // - at most return what the caller can read (`buf.len()`) + // - at most what we have (`read_buffer.len()`) + let split_index = min(buf.len(), read_buffer.len()); + + let bytes_to_return = read_buffer.split_off(split_index); + buf.copy_from_slice(&bytes_to_return); + + Poll::Ready(Ok(bytes_to_return.len())) + } +} + +impl AsyncWrite for DataChannel { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + debug_assert!(self.buffered_amount() <= MAX_BUFFER); + let remaining_space = MAX_BUFFER - self.buffered_amount(); + + if remaining_space == 0 { + self.write_waker.register(cx.waker()); + return Poll::Pending; + } + + let bytes_to_send = min(buf.len(), remaining_space); + + if self + .inner + .send_with_u8_array(&buf[..bytes_to_send]) + .is_err() + { + return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())); + } + + Poll::Ready(Ok(bytes_to_send)) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if self.buffered_amount() == 0 { + return Poll::Ready(Ok(())); + } + + self.write_waker.register(cx.waker()); + Poll::Pending + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if self.ready_state() == RtcDataChannelState::Closed { + return Poll::Ready(Ok(())); + } + + if self.ready_state() != RtcDataChannelState::Closing { + self.inner.close(); + } + + self.close_waker.register(cx.waker()); + Poll::Pending + } +} diff --git a/transports/webrtc-websys/src/stream/framed_dc.rs b/transports/webrtc-websys/src/stream/framed_dc.rs index 5202f9d4a02..c4dd99b4b3b 100644 --- a/transports/webrtc-websys/src/stream/framed_dc.rs +++ b/transports/webrtc-websys/src/stream/framed_dc.rs @@ -18,18 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::poll_data_channel::{DataChannel, PollDataChannel}; use asynchronous_codec::Framed; + use libp2p_webrtc_utils::proto::Message; use libp2p_webrtc_utils::stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; -use std::cell::RefCell; -use std::rc::Rc; -pub(crate) type FramedDc = Framed>; -pub(crate) fn new(data_channel: Rc>) -> FramedDc { - let mut inner = PollDataChannel::new(data_channel); - inner.set_read_buf_capacity(MAX_MSG_LEN); +use super::data_channel::DataChannel; +pub(crate) type FramedDc = Framed>; +pub(crate) fn new(inner: DataChannel) -> FramedDc { let mut framed = Framed::new( inner, quick_protobuf_codec::Codec::new(MAX_MSG_LEN - VARINT_LEN), diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs deleted file mode 100644 index edbf9104eff..00000000000 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ /dev/null @@ -1,280 +0,0 @@ -// This crate inspired by webrtc::data::data_channel::poll_data_channel.rs -use crate::error::Error; -use futures::channel; -use futures::{AsyncRead, AsyncWrite, FutureExt, StreamExt}; -use std::cell::RefCell; -use std::fmt; -use std::io; -use std::pin::Pin; -use std::rc::Rc; -use std::result::Result; -use std::task::{ready, Context, Poll}; -use wasm_bindgen::{prelude::*, JsCast}; -use web_sys::{MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelState}; - -/// Default capacity of the temporary read buffer used by `webrtc_sctp::stream::PollStream`. -const DEFAULT_READ_BUF_SIZE: usize = 8192; - -/// [`DataChannel`] is a wrapper around around [`RtcDataChannel`] which initializes event callback handlers. -/// -/// Separating this into its own struct enables us fine grained control over when the callbacks are initialized. -pub(crate) struct DataChannel { - /// The [RtcDataChannel] being wrapped. - inner: RtcDataChannel, - - /// Receive messages from the [RtcDataChannel] callback - /// mpsc since multiple messages may be sent - rx_onmessage: channel::mpsc::Receiver>, - - /// Receive onopen event from the [RtcDataChannel] callback - rx_onopen: channel::mpsc::Receiver<()>, - - /// Receieve onbufferedamountlow event from the [RtcDataChannel] callback - /// mpsc since multiple `onbufferedamountlow` events may be sent - rx_onbufferedamountlow: channel::mpsc::Receiver<()>, - - /// Receive onclose event from the [RtcDataChannel] callback - /// oneshot since only one `onclose` event is sent - rx_onclose: channel::oneshot::Receiver<()>, -} - -impl DataChannel { - /// Constructs a new [`DataChannel`] - /// and initializes the event callback handlers. - pub(crate) fn new(data_channel: RtcDataChannel) -> Self { - /* - * On Open - */ - let (mut tx_onopen, rx_onopen) = channel::mpsc::channel(2); - - let onopen_callback = Closure::::new(move |_ev: RtcDataChannelEvent| { - log::debug!("Data Channel opened"); - if let Err(e) = tx_onopen.try_send(()) { - log::error!("Error sending onopen event {:?}", e); - } - }); - - data_channel.set_onopen(Some(onopen_callback.as_ref().unchecked_ref())); - onopen_callback.forget(); - - /* - * On Message Stream - */ - let (mut tx_onmessage, rx_onmessage) = channel::mpsc::channel(16); // TODO: How big should this be? Does it need to be large enough to handle incoming messages faster than we can process them? - - let onmessage_callback = Closure::::new(move |ev: MessageEvent| { - let data = ev.data(); - // Convert from Js ArrayBuffer to Vec - let data = js_sys::Uint8Array::new(&data).to_vec(); - if let Err(e) = tx_onmessage.try_send(data) { - log::error!("Error sending onmessage event {:?}", e) - } - }); - - data_channel.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref())); - onmessage_callback.forget(); - - // On Close - let (tx_onclose, rx_onclose) = channel::oneshot::channel(); - - let onclose_callback = Closure::once_into_js(move |_ev: RtcDataChannelEvent| { - log::trace!("Data Channel closed"); - // TODO: This is Erroring, likely because the channel is already closed by the time we try to send/receive this? - if let Err(e) = tx_onclose.send(()) { - log::error!("Error sending onclose event {:?}", e); - } - }); - - data_channel.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); - // Note: `once_into_js` Closure does NOT call `forget()`, see the wasm_bindgen::Closure docs for more info - - /* - * Convert `RTCDataChannel: bufferedamountlow event` Low Event Callback to Future - */ - let (mut tx_onbufferedamountlow, rx_onbufferedamountlow) = channel::mpsc::channel(2); - - let onbufferedamountlow_callback = - Closure::::new(move |_ev: RtcDataChannelEvent| { - if let Err(e) = tx_onbufferedamountlow.try_send(()) { - log::warn!( - "Sending onbufferedamountlow failed, channel is probably closed {:?}", - e - ) - } - }); - - data_channel - .set_onbufferedamountlow(Some(onbufferedamountlow_callback.as_ref().unchecked_ref())); - onbufferedamountlow_callback.forget(); - - Self { - inner: data_channel, - rx_onmessage, - rx_onopen, - rx_onclose, - rx_onbufferedamountlow, - } - } - - /// Returns the [RtcDataChannelState] of the [RtcDataChannel] - fn ready_state(&self) -> RtcDataChannelState { - self.inner.ready_state() - } - - /// Send data over this [RtcDataChannel] - fn send(&self, data: &[u8]) -> Result<(), JsValue> { - self.inner.send_with_u8_array(data) - } - - /// Returns the current [RtcDataChannel] BufferedAmount - fn buffered_amount(&self) -> u32 { - self.inner.buffered_amount() - } -} - -/// A wrapper around around [`DataChannel`], which implements [`AsyncRead`] and -/// [`AsyncWrite`]. -pub(crate) struct PollDataChannel { - /// The [DataChannel] - data_channel: Rc>, - - read_buf_cap: usize, -} - -impl PollDataChannel { - /// Constructs a new `PollDataChannel`. - pub(crate) fn new(data_channel: Rc>) -> Self { - Self { - data_channel, - read_buf_cap: DEFAULT_READ_BUF_SIZE, - } - } - - /// Obtain a clone of the mutable smart pointer to the [`DataChannel`]. - fn clone_inner(&self) -> Rc> { - self.data_channel.clone() - } - - /// Set the capacity of the temporary read buffer (default: 8192). - pub(crate) fn set_read_buf_capacity(&mut self, capacity: usize) { - self.read_buf_cap = capacity - } - - /// Get Ready State of [RtcDataChannel] - fn ready_state(&self) -> RtcDataChannelState { - self.data_channel.borrow().ready_state() - } - - /// Send data buffer - fn send(&self, data: &[u8]) -> Result<(), Error> { - self.data_channel.borrow().send(data)?; - Ok(()) - } -} - -impl AsyncRead for PollDataChannel { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - match ready!(self - .data_channel - .borrow_mut() - .rx_onmessage - .poll_next_unpin(cx)) - { - Some(data) => { - let data_len = data.len(); - let buf_len = buf.len(); - log::trace!("poll_read [{:?} of {} bytes]", data_len, buf_len); - let len = std::cmp::min(data_len, buf_len); - buf[..len].copy_from_slice(&data[..len]); - Poll::Ready(Ok(len)) - } - None => Poll::Ready(Ok(0)), // if None, the stream is exhausted, no data to read - } - } -} - -impl AsyncWrite for PollDataChannel { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - log::trace!( - "poll_write: [{:?}], state: {:?}", - buf.len(), - self.ready_state() - ); - // If the data channel is not open, - // poll on open future until the channel is open - if self.ready_state() != RtcDataChannelState::Open { - ready!(self.data_channel.borrow_mut().rx_onopen.poll_next_unpin(cx)).unwrap(); - } - - // Now that the channel is open, send the data - match self.send(buf) { - Ok(_) => Poll::Ready(Ok(buf.len())), - Err(e) => Poll::Ready(Err(io::Error::new( - io::ErrorKind::Other, - format!("Error sending data: {:?}", e), - ))), - } - } - - /// Attempt to flush the object, ensuring that any buffered data reach their destination. - /// On success, returns Poll::Ready(Ok(())). - /// If flushing cannot immediately complete, this method returns Poll::Pending and arranges for the current task (via cx.waker().wake_by_ref()) to receive a notification when the object can make progress towards flushing. - /// - /// With RtcDataChannel, there no native future to await for flush to complete. - /// However, Whenever this value decreases to fall to or below the value specified in the - /// bufferedAmountLowThreshold property, the user agent fires the bufferedamountlow event. - /// - /// We can therefore create a callback future called `onbufferedamountlow_fut` to listen for `bufferedamountlow` event and wake the task - /// The default `bufferedAmountLowThreshold` value is 0. - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - // if bufferedamountlow empty, return ready - if self.data_channel.borrow().buffered_amount() == 0 { - return Poll::Ready(Ok(())); - } - - // Otherwise, wait for the event to occur, so poll on onbufferedamountlow_fut - match self - .data_channel - .borrow_mut() - .rx_onbufferedamountlow - .poll_next_unpin(cx) - { - Poll::Pending => Poll::Pending, - _ => Poll::Ready(Ok(())), - } - } - - /// Initiates or attempts to shut down this writer, - /// returning success when the connection callback returns has completely shut down. - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - log::trace!("poll_close"); - self.data_channel.borrow().inner.close(); - // TODO: Confirm that channels are closing properly. Poll onclose trigger onclose event, but this should be tested (currently) is not tested - let _ = ready!(self.data_channel.borrow_mut().rx_onclose.poll_unpin(cx)); - log::trace!("close complete"); - Poll::Ready(Ok(())) - } -} - -impl Clone for PollDataChannel { - fn clone(&self) -> PollDataChannel { - PollDataChannel::new(self.clone_inner()) - } -} - -impl fmt::Debug for PollDataChannel { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PollDataChannel") - .field("data_channel", &self.data_channel.borrow().inner) - .field("read_buf_cap", &self.read_buf_cap) - .finish() - } -} From d89ebe1661a30ffe5ba4f8418d31f680893f2755 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 14:52:12 +1000 Subject: [PATCH 184/235] Ensure DC is ready for writing --- .../webrtc-websys/src/stream/data_channel.rs | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/transports/webrtc-websys/src/stream/data_channel.rs b/transports/webrtc-websys/src/stream/data_channel.rs index d7f9266b5dd..1be64e67fe4 100644 --- a/transports/webrtc-websys/src/stream/data_channel.rs +++ b/transports/webrtc-websys/src/stream/data_channel.rs @@ -99,7 +99,7 @@ impl DataChannel { return; } - read_buffer.copy_from_slice(&data.to_vec()); + read_buffer.extend_from_slice(&data.to_vec()); new_data_waker.wake(); } }) @@ -125,29 +125,35 @@ impl DataChannel { fn buffered_amount(&self) -> usize { self.inner.buffered_amount() as usize } -} -impl AsyncRead for DataChannel { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { + fn poll_open(&mut self, cx: &mut Context) -> Poll> { match self.ready_state() { RtcDataChannelState::Connecting => { self.open_waker.register(cx.waker()); - return Poll::Pending; + Poll::Pending } RtcDataChannelState::Closing | RtcDataChannelState::Closed => { - return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())) + Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())) } - RtcDataChannelState::Open | RtcDataChannelState::__Nonexhaustive => {} + RtcDataChannelState::Open | RtcDataChannelState::__Nonexhaustive => Poll::Ready(Ok(())), } + } +} - let mut read_buffer = self.read_buffer.lock().unwrap(); +impl AsyncRead for DataChannel { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let this = self.get_mut(); + + futures::ready!(this.poll_open(cx))?; + + let mut read_buffer = this.read_buffer.lock().unwrap(); if read_buffer.is_empty() { - self.new_data_waker.register(cx.waker()); + this.new_data_waker.register(cx.waker()); return Poll::Pending; } @@ -156,10 +162,11 @@ impl AsyncRead for DataChannel { // - at most what we have (`read_buffer.len()`) let split_index = min(buf.len(), read_buffer.len()); - let bytes_to_return = read_buffer.split_off(split_index); - buf.copy_from_slice(&bytes_to_return); + let bytes_to_return = read_buffer.split_to(split_index); + let len = bytes_to_return.len(); + buf[..len].copy_from_slice(&bytes_to_return); - Poll::Ready(Ok(bytes_to_return.len())) + Poll::Ready(Ok(len)) } } @@ -169,17 +176,21 @@ impl AsyncWrite for DataChannel { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - debug_assert!(self.buffered_amount() <= MAX_BUFFER); - let remaining_space = MAX_BUFFER - self.buffered_amount(); + let this = self.get_mut(); + + futures::ready!(this.poll_open(cx))?; + + debug_assert!(this.buffered_amount() <= MAX_BUFFER); + let remaining_space = MAX_BUFFER - this.buffered_amount(); if remaining_space == 0 { - self.write_waker.register(cx.waker()); + this.write_waker.register(cx.waker()); return Poll::Pending; } let bytes_to_send = min(buf.len(), remaining_space); - if self + if this .inner .send_with_u8_array(&buf[..bytes_to_send]) .is_err() From 863647d03eaf68d9561186409896ade91879a5da Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 14:54:38 +1000 Subject: [PATCH 185/235] Don't leak closures and make them FnMuts --- .../webrtc-websys/src/stream/data_channel.rs | 76 ++++++++++--------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/transports/webrtc-websys/src/stream/data_channel.rs b/transports/webrtc-websys/src/stream/data_channel.rs index 1be64e67fe4..22b24fa2ef7 100644 --- a/transports/webrtc-websys/src/stream/data_channel.rs +++ b/transports/webrtc-websys/src/stream/data_channel.rs @@ -6,9 +6,9 @@ use std::task::{Context, Poll}; use bytes::BytesMut; use futures::task::AtomicWaker; -use futures::{AsyncRead, AsyncWrite}; +use futures::{ready, AsyncRead, AsyncWrite}; use wasm_bindgen::{prelude::*, JsCast}; -use web_sys::{MessageEvent, RtcDataChannel, RtcDataChannelState}; +use web_sys::{Event, MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelState}; /// WebRTC data channels only support backpressure for reading in a limited way. /// We have to check the `bufferedAmount` property and compare it to chosen constant. @@ -35,54 +35,55 @@ pub(crate) struct DataChannel { /// Waker for when we are waiting for the DC to be closed. close_waker: Arc, + + // Store the closures for proper garbage collection. + // These are wrapped in an `Arc` so we can implement `Clone`. + _on_open_closure: Arc>, + _on_write_closure: Arc>, + _on_close_closure: Arc>, + _on_message_closure: Arc>, } impl DataChannel { pub(crate) fn new(inner: RtcDataChannel) -> Self { let open_waker = Arc::new(AtomicWaker::new()); - inner.set_onopen(Some( - Closure::once_into_js({ - let open_waker = open_waker.clone(); + let on_open_closure = Closure::new({ + let open_waker = open_waker.clone(); - move || { - log::trace!("DataChannel opened"); - open_waker.wake(); - } - }) - .unchecked_ref(), - )); + move |_: RtcDataChannelEvent| { + log::trace!("DataChannel opened"); + open_waker.wake(); + } + }); + inner.set_onopen(Some(on_open_closure.as_ref().unchecked_ref())); let write_waker = Arc::new(AtomicWaker::new()); inner.set_buffered_amount_low_threshold(0); - inner.set_onbufferedamountlow(Some( - Closure::once_into_js({ - let write_waker = write_waker.clone(); + let on_write_closure = Closure::new({ + let write_waker = write_waker.clone(); - move || { - log::trace!("DataChannel available for writing (again)"); - write_waker.wake(); - } - }) - .unchecked_ref(), - )); + move |_: Event| { + log::trace!("DataChannel available for writing (again)"); + write_waker.wake(); + } + }); + inner.set_onbufferedamountlow(Some(on_write_closure.as_ref().unchecked_ref())); let close_waker = Arc::new(AtomicWaker::new()); - inner.set_onclose(Some( - Closure::once_into_js({ - let close_waker = close_waker.clone(); + let on_close_closure = Closure::new({ + let close_waker = close_waker.clone(); - move || { - log::trace!("DataChannel closed"); - close_waker.wake(); - } - }) - .unchecked_ref(), - )); + move |_: Event| { + log::trace!("DataChannel closed"); + close_waker.wake(); + } + }); + inner.set_onclose(Some(on_close_closure.as_ref().unchecked_ref())); let new_data_waker = Arc::new(AtomicWaker::new()); let read_buffer = Arc::new(Mutex::new(BytesMut::new())); // We purposely don't use `with_capacity` so we don't eagerly allocate `MAX_READ_BUFFER` per stream. - let onmessage_callback = Closure::::new({ + let on_message_closure = Closure::::new({ let new_data_waker = new_data_waker.clone(); let read_buffer = read_buffer.clone(); @@ -102,9 +103,8 @@ impl DataChannel { read_buffer.extend_from_slice(&data.to_vec()); new_data_waker.wake(); } - }) - .into_js_value(); - inner.set_onmessage(Some(onmessage_callback.unchecked_ref())); + }); + inner.set_onmessage(Some(on_message_closure.as_ref().unchecked_ref())); Self { inner, @@ -113,6 +113,10 @@ impl DataChannel { open_waker, write_waker, close_waker, + _on_open_closure: Arc::new(on_open_closure), + _on_write_closure: Arc::new(on_write_closure), + _on_close_closure: Arc::new(on_close_closure), + _on_message_closure: Arc::new(on_message_closure), } } From d7930d2c791250556214a3cb8a92b07f163be9e1 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 15:22:46 +1000 Subject: [PATCH 186/235] Unify `Stream` implementations --- Cargo.lock | 8 +- transports/webrtc-utils/Cargo.toml | 2 + transports/webrtc-utils/src/lib.rs | 5 +- transports/webrtc-utils/src/stream.rs | 268 +++++++++++++++++- .../src/stream/drop_listener.rs | 37 +-- .../src/stream/framed_dc.rs | 17 +- transports/webrtc-utils/src/stream/state.rs | 24 +- transports/webrtc-websys/Cargo.toml | 3 - transports/webrtc-websys/src/stream.rs | 193 ++----------- .../{data_channel.rs => poll_data_channel.rs} | 13 +- transports/webrtc/Cargo.toml | 3 - transports/webrtc/src/tokio/stream.rs | 237 ++-------------- .../webrtc/src/tokio/stream/drop_listener.rs | 130 --------- .../webrtc/src/tokio/stream/framed_dc.rs | 44 --- 14 files changed, 366 insertions(+), 618 deletions(-) rename transports/{webrtc-websys => webrtc-utils}/src/stream/drop_listener.rs (85%) rename transports/{webrtc-websys => webrtc-utils}/src/stream/framed_dc.rs (74%) rename transports/webrtc-websys/src/stream/{data_channel.rs => poll_data_channel.rs} (96%) delete mode 100644 transports/webrtc/src/tokio/stream/drop_listener.rs delete mode 100644 transports/webrtc/src/tokio/stream/framed_dc.rs diff --git a/Cargo.lock b/Cargo.lock index ed93bd808b3..80a8ba47430 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3396,7 +3396,6 @@ version = "0.7.0-alpha" dependencies = [ "anyhow", "async-trait", - "asynchronous-codec", "bytes", "env_logger 0.10.0", "futures", @@ -3412,8 +3411,6 @@ dependencies = [ "libp2p-webrtc-utils", "log", "multihash", - "quick-protobuf", - "quick-protobuf-codec", "quickcheck", "rand 0.8.5", "rcgen 0.10.0", @@ -3432,6 +3429,7 @@ dependencies = [ name = "libp2p-webrtc-utils" version = "0.1.0" dependencies = [ + "asynchronous-codec", "bytes", "futures", "hex", @@ -3447,13 +3445,13 @@ dependencies = [ "sha2 0.10.7", "thiserror", "tinytemplate", + "unsigned-varint", ] [[package]] name = "libp2p-webrtc-websys" version = "0.1.0-alpha" dependencies = [ - "asynchronous-codec", "bytes", "futures", "futures-timer", @@ -3466,8 +3464,6 @@ dependencies = [ "libp2p-swarm", "libp2p-webrtc-utils", "log", - "quick-protobuf", - "quick-protobuf-codec", "send_wrapper 0.6.0", "serde", "thiserror", diff --git a/transports/webrtc-utils/Cargo.toml b/transports/webrtc-utils/Cargo.toml index 7c84ed4a246..878f89d4614 100644 --- a/transports/webrtc-utils/Cargo.toml +++ b/transports/webrtc-utils/Cargo.toml @@ -24,6 +24,8 @@ serde = { version = "1.0", features = ["derive"] } sha2 = "0.10.7" thiserror = "1" tinytemplate = "1.2" +asynchronous-codec = "0.6" [dev-dependencies] hex-literal = "0.4" +unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } diff --git a/transports/webrtc-utils/src/lib.rs b/transports/webrtc-utils/src/lib.rs index 419775d362a..2038fd517ec 100644 --- a/transports/webrtc-utils/src/lib.rs +++ b/transports/webrtc-utils/src/lib.rs @@ -1,6 +1,6 @@ #![doc = include_str!("../README.md")] -pub mod proto { +mod proto { #![allow(unreachable_pub)] include!("generated/mod.rs"); pub use self::webrtc::pb::{mod_Message::Flag, Message}; @@ -10,8 +10,9 @@ pub mod error; pub mod fingerprint; pub mod noise; pub mod sdp; -pub mod stream; +mod stream; pub mod transport; pub use error::Error; +pub use stream::{DropListener, Stream}; pub use transport::parse_webrtc_dial_addr; diff --git a/transports/webrtc-utils/src/stream.rs b/transports/webrtc-utils/src/stream.rs index cff72e63645..6428197cf8c 100644 --- a/transports/webrtc-utils/src/stream.rs +++ b/transports/webrtc-utils/src/stream.rs @@ -1,14 +1,272 @@ -pub mod state; +mod drop_listener; +mod framed_dc; +mod state; + +use crate::proto::{Flag, Message}; +use crate::stream::state::Closing; +use bytes::Bytes; +use drop_listener::GracefullyClosed; +use framed_dc::FramedDc; +use futures::channel::oneshot; +use futures::{ready, AsyncRead, AsyncWrite, Sink, SinkExt, StreamExt}; +use state::State; +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; + +pub use drop_listener::DropListener; /// Maximum length of a message. /// /// "As long as message interleaving is not supported, the sender SHOULD limit the maximum message /// size to 16 KB to avoid monopolization." /// Source: -pub const MAX_MSG_LEN: usize = 16384; // 16kiB +const MAX_MSG_LEN: usize = 16384; // 16kiB /// Length of varint, in bytes. -pub const VARINT_LEN: usize = 2; +const VARINT_LEN: usize = 2; /// Overhead of the protobuf encoding, in bytes. -pub const PROTO_OVERHEAD: usize = 5; +const PROTO_OVERHEAD: usize = 5; /// Maximum length of data, in bytes. -pub const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; +const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; + +/// A substream on top of a WebRTC data channel. +/// +/// To be a proper libp2p substream, we need to implement [`AsyncRead`] and [`AsyncWrite`] as well +/// as support a half-closed state which we do by framing messages in a protobuf envelope. +pub struct Stream { + io: FramedDc, + state: State, + read_buffer: Bytes, + /// Dropping this will close the oneshot and notify the receiver by emitting `Canceled`. + drop_notifier: Option>, +} + +impl Stream +where + T: AsyncRead + AsyncWrite + Unpin + Clone, +{ + /// Returns a new `Substream` and a listener, which will notify the receiver when/if the substream + /// is dropped. + pub fn new(data_channel: T) -> (Self, DropListener) { + let (sender, receiver) = oneshot::channel(); + + let substream = Self { + io: framed_dc::new(data_channel.clone()), + state: State::Open, + read_buffer: Bytes::default(), + drop_notifier: Some(sender), + }; + let listener = DropListener::new(framed_dc::new(data_channel), receiver); + + (substream, listener) + } + + /// Gracefully closes the "read-half" of the substream. + pub fn poll_close_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + loop { + match self.state.close_read_barrier()? { + Some(Closing::Requested) => { + ready!(self.io.poll_ready_unpin(cx))?; + + self.io.start_send_unpin(Message { + flag: Some(Flag::STOP_SENDING), + message: None, + })?; + self.state.close_read_message_sent(); + + continue; + } + Some(Closing::MessageSent) => { + ready!(self.io.poll_flush_unpin(cx))?; + + self.state.read_closed(); + + return Poll::Ready(Ok(())); + } + None => return Poll::Ready(Ok(())), + } + } + } +} + +impl AsyncRead for Stream +where + T: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + loop { + self.state.read_barrier()?; + + if !self.read_buffer.is_empty() { + let n = std::cmp::min(self.read_buffer.len(), buf.len()); + let data = self.read_buffer.split_to(n); + buf[0..n].copy_from_slice(&data[..]); + + return Poll::Ready(Ok(n)); + } + + let Self { + read_buffer, + io, + state, + .. + } = &mut *self; + + match ready!(io_poll_next(io, cx))? { + Some((flag, message)) => { + if let Some(flag) = flag { + state.handle_inbound_flag(flag, read_buffer); + } + + debug_assert!(read_buffer.is_empty()); + if let Some(message) = message { + *read_buffer = message.into(); + } + } + None => { + state.handle_inbound_flag(Flag::FIN, read_buffer); + return Poll::Ready(Ok(0)); + } + } + } + } +} + +impl AsyncWrite for Stream +where + T: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + while self.state.read_flags_in_async_write() { + // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the + // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? + + let Self { + read_buffer, + io, + state, + .. + } = &mut *self; + + match io_poll_next(io, cx)? { + Poll::Ready(Some((Some(flag), message))) => { + // Read side is closed. Discard any incoming messages. + drop(message); + // But still handle flags, e.g. a `Flag::StopSending`. + state.handle_inbound_flag(flag, read_buffer) + } + Poll::Ready(Some((None, message))) => drop(message), + Poll::Ready(None) | Poll::Pending => break, + } + } + + self.state.write_barrier()?; + + ready!(self.io.poll_ready_unpin(cx))?; + + let n = usize::min(buf.len(), MAX_DATA_LEN); + + Pin::new(&mut self.io).start_send(Message { + flag: None, + message: Some(buf[0..n].into()), + })?; + + Poll::Ready(Ok(n)) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.io.poll_flush_unpin(cx).map_err(Into::into) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + loop { + match self.state.close_write_barrier()? { + Some(Closing::Requested) => { + ready!(self.io.poll_ready_unpin(cx))?; + + self.io.start_send_unpin(Message { + flag: Some(Flag::FIN), + message: None, + })?; + self.state.close_write_message_sent(); + + continue; + } + Some(Closing::MessageSent) => { + ready!(self.io.poll_flush_unpin(cx))?; + + self.state.write_closed(); + let _ = self + .drop_notifier + .take() + .expect("to not close twice") + .send(GracefullyClosed {}); + + return Poll::Ready(Ok(())); + } + None => return Poll::Ready(Ok(())), + } + } + } +} + +fn io_poll_next( + io: &mut FramedDc, + cx: &mut Context<'_>, +) -> Poll, Option>)>>> +where + T: AsyncRead + AsyncWrite + Unpin, +{ + match ready!(io.poll_next_unpin(cx)) + .transpose() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? + { + Some(Message { flag, message }) => Poll::Ready(Ok(Some((flag, message)))), + None => Poll::Ready(Ok(None)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use asynchronous_codec::Encoder; + use bytes::BytesMut; + use quick_protobuf::{MessageWrite, Writer}; + use unsigned_varint::codec::UviBytes; + + #[test] + fn max_data_len() { + // Largest possible message. + let message = [0; MAX_DATA_LEN]; + + let protobuf = Message { + flag: Some(Flag::FIN), + message: Some(message.to_vec()), + }; + + let mut encoded_msg = Vec::new(); + let mut writer = Writer::new(&mut encoded_msg); + protobuf + .write_message(&mut writer) + .expect("Encoding to succeed"); + assert_eq!(encoded_msg.len(), message.len() + PROTO_OVERHEAD); + + let mut uvi = UviBytes::default(); + let mut dst = BytesMut::new(); + uvi.encode(encoded_msg.as_slice(), &mut dst).unwrap(); + + // Ensure the varint prefixed and protobuf encoded largest message is no longer than the + // maximum limit specified in the libp2p WebRTC specification. + assert_eq!(dst.len(), MAX_MSG_LEN); + + assert_eq!(dst.len() - encoded_msg.len(), VARINT_LEN); + } +} diff --git a/transports/webrtc-websys/src/stream/drop_listener.rs b/transports/webrtc-utils/src/stream/drop_listener.rs similarity index 85% rename from transports/webrtc-websys/src/stream/drop_listener.rs rename to transports/webrtc-utils/src/stream/drop_listener.rs index ea633bdff3b..0a04bb34ae8 100644 --- a/transports/webrtc-websys/src/stream/drop_listener.rs +++ b/transports/webrtc-utils/src/stream/drop_listener.rs @@ -18,47 +18,52 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::framed_dc::FramedDc; -use futures::channel::oneshot; -use futures::channel::oneshot::Canceled; -use futures::{FutureExt, SinkExt}; -use libp2p_webrtc_utils::proto::{Flag, Message}; use std::future::Future; use std::io; use std::pin::Pin; use std::task::{Context, Poll}; +use futures::channel::oneshot; +use futures::channel::oneshot::Canceled; +use futures::{AsyncRead, AsyncWrite, FutureExt, SinkExt}; + +use crate::proto::{Flag, Message}; +use crate::stream::framed_dc::FramedDc; + #[must_use] -pub(crate) struct DropListener { - state: State, +pub struct DropListener { + state: State, } -impl DropListener { - pub(crate) fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { +impl DropListener { + pub fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { Self { state: State::Idle { stream, receiver }, } } } -enum State { +enum State { /// The [`DropListener`] is idle and waiting to be activated. Idle { - stream: FramedDc, + stream: FramedDc, receiver: oneshot::Receiver, }, /// The stream got dropped and we are sending a reset flag. SendingReset { - stream: FramedDc, + stream: FramedDc, }, Flushing { - stream: FramedDc, + stream: FramedDc, }, /// Bad state transition. Poisoned, } -impl Future for DropListener { +impl Future for DropListener +where + T: AsyncRead + AsyncWrite + Unpin, +{ type Output = io::Result<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -74,7 +79,7 @@ impl Future for DropListener { return Poll::Ready(Ok(())); } Poll::Ready(Err(Canceled)) => { - log::info!("Stream dropped without graceful close, sending Reset"); + log::info!("Substream dropped without graceful close, sending Reset"); *state = State::SendingReset { stream }; continue; } @@ -113,4 +118,4 @@ impl Future for DropListener { } /// Indicates that our substream got gracefully closed. -pub(crate) struct GracefullyClosed {} +pub struct GracefullyClosed {} diff --git a/transports/webrtc-websys/src/stream/framed_dc.rs b/transports/webrtc-utils/src/stream/framed_dc.rs similarity index 74% rename from transports/webrtc-websys/src/stream/framed_dc.rs rename to transports/webrtc-utils/src/stream/framed_dc.rs index c4dd99b4b3b..4409b79a0ed 100644 --- a/transports/webrtc-websys/src/stream/framed_dc.rs +++ b/transports/webrtc-utils/src/stream/framed_dc.rs @@ -19,19 +19,22 @@ // DEALINGS IN THE SOFTWARE. use asynchronous_codec::Framed; +use futures::{AsyncRead, AsyncWrite}; -use libp2p_webrtc_utils::proto::Message; -use libp2p_webrtc_utils::stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; +use crate::proto::Message; +use crate::stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; -use super::data_channel::DataChannel; - -pub(crate) type FramedDc = Framed>; -pub(crate) fn new(inner: DataChannel) -> FramedDc { +pub(crate) type FramedDc = Framed>; +pub(crate) fn new(inner: T) -> FramedDc +where + T: AsyncRead + AsyncWrite, +{ let mut framed = Framed::new( inner, quick_protobuf_codec::Codec::new(MAX_MSG_LEN - VARINT_LEN), ); - // If not set, `Framed` buffers up to 16377 bytes of data before sending + // If not set, `Framed` buffers up to 131kB of data before sending, which leads to "outbound + // packet larger than maximum message size" error in webrtc-rs. framed.set_send_high_water_mark(MAX_DATA_LEN); framed } diff --git a/transports/webrtc-utils/src/stream/state.rs b/transports/webrtc-utils/src/stream/state.rs index 60a6edd7c64..e25171876d4 100644 --- a/transports/webrtc-utils/src/stream/state.rs +++ b/transports/webrtc-utils/src/stream/state.rs @@ -25,7 +25,7 @@ use std::io; use crate::proto::Flag; #[derive(Debug, Copy, Clone)] -pub enum State { +pub(crate) enum State { Open, ReadClosed, WriteClosed, @@ -49,14 +49,14 @@ pub enum State { /// Gracefully closing the read or write requires sending the `STOP_SENDING` or `FIN` flag respectively /// and flushing the underlying connection. #[derive(Debug, Copy, Clone)] -pub enum Closing { +pub(crate) enum Closing { Requested, MessageSent, } impl State { /// Performs a state transition for a flag contained in an inbound message. - pub fn handle_inbound_flag(&mut self, flag: Flag, buffer: &mut Bytes) { + pub(crate) fn handle_inbound_flag(&mut self, flag: Flag, buffer: &mut Bytes) { let current = *self; match (current, flag) { @@ -80,7 +80,7 @@ impl State { } } - pub fn write_closed(&mut self) { + pub(crate) fn write_closed(&mut self) { match self { State::ClosingWrite { read_closed: true, @@ -108,7 +108,7 @@ impl State { } } - pub fn close_write_message_sent(&mut self) { + pub(crate) fn close_write_message_sent(&mut self) { match self { State::ClosingWrite { inner, read_closed } => { debug_assert!(matches!(inner, Closing::Requested)); @@ -128,7 +128,7 @@ impl State { } } - pub fn read_closed(&mut self) { + pub(crate) fn read_closed(&mut self) { match self { State::ClosingRead { write_closed: true, @@ -156,7 +156,7 @@ impl State { } } - pub fn close_read_message_sent(&mut self) { + pub(crate) fn close_read_message_sent(&mut self) { match self { State::ClosingRead { inner, @@ -183,12 +183,12 @@ impl State { /// /// This is necessary for read-closed streams because we would otherwise not read any more flags from /// the socket. - pub fn read_flags_in_async_write(&self) -> bool { + pub(crate) fn read_flags_in_async_write(&self) -> bool { matches!(self, Self::ReadClosed) } /// Acts as a "barrier" for [`futures::AsyncRead::poll_read`]. - pub fn read_barrier(&self) -> io::Result<()> { + pub(crate) fn read_barrier(&self) -> io::Result<()> { use State::*; let kind = match self { @@ -210,7 +210,7 @@ impl State { } /// Acts as a "barrier" for [`futures::AsyncWrite::poll_write`]. - pub fn write_barrier(&self) -> io::Result<()> { + pub(crate) fn write_barrier(&self) -> io::Result<()> { use State::*; let kind = match self { @@ -233,7 +233,7 @@ impl State { } /// Acts as a "barrier" for [`futures::AsyncWrite::poll_close`]. - pub fn close_write_barrier(&mut self) -> io::Result> { + pub(crate) fn close_write_barrier(&mut self) -> io::Result> { loop { match &self { State::WriteClosed => return Ok(None), @@ -278,7 +278,7 @@ impl State { } /// Acts as a "barrier" for `stream::poll_close_read`. - pub fn close_read_barrier(&mut self) -> io::Result> { + pub(crate) fn close_read_barrier(&mut self) -> io::Result> { loop { match self { State::ReadClosed => return Ok(None), diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index 13504e2247d..07a5e0984af 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -11,7 +11,6 @@ rust-version = { workspace = true } version = "0.1.0-alpha" [dependencies] -asynchronous-codec = "0.6" bytes = "1" futures = "0.3" futures-timer = "3" @@ -21,8 +20,6 @@ libp2p-core = { workspace = true } libp2p-identity = { workspace = true } libp2p-webrtc-utils = { workspace = true } log = "0.4.19" -quick-protobuf = "0.8" -quick-protobuf-codec = { workspace = true } send_wrapper = { version = "0.6.0", features = ["futures"] } serde = { version = "1.0", features = ["derive"] } thiserror = "1" diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 5038008040b..609a5029638 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -1,27 +1,14 @@ //! The WebRTC [Stream] over the Connection -use std::io; use std::pin::Pin; -use std::task::{ready, Context, Poll}; +use std::task::{Context, Poll}; -use bytes::Bytes; -use futures::channel::oneshot; -use futures::{AsyncRead, AsyncWrite, Sink, SinkExt, StreamExt}; +use futures::{AsyncRead, AsyncWrite}; use send_wrapper::SendWrapper; use web_sys::{RtcDataChannel, RtcDataChannelInit, RtcDataChannelType, RtcPeerConnection}; -pub(crate) use drop_listener::{DropListener, GracefullyClosed}; -use libp2p_webrtc_utils::proto::{Flag, Message}; -use libp2p_webrtc_utils::stream::{ - state::{Closing, State}, - MAX_DATA_LEN, -}; +use self::poll_data_channel::PollDataChannel; -use self::data_channel::DataChannel; -use self::framed_dc::FramedDc; - -mod data_channel; -mod drop_listener; -mod framed_dc; +mod poll_data_channel; /// The Browser Default is Blob, so we must set ours to Arraybuffer explicitly const ARRAY_BUFFER_BINARY_TYPE: RtcDataChannelType = RtcDataChannelType::Arraybuffer; @@ -64,173 +51,49 @@ impl RtcDataChannelBuilder { /// Stream over the Connection pub struct Stream { /// Wrapper for the inner stream to make it Send - inner: SendWrapper, - /// State of the Stream - state: State, - /// Buffer for incoming data - read_buffer: Bytes, - // Dropping this will close the oneshot and notify the receiver by emitting `Canceled`. - drop_notifier: Option>, + inner: SendWrapper>, } +pub(crate) type DropListener = libp2p_webrtc_utils::DropListener; + impl Stream { - /// Create a new WebRTC Stream - pub(crate) fn new(channel: RtcDataChannel) -> (Self, DropListener) { - let (sender, receiver) = oneshot::channel(); - let channel = DataChannel::new(channel); - - let drop_listener = DropListener::new(framed_dc::new(channel.clone()), receiver); - - let stream = Self { - inner: SendWrapper::new(framed_dc::new(channel)), - read_buffer: Bytes::new(), - state: State::Open, - drop_notifier: Some(sender), - }; - (stream, drop_listener) + pub(crate) fn new(data_channel: RtcDataChannel) -> (Self, DropListener) { + let (inner, drop_listener) = + libp2p_webrtc_utils::Stream::new(PollDataChannel::new(data_channel)); + + ( + Self { + inner: SendWrapper::new(inner), + }, + drop_listener, + ) } } impl AsyncRead for Stream { fn poll_read( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], - ) -> Poll> { - loop { - self.state.read_barrier()?; - - // Return buffered data, if any - if !self.read_buffer.is_empty() { - let n = std::cmp::min(self.read_buffer.len(), buf.len()); - let data = self.read_buffer.split_to(n); - buf[0..n].copy_from_slice(&data[..]); - - return Poll::Ready(Ok(n)); - } - - let Self { - read_buffer, - state, - inner, - .. - } = &mut *self; - - match ready!(io_poll_next(&mut *inner, cx))? { - Some((flag, message)) => { - if let Some(flag) = flag { - state.handle_inbound_flag(flag, read_buffer); - } - - debug_assert!(read_buffer.is_empty()); - if let Some(message) = message { - *read_buffer = message.into(); - } - // continue to loop - } - None => { - state.handle_inbound_flag(Flag::FIN, read_buffer); - return Poll::Ready(Ok(0)); - } - } - } + ) -> Poll> { + Pin::new(&mut *self.get_mut().inner).poll_read(cx, buf) } } impl AsyncWrite for Stream { fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context, + self: Pin<&mut Self>, + cx: &mut Context<'_>, buf: &[u8], - ) -> Poll> { - while self.state.read_flags_in_async_write() { - // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the - // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? - - let Self { - read_buffer, - state, - inner, - .. - } = &mut *self; - - match io_poll_next(&mut *inner, cx)? { - Poll::Ready(Some((Some(flag), message))) => { - // Read side is closed. Discard any incoming messages. - drop(message); - // But still handle flags, e.g. a `Flag::StopSending`. - state.handle_inbound_flag(flag, read_buffer) - } - Poll::Ready(Some((None, message))) => drop(message), - Poll::Ready(None) | Poll::Pending => break, - } - } - - self.state.write_barrier()?; - - ready!(self.inner.poll_ready_unpin(cx))?; - - let n = usize::min(buf.len(), MAX_DATA_LEN); - - Pin::new(&mut *self.inner).start_send(Message { - flag: None, - message: Some(buf[0..n].into()), - })?; - - Poll::Ready(Ok(n)) + ) -> Poll> { + Pin::new(&mut *self.get_mut().inner).poll_write(cx, buf) } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - self.inner.poll_flush_unpin(cx).map_err(Into::into) + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut *self.get_mut().inner).poll_flush(cx) } - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - log::trace!("poll_closing"); - - loop { - match self.state.close_write_barrier()? { - Some(Closing::Requested) => { - ready!(self.inner.poll_ready_unpin(cx))?; - - log::debug!("Sending FIN flag"); - - self.inner.start_send_unpin(Message { - flag: Some(Flag::FIN), - message: None, - })?; - self.state.close_write_message_sent(); - - continue; - } - Some(Closing::MessageSent) => { - ready!(self.inner.poll_flush_unpin(cx))?; - - self.state.write_closed(); - - // TODO: implement drop notifier. Drop notifier built into the Browser as 'onclose' event - let _ = self - .drop_notifier - .take() - .expect("to not close twice") - .send(GracefullyClosed {}); - - return Poll::Ready(Ok(())); - } - None => return Poll::Ready(Ok(())), - } - } - } -} - -fn io_poll_next( - io: &mut FramedDc, - cx: &mut Context<'_>, -) -> Poll, Option>)>>> { - match ready!(io.poll_next_unpin(cx)) - .transpose() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? - { - Some(Message { flag, message }) => Poll::Ready(Ok(Some((flag, message)))), - None => Poll::Ready(Ok(None)), + 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/webrtc-websys/src/stream/data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs similarity index 96% rename from transports/webrtc-websys/src/stream/data_channel.rs rename to transports/webrtc-websys/src/stream/poll_data_channel.rs index 22b24fa2ef7..1c2700d7e13 100644 --- a/transports/webrtc-websys/src/stream/data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -6,7 +6,7 @@ use std::task::{Context, Poll}; use bytes::BytesMut; use futures::task::AtomicWaker; -use futures::{ready, AsyncRead, AsyncWrite}; +use futures::{AsyncRead, AsyncWrite}; use wasm_bindgen::{prelude::*, JsCast}; use web_sys::{Event, MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelState}; @@ -17,10 +17,9 @@ use web_sys::{Event, MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataC /// As per spec, we limit the maximum amount to 16KB, see . const MAX_BUFFER: usize = 16 * 1024; -/// [`DataChannel`] is a wrapper around around [`RtcDataChannel`] which implements [`AsyncRead`] and [`AsyncWrite`]. - +/// [`PollDataChannel`] is a wrapper around around [`RtcDataChannel`] which implements [`AsyncRead`] and [`AsyncWrite`]. #[derive(Debug, Clone)] -pub(crate) struct DataChannel { +pub(crate) struct PollDataChannel { /// The [RtcDataChannel] being wrapped. inner: RtcDataChannel, @@ -44,7 +43,7 @@ pub(crate) struct DataChannel { _on_message_closure: Arc>, } -impl DataChannel { +impl PollDataChannel { pub(crate) fn new(inner: RtcDataChannel) -> Self { let open_waker = Arc::new(AtomicWaker::new()); let on_open_closure = Closure::new({ @@ -144,7 +143,7 @@ impl DataChannel { } } -impl AsyncRead for DataChannel { +impl AsyncRead for PollDataChannel { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -174,7 +173,7 @@ impl AsyncRead for DataChannel { } } -impl AsyncWrite for DataChannel { +impl AsyncWrite for PollDataChannel { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 3c9873c9e74..6cb2b0a2a08 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -12,7 +12,6 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-trait = "0.1" -asynchronous-codec = "0.6" bytes = "1" futures = "0.3" futures-timer = "3" @@ -24,8 +23,6 @@ libp2p-identity = { workspace = true } libp2p-webrtc-utils = { workspace = true } log = "0.4" multihash = { workspace = true } -quick-protobuf = "0.8" -quick-protobuf-codec = { workspace = true } rand = "0.8" rcgen = "0.10.0" serde = { version = "1.0", features = ["derive"] } diff --git a/transports/webrtc/src/tokio/stream.rs b/transports/webrtc/src/tokio/stream.rs index d07d8c8faa7..c8a9f34d8ca 100644 --- a/transports/webrtc/src/tokio/stream.rs +++ b/transports/webrtc/src/tokio/stream.rs @@ -18,259 +18,60 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use asynchronous_codec::Framed; -use bytes::Bytes; -use futures::{channel::oneshot, prelude::*, ready}; -use tokio_util::compat::Compat; -use webrtc::data::data_channel::{DataChannel, PollDataChannel}; - use std::{ - io, pin::Pin, sync::Arc, task::{Context, Poll}, }; -use libp2p_webrtc_utils::proto::{Flag, Message}; - -use crate::tokio::stream::{drop_listener::GracefullyClosed, framed_dc::FramedDc}; -use libp2p_webrtc_utils::stream::{ - state::{Closing, State}, - MAX_DATA_LEN, -}; - -mod drop_listener; -mod framed_dc; +use futures::prelude::*; +use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; +use webrtc::data::data_channel::{DataChannel, PollDataChannel}; -pub(crate) use drop_listener::DropListener; /// A substream on top of a WebRTC data channel. /// /// To be a proper libp2p substream, we need to implement [`AsyncRead`] and [`AsyncWrite`] as well /// as support a half-closed state which we do by framing messages in a protobuf envelope. pub struct Stream { - io: FramedDc, - state: State, - read_buffer: Bytes, - /// Dropping this will close the oneshot and notify the receiver by emitting `Canceled`. - drop_notifier: Option>, + inner: libp2p_webrtc_utils::Stream>, } +pub(crate) type DropListener = libp2p_webrtc_utils::DropListener>; + impl Stream { /// Returns a new `Substream` and a listener, which will notify the receiver when/if the substream /// is dropped. pub(crate) fn new(data_channel: Arc) -> (Self, DropListener) { - let (sender, receiver) = oneshot::channel(); - - let substream = Self { - io: framed_dc::new(data_channel.clone()), - state: State::Open, - read_buffer: Bytes::default(), - drop_notifier: Some(sender), - }; - let listener = DropListener::new(framed_dc::new(data_channel), receiver); - - (substream, listener) - } - - /// Gracefully closes the "read-half" of the substream. - pub fn poll_close_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - loop { - match self.state.close_read_barrier()? { - Some(Closing::Requested) => { - ready!(self.io.poll_ready_unpin(cx))?; + let (inner, drop_listener) = + libp2p_webrtc_utils::Stream::new(PollDataChannel::new(data_channel).compat()); - self.io.start_send_unpin(Message { - flag: Some(Flag::STOP_SENDING), - message: None, - })?; - self.state.close_read_message_sent(); - - continue; - } - Some(Closing::MessageSent) => { - ready!(self.io.poll_flush_unpin(cx))?; - - self.state.read_closed(); - - return Poll::Ready(Ok(())); - } - None => return Poll::Ready(Ok(())), - } - } + (Self { inner }, drop_listener) } } - impl AsyncRead for Stream { fn poll_read( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], - ) -> Poll> { - loop { - self.state.read_barrier()?; - - if !self.read_buffer.is_empty() { - let n = std::cmp::min(self.read_buffer.len(), buf.len()); - let data = self.read_buffer.split_to(n); - buf[0..n].copy_from_slice(&data[..]); - - return Poll::Ready(Ok(n)); - } - - let Self { - read_buffer, - io, - state, - .. - } = &mut *self; - - match ready!(io_poll_next(io, cx))? { - Some((flag, message)) => { - if let Some(flag) = flag { - state.handle_inbound_flag(flag, read_buffer); - } - - debug_assert!(read_buffer.is_empty()); - if let Some(message) = message { - *read_buffer = message.into(); - } - } - None => { - state.handle_inbound_flag(Flag::FIN, read_buffer); - return Poll::Ready(Ok(0)); - } - } - } + ) -> Poll> { + Pin::new(&mut self.get_mut().inner).poll_read(cx, buf) } } impl AsyncWrite for Stream { fn poll_write( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], - ) -> Poll> { - while self.state.read_flags_in_async_write() { - // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the - // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? - - let Self { - read_buffer, - io, - state, - .. - } = &mut *self; - - match io_poll_next(io, cx)? { - Poll::Ready(Some((Some(flag), message))) => { - // Read side is closed. Discard any incoming messages. - drop(message); - // But still handle flags, e.g. a `Flag::StopSending`. - state.handle_inbound_flag(flag, read_buffer) - } - Poll::Ready(Some((None, message))) => drop(message), - Poll::Ready(None) | Poll::Pending => break, - } - } - - self.state.write_barrier()?; - - ready!(self.io.poll_ready_unpin(cx))?; - - let n = usize::min(buf.len(), MAX_DATA_LEN); - - Pin::new(&mut self.io).start_send(Message { - flag: None, - message: Some(buf[0..n].into()), - })?; - - Poll::Ready(Ok(n)) - } - - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.io.poll_flush_unpin(cx).map_err(Into::into) - } - - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - loop { - match self.state.close_write_barrier()? { - Some(Closing::Requested) => { - ready!(self.io.poll_ready_unpin(cx))?; - - self.io.start_send_unpin(Message { - flag: Some(Flag::FIN), - message: None, - })?; - self.state.close_write_message_sent(); - - continue; - } - Some(Closing::MessageSent) => { - ready!(self.io.poll_flush_unpin(cx))?; - - self.state.write_closed(); - let _ = self - .drop_notifier - .take() - .expect("to not close twice") - .send(GracefullyClosed {}); - - return Poll::Ready(Ok(())); - } - None => return Poll::Ready(Ok(())), - } - } + ) -> Poll> { + Pin::new(&mut self.get_mut().inner).poll_write(cx, buf) } -} -fn io_poll_next( - io: &mut Framed, quick_protobuf_codec::Codec>, - cx: &mut Context<'_>, -) -> Poll, Option>)>>> { - match ready!(io.poll_next_unpin(cx)) - .transpose() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? - { - Some(Message { flag, message }) => Poll::Ready(Ok(Some((flag, message)))), - None => Poll::Ready(Ok(None)), + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.get_mut().inner).poll_flush(cx) } -} - -#[cfg(test)] -mod tests { - use libp2p_webrtc_utils::stream::{MAX_MSG_LEN, PROTO_OVERHEAD, VARINT_LEN}; - - use super::*; - use asynchronous_codec::Encoder; - use bytes::BytesMut; - use quick_protobuf::{MessageWrite, Writer}; - use unsigned_varint::codec::UviBytes; - - #[test] - fn max_data_len() { - // Largest possible message. - let message = [0; MAX_DATA_LEN]; - - let protobuf = libp2p_webrtc_utils::proto::Message { - flag: Some(libp2p_webrtc_utils::proto::Flag::FIN), - message: Some(message.to_vec()), - }; - - let mut encoded_msg = Vec::new(); - let mut writer = Writer::new(&mut encoded_msg); - protobuf - .write_message(&mut writer) - .expect("Encoding to succeed"); - assert_eq!(encoded_msg.len(), message.len() + PROTO_OVERHEAD); - - let mut uvi = UviBytes::default(); - let mut dst = BytesMut::new(); - uvi.encode(encoded_msg.as_slice(), &mut dst).unwrap(); - - // Ensure the varint prefixed and protobuf encoded largest message is no longer than the - // maximum limit specified in the libp2p WebRTC specification. - assert_eq!(dst.len(), MAX_MSG_LEN); - assert_eq!(dst.len() - encoded_msg.len(), VARINT_LEN); + 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/webrtc/src/tokio/stream/drop_listener.rs b/transports/webrtc/src/tokio/stream/drop_listener.rs deleted file mode 100644 index 0fc59f1dac7..00000000000 --- a/transports/webrtc/src/tokio/stream/drop_listener.rs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use futures::channel::oneshot; -use futures::channel::oneshot::Canceled; -use futures::{FutureExt, SinkExt}; - -use std::future::Future; -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use crate::tokio::stream::framed_dc::FramedDc; -use libp2p_webrtc_utils::proto::{Flag, Message}; - -#[must_use] -pub(crate) struct DropListener { - state: State, -} - -impl DropListener { - pub(crate) fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { - let substream_id = stream.get_ref().stream_identifier(); - - Self { - state: State::Idle { - stream, - receiver, - substream_id, - }, - } - } -} - -enum State { - /// The [`DropListener`] is idle and waiting to be activated. - Idle { - stream: FramedDc, - receiver: oneshot::Receiver, - substream_id: u16, - }, - /// The stream got dropped and we are sending a reset flag. - SendingReset { - stream: FramedDc, - }, - Flushing { - stream: FramedDc, - }, - /// Bad state transition. - Poisoned, -} - -impl Future for DropListener { - type Output = io::Result<()>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let state = &mut self.get_mut().state; - - loop { - match std::mem::replace(state, State::Poisoned) { - State::Idle { - stream, - substream_id, - mut receiver, - } => match receiver.poll_unpin(cx) { - Poll::Ready(Ok(GracefullyClosed {})) => { - return Poll::Ready(Ok(())); - } - Poll::Ready(Err(Canceled)) => { - log::info!("Substream {substream_id} dropped without graceful close, sending Reset"); - *state = State::SendingReset { stream }; - continue; - } - Poll::Pending => { - *state = State::Idle { - stream, - substream_id, - receiver, - }; - return Poll::Pending; - } - }, - State::SendingReset { mut stream } => match stream.poll_ready_unpin(cx)? { - Poll::Ready(()) => { - stream.start_send_unpin(Message { - flag: Some(Flag::RESET), - message: None, - })?; - *state = State::Flushing { stream }; - continue; - } - Poll::Pending => { - *state = State::SendingReset { stream }; - return Poll::Pending; - } - }, - State::Flushing { mut stream } => match stream.poll_flush_unpin(cx)? { - Poll::Ready(()) => return Poll::Ready(Ok(())), - Poll::Pending => { - *state = State::Flushing { stream }; - return Poll::Pending; - } - }, - State::Poisoned => { - unreachable!() - } - } - } - } -} - -/// Indicates that our substream got gracefully closed. -pub(crate) struct GracefullyClosed {} diff --git a/transports/webrtc/src/tokio/stream/framed_dc.rs b/transports/webrtc/src/tokio/stream/framed_dc.rs deleted file mode 100644 index ee0e8baad50..00000000000 --- a/transports/webrtc/src/tokio/stream/framed_dc.rs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use asynchronous_codec::Framed; -use tokio_util::compat::Compat; -use tokio_util::compat::TokioAsyncReadCompatExt; -use webrtc::data::data_channel::{DataChannel, PollDataChannel}; - -use std::sync::Arc; - -use libp2p_webrtc_utils::proto::Message; -use libp2p_webrtc_utils::stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; - -pub(crate) type FramedDc = Framed, quick_protobuf_codec::Codec>; -pub(crate) fn new(data_channel: Arc) -> FramedDc { - let mut inner = PollDataChannel::new(data_channel); - inner.set_read_buf_capacity(MAX_MSG_LEN); - - let mut framed = Framed::new( - inner.compat(), - quick_protobuf_codec::Codec::new(MAX_MSG_LEN - VARINT_LEN), - ); - // If not set, `Framed` buffers up to 131kB of data before sending, which leads to "outbound - // packet larger than maximum message size" error in webrtc-rs. - framed.set_send_high_water_mark(MAX_DATA_LEN); - framed -} From dd1e8a0b4551592dab73c12bc71e51a1a709ca5e Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 15:26:07 +1000 Subject: [PATCH 187/235] Retain read_buf_capacity for native WebRTC impl --- transports/webrtc-utils/src/lib.rs | 2 +- transports/webrtc-utils/src/stream.rs | 2 +- transports/webrtc/src/tokio/stream.rs | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/transports/webrtc-utils/src/lib.rs b/transports/webrtc-utils/src/lib.rs index 2038fd517ec..df9f1445ad9 100644 --- a/transports/webrtc-utils/src/lib.rs +++ b/transports/webrtc-utils/src/lib.rs @@ -14,5 +14,5 @@ mod stream; pub mod transport; pub use error::Error; -pub use stream::{DropListener, Stream}; +pub use stream::{DropListener, Stream, MAX_MSG_LEN}; pub use transport::parse_webrtc_dial_addr; diff --git a/transports/webrtc-utils/src/stream.rs b/transports/webrtc-utils/src/stream.rs index 6428197cf8c..2b13cefabd0 100644 --- a/transports/webrtc-utils/src/stream.rs +++ b/transports/webrtc-utils/src/stream.rs @@ -21,7 +21,7 @@ pub use drop_listener::DropListener; /// "As long as message interleaving is not supported, the sender SHOULD limit the maximum message /// size to 16 KB to avoid monopolization." /// Source: -const MAX_MSG_LEN: usize = 16384; // 16kiB +pub const MAX_MSG_LEN: usize = 16384; // 16kiB /// Length of varint, in bytes. const VARINT_LEN: usize = 2; /// Overhead of the protobuf encoding, in bytes. diff --git a/transports/webrtc/src/tokio/stream.rs b/transports/webrtc/src/tokio/stream.rs index c8a9f34d8ca..5731736481e 100644 --- a/transports/webrtc/src/tokio/stream.rs +++ b/transports/webrtc/src/tokio/stream.rs @@ -25,6 +25,7 @@ use std::{ }; use futures::prelude::*; +use libp2p_webrtc_utils::MAX_MSG_LEN; use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; use webrtc::data::data_channel::{DataChannel, PollDataChannel}; @@ -42,8 +43,10 @@ impl Stream { /// Returns a new `Substream` and a listener, which will notify the receiver when/if the substream /// is dropped. pub(crate) fn new(data_channel: Arc) -> (Self, DropListener) { - let (inner, drop_listener) = - libp2p_webrtc_utils::Stream::new(PollDataChannel::new(data_channel).compat()); + let mut data_channel = PollDataChannel::new(data_channel).compat(); + data_channel.get_mut().set_read_buf_capacity(MAX_MSG_LEN); + + let (inner, drop_listener) = libp2p_webrtc_utils::Stream::new(data_channel); (Self { inner }, drop_listener) } From 507ce67dde11f52638acf7cebd61efd64062872d Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 15:50:10 +1000 Subject: [PATCH 188/235] Unify SPF answer creation --- transports/webrtc-utils/src/sdp.rs | 64 ++++++++++++++++++------- transports/webrtc-websys/src/sdp.rs | 23 ++------- transports/webrtc-websys/src/upgrade.rs | 1 - transports/webrtc/src/tokio/sdp.rs | 23 +-------- 4 files changed, 50 insertions(+), 61 deletions(-) diff --git a/transports/webrtc-utils/src/sdp.rs b/transports/webrtc-utils/src/sdp.rs index 4c9bdf9f8e5..572c4d0e2c4 100644 --- a/transports/webrtc-utils/src/sdp.rs +++ b/transports/webrtc-utils/src/sdp.rs @@ -137,24 +137,13 @@ use rand::{thread_rng, Rng}; // // a=end-of-candidates -/// Indicates the IP version used in WebRTC: `IP4` or `IP6`. -#[derive(Serialize)] -enum IpVersion { - IP4, - IP6, -} - -/// Context passed to the templating engine, which replaces the above placeholders (e.g. -/// `{IP_VERSION}`) with real values. -#[derive(Serialize)] -struct DescriptionContext { - pub(crate) ip_version: IpVersion, - pub(crate) target_ip: IpAddr, - pub(crate) target_port: u16, - pub(crate) fingerprint_algorithm: String, - pub(crate) fingerprint_value: String, - pub(crate) ufrag: String, - pub(crate) pwd: String, +pub fn answer(addr: SocketAddr, server_fingerprint: &Fingerprint, client_ufrag: &str) -> String { + render_description( + SERVER_SESSION_DESCRIPTION, + addr, + server_fingerprint, + client_ufrag, + ) } /// Renders a [`TinyTemplate`] description using the provided arguments. @@ -206,6 +195,45 @@ pub fn fingerprint(sdp: &str) -> Option { None } +/// Indicates the IP version used in WebRTC: `IP4` or `IP6`. +#[derive(Serialize)] +enum IpVersion { + IP4, + IP6, +} + +/// Context passed to the templating engine, which replaces the above placeholders (e.g. +/// `{IP_VERSION}`) with real values. +#[derive(Serialize)] +struct DescriptionContext { + pub(crate) ip_version: IpVersion, + pub(crate) target_ip: IpAddr, + pub(crate) target_port: u16, + pub(crate) fingerprint_algorithm: String, + pub(crate) fingerprint_value: String, + pub(crate) ufrag: String, + pub(crate) pwd: String, +} + +const SERVER_SESSION_DESCRIPTION: &str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +t=0 0 +a=ice-lite +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +c=IN {ip_version} {target_ip} +a=mid:0 +a=ice-options:ice2 +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} +a=setup:passive +a=sctp-port:5000 +a=max-message-size:16384 +a=candidate:1467250027 1 UDP 1467250027 {target_ip} {target_port} typ host +a=end-of-candidates +"; + /// Generates a random ufrag and adds a prefix according to the spec. pub fn random_ufrag() -> String { format!( diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc-websys/src/sdp.rs index 33488b52f2c..f7add5723c7 100644 --- a/transports/webrtc-websys/src/sdp.rs +++ b/transports/webrtc-websys/src/sdp.rs @@ -1,6 +1,5 @@ use js_sys::Reflect; use libp2p_webrtc_utils::fingerprint::Fingerprint; -use libp2p_webrtc_utils::sdp::render_description; use std::net::SocketAddr; use wasm_bindgen::JsValue; use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; @@ -12,8 +11,7 @@ pub(crate) fn answer( client_ufrag: &str, ) -> RtcSessionDescriptionInit { let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); - answer_obj.sdp(&render_description( - SESSION_DESCRIPTION, + answer_obj.sdp(&libp2p_webrtc_utils::sdp::answer( addr, server_fingerprint, client_ufrag, @@ -29,6 +27,8 @@ pub(crate) fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescription let offer = Reflect::get(&offer, &JsValue::from_str("sdp")).unwrap(); let offer = offer.as_string().unwrap(); + log::info!("OFFER: {offer}"); + let lines = offer.split("\r\n"); // find line and replace a=ice-ufrag: with "\r\na=ice-ufrag:{client_ufrag}\r\n" @@ -57,20 +57,3 @@ pub(crate) fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescription offer_obj } - -const SESSION_DESCRIPTION: &str = "v=0 -o=- 0 0 IN {ip_version} {target_ip} -s=- -c=IN {ip_version} {target_ip} -t=0 0 -a=ice-lite -m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel -a=mid:0 -a=setup:passive -a=ice-ufrag:{ufrag} -a=ice-pwd:{pwd} -a=fingerprint:{fingerprint_algorithm} {fingerprint_value} -a=sctp-port:5000 -a=max-message-size:16384 -a=candidate:1467250027 1 UDP 1467250027 {target_ip} {target_port} typ host -"; diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index 329813e3bb5..1af46836801 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -81,7 +81,6 @@ async fn outbound_inner( /* * ANSWER */ - // TODO: Update SDP Answer format for Browser WebRTC let answer_obj = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); log::trace!("Answer SDP: {:?}", answer_obj); let srd_promise = peer_connection.set_remote_description(&answer_obj); diff --git a/transports/webrtc/src/tokio/sdp.rs b/transports/webrtc/src/tokio/sdp.rs index ff3eba3554e..a167849df48 100644 --- a/transports/webrtc/src/tokio/sdp.rs +++ b/transports/webrtc/src/tokio/sdp.rs @@ -30,8 +30,7 @@ pub(crate) fn answer( server_fingerprint: &Fingerprint, client_ufrag: &str, ) -> RTCSessionDescription { - RTCSessionDescription::answer(render_description( - SERVER_SESSION_DESCRIPTION, + RTCSessionDescription::answer(libp2p_webrtc_utils::sdp::answer( addr, server_fingerprint, client_ufrag, @@ -68,23 +67,3 @@ a=setup:actpass a=sctp-port:5000 a=max-message-size:16384 "; - -const SERVER_SESSION_DESCRIPTION: &str = "v=0 -o=- 0 0 IN {ip_version} {target_ip} -s=- -t=0 0 -a=ice-lite -m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel -c=IN {ip_version} {target_ip} -a=mid:0 -a=ice-options:ice2 -a=ice-ufrag:{ufrag} -a=ice-pwd:{pwd} -a=fingerprint:{fingerprint_algorithm} {fingerprint_value} - -a=setup:passive -a=sctp-port:5000 -a=max-message-size:16384 -a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host -a=end-of-candidates -"; From 8230ca5a4aed282a5f9a095ed9c3a6fae817f40f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 16:08:26 +1000 Subject: [PATCH 189/235] Polish SDP creation --- transports/webrtc-utils/src/sdp.rs | 8 +++-- transports/webrtc-websys/src/sdp.rs | 41 +++++++++++++------------ transports/webrtc-websys/src/upgrade.rs | 2 -- transports/webrtc/src/tokio/sdp.rs | 9 ++++-- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/transports/webrtc-utils/src/sdp.rs b/transports/webrtc-utils/src/sdp.rs index 572c4d0e2c4..afae4aeb07e 100644 --- a/transports/webrtc-utils/src/sdp.rs +++ b/transports/webrtc-utils/src/sdp.rs @@ -138,12 +138,16 @@ use rand::{thread_rng, Rng}; // a=end-of-candidates pub fn answer(addr: SocketAddr, server_fingerprint: &Fingerprint, client_ufrag: &str) -> String { - render_description( + let answer = render_description( SERVER_SESSION_DESCRIPTION, addr, server_fingerprint, client_ufrag, - ) + ); + + log::trace!("Created SDP answer: {answer}"); + + answer } /// Renders a [`TinyTemplate`] description using the provided arguments. diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc-websys/src/sdp.rs index f7add5723c7..773120c0465 100644 --- a/transports/webrtc-websys/src/sdp.rs +++ b/transports/webrtc-websys/src/sdp.rs @@ -23,37 +23,40 @@ pub(crate) fn answer( /// /// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. pub(crate) fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescriptionInit { - //JsValue to String - let offer = Reflect::get(&offer, &JsValue::from_str("sdp")).unwrap(); - let offer = offer.as_string().unwrap(); - - log::info!("OFFER: {offer}"); - - let lines = offer.split("\r\n"); + let offer = Reflect::get(&offer, &JsValue::from_str("sdp")) + .unwrap() + .as_string() + .unwrap(); // find line and replace a=ice-ufrag: with "\r\na=ice-ufrag:{client_ufrag}\r\n" - // find line andreplace a=ice-pwd: with "\r\na=ice-ufrag:{client_ufrag}\r\n" + // find line and replace a=ice-pwd: with "\r\na=ice-ufrag:{client_ufrag}\r\n" - let mut munged_offer_sdp = String::new(); + let mut munged_sdp_offer = String::new(); - for line in lines { + for line in offer.split("\r\n") { if line.starts_with("a=ice-ufrag:") { - munged_offer_sdp.push_str(&format!("a=ice-ufrag:{}\r\n", client_ufrag)); - } else if line.starts_with("a=ice-pwd:") { - munged_offer_sdp.push_str(&format!("a=ice-pwd:{}\r\n", client_ufrag)); - } else if !line.is_empty() { - munged_offer_sdp.push_str(&format!("{}\r\n", line)); + munged_sdp_offer.push_str(&format!("a=ice-ufrag:{client_ufrag}\r\n")); + continue; + } + + if line.starts_with("a=ice-pwd:") { + munged_sdp_offer.push_str(&format!("a=ice-pwd:{client_ufrag}\r\n")); + continue; + } + + if !line.is_empty() { + munged_sdp_offer.push_str(&format!("{}\r\n", line)); + continue; } } // remove any double \r\n - let munged_offer_sdp = munged_offer_sdp.replace("\r\n\r\n", "\r\n"); + let munged_sdp_offer = munged_sdp_offer.replace("\r\n\r\n", "\r\n"); - log::trace!("munged_offer_sdp: {}", munged_offer_sdp); + log::trace!("Created SDP offer: {munged_sdp_offer}"); - // setLocalDescription let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); - offer_obj.sdp(&munged_offer_sdp); + offer_obj.sdp(&munged_sdp_offer); offer_obj } diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index 1af46836801..4d694febe63 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -72,7 +72,6 @@ async fn outbound_inner( */ let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send let offer_obj = sdp::offer(offer, &ufrag); - log::trace!("Offer SDP: {:?}", offer_obj); let sld_promise = peer_connection.set_local_description(&offer_obj); JsFuture::from(sld_promise) .await @@ -82,7 +81,6 @@ async fn outbound_inner( * ANSWER */ let answer_obj = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); - log::trace!("Answer SDP: {:?}", answer_obj); let srd_promise = peer_connection.set_remote_description(&answer_obj); JsFuture::from(srd_promise) .await diff --git a/transports/webrtc/src/tokio/sdp.rs b/transports/webrtc/src/tokio/sdp.rs index a167849df48..88138800bc4 100644 --- a/transports/webrtc/src/tokio/sdp.rs +++ b/transports/webrtc/src/tokio/sdp.rs @@ -42,13 +42,16 @@ pub(crate) fn answer( /// /// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. pub(crate) fn offer(addr: SocketAddr, client_ufrag: &str) -> RTCSessionDescription { - RTCSessionDescription::offer(render_description( + let offer = render_description( CLIENT_SESSION_DESCRIPTION, addr, &Fingerprint::from([0xFF; 32]), client_ufrag, - )) - .unwrap() + ); + + log::trace!("Created SDP offer: {offer}"); + + RTCSessionDescription::offer(offer).unwrap() } const CLIENT_SESSION_DESCRIPTION: &str = "v=0 From 519a48b135d043bb5d71cbeef8afa9af61e02ef9 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 16:33:30 +1000 Subject: [PATCH 190/235] Tidy up upgrade process by introducing wrapper for web_sys object --- Cargo.lock | 1 + transports/webrtc-utils/src/sdp.rs | 36 ----- transports/webrtc-websys/Cargo.toml | 1 + transports/webrtc-websys/src/connection.rs | 163 +++++++++++++++++++-- transports/webrtc-websys/src/sdp.rs | 9 +- transports/webrtc-websys/src/stream.rs | 48 +----- transports/webrtc-websys/src/upgrade.rs | 75 ++-------- 7 files changed, 172 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80a8ba47430..31061ae33bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3456,6 +3456,7 @@ dependencies = [ "futures", "futures-timer", "getrandom 0.2.10", + "hex", "hex-literal", "js-sys", "libp2p-core", diff --git a/transports/webrtc-utils/src/sdp.rs b/transports/webrtc-utils/src/sdp.rs index afae4aeb07e..6a9aac3be8f 100644 --- a/transports/webrtc-utils/src/sdp.rs +++ b/transports/webrtc-utils/src/sdp.rs @@ -179,26 +179,6 @@ pub fn render_description( tt.render("description", &context).unwrap() } -/// Get Fingerprint from SDP -/// Gets the fingerprint from: a=fingerprint: hash-algo fingerprint -pub fn fingerprint(sdp: &str) -> Option { - // split the sdp by new lines / carriage returns - let lines = sdp.split("\r\n"); - - // iterate through the lines to find the one starting with a=fingerprint: - // get the value after the first space - // return the value as a Fingerprint - for line in lines { - if line.starts_with("a=fingerprint:") { - let fingerprint = line.split(' ').nth(1).unwrap(); - let bytes = hex::decode(fingerprint.replace(':', "")).unwrap(); - let arr: [u8; 32] = bytes.as_slice().try_into().unwrap(); - return Some(Fingerprint::from(arr)); - } - } - None -} - /// Indicates the IP version used in WebRTC: `IP4` or `IP6`. #[derive(Serialize)] enum IpVersion { @@ -249,19 +229,3 @@ pub fn random_ufrag() -> String { .collect::() ) } - -#[cfg(test)] -mod sdp_tests { - use super::*; - - #[test] - fn test_fingerprint() { - let sdp: &str = "v=0\r\no=- 0 0 IN IP6 ::1\r\ns=-\r\nc=IN IP6 ::1\r\nt=0 0\r\na=ice-lite\r\nm=application 61885 UDP/DTLS/SCTP webrtc-datachannel\r\na=mid:0\r\na=setup:passive\r\na=ice-ufrag:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\r\na=ice-pwd:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\r\na=fingerprint:sha-256 A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89\r\na=sctp-port:5000\r\na=max-message-size:16384\r\na=candidate:1467250027 1 UDP 1467250027 ::1 61885 typ host\r\n"; - let fingerprint = match fingerprint(sdp) { - Some(fingerprint) => fingerprint, - None => panic!("No fingerprint found"), - }; - assert_eq!(fingerprint.algorithm(), "sha-256"); - assert_eq!(fingerprint.to_sdp_format(), "A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89"); - } -} diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index 07a5e0984af..6a7a49528b4 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -20,6 +20,7 @@ libp2p-core = { workspace = true } libp2p-identity = { workspace = true } libp2p-webrtc-utils = { workspace = true } log = "0.4.19" +hex = "0.4.3" send_wrapper = { version = "0.6.0", features = ["futures"] } serde = { version = "1.0", features = ["derive"] } thiserror = "1" diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index c51b579dbbb..27295138f6e 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -1,19 +1,25 @@ //! Websys WebRTC Peer Connection //! //! Creates and manages the [RtcPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) -use crate::stream::RtcDataChannelBuilder; +use crate::stream::DropListener; use super::{Error, Stream}; use futures::channel; use futures::stream::FuturesUnordered; use futures::StreamExt; +use js_sys::{Object, Reflect}; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; +use libp2p_webrtc_utils::fingerprint::Fingerprint; use send_wrapper::SendWrapper; use std::pin::Pin; use std::task::Waker; use std::task::{ready, Context, Poll}; use wasm_bindgen::prelude::*; -use web_sys::{RtcDataChannel, RtcDataChannelEvent, RtcPeerConnection}; +use wasm_bindgen_futures::JsFuture; +use web_sys::{ + RtcConfiguration, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelInit, RtcDataChannelType, + RtcSessionDescriptionInit, +}; /// A WebRTC Connection pub struct Connection { @@ -67,7 +73,9 @@ impl ConnectionInner { } }); - peer_connection.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); + peer_connection + .inner + .set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); ondatachannel_callback.forget(); Self { @@ -81,18 +89,17 @@ impl ConnectionInner { /// Initiates and polls a future from `create_data_channel`. /// Takes the RtcPeerConnection and creates a regular DataChannel - fn poll_create_data_channel(&mut self, _cx: &mut Context) -> Poll> { - // Create Regular Data Channel + fn poll_create_data_channel(&mut self, _cx: &mut Context) -> Poll { log::trace!("Creating outbound data channel"); - let dc = RtcDataChannelBuilder::default().build_with(&self.peer_connection); - let (channel, drop_listener) = Stream::new(dc); + + let (stream, drop_listener) = self.peer_connection.new_stream(); self.drop_listeners.push(drop_listener); if let Some(waker) = self.no_drop_listeners_waker.take() { waker.wake() } - Poll::Ready(Ok(channel)) + Poll::Ready(stream) } /// Polls the ondatachannel callback for inbound data channel stream. @@ -146,7 +153,7 @@ impl ConnectionInner { fn close_connection(&mut self) { if !self.closed { log::trace!("connection::close_connection"); - self.peer_connection.close(); + self.peer_connection.inner.close(); self.closed = true; } } @@ -179,7 +186,9 @@ impl StreamMuxer for Connection { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - self.inner.poll_create_data_channel(cx) + let stream = ready!(self.inner.poll_create_data_channel(cx)); + + Poll::Ready(Ok(stream)) } /// Closes the Peer Connection. @@ -201,3 +210,137 @@ impl StreamMuxer for Connection { self.inner.poll(cx) } } + +pub(crate) struct RtcPeerConnection { + inner: web_sys::RtcPeerConnection, +} + +impl RtcPeerConnection { + pub(crate) async fn new(algorithm: String) -> Result { + let algo: Object = Object::new(); + Reflect::set(&algo, &"name".into(), &"ECDSA".into()).unwrap(); + Reflect::set(&algo, &"namedCurve".into(), &"P-256".into()).unwrap(); + Reflect::set(&algo, &"hash".into(), &algorithm.into()).unwrap(); + + let certificate_promise = + web_sys::RtcPeerConnection::generate_certificate_with_object(&algo) + .expect("certificate to be valid"); + + let certificate = JsFuture::from(certificate_promise).await?; + + let mut config = RtcConfiguration::default(); + // wrap certificate in a js Array first before adding it to the config object + let certificate_arr = js_sys::Array::new(); + certificate_arr.push(&certificate); + config.certificates(&certificate_arr); + + let inner = web_sys::RtcPeerConnection::new_with_configuration(&config)?; + + Ok(Self { inner }) + } + + pub(crate) fn new_handshake_stream(&self) -> (Stream, DropListener) { + Stream::new(self.new_data_channel(true)) + } + + pub(crate) fn new_stream(&self) -> (Stream, DropListener) { + Stream::new(self.new_data_channel(false)) + } + + fn new_data_channel(&self, negotiated: bool) -> RtcDataChannel { + const LABEL: &str = ""; + + let dc = match negotiated { + true => { + let mut options = RtcDataChannelInit::new(); + options.negotiated(true).id(0); // id is only ever set to zero when negotiated is true + + self.inner + .create_data_channel_with_data_channel_dict(LABEL, &options) + } + false => self.inner.create_data_channel(LABEL), + }; + dc.set_binary_type(RtcDataChannelType::Arraybuffer); // Hardcoded here, it's the only type we use + + dc + } + + pub(crate) async fn create_offer(&self) -> Result { + let offer = JsFuture::from(self.inner.create_offer()).await?; + + let offer = Reflect::get(&offer, &JsValue::from_str("sdp")) + .unwrap() + .as_string() + .unwrap(); + + Ok(offer) + } + + pub(crate) async fn set_local_description( + &self, + sdp: RtcSessionDescriptionInit, + ) -> Result<(), Error> { + let promise = self.inner.set_local_description(&sdp); + JsFuture::from(promise).await?; + + Ok(()) + } + + pub(crate) fn local_fingerprint(&self) -> Result { + let sdp = &self + .inner + .local_description() + .ok_or_else(|| Error::JsError("No local description".to_string()))? + .sdp(); + + let fingerprint = parse_fingerprint(sdp) + .ok_or_else(|| Error::JsError("No fingerprint in SDP".to_string()))?; + + Ok(fingerprint) + } + + pub(crate) async fn set_remote_description( + &self, + sdp: RtcSessionDescriptionInit, + ) -> Result<(), Error> { + let promise = self.inner.set_remote_description(&sdp); + JsFuture::from(promise).await?; + + Ok(()) + } +} + +/// Parse Fingerprint from a SDP. +fn parse_fingerprint(sdp: &str) -> Option { + // split the sdp by new lines / carriage returns + let lines = sdp.split("\r\n"); + + // iterate through the lines to find the one starting with a=fingerprint: + // get the value after the first space + // return the value as a Fingerprint + for line in lines { + if line.starts_with("a=fingerprint:") { + let fingerprint = line.split(' ').nth(1).unwrap(); + let bytes = hex::decode(fingerprint.replace(':', "")).unwrap(); + let arr: [u8; 32] = bytes.as_slice().try_into().unwrap(); + return Some(Fingerprint::from(arr)); + } + } + None +} + +#[cfg(test)] +mod sdp_tests { + use super::*; + + #[test] + fn test_fingerprint() { + let sdp: &str = "v=0\r\no=- 0 0 IN IP6 ::1\r\ns=-\r\nc=IN IP6 ::1\r\nt=0 0\r\na=ice-lite\r\nm=application 61885 UDP/DTLS/SCTP webrtc-datachannel\r\na=mid:0\r\na=setup:passive\r\na=ice-ufrag:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\r\na=ice-pwd:libp2p+webrtc+v1/YwapWySn6fE6L9i47PhlB6X4gzNXcgFs\r\na=fingerprint:sha-256 A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89\r\na=sctp-port:5000\r\na=max-message-size:16384\r\na=candidate:1467250027 1 UDP 1467250027 ::1 61885 typ host\r\n"; + let fingerprint = match parse_fingerprint(sdp) { + Some(fingerprint) => fingerprint, + None => panic!("No fingerprint found"), + }; + assert_eq!(fingerprint.algorithm(), "sha-256"); + assert_eq!(fingerprint.to_sdp_format(), "A8:17:77:1E:02:7E:D1:2B:53:92:70:A6:8E:F9:02:CC:21:72:3A:92:5D:F4:97:5F:27:C4:5E:75:D4:F4:31:89"); + } +} diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc-websys/src/sdp.rs index 773120c0465..82d6fea4dfe 100644 --- a/transports/webrtc-websys/src/sdp.rs +++ b/transports/webrtc-websys/src/sdp.rs @@ -1,7 +1,5 @@ -use js_sys::Reflect; use libp2p_webrtc_utils::fingerprint::Fingerprint; use std::net::SocketAddr; -use wasm_bindgen::JsValue; use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; /// Creates the SDP answer used by the client. @@ -22,12 +20,7 @@ pub(crate) fn answer( /// Creates the munged SDP offer from the Browser's given SDP offer /// /// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. -pub(crate) fn offer(offer: JsValue, client_ufrag: &str) -> RtcSessionDescriptionInit { - let offer = Reflect::get(&offer, &JsValue::from_str("sdp")) - .unwrap() - .as_string() - .unwrap(); - +pub(crate) fn offer(offer: String, client_ufrag: &str) -> RtcSessionDescriptionInit { // find line and replace a=ice-ufrag: with "\r\na=ice-ufrag:{client_ufrag}\r\n" // find line and replace a=ice-pwd: with "\r\na=ice-ufrag:{client_ufrag}\r\n" diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 609a5029638..8667d187da6 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -1,53 +1,13 @@ //! The WebRTC [Stream] over the Connection -use std::pin::Pin; -use std::task::{Context, Poll}; - +use self::poll_data_channel::PollDataChannel; use futures::{AsyncRead, AsyncWrite}; use send_wrapper::SendWrapper; -use web_sys::{RtcDataChannel, RtcDataChannelInit, RtcDataChannelType, RtcPeerConnection}; - -use self::poll_data_channel::PollDataChannel; +use std::pin::Pin; +use std::task::{Context, Poll}; +use web_sys::RtcDataChannel; mod poll_data_channel; -/// The Browser Default is Blob, so we must set ours to Arraybuffer explicitly -const ARRAY_BUFFER_BINARY_TYPE: RtcDataChannelType = RtcDataChannelType::Arraybuffer; - -/// Builder for DataChannel -#[derive(Default, Debug)] -pub(crate) struct RtcDataChannelBuilder { - negotiated: bool, -} - -/// Builds a Data Channel with selected options and given peer connection -/// -/// The default config is used in most cases, except when negotiating a Noise handshake -impl RtcDataChannelBuilder { - /// Sets the DataChannel to be used for the Noise handshake - /// Defaults to false - pub(crate) fn negotiated(&mut self, negotiated: bool) -> &mut Self { - self.negotiated = negotiated; - self - } - - /// Builds the WebRTC DataChannel from [RtcPeerConnection] with the given configuration - pub(crate) fn build_with(&self, peer_connection: &RtcPeerConnection) -> RtcDataChannel { - const LABEL: &str = ""; - - let dc = match self.negotiated { - true => { - let mut data_channel_dict = RtcDataChannelInit::new(); - data_channel_dict.negotiated(true).id(0); // id is only ever set to zero when negotiated is true - peer_connection - .create_data_channel_with_data_channel_dict(LABEL, &data_channel_dict) - } - false => peer_connection.create_data_channel(LABEL), - }; - dc.set_binary_type(ARRAY_BUFFER_BINARY_TYPE); // Hardcoded here, it's the only type we use - dc - } -} - /// Stream over the Connection pub struct Stream { /// Wrapper for the inner stream to make it Send diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index 4d694febe63..a27d7626fc9 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -1,19 +1,12 @@ -use super::stream::Stream; use super::Error; +use crate::connection::RtcPeerConnection; use crate::sdp; -use crate::stream::RtcDataChannelBuilder; use crate::Connection; -use js_sys::{Object, Reflect}; use libp2p_identity::{Keypair, PeerId}; use libp2p_webrtc_utils::fingerprint::Fingerprint; use libp2p_webrtc_utils::noise; use send_wrapper::SendWrapper; use std::net::SocketAddr; -use wasm_bindgen_futures::JsFuture; -use web_sys::{RtcConfiguration, RtcPeerConnection}; - -const SHA2_256: u64 = 0x12; -const SHA2_512: u64 = 0x13; /// Upgrades an outbound WebRTC connection by creating the data channel /// and conducting a Noise handshake @@ -32,69 +25,25 @@ async fn outbound_inner( remote_fingerprint: Fingerprint, id_keys: Keypair, ) -> Result<(PeerId, Connection), Error> { - let hash = match remote_fingerprint.to_multihash().code() { - SHA2_256 => "sha-256", - SHA2_512 => "sha-512", - _ => return Err(Error::JsError("unsupported hash".to_string())), - }; - - let algo: js_sys::Object = Object::new(); - Reflect::set(&algo, &"name".into(), &"ECDSA".into()).unwrap(); - Reflect::set(&algo, &"namedCurve".into(), &"P-256".into()).unwrap(); - Reflect::set(&algo, &"hash".into(), &hash.into()).unwrap(); - - let certificate_promise = RtcPeerConnection::generate_certificate_with_object(&algo) - .expect("certificate to be valid"); - - let certificate = JsFuture::from(certificate_promise).await?; // Needs to be Send - - let mut config = RtcConfiguration::default(); - // wrap certificate in a js Array first before adding it to the config object - let certificate_arr = js_sys::Array::new(); - certificate_arr.push(&certificate); - config.certificates(&certificate_arr); - - let peer_connection = web_sys::RtcPeerConnection::new_with_configuration(&config)?; + let rtc_peer_connection = RtcPeerConnection::new(remote_fingerprint.algorithm()).await?; // Create stream for Noise handshake // Must create data channel before Offer is created for it to be included in the SDP - let handshake_data_channel = RtcDataChannelBuilder::default() - .negotiated(true) - .build_with(&peer_connection); - - let (channel, listener) = Stream::new(handshake_data_channel); + let (channel, listener) = rtc_peer_connection.new_handshake_stream(); drop(listener); let ufrag = libp2p_webrtc_utils::sdp::random_ufrag(); - /* - * OFFER - */ - let offer = JsFuture::from(peer_connection.create_offer()).await?; // Needs to be Send - let offer_obj = sdp::offer(offer, &ufrag); - let sld_promise = peer_connection.set_local_description(&offer_obj); - JsFuture::from(sld_promise) - .await - .expect("set_local_description to succeed"); + let offer = rtc_peer_connection.create_offer().await?; + let munged_offer = sdp::offer(offer, &ufrag); + rtc_peer_connection + .set_local_description(munged_offer) + .await?; - /* - * ANSWER - */ - let answer_obj = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); - let srd_promise = peer_connection.set_remote_description(&answer_obj); - JsFuture::from(srd_promise) - .await - .expect("set_remote_description to succeed"); + let answer = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); + rtc_peer_connection.set_remote_description(answer).await?; - // get local_fingerprint from local RtcPeerConnection peer_connection certificate - let local_sdp = match &peer_connection.local_description() { - Some(description) => description.sdp(), - None => return Err(Error::JsError("local_description is None".to_string())), - }; - let local_fingerprint = match libp2p_webrtc_utils::sdp::fingerprint(&local_sdp) { - Some(fingerprint) => fingerprint, - None => return Err(Error::JsError("No local fingerprint found".to_string())), - }; + let local_fingerprint = rtc_peer_connection.local_fingerprint()?; log::trace!("local_fingerprint: {:?}", local_fingerprint); log::trace!("remote_fingerprint: {:?}", remote_fingerprint); @@ -103,5 +52,5 @@ async fn outbound_inner( log::debug!("Remote peer identified as {peer_id}"); - Ok((peer_id, Connection::new(peer_connection))) + Ok((peer_id, Connection::new(rtc_peer_connection))) } From 75fcbdf5457ff7009d707492c686810491df9890 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 16:35:53 +1000 Subject: [PATCH 191/235] Tidy comments and fn names --- transports/webrtc-websys/src/connection.rs | 11 +++-------- transports/webrtc-websys/src/stream.rs | 4 +++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 27295138f6e..defac9bc16c 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -107,7 +107,7 @@ impl ConnectionInner { /// To poll for inbound WebRTCStreams, we need to poll for the ondatachannel callback /// We only get that callback for inbound data channels on our connections. /// This callback is converted to a future using channel, which we can poll here - fn poll_ondatachannel(&mut self, cx: &mut Context) -> Poll> { + fn poll_inbound(&mut self, cx: &mut Context) -> Poll> { match ready!(self.rx_ondatachannel.poll_next_unpin(cx)) { Some(dc) => { // Create a WebRTC Stream from the Data Channel @@ -168,20 +168,16 @@ impl Drop for ConnectionInner { /// WebRTC native multiplexing /// Allows users to open substreams impl StreamMuxer for Connection { - type Substream = Stream; // A Stream of a WebRTC PeerConnection is a Data Channel + type Substream = Stream; type Error = Error; - /// Polls for inbound connections by waiting for the ondatachannel callback to be triggered fn poll_inbound( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - // Inbound substream is signalled by an ondatachannel event - self.inner.poll_ondatachannel(cx) + self.inner.poll_inbound(cx) } - // We create the Data Channel here from the Peer Connection - // then wait for the Data Channel to be opened fn poll_outbound( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -202,7 +198,6 @@ impl StreamMuxer for Connection { Poll::Ready(Ok(())) } - /// Polls the connection fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 8667d187da6..862d5a320df 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -8,7 +8,9 @@ use web_sys::RtcDataChannel; mod poll_data_channel; -/// Stream over the Connection +/// A stream over a WebRTC connection. +/// +/// Backed by a WebRTC data channel. pub struct Stream { /// Wrapper for the inner stream to make it Send inner: SendWrapper>, From 91ca28ea6d89e18e6845ca182e5d9338ac6d4836 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 16:42:58 +1000 Subject: [PATCH 192/235] Tidy up new data channel creation --- transports/webrtc-websys/src/connection.rs | 80 ++++++++++++---------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index defac9bc16c..5f1827bafc8 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -4,7 +4,7 @@ use crate::stream::DropListener; use super::{Error, Stream}; -use futures::channel; +use futures::channel::mpsc; use futures::stream::FuturesUnordered; use futures::StreamExt; use js_sys::{Object, Reflect}; @@ -43,12 +43,14 @@ struct ConnectionInner { peer_connection: RtcPeerConnection, /// Whether the connection is closed closed: bool, - /// A channel that signals incoming data channels - rx_ondatachannel: channel::mpsc::Receiver, + /// An [`mpsc::channel`] for all inbound data channels. + /// + /// Because the browser's WebRTC API is event-based, we need to use a channel to obtain all inbound data channels. + inbound_data_channels: mpsc::Receiver, /// A list of futures, which, once completed, signal that a [`Stream`] has been dropped. /// Currently unimplemented, will be implemented in a future PR. - drop_listeners: FuturesUnordered, + drop_listeners: FuturesUnordered, /// Currently unimplemented, will be implemented in a future PR. no_drop_listeners_waker: Option, } @@ -57,19 +59,22 @@ impl ConnectionInner { /// Create a new inner WebRTC Connection fn new(peer_connection: RtcPeerConnection) -> Self { // An ondatachannel Future enables us to poll for incoming data channel events in poll_incoming - let (mut tx_ondatachannel, rx_ondatachannel) = channel::mpsc::channel(4); // we may get more than one data channel opened on a single peer connection + let (mut tx_ondatachannel, rx_ondatachannel) = mpsc::channel(4); // we may get more than one data channel opened on a single peer connection // Wake the Future in the ondatachannel callback let ondatachannel_callback = Closure::::new(move |ev: RtcDataChannelEvent| { - let dc2 = ev.channel(); - log::trace!("ondatachannel_callback triggered"); - match tx_ondatachannel.try_send(dc2) { - Ok(_) => log::trace!("ondatachannel_callback sent data channel"), - Err(e) => log::error!( - "ondatachannel_callback: failed to send data channel: {:?}", - e - ), + log::trace!("New data channel"); + + if let Err(e) = tx_ondatachannel.try_send(ev.channel()) { + if e.is_full() { + log::warn!("Remote is opening too many data channels, we can't keep up!"); + return; + } + + if e.is_disconnected() { + log::warn!("Receiver is gone, are we shutting down?"); + } } }); @@ -83,7 +88,7 @@ impl ConnectionInner { closed: false, drop_listeners: FuturesUnordered::default(), no_drop_listeners_waker: None, - rx_ondatachannel, + inbound_data_channels: rx_ondatachannel, } } @@ -92,34 +97,19 @@ impl ConnectionInner { fn poll_create_data_channel(&mut self, _cx: &mut Context) -> Poll { log::trace!("Creating outbound data channel"); - let (stream, drop_listener) = self.peer_connection.new_stream(); - - self.drop_listeners.push(drop_listener); - if let Some(waker) = self.no_drop_listeners_waker.take() { - waker.wake() - } + let data_channel = self.peer_connection.new_regular_data_channel(); + let stream = self.new_stream_from_data_channel(data_channel); Poll::Ready(stream) } - /// Polls the ondatachannel callback for inbound data channel stream. - /// - /// To poll for inbound WebRTCStreams, we need to poll for the ondatachannel callback - /// We only get that callback for inbound data channels on our connections. - /// This callback is converted to a future using channel, which we can poll here + /// Polls the chann fn poll_inbound(&mut self, cx: &mut Context) -> Poll> { - match ready!(self.rx_ondatachannel.poll_next_unpin(cx)) { - Some(dc) => { - // Create a WebRTC Stream from the Data Channel - let (channel, drop_listener) = Stream::new(dc); - - self.drop_listeners.push(drop_listener); - if let Some(waker) = self.no_drop_listeners_waker.take() { - waker.wake() - } + match ready!(self.inbound_data_channels.poll_next_unpin(cx)) { + Some(data_channel) => { + let stream = self.new_stream_from_data_channel(data_channel); - log::trace!("connection::poll_ondatachannel ready"); - Poll::Ready(Ok(channel)) + Poll::Ready(Ok(stream)) } None => { self.no_drop_listeners_waker = Some(cx.waker().clone()); @@ -128,6 +118,16 @@ impl ConnectionInner { } } + fn new_stream_from_data_channel(&mut self, data_channel: RtcDataChannel) -> Stream { + let (stream, drop_listener) = Stream::new(data_channel); + + self.drop_listeners.push(drop_listener); + if let Some(waker) = self.no_drop_listeners_waker.take() { + waker.wake() + } + stream + } + /// Poll the Connection /// /// Mostly handles Dropped Listeners @@ -234,12 +234,16 @@ impl RtcPeerConnection { Ok(Self { inner }) } + /// Creates the stream for the initial noise handshake. + /// + /// The underlying data channel MUST have `negotiated` set to `true` and carry the ID 0. pub(crate) fn new_handshake_stream(&self) -> (Stream, DropListener) { Stream::new(self.new_data_channel(true)) } - pub(crate) fn new_stream(&self) -> (Stream, DropListener) { - Stream::new(self.new_data_channel(false)) + /// Creates a regular data channel for when the connection is already established. + pub(crate) fn new_regular_data_channel(&self) -> RtcDataChannel { + self.new_data_channel(false) } fn new_data_channel(&self, negotiated: bool) -> RtcDataChannel { From 66fdf452977236dfa4ab103489fa076af270f959 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 16:43:37 +1000 Subject: [PATCH 193/235] Rename field --- transports/webrtc-websys/src/connection.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 5f1827bafc8..54b7ea4e351 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -40,14 +40,13 @@ impl Connection { /// Inner Connection that is wrapped in [SendWrapper] struct ConnectionInner { /// The [RtcPeerConnection] that is used for the WebRTC Connection - peer_connection: RtcPeerConnection, + inner: RtcPeerConnection, /// Whether the connection is closed closed: bool, /// An [`mpsc::channel`] for all inbound data channels. /// /// Because the browser's WebRTC API is event-based, we need to use a channel to obtain all inbound data channels. inbound_data_channels: mpsc::Receiver, - /// A list of futures, which, once completed, signal that a [`Stream`] has been dropped. /// Currently unimplemented, will be implemented in a future PR. drop_listeners: FuturesUnordered, @@ -84,7 +83,7 @@ impl ConnectionInner { ondatachannel_callback.forget(); Self { - peer_connection, + inner: peer_connection, closed: false, drop_listeners: FuturesUnordered::default(), no_drop_listeners_waker: None, @@ -97,7 +96,7 @@ impl ConnectionInner { fn poll_create_data_channel(&mut self, _cx: &mut Context) -> Poll { log::trace!("Creating outbound data channel"); - let data_channel = self.peer_connection.new_regular_data_channel(); + let data_channel = self.inner.new_regular_data_channel(); let stream = self.new_stream_from_data_channel(data_channel); Poll::Ready(stream) @@ -153,7 +152,7 @@ impl ConnectionInner { fn close_connection(&mut self) { if !self.closed { log::trace!("connection::close_connection"); - self.peer_connection.inner.close(); + self.inner.inner.close(); self.closed = true; } } From 2240f7bdc4b09f1858ced6421d331f431b723582 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 16:44:42 +1000 Subject: [PATCH 194/235] Don't leak closure --- transports/webrtc-websys/src/connection.rs | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 54b7ea4e351..6c5f201a08b 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -52,6 +52,8 @@ struct ConnectionInner { drop_listeners: FuturesUnordered, /// Currently unimplemented, will be implemented in a future PR. no_drop_listeners_waker: Option, + + _ondatachannel_closure: Closure, } impl ConnectionInner { @@ -60,27 +62,24 @@ impl ConnectionInner { // An ondatachannel Future enables us to poll for incoming data channel events in poll_incoming let (mut tx_ondatachannel, rx_ondatachannel) = mpsc::channel(4); // we may get more than one data channel opened on a single peer connection - // Wake the Future in the ondatachannel callback - let ondatachannel_callback = - Closure::::new(move |ev: RtcDataChannelEvent| { - log::trace!("New data channel"); + let ondatachannel_closure = Closure::new(move |ev: RtcDataChannelEvent| { + log::trace!("New data channel"); - if let Err(e) = tx_ondatachannel.try_send(ev.channel()) { - if e.is_full() { - log::warn!("Remote is opening too many data channels, we can't keep up!"); - return; - } + if let Err(e) = tx_ondatachannel.try_send(ev.channel()) { + if e.is_full() { + log::warn!("Remote is opening too many data channels, we can't keep up!"); + return; + } - if e.is_disconnected() { - log::warn!("Receiver is gone, are we shutting down?"); - } + if e.is_disconnected() { + log::warn!("Receiver is gone, are we shutting down?"); } - }); + } + }); peer_connection .inner - .set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); - ondatachannel_callback.forget(); + .set_ondatachannel(Some(ondatachannel_closure.as_ref().unchecked_ref())); Self { inner: peer_connection, @@ -88,6 +87,7 @@ impl ConnectionInner { drop_listeners: FuturesUnordered::default(), no_drop_listeners_waker: None, inbound_data_channels: rx_ondatachannel, + _ondatachannel_closure: ondatachannel_closure, } } From 33f412c1facb79fb41568207c25f5d5e48dc3804 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 16:53:00 +1000 Subject: [PATCH 195/235] Remove `ConnectionInner` --- transports/webrtc-websys/src/connection.rs | 125 ++++++++------------- transports/webrtc-websys/src/stream.rs | 4 +- 2 files changed, 47 insertions(+), 82 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 6c5f201a08b..c995642ace8 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -1,9 +1,7 @@ -//! Websys WebRTC Peer Connection -//! -//! Creates and manages the [RtcPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) -use crate::stream::DropListener; +//! A libp2p connection backed by an [RtcPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection). use super::{Error, Stream}; +use crate::stream::DropListener; use futures::channel::mpsc; use futures::stream::FuturesUnordered; use futures::StreamExt; @@ -21,44 +19,32 @@ use web_sys::{ RtcSessionDescriptionInit, }; -/// A WebRTC Connection +/// A WebRTC Connection. +/// +/// All connections need to be [`Send`] which is why some fields are wrapped in [`SendWrapper`]. +/// This is safe because WASM is single-threaded. pub struct Connection { - // Swarm needs all types to be Send. WASM is single-threaded - // and it is safe to use SendWrapper. - inner: SendWrapper, -} - -impl Connection { - /// Create a new WebRTC Connection - pub(crate) fn new(peer_connection: RtcPeerConnection) -> Self { - Self { - inner: SendWrapper::new(ConnectionInner::new(peer_connection)), - } - } -} - -/// Inner Connection that is wrapped in [SendWrapper] -struct ConnectionInner { /// The [RtcPeerConnection] that is used for the WebRTC Connection - inner: RtcPeerConnection, + inner: SendWrapper, + /// Whether the connection is closed closed: bool, /// An [`mpsc::channel`] for all inbound data channels. /// /// Because the browser's WebRTC API is event-based, we need to use a channel to obtain all inbound data channels. - inbound_data_channels: mpsc::Receiver, + inbound_data_channels: SendWrapper>, /// A list of futures, which, once completed, signal that a [`Stream`] has been dropped. /// Currently unimplemented, will be implemented in a future PR. drop_listeners: FuturesUnordered, /// Currently unimplemented, will be implemented in a future PR. no_drop_listeners_waker: Option, - _ondatachannel_closure: Closure, + _ondatachannel_closure: SendWrapper>, } -impl ConnectionInner { +impl Connection { /// Create a new inner WebRTC Connection - fn new(peer_connection: RtcPeerConnection) -> Self { + pub(crate) fn new(peer_connection: RtcPeerConnection) -> Self { // An ondatachannel Future enables us to poll for incoming data channel events in poll_incoming let (mut tx_ondatachannel, rx_ondatachannel) = mpsc::channel(4); // we may get more than one data channel opened on a single peer connection @@ -76,44 +62,17 @@ impl ConnectionInner { } } }); - peer_connection .inner .set_ondatachannel(Some(ondatachannel_closure.as_ref().unchecked_ref())); Self { - inner: peer_connection, + inner: SendWrapper::new(peer_connection), closed: false, drop_listeners: FuturesUnordered::default(), no_drop_listeners_waker: None, - inbound_data_channels: rx_ondatachannel, - _ondatachannel_closure: ondatachannel_closure, - } - } - - /// Initiates and polls a future from `create_data_channel`. - /// Takes the RtcPeerConnection and creates a regular DataChannel - fn poll_create_data_channel(&mut self, _cx: &mut Context) -> Poll { - log::trace!("Creating outbound data channel"); - - let data_channel = self.inner.new_regular_data_channel(); - let stream = self.new_stream_from_data_channel(data_channel); - - Poll::Ready(stream) - } - - /// Polls the chann - fn poll_inbound(&mut self, cx: &mut Context) -> Poll> { - match ready!(self.inbound_data_channels.poll_next_unpin(cx)) { - Some(data_channel) => { - let stream = self.new_stream_from_data_channel(data_channel); - - Poll::Ready(Ok(stream)) - } - None => { - self.no_drop_listeners_waker = Some(cx.waker().clone()); - Poll::Pending - } + inbound_data_channels: SendWrapper::new(rx_ondatachannel), + _ondatachannel_closure: SendWrapper::new(ondatachannel_closure), } } @@ -127,24 +86,6 @@ impl ConnectionInner { stream } - /// Poll the Connection - /// - /// Mostly handles Dropped Listeners - fn poll(&mut self, cx: &mut Context) -> Poll> { - loop { - match ready!(self.drop_listeners.poll_next_unpin(cx)) { - Some(Ok(())) => {} - Some(Err(e)) => { - log::debug!("a DropListener failed: {e}") - } - None => { - self.no_drop_listeners_waker = Some(cx.waker().clone()); - return Poll::Pending; - } - } - } - } - /// Closes the Peer Connection. /// /// This closes the data channels also and they will return an error @@ -158,7 +99,7 @@ impl ConnectionInner { } } -impl Drop for ConnectionInner { +impl Drop for Connection { fn drop(&mut self) { self.close_connection(); } @@ -174,14 +115,27 @@ impl StreamMuxer for Connection { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - self.inner.poll_inbound(cx) + match ready!(self.inbound_data_channels.poll_next_unpin(cx)) { + Some(data_channel) => { + let stream = self.new_stream_from_data_channel(data_channel); + + Poll::Ready(Ok(stream)) + } + None => { + self.no_drop_listeners_waker = Some(cx.waker().clone()); + Poll::Pending + } + } } fn poll_outbound( mut self: Pin<&mut Self>, - cx: &mut Context<'_>, + _: &mut Context<'_>, ) -> Poll> { - let stream = ready!(self.inner.poll_create_data_channel(cx)); + log::trace!("Creating outbound data channel"); + + let data_channel = self.inner.new_regular_data_channel(); + let stream = self.new_stream_from_data_channel(data_channel); Poll::Ready(Ok(stream)) } @@ -193,7 +147,7 @@ impl StreamMuxer for Connection { ) -> Poll> { log::trace!("connection::poll_close"); - self.inner.close_connection(); + self.close_connection(); Poll::Ready(Ok(())) } @@ -201,7 +155,18 @@ impl StreamMuxer for Connection { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - self.inner.poll(cx) + loop { + match ready!(self.drop_listeners.poll_next_unpin(cx)) { + Some(Ok(())) => {} + Some(Err(e)) => { + log::debug!("a DropListener failed: {e}") + } + None => { + self.no_drop_listeners_waker = Some(cx.waker().clone()); + return Poll::Pending; + } + } + } } } diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 862d5a320df..812aa5afbbf 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -16,7 +16,7 @@ pub struct Stream { inner: SendWrapper>, } -pub(crate) type DropListener = libp2p_webrtc_utils::DropListener; +pub(crate) type DropListener = SendWrapper>; impl Stream { pub(crate) fn new(data_channel: RtcDataChannel) -> (Self, DropListener) { @@ -27,7 +27,7 @@ impl Stream { Self { inner: SendWrapper::new(inner), }, - drop_listener, + SendWrapper::new(drop_listener), ) } } From 2e758ab0e34b2afe7d552ad1c7887154f386ca9d Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 17:24:18 +1000 Subject: [PATCH 196/235] Restore public API of `libp2p-webrtc` --- transports/webrtc-utils/src/fingerprint.rs | 20 +-- transports/webrtc-utils/src/lib.rs | 3 +- transports/webrtc-utils/src/noise.rs | 4 +- transports/webrtc-utils/src/sdp.rs | 170 +++++------------- transports/webrtc-utils/src/stream.rs | 21 +++ .../webrtc-utils/src/stream/drop_listener.rs | 8 +- transports/webrtc-utils/src/transport.rs | 64 +++++++ transports/webrtc-websys/src/connection.rs | 4 +- transports/webrtc-websys/src/sdp.rs | 4 +- transports/webrtc-websys/src/upgrade.rs | 4 +- transports/webrtc/src/tokio/certificate.rs | 5 +- transports/webrtc/src/tokio/fingerprint.rs | 66 +++++-- transports/webrtc/src/tokio/mod.rs | 1 + transports/webrtc/src/tokio/sdp.rs | 78 +++++++- transports/webrtc/src/tokio/stream.rs | 2 +- transports/webrtc/src/tokio/transport.rs | 64 +------ transports/webrtc/src/tokio/upgrade.rs | 7 +- 17 files changed, 298 insertions(+), 227 deletions(-) diff --git a/transports/webrtc-utils/src/fingerprint.rs b/transports/webrtc-utils/src/fingerprint.rs index b0a4420f52f..a02c4d1116d 100644 --- a/transports/webrtc-utils/src/fingerprint.rs +++ b/transports/webrtc-utils/src/fingerprint.rs @@ -33,8 +33,14 @@ type Multihash = multihash::Multihash<64>; pub struct Fingerprint([u8; 32]); impl Fingerprint { + pub const FF: Fingerprint = Fingerprint([0xFF; 32]); + + pub const fn raw(digest: [u8; 32]) -> Self { + Fingerprint(digest) + } + /// Creates a new [Fingerprint] from a raw certificate by hashing the given bytes with SHA256. - pub fn new(bytes: &[u8]) -> Self { + pub fn from_certificate(bytes: &[u8]) -> Self { Fingerprint(sha2::Sha256::digest(bytes).into()) } @@ -75,12 +81,6 @@ impl fmt::Debug for Fingerprint { } } -impl From<[u8; 32]> for Fingerprint { - fn from(bytes: [u8; 32]) -> Self { - Self(bytes) - } -} - #[cfg(test)] mod tests { use super::*; @@ -91,7 +91,7 @@ mod tests { #[test] fn sdp_format() { - let fp = Fingerprint::from(REGULAR_FORMAT); + let fp = Fingerprint::raw(REGULAR_FORMAT); let formatted = fp.to_sdp_format(); @@ -103,7 +103,7 @@ mod tests { let mut bytes = [0; 32]; bytes.copy_from_slice(&hex::decode(SDP_FORMAT.replace(':', "")).unwrap()); - let fp = Fingerprint::from(bytes); - assert_eq!(fp, Fingerprint::from(REGULAR_FORMAT)); + let fp = Fingerprint::raw(bytes); + assert_eq!(fp, Fingerprint::raw(REGULAR_FORMAT)); } } diff --git a/transports/webrtc-utils/src/lib.rs b/transports/webrtc-utils/src/lib.rs index df9f1445ad9..d58ee01470d 100644 --- a/transports/webrtc-utils/src/lib.rs +++ b/transports/webrtc-utils/src/lib.rs @@ -7,12 +7,13 @@ mod proto { } pub mod error; -pub mod fingerprint; +mod fingerprint; pub mod noise; pub mod sdp; mod stream; pub mod transport; pub use error::Error; +pub use fingerprint::{Fingerprint, SHA256}; pub use stream::{DropListener, Stream, MAX_MSG_LEN}; pub use transport::parse_webrtc_dial_addr; diff --git a/transports/webrtc-utils/src/noise.rs b/transports/webrtc-utils/src/noise.rs index 276e6991527..00c770cec89 100644 --- a/transports/webrtc-utils/src/noise.rs +++ b/transports/webrtc-utils/src/noise.rs @@ -92,10 +92,10 @@ mod tests { #[test] fn noise_prologue_tests() { - let a = Fingerprint::from(hex!( + let a = Fingerprint::raw(hex!( "3e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870" )); - let b = Fingerprint::from(hex!( + let b = Fingerprint::raw(hex!( "30fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99" )); diff --git a/transports/webrtc-utils/src/sdp.rs b/transports/webrtc-utils/src/sdp.rs index 6a9aac3be8f..7c4facaf27e 100644 --- a/transports/webrtc-utils/src/sdp.rs +++ b/transports/webrtc-utils/src/sdp.rs @@ -26,78 +26,18 @@ use tinytemplate::TinyTemplate; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -// An SDP message that constitutes the offer. -// -// Main RFC: -// `sctp-port` and `max-message-size` attrs RFC: -// `group` and `mid` attrs RFC: -// `ice-ufrag`, `ice-pwd` and `ice-options` attrs RFC: -// `setup` attr RFC: -// -// Short description: -// -// v= -> always 0 -// o= -// -// identifies the creator of the SDP document. We are allowed to use dummy values -// (`-` and `0.0.0.0` as ) to remain anonymous, which we do. Note that "IN" means -// "Internet". -// -// s= -// -// We are allowed to pass a dummy `-`. -// -// c= -// -// Indicates the IP address of the remote. -// Note that "IN" means "Internet". -// -// t= -// -// Start and end of the validity of the session. `0 0` means that the session never expires. -// -// m= ... -// -// A `m=` line describes a request to establish a certain protocol. The protocol in this line -// (i.e. `TCP/DTLS/SCTP` or `UDP/DTLS/SCTP`) must always be the same as the one in the offer. -// We know that this is true because we tweak the offer to match the protocol. The `` -// component must always be `webrtc-datachannel` for WebRTC. -// RFCs: 8839, 8866, 8841 -// -// a=mid: -// -// Media ID - uniquely identifies this media stream (RFC9143). -// -// a=ice-options:ice2 -// -// Indicates that we are complying with RFC8839 (as oppposed to the legacy RFC5245). -// -// a=ice-ufrag: -// a=ice-pwd: -// -// ICE username and password, which are used for establishing and -// maintaining the ICE connection. (RFC8839) -// MUST match ones used by the answerer (server). -// -// a=fingerprint:sha-256 -// -// Fingerprint of the certificate that the remote will use during the TLS -// handshake. (RFC8122) -// -// a=setup:actpass -// -// The endpoint that is the offerer MUST use the setup attribute value of setup:actpass and be -// prepared to receive a client_hello before it receives the answer. -// -// a=sctp-port: -// -// The SCTP port (RFC8841) -// Note it's different from the "m=" line port value, which indicates the port of the -// underlying transport-layer protocol (UDP or TCP). -// -// a=max-message-size: -// -// The maximum SCTP user message size (in bytes). (RFC8841) +pub fn answer(addr: SocketAddr, server_fingerprint: Fingerprint, client_ufrag: &str) -> String { + let answer = render_description( + SERVER_SESSION_DESCRIPTION, + addr, + server_fingerprint, + client_ufrag, + ); + + log::trace!("Created SDP answer: {answer}"); + + answer +} // See [`CLIENT_SESSION_DESCRIPTION`]. // @@ -136,25 +76,50 @@ use rand::{thread_rng, Rng}; // A transport address for a candidate that can be used for connectivity checks (RFC8839). // // a=end-of-candidates +const SERVER_SESSION_DESCRIPTION: &str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +t=0 0 +a=ice-lite +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +c=IN {ip_version} {target_ip} +a=mid:0 +a=ice-options:ice2 +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} +a=setup:passive +a=sctp-port:5000 +a=max-message-size:16384 +a=candidate:1467250027 1 UDP 1467250027 {target_ip} {target_port} typ host +a=end-of-candidates +"; -pub fn answer(addr: SocketAddr, server_fingerprint: &Fingerprint, client_ufrag: &str) -> String { - let answer = render_description( - SERVER_SESSION_DESCRIPTION, - addr, - server_fingerprint, - client_ufrag, - ); - - log::trace!("Created SDP answer: {answer}"); +/// Indicates the IP version used in WebRTC: `IP4` or `IP6`. +#[derive(Serialize)] +enum IpVersion { + IP4, + IP6, +} - answer +/// Context passed to the templating engine, which replaces the above placeholders (e.g. +/// `{IP_VERSION}`) with real values. +#[derive(Serialize)] +struct DescriptionContext { + pub(crate) ip_version: IpVersion, + pub(crate) target_ip: IpAddr, + pub(crate) target_port: u16, + pub(crate) fingerprint_algorithm: String, + pub(crate) fingerprint_value: String, + pub(crate) ufrag: String, + pub(crate) pwd: String, } /// Renders a [`TinyTemplate`] description using the provided arguments. pub fn render_description( description: &str, addr: SocketAddr, - fingerprint: &Fingerprint, + fingerprint: Fingerprint, ufrag: &str, ) -> String { let mut tt = TinyTemplate::new(); @@ -179,45 +144,6 @@ pub fn render_description( tt.render("description", &context).unwrap() } -/// Indicates the IP version used in WebRTC: `IP4` or `IP6`. -#[derive(Serialize)] -enum IpVersion { - IP4, - IP6, -} - -/// Context passed to the templating engine, which replaces the above placeholders (e.g. -/// `{IP_VERSION}`) with real values. -#[derive(Serialize)] -struct DescriptionContext { - pub(crate) ip_version: IpVersion, - pub(crate) target_ip: IpAddr, - pub(crate) target_port: u16, - pub(crate) fingerprint_algorithm: String, - pub(crate) fingerprint_value: String, - pub(crate) ufrag: String, - pub(crate) pwd: String, -} - -const SERVER_SESSION_DESCRIPTION: &str = "v=0 -o=- 0 0 IN {ip_version} {target_ip} -s=- -t=0 0 -a=ice-lite -m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel -c=IN {ip_version} {target_ip} -a=mid:0 -a=ice-options:ice2 -a=ice-ufrag:{ufrag} -a=ice-pwd:{pwd} -a=fingerprint:{fingerprint_algorithm} {fingerprint_value} -a=setup:passive -a=sctp-port:5000 -a=max-message-size:16384 -a=candidate:1467250027 1 UDP 1467250027 {target_ip} {target_port} typ host -a=end-of-candidates -"; - /// Generates a random ufrag and adds a prefix according to the spec. pub fn random_ufrag() -> String { format!( diff --git a/transports/webrtc-utils/src/stream.rs b/transports/webrtc-utils/src/stream.rs index 2b13cefabd0..4819c4449b1 100644 --- a/transports/webrtc-utils/src/stream.rs +++ b/transports/webrtc-utils/src/stream.rs @@ -1,3 +1,24 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// Copyright 2023 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + mod drop_listener; mod framed_dc; mod state; diff --git a/transports/webrtc-utils/src/stream/drop_listener.rs b/transports/webrtc-utils/src/stream/drop_listener.rs index 0a04bb34ae8..b6c952af3fa 100644 --- a/transports/webrtc-utils/src/stream/drop_listener.rs +++ b/transports/webrtc-utils/src/stream/drop_listener.rs @@ -18,15 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use futures::channel::oneshot; +use futures::channel::oneshot::Canceled; +use futures::{AsyncRead, AsyncWrite, FutureExt, SinkExt}; + use std::future::Future; use std::io; use std::pin::Pin; use std::task::{Context, Poll}; -use futures::channel::oneshot; -use futures::channel::oneshot::Canceled; -use futures::{AsyncRead, AsyncWrite, FutureExt, SinkExt}; - use crate::proto::{Flag, Message}; use crate::stream::framed_dc::FramedDc; diff --git a/transports/webrtc-utils/src/transport.rs b/transports/webrtc-utils/src/transport.rs index 27d6b383e08..440ad73ed02 100644 --- a/transports/webrtc-utils/src/transport.rs +++ b/transports/webrtc-utils/src/transport.rs @@ -35,3 +35,67 @@ pub fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerpri Some((SocketAddr::new(ip, port), fingerprint)) } + +#[cfg(test)] +mod tests { + use super::*; + use std::net::{Ipv4Addr, Ipv6Addr}; + + #[test] + fn parse_valid_address_with_certhash_and_p2p() { + let addr = "/ip4/127.0.0.1/udp/39901/webrtc-direct/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/p2p/12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" + .parse() + .unwrap(); + + let maybe_parsed = parse_webrtc_dial_addr(&addr); + + assert_eq!( + maybe_parsed, + Some(( + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 39901), + Fingerprint::raw(hex_literal::hex!( + "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" + )) + )) + ); + } + + #[test] + fn peer_id_is_not_required() { + let addr = "/ip4/127.0.0.1/udp/39901/webrtc-direct/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" + .parse() + .unwrap(); + + let maybe_parsed = parse_webrtc_dial_addr(&addr); + + assert_eq!( + maybe_parsed, + Some(( + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 39901), + Fingerprint::raw(hex_literal::hex!( + "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" + )) + )) + ); + } + + #[test] + fn parse_ipv6() { + let addr = + "/ip6/::1/udp/12345/webrtc-direct/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/p2p/12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" + .parse() + .unwrap(); + + let maybe_parsed = parse_webrtc_dial_addr(&addr); + + assert_eq!( + maybe_parsed, + Some(( + SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 12345), + Fingerprint::raw(hex_literal::hex!( + "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" + )) + )) + ); + } +} diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index c995642ace8..8e847d60aca 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -7,7 +7,7 @@ use futures::stream::FuturesUnordered; use futures::StreamExt; use js_sys::{Object, Reflect}; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; -use libp2p_webrtc_utils::fingerprint::Fingerprint; +use libp2p_webrtc_utils::Fingerprint; use send_wrapper::SendWrapper; use std::pin::Pin; use std::task::Waker; @@ -286,7 +286,7 @@ fn parse_fingerprint(sdp: &str) -> Option { let fingerprint = line.split(' ').nth(1).unwrap(); let bytes = hex::decode(fingerprint.replace(':', "")).unwrap(); let arr: [u8; 32] = bytes.as_slice().try_into().unwrap(); - return Some(Fingerprint::from(arr)); + return Some(Fingerprint::raw(arr)); } } None diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc-websys/src/sdp.rs index 82d6fea4dfe..6f50262b988 100644 --- a/transports/webrtc-websys/src/sdp.rs +++ b/transports/webrtc-websys/src/sdp.rs @@ -1,11 +1,11 @@ -use libp2p_webrtc_utils::fingerprint::Fingerprint; +use libp2p_webrtc_utils::Fingerprint; use std::net::SocketAddr; use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; /// Creates the SDP answer used by the client. pub(crate) fn answer( addr: SocketAddr, - server_fingerprint: &Fingerprint, + server_fingerprint: Fingerprint, client_ufrag: &str, ) -> RtcSessionDescriptionInit { let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index a27d7626fc9..092baed50c4 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -3,8 +3,8 @@ use crate::connection::RtcPeerConnection; use crate::sdp; use crate::Connection; use libp2p_identity::{Keypair, PeerId}; -use libp2p_webrtc_utils::fingerprint::Fingerprint; use libp2p_webrtc_utils::noise; +use libp2p_webrtc_utils::Fingerprint; use send_wrapper::SendWrapper; use std::net::SocketAddr; @@ -40,7 +40,7 @@ async fn outbound_inner( .set_local_description(munged_offer) .await?; - let answer = sdp::answer(sock_addr, &remote_fingerprint, &ufrag); + let answer = sdp::answer(sock_addr, remote_fingerprint, &ufrag); rtc_peer_connection.set_remote_description(answer).await?; let local_fingerprint = rtc_peer_connection.local_fingerprint()?; diff --git a/transports/webrtc/src/tokio/certificate.rs b/transports/webrtc/src/tokio/certificate.rs index c84096208d3..8e73a92a647 100644 --- a/transports/webrtc/src/tokio/certificate.rs +++ b/transports/webrtc/src/tokio/certificate.rs @@ -18,11 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use crate::tokio::fingerprint::Fingerprint; use rand::{distributions::DistString, CryptoRng, Rng}; use webrtc::peer_connection::certificate::RTCCertificate; -use crate::tokio::fingerprint::{self, Fingerprint}; - #[derive(Debug, Clone, PartialEq)] pub struct Certificate { inner: RTCCertificate, @@ -58,7 +57,7 @@ impl Certificate { .find(|f| f.algorithm == "sha-256") .expect("a SHA-256 fingerprint"); - fingerprint::try_from_rtc_dtls(sha256_fingerprint).expect("we filtered by sha-256") + Fingerprint::try_from_rtc_dtls(sha256_fingerprint).expect("we filtered by sha-256") } /// Parses a certificate from the ASCII PEM format. diff --git a/transports/webrtc/src/tokio/fingerprint.rs b/transports/webrtc/src/tokio/fingerprint.rs index 992de903edb..c075e486232 100644 --- a/transports/webrtc/src/tokio/fingerprint.rs +++ b/transports/webrtc/src/tokio/fingerprint.rs @@ -1,4 +1,3 @@ -// Copyright 2023 Doug Anderson // Copyright 2022 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,18 +18,65 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -pub(crate) use libp2p_webrtc_utils::fingerprint::Fingerprint; -use libp2p_webrtc_utils::fingerprint::SHA256; use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; -/// Converts [`RTCDtlsFingerprint`] to [`Fingerprint`]. -pub(crate) fn try_from_rtc_dtls(fp: &RTCDtlsFingerprint) -> Option { - if fp.algorithm != SHA256 { - return None; +const SHA256: &str = "sha-256"; + +type Multihash = multihash::Multihash<64>; + +/// A certificate fingerprint that is assumed to be created using the SHA256 hash algorithm. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub struct Fingerprint(libp2p_webrtc_utils::Fingerprint); + +impl Fingerprint { + #[cfg(test)] + pub fn raw(bytes: [u8; 32]) -> Self { + Self(libp2p_webrtc_utils::Fingerprint::raw(bytes)) + } + + /// Creates a fingerprint from a raw certificate. + pub fn from_certificate(bytes: &[u8]) -> Self { + Fingerprint(libp2p_webrtc_utils::Fingerprint::from_certificate(bytes)) } - let mut buf = [0; 32]; - hex::decode_to_slice(fp.value.replace(':', ""), &mut buf).ok()?; + /// Converts [`RTCDtlsFingerprint`] to [`Fingerprint`]. + pub fn try_from_rtc_dtls(fp: &RTCDtlsFingerprint) -> Option { + if fp.algorithm != SHA256 { + return None; + } - Some(Fingerprint::from(buf)) + let mut buf = [0; 32]; + hex::decode_to_slice(fp.value.replace(':', ""), &mut buf).ok()?; + + Some(Self(libp2p_webrtc_utils::Fingerprint::raw(buf))) + } + + /// Converts [`Multihash`](multihash::Multihash) to [`Fingerprint`]. + pub fn try_from_multihash(hash: Multihash) -> Option { + Some(Self(libp2p_webrtc_utils::Fingerprint::try_from_multihash( + hash, + )?)) + } + + /// Converts this fingerprint to [`Multihash`](multihash::Multihash). + pub fn to_multihash(self) -> Multihash { + self.0.to_multihash() + } + + /// Formats this fingerprint as uppercase hex, separated by colons (`:`). + /// + /// This is the format described in . + pub fn to_sdp_format(self) -> String { + self.0.to_sdp_format() + } + + /// Returns the algorithm used (e.g. "sha-256"). + /// See + pub fn algorithm(&self) -> String { + self.0.algorithm() + } + + pub(crate) fn into_inner(self) -> libp2p_webrtc_utils::Fingerprint { + self.0 + } } diff --git a/transports/webrtc/src/tokio/mod.rs b/transports/webrtc/src/tokio/mod.rs index 90e3d590312..4f2c0dd9116 100644 --- a/transports/webrtc/src/tokio/mod.rs +++ b/transports/webrtc/src/tokio/mod.rs @@ -32,4 +32,5 @@ mod upgrade; pub use certificate::Certificate; pub use connection::Connection; pub use error::Error; +pub use fingerprint::Fingerprint; pub use transport::Transport; diff --git a/transports/webrtc/src/tokio/sdp.rs b/transports/webrtc/src/tokio/sdp.rs index 88138800bc4..e49345a01b2 100644 --- a/transports/webrtc/src/tokio/sdp.rs +++ b/transports/webrtc/src/tokio/sdp.rs @@ -18,16 +18,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::tokio::fingerprint::Fingerprint; pub(crate) use libp2p_webrtc_utils::sdp::random_ufrag; use libp2p_webrtc_utils::sdp::render_description; +use libp2p_webrtc_utils::Fingerprint; use std::net::SocketAddr; use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; /// Creates the SDP answer used by the client. pub(crate) fn answer( addr: SocketAddr, - server_fingerprint: &Fingerprint, + server_fingerprint: Fingerprint, client_ufrag: &str, ) -> RTCSessionDescription { RTCSessionDescription::answer(libp2p_webrtc_utils::sdp::answer( @@ -45,7 +45,7 @@ pub(crate) fn offer(addr: SocketAddr, client_ufrag: &str) -> RTCSessionDescripti let offer = render_description( CLIENT_SESSION_DESCRIPTION, addr, - &Fingerprint::from([0xFF; 32]), + Fingerprint::FF, client_ufrag, ); @@ -54,6 +54,78 @@ pub(crate) fn offer(addr: SocketAddr, client_ufrag: &str) -> RTCSessionDescripti RTCSessionDescription::offer(offer).unwrap() } +// An SDP message that constitutes the offer. +// +// Main RFC: +// `sctp-port` and `max-message-size` attrs RFC: +// `group` and `mid` attrs RFC: +// `ice-ufrag`, `ice-pwd` and `ice-options` attrs RFC: +// `setup` attr RFC: +// +// Short description: +// +// v= -> always 0 +// o= +// +// identifies the creator of the SDP document. We are allowed to use dummy values +// (`-` and `0.0.0.0` as ) to remain anonymous, which we do. Note that "IN" means +// "Internet". +// +// s= +// +// We are allowed to pass a dummy `-`. +// +// c= +// +// Indicates the IP address of the remote. +// Note that "IN" means "Internet". +// +// t= +// +// Start and end of the validity of the session. `0 0` means that the session never expires. +// +// m= ... +// +// A `m=` line describes a request to establish a certain protocol. The protocol in this line +// (i.e. `TCP/DTLS/SCTP` or `UDP/DTLS/SCTP`) must always be the same as the one in the offer. +// We know that this is true because we tweak the offer to match the protocol. The `` +// component must always be `webrtc-datachannel` for WebRTC. +// RFCs: 8839, 8866, 8841 +// +// a=mid: +// +// Media ID - uniquely identifies this media stream (RFC9143). +// +// a=ice-options:ice2 +// +// Indicates that we are complying with RFC8839 (as oppposed to the legacy RFC5245). +// +// a=ice-ufrag: +// a=ice-pwd: +// +// ICE username and password, which are used for establishing and +// maintaining the ICE connection. (RFC8839) +// MUST match ones used by the answerer (server). +// +// a=fingerprint:sha-256 +// +// Fingerprint of the certificate that the remote will use during the TLS +// handshake. (RFC8122) +// +// a=setup:actpass +// +// The endpoint that is the offerer MUST use the setup attribute value of setup:actpass and be +// prepared to receive a client_hello before it receives the answer. +// +// a=sctp-port: +// +// The SCTP port (RFC8841) +// Note it's different from the "m=" line port value, which indicates the port of the +// underlying transport-layer protocol (UDP or TCP). +// +// a=max-message-size: +// +// The maximum SCTP user message size (in bytes). (RFC8841) const CLIENT_SESSION_DESCRIPTION: &str = "v=0 o=- 0 0 IN {ip_version} {target_ip} s=- diff --git a/transports/webrtc/src/tokio/stream.rs b/transports/webrtc/src/tokio/stream.rs index 5731736481e..4278a751e27 100644 --- a/transports/webrtc/src/tokio/stream.rs +++ b/transports/webrtc/src/tokio/stream.rs @@ -1,4 +1,4 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. +// Copyright 2023 Protocol Labs. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index 1abe9f9f6c4..4b3f15d5978 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -140,7 +140,7 @@ impl libp2p_core::Transport for Transport { sock_addr, config.inner, udp_mux, - client_fingerprint, + client_fingerprint.into_inner(), server_fingerprint, config.id_keys, ) @@ -337,7 +337,7 @@ impl Stream for ListenStream { new_addr.addr, self.config.inner.clone(), self.udp_mux.udp_mux_handle(), - self.config.fingerprint, + self.config.fingerprint.into_inner(), new_addr.ufrag, self.config.id_keys.clone(), ) @@ -435,7 +435,7 @@ mod tests { use futures::future::poll_fn; use libp2p_core::{multiaddr::Protocol, Transport as _}; use rand::thread_rng; - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + use std::net::{IpAddr, Ipv6Addr}; #[test] fn missing_webrtc_protocol() { @@ -446,44 +446,6 @@ mod tests { assert!(maybe_parsed.is_none()); } - #[test] - fn parse_valid_address_with_certhash_and_p2p() { - let addr = "/ip4/127.0.0.1/udp/39901/webrtc-direct/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/p2p/12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" - .parse() - .unwrap(); - - let maybe_parsed = libp2p_webrtc_utils::parse_webrtc_dial_addr(&addr); - - assert_eq!( - maybe_parsed, - Some(( - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 39901), - Fingerprint::from(hex_literal::hex!( - "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" - )) - )) - ); - } - - #[test] - fn peer_id_is_not_required() { - let addr = "/ip4/127.0.0.1/udp/39901/webrtc-direct/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" - .parse() - .unwrap(); - - let maybe_parsed = libp2p_webrtc_utils::parse_webrtc_dial_addr(&addr); - - assert_eq!( - maybe_parsed, - Some(( - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 39901), - Fingerprint::from(hex_literal::hex!( - "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" - )) - )) - ); - } - #[test] fn tcp_is_invalid_protocol() { let addr = "/ip4/127.0.0.1/tcp/12345/webrtc-direct/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" @@ -506,26 +468,6 @@ mod tests { assert!(maybe_parsed.is_none()); } - #[test] - fn parse_ipv6() { - let addr = - "/ip6/::1/udp/12345/webrtc-direct/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/p2p/12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" - .parse() - .unwrap(); - - let maybe_parsed = libp2p_webrtc_utils::parse_webrtc_dial_addr(&addr); - - assert_eq!( - maybe_parsed, - Some(( - SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 12345), - Fingerprint::from(hex_literal::hex!( - "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" - )) - )) - ); - } - #[test] fn can_parse_valid_addr_without_certhash() { let addr = "/ip6/::1/udp/12345/webrtc-direct".parse().unwrap(); diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index 6d05f005d86..414fc2721d0 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_webrtc_utils::noise; +use libp2p_webrtc_utils::{noise, Fingerprint}; use futures::channel::oneshot; use futures::future::Either; @@ -37,7 +37,6 @@ use webrtc::ice::udp_network::UDPNetwork; use webrtc::peer_connection::configuration::RTCConfiguration; use webrtc::peer_connection::RTCPeerConnection; -use crate::tokio::fingerprint::Fingerprint; use crate::tokio::sdp::random_ufrag; use crate::tokio::{error::Error, sdp, stream::Stream, Connection}; @@ -58,7 +57,7 @@ pub(crate) async fn outbound( log::debug!("created SDP offer for outbound connection: {:?}", offer.sdp); peer_connection.set_local_description(offer).await?; - let answer = sdp::answer(addr, &server_fingerprint, &ufrag); + let answer = sdp::answer(addr, server_fingerprint, &ufrag); log::debug!( "calculated SDP answer for outbound connection: {:?}", answer @@ -187,7 +186,7 @@ fn setting_engine( async fn get_remote_fingerprint(conn: &RTCPeerConnection) -> Fingerprint { let cert_bytes = conn.sctp().transport().get_remote_certificate().await; - Fingerprint::new(&cert_bytes) + Fingerprint::from_certificate(&cert_bytes) } async fn create_substream_for_noise_handshake(conn: &RTCPeerConnection) -> Result { From 70a2ff392f74b5f69a0126752dd7fa3d902126a5 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 17:32:46 +1000 Subject: [PATCH 197/235] Minimize diff and get rid of shared error --- Cargo.lock | 1 + examples/browser-webrtc/.gitignore | 1 - interop-tests/README.md | 2 +- transports/webrtc-utils/CHANGELOG.md | 2 +- transports/webrtc-utils/README.md | 26 ----------------- transports/webrtc-utils/src/error.rs | 42 --------------------------- transports/webrtc-utils/src/lib.rs | 4 --- transports/webrtc-utils/src/noise.rs | 5 ++-- transports/webrtc-websys/Cargo.toml | 18 ++---------- transports/webrtc-websys/src/error.rs | 20 ++----------- transports/webrtc/src/tokio/error.rs | 15 ---------- 11 files changed, 11 insertions(+), 125 deletions(-) delete mode 100644 examples/browser-webrtc/.gitignore delete mode 100644 transports/webrtc-utils/README.md delete mode 100644 transports/webrtc-utils/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index ed1d661c5cb..7b9156c4f30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3473,6 +3473,7 @@ dependencies = [ "js-sys", "libp2p-core", "libp2p-identity", + "libp2p-noise", "libp2p-ping", "libp2p-swarm", "libp2p-webrtc-utils", diff --git a/examples/browser-webrtc/.gitignore b/examples/browser-webrtc/.gitignore deleted file mode 100644 index 01d0a084587..00000000000 --- a/examples/browser-webrtc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -pkg/ diff --git a/interop-tests/README.md b/interop-tests/README.md index 267ce45921a..9dcb4eded92 100644 --- a/interop-tests/README.md +++ b/interop-tests/README.md @@ -15,7 +15,7 @@ can dial/listen for ourselves we can do the following: 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 +# 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. diff --git a/transports/webrtc-utils/CHANGELOG.md b/transports/webrtc-utils/CHANGELOG.md index c3485aa1dbf..d575ea879f8 100644 --- a/transports/webrtc-utils/CHANGELOG.md +++ b/transports/webrtc-utils/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.1.0 +## 0.1.0 - unreleased - Initial release. See [PR 4248]. diff --git a/transports/webrtc-utils/README.md b/transports/webrtc-utils/README.md deleted file mode 100644 index 58e309fb1e3..00000000000 --- a/transports/webrtc-utils/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# WebRTC Common Utilities - -Tools that are common to more than one WebRTC transport: - -```rust -// Protobuf Message framing and Flags -use libp2p_webrtc_utils::proto::{Flag, Message}; - -// Length constants -use libp2p_webrtc_utils::stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; - -// Stream state machine -use libp2p_webrtc_utils::stream::state::{Closing, State}; - -// Noise Identity Fingerprinting utilities and SHA256 string constant -use libp2p_webrtc_utils::fingerprint::{Fingerprint, SHA256}; - -// Utility Error types -use libp2p_webrtc_utils::Error; - -// Session Description Protocol ufrag generation and rendering -use libp2p_webrtc_utils::sdp::{random_ufrag, render_description}; - -// WebRTC Dial Address parsing -use libp2p_webrtc_utils::parse_webrtc_dial_addr; -``` diff --git a/transports/webrtc-utils/src/error.rs b/transports/webrtc-utils/src/error.rs deleted file mode 100644 index 1ff0cc17900..00000000000 --- a/transports/webrtc-utils/src/error.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2023 Doug Anderson -// Copyright 2022 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use libp2p_identity::PeerId; -use thiserror::Error; - -/// Error in WebRTC. -#[derive(Error, Debug)] -pub enum Error { - #[error("IO error")] - Io(#[from] std::io::Error), - #[error("failed to authenticate peer")] - Authentication(#[from] libp2p_noise::Error), - - // Authentication errors. - #[error("invalid peer ID (expected {expected}, got {got})")] - InvalidPeerID { expected: PeerId, got: PeerId }, - - #[error("no active listeners, can not dial without a previous listen")] - NoListeners, - - #[error("internal error: {0} (see debug logs)")] - Internal(String), -} diff --git a/transports/webrtc-utils/src/lib.rs b/transports/webrtc-utils/src/lib.rs index d58ee01470d..d60b3d288ce 100644 --- a/transports/webrtc-utils/src/lib.rs +++ b/transports/webrtc-utils/src/lib.rs @@ -1,19 +1,15 @@ -#![doc = include_str!("../README.md")] - mod proto { #![allow(unreachable_pub)] include!("generated/mod.rs"); pub use self::webrtc::pb::{mod_Message::Flag, Message}; } -pub mod error; mod fingerprint; pub mod noise; pub mod sdp; mod stream; pub mod transport; -pub use error::Error; pub use fingerprint::{Fingerprint, SHA256}; pub use stream::{DropListener, Stream, MAX_MSG_LEN}; pub use transport::parse_webrtc_dial_addr; diff --git a/transports/webrtc-utils/src/noise.rs b/transports/webrtc-utils/src/noise.rs index 00c770cec89..023766bc1df 100644 --- a/transports/webrtc-utils/src/noise.rs +++ b/transports/webrtc-utils/src/noise.rs @@ -25,14 +25,13 @@ use libp2p_identity::PeerId; use libp2p_noise as noise; use crate::fingerprint::Fingerprint; -use crate::Error; pub async fn inbound( id_keys: identity::Keypair, stream: T, client_fingerprint: Fingerprint, server_fingerprint: Fingerprint, -) -> Result +) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, { @@ -54,7 +53,7 @@ pub async fn outbound( stream: T, server_fingerprint: Fingerprint, client_fingerprint: Fingerprint, -) -> Result +) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, { diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index 6a7a49528b4..e7f2000c395 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -15,31 +15,19 @@ bytes = "1" futures = "0.3" futures-timer = "3" getrandom = { version = "0.2.9", features = ["js"] } +hex = "0.4.3" js-sys = { version = "0.3" } libp2p-core = { workspace = true } libp2p-identity = { workspace = true } +libp2p-noise = { workspace = true } libp2p-webrtc-utils = { workspace = true } log = "0.4.19" -hex = "0.4.3" send_wrapper = { version = "0.6.0", features = ["futures"] } serde = { version = "1.0", features = ["derive"] } thiserror = "1" wasm-bindgen = { version = "0.2.87" } wasm-bindgen-futures = { version = "0.4.37" } -web-sys = { version = "0.3.64", features = [ - "MessageEvent", - "RtcCertificate", - "RtcConfiguration", - "RtcDataChannel", - "RtcDataChannelEvent", - "RtcDataChannelInit", - "RtcDataChannelState", - "RtcDataChannelType", - "RtcPeerConnection", - "RtcSdpType", - "RtcSessionDescription", - "RtcSessionDescriptionInit", -] } +web-sys = { version = "0.3.64", features = ["MessageEvent", "RtcCertificate", "RtcConfiguration", "RtcDataChannel", "RtcDataChannelEvent", "RtcDataChannelInit", "RtcDataChannelState", "RtcDataChannelType", "RtcPeerConnection", "RtcSdpType", "RtcSessionDescription", "RtcSessionDescriptionInit", ] } [dev-dependencies] hex-literal = "0.4" diff --git a/transports/webrtc-websys/src/error.rs b/transports/webrtc-websys/src/error.rs index dc536f8dc3c..e226dea8069 100644 --- a/transports/webrtc-websys/src/error.rs +++ b/transports/webrtc-websys/src/error.rs @@ -18,6 +18,9 @@ pub enum Error { #[error("Connection error: {0}")] Connection(String), + + #[error("Authentication error")] + Authentication(#[from] libp2p_noise::Error), } impl Error { @@ -35,23 +38,6 @@ impl Error { } } -/// Ensure the libp2p_webrtc_utils::Error is converted to the WebRTC error so we don't expose it to the user -/// via our public API. -impl From for Error { - fn from(e: libp2p_webrtc_utils::Error) -> Self { - match e { - libp2p_webrtc_utils::Error::Io(e) => Error::JsError(e.to_string()), - libp2p_webrtc_utils::Error::Authentication(e) => Error::JsError(e.to_string()), - libp2p_webrtc_utils::Error::InvalidPeerID { expected, got } => Error::JsError(format!( - "Invalid peer ID (expected {}, got {})", - expected, got - )), - libp2p_webrtc_utils::Error::NoListeners => Error::JsError("No listeners".to_string()), - libp2p_webrtc_utils::Error::Internal(e) => Error::JsError(e), - } - } -} - impl std::convert::From for Error { fn from(value: JsValue) -> Self { Error::from_js_value(value) diff --git a/transports/webrtc/src/tokio/error.rs b/transports/webrtc/src/tokio/error.rs index 996111317b0..1b274686ef3 100644 --- a/transports/webrtc/src/tokio/error.rs +++ b/transports/webrtc/src/tokio/error.rs @@ -19,7 +19,6 @@ // DEALINGS IN THE SOFTWARE. use libp2p_identity::PeerId; -use libp2p_webrtc_utils::Error as UtilsError; use thiserror::Error; /// Error in WebRTC. @@ -45,17 +44,3 @@ pub enum Error { #[error("internal error: {0} (see debug logs)")] Internal(String), } - -/// Ensure the Utilities error is converted to the WebRTC error so we don't expose it to the user -/// via our public API. -impl From for Error { - fn from(e: UtilsError) -> Self { - match e { - UtilsError::Io(e) => Error::Io(e), - UtilsError::Authentication(e) => Error::Authentication(e), - UtilsError::InvalidPeerID { expected, got } => Error::InvalidPeerID { expected, got }, - UtilsError::NoListeners => Error::NoListeners, - UtilsError::Internal(e) => Error::Internal(e), - } - } -} From f7d2cb14d0e5d1164b22b7d060e9868293c15ef1 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 17:37:53 +1000 Subject: [PATCH 198/235] Reduce diff --- interop-tests/README.md | 2 +- transports/webrtc-utils/src/lib.rs | 2 +- transports/webrtc-utils/src/stream.rs | 33 +++++++++++++++------------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/interop-tests/README.md b/interop-tests/README.md index 9dcb4eded92..bab98df7987 100644 --- a/interop-tests/README.md +++ b/interop-tests/README.md @@ -23,7 +23,7 @@ Firefox is not yet supported as it doesn't support all required features yet (in v114 there is no support for certhashes). 1. Build the wasm package: `wasm-pack build --target web` -2. Run the webtransport dialer: `redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webtransport is_dialer=true cargo run --bin wasm_ping` +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 this test with webrtc-direct diff --git a/transports/webrtc-utils/src/lib.rs b/transports/webrtc-utils/src/lib.rs index d60b3d288ce..c744634de30 100644 --- a/transports/webrtc-utils/src/lib.rs +++ b/transports/webrtc-utils/src/lib.rs @@ -8,7 +8,7 @@ mod fingerprint; pub mod noise; pub mod sdp; mod stream; -pub mod transport; +mod transport; pub use fingerprint::{Fingerprint, SHA256}; pub use stream::{DropListener, Stream, MAX_MSG_LEN}; diff --git a/transports/webrtc-utils/src/stream.rs b/transports/webrtc-utils/src/stream.rs index 4819c4449b1..faa92369f69 100644 --- a/transports/webrtc-utils/src/stream.rs +++ b/transports/webrtc-utils/src/stream.rs @@ -19,24 +19,28 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -mod drop_listener; -mod framed_dc; -mod state; +use bytes::Bytes; +use futures::{channel::oneshot, prelude::*, ready}; + +use std::{ + io, + pin::Pin, + task::{Context, Poll}, +}; use crate::proto::{Flag, Message}; -use crate::stream::state::Closing; -use bytes::Bytes; -use drop_listener::GracefullyClosed; -use framed_dc::FramedDc; -use futures::channel::oneshot; -use futures::{ready, AsyncRead, AsyncWrite, Sink, SinkExt, StreamExt}; -use state::State; -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; +use crate::{ + stream::drop_listener::GracefullyClosed, + stream::framed_dc::FramedDc, + stream::state::{Closing, State}, +}; pub use drop_listener::DropListener; +mod drop_listener; +mod framed_dc; +mod state; + /// Maximum length of a message. /// /// "As long as message interleaving is not supported, the sender SHOULD limit the maximum message @@ -257,12 +261,13 @@ where #[cfg(test)] mod tests { - use super::*; use asynchronous_codec::Encoder; use bytes::BytesMut; use quick_protobuf::{MessageWrite, Writer}; use unsigned_varint::codec::UviBytes; + use super::*; + #[test] fn max_data_len() { // Largest possible message. From c6418df70427c335bff09628283e48a639f3cf87 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 17:40:57 +1000 Subject: [PATCH 199/235] Reduce `libp2p-webrtc` version to 0.6.1-alpha --- Cargo.toml | 2 +- transports/webrtc/CHANGELOG.md | 3 +-- transports/webrtc/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 34637eb61c9..991836b3e37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ libp2p-tcp = { version = "0.40.0", path = "transports/tcp" } libp2p-tls = { version = "0.2.1", path = "transports/tls" } libp2p-uds = { version = "0.39.0", path = "transports/uds" } libp2p-wasm-ext = { version = "0.40.0", path = "transports/wasm-ext" } -libp2p-webrtc = { version = "0.7.0-alpha", path = "transports/webrtc" } +libp2p-webrtc = { version = "0.6.1-alpha", path = "transports/webrtc" } libp2p-webrtc-utils = { version = "0.1.0", path = "transports/webrtc-utils" } libp2p-webrtc-websys = { version = "0.1.0-alpha", path = "transports/webrtc-websys" } libp2p-websocket = { version = "0.42.1", path = "transports/websocket" } diff --git a/transports/webrtc/CHANGELOG.md b/transports/webrtc/CHANGELOG.md index 3a102ce2847..78d32f52617 100644 --- a/transports/webrtc/CHANGELOG.md +++ b/transports/webrtc/CHANGELOG.md @@ -1,7 +1,6 @@ -## 0.7.0-alpha +## 0.6.1-alpha - unreleased - Move common dependencies to `libp2p-webrtc-utils` crate. -- Use new `libp2p-webrtc-utils` crate as a common dependency. See [PR 4248]. [PR 4248]: https://github.com/libp2p/rust-libp2p/pull/4248 diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 6cb2b0a2a08..79e3ce3575e 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-webrtc" -version = "0.7.0-alpha" +version = "0.6.1-alpha" authors = ["Parity Technologies "] description = "WebRTC transport for libp2p" repository = "https://github.com/libp2p/rust-libp2p" From 2499b9f342354a43aded432800354eb48b3fa580 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 17:42:29 +1000 Subject: [PATCH 200/235] Reduce diff --- transports/webrtc/src/tokio/certificate.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/transports/webrtc/src/tokio/certificate.rs b/transports/webrtc/src/tokio/certificate.rs index 8e73a92a647..748cfdb6ffd 100644 --- a/transports/webrtc/src/tokio/certificate.rs +++ b/transports/webrtc/src/tokio/certificate.rs @@ -18,10 +18,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::tokio::fingerprint::Fingerprint; use rand::{distributions::DistString, CryptoRng, Rng}; use webrtc::peer_connection::certificate::RTCCertificate; +use crate::tokio::fingerprint::Fingerprint; + #[derive(Debug, Clone, PartialEq)] pub struct Certificate { inner: RTCCertificate, From 771ff8108d6b470ca59e93aee5c1e8ca3427f1a0 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 17:47:48 +1000 Subject: [PATCH 201/235] Update lockfile --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 7b9156c4f30..69be15833c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3404,7 +3404,7 @@ dependencies = [ [[package]] name = "libp2p-webrtc" -version = "0.7.0-alpha" +version = "0.6.1-alpha" dependencies = [ "anyhow", "async-trait", From 2141feadb31efa2b8f2a5f7c603c1889cb5b9d25 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 17:51:01 +1000 Subject: [PATCH 202/235] Minimize deps --- Cargo.lock | 14 -------------- examples/browser-webrtc/Cargo.toml | 3 --- examples/browser-webrtc/src/main.rs | 5 +++-- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69be15833c3..48bb7bad63f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -746,15 +746,12 @@ dependencies = [ "libp2p-webrtc-websys", "log", "mime_guess", - "multiaddr", "rand 0.8.5", "rust-embed", "tokio", - "tokio-stream", "tokio-util", "tower", "tower-http", - "void", "wasm-bindgen", "wasm-bindgen-futures", "wasm-logger", @@ -5894,17 +5891,6 @@ dependencies = [ "webpki 0.22.0", ] -[[package]] -name = "tokio-stream" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" -dependencies = [ - "futures-core", - "pin-project-lite 0.2.12", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.8" diff --git a/examples/browser-webrtc/Cargo.toml b/examples/browser-webrtc/Cargo.toml index 053a72a5a48..cf02458ba3a 100644 --- a/examples/browser-webrtc/Cargo.toml +++ b/examples/browser-webrtc/Cargo.toml @@ -15,7 +15,6 @@ anyhow = "1.0.72" env_logger = "0.10" futures = "0.3.28" log = "0.4" -multiaddr = { workspace = true } rand = "0.8" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -24,11 +23,9 @@ libp2p = { path = "../../libp2p", features = ["ed25519", "macros", "ping", "wasm libp2p-webrtc = { workspace = true, features = ["tokio"] } rust-embed = { version = "6.8.1", features = ["include-exclude", "interpolate-folder-path"] } tokio = { version = "1.29", features = ["macros", "net", "rt", "signal"] } -tokio-stream = "0.1" tokio-util = { version = "0.7", features = ["compat"] } tower = "0.4" tower-http = { version = "0.4.0", features = ["cors"] } -void = "1" mime_guess = "2.0.4" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/browser-webrtc/src/main.rs b/examples/browser-webrtc/src/main.rs index b1bc427b820..8a2cf61b7f0 100644 --- a/examples/browser-webrtc/src/main.rs +++ b/examples/browser-webrtc/src/main.rs @@ -8,11 +8,12 @@ use futures::StreamExt; use libp2p::{ core::muxing::StreamMuxerBox, core::Transport, - identity, ping, + identity, + multiaddr::{Multiaddr, Protocol}, + ping, swarm::{keep_alive, NetworkBehaviour, SwarmBuilder, SwarmEvent}, }; use libp2p_webrtc as webrtc; -use multiaddr::{Multiaddr, Protocol}; use rand::thread_rng; use std::{ net::Ipv6Addr, From ddc1f1d56a0ecf5f062052bf55430080700974b1 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Sep 2023 17:53:08 +1000 Subject: [PATCH 203/235] Move `libp2p-webrtc-utils`to `misc/` --- Cargo.toml | 4 ++-- {transports => misc}/webrtc-utils/CHANGELOG.md | 0 {transports => misc}/webrtc-utils/Cargo.toml | 0 {transports => misc}/webrtc-utils/src/fingerprint.rs | 0 {transports => misc}/webrtc-utils/src/generated/message.proto | 0 {transports => misc}/webrtc-utils/src/generated/mod.rs | 0 {transports => misc}/webrtc-utils/src/generated/webrtc/mod.rs | 0 {transports => misc}/webrtc-utils/src/generated/webrtc/pb.rs | 0 {transports => misc}/webrtc-utils/src/lib.rs | 0 {transports => misc}/webrtc-utils/src/noise.rs | 0 {transports => misc}/webrtc-utils/src/sdp.rs | 0 {transports => misc}/webrtc-utils/src/stream.rs | 0 {transports => misc}/webrtc-utils/src/stream/drop_listener.rs | 0 {transports => misc}/webrtc-utils/src/stream/framed_dc.rs | 0 {transports => misc}/webrtc-utils/src/stream/state.rs | 0 {transports => misc}/webrtc-utils/src/transport.rs | 0 16 files changed, 2 insertions(+), 2 deletions(-) rename {transports => misc}/webrtc-utils/CHANGELOG.md (100%) rename {transports => misc}/webrtc-utils/Cargo.toml (100%) rename {transports => misc}/webrtc-utils/src/fingerprint.rs (100%) rename {transports => misc}/webrtc-utils/src/generated/message.proto (100%) rename {transports => misc}/webrtc-utils/src/generated/mod.rs (100%) rename {transports => misc}/webrtc-utils/src/generated/webrtc/mod.rs (100%) rename {transports => misc}/webrtc-utils/src/generated/webrtc/pb.rs (100%) rename {transports => misc}/webrtc-utils/src/lib.rs (100%) rename {transports => misc}/webrtc-utils/src/noise.rs (100%) rename {transports => misc}/webrtc-utils/src/sdp.rs (100%) rename {transports => misc}/webrtc-utils/src/stream.rs (100%) rename {transports => misc}/webrtc-utils/src/stream/drop_listener.rs (100%) rename {transports => misc}/webrtc-utils/src/stream/framed_dc.rs (100%) rename {transports => misc}/webrtc-utils/src/stream/state.rs (100%) rename {transports => misc}/webrtc-utils/src/transport.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 991836b3e37..078cd8fe7bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "misc/quickcheck-ext", "misc/rw-stream-sink", "misc/server", + "misc/webrtc-utils", "muxers/mplex", "muxers/test-harness", "muxers/yamux", @@ -55,7 +56,6 @@ members = [ "transports/uds", "transports/wasm-ext", "transports/webrtc", - "transports/webrtc-utils", "transports/webrtc-websys", "transports/websocket", "transports/webtransport-websys", @@ -103,7 +103,7 @@ libp2p-tls = { version = "0.2.1", path = "transports/tls" } libp2p-uds = { version = "0.39.0", path = "transports/uds" } libp2p-wasm-ext = { version = "0.40.0", path = "transports/wasm-ext" } libp2p-webrtc = { version = "0.6.1-alpha", path = "transports/webrtc" } -libp2p-webrtc-utils = { version = "0.1.0", path = "transports/webrtc-utils" } +libp2p-webrtc-utils = { version = "0.1.0", path = "misc/webrtc-utils" } libp2p-webrtc-websys = { version = "0.1.0-alpha", path = "transports/webrtc-websys" } libp2p-websocket = { version = "0.42.1", path = "transports/websocket" } libp2p-webtransport-websys = { version = "0.1.0", path = "transports/webtransport-websys" } diff --git a/transports/webrtc-utils/CHANGELOG.md b/misc/webrtc-utils/CHANGELOG.md similarity index 100% rename from transports/webrtc-utils/CHANGELOG.md rename to misc/webrtc-utils/CHANGELOG.md diff --git a/transports/webrtc-utils/Cargo.toml b/misc/webrtc-utils/Cargo.toml similarity index 100% rename from transports/webrtc-utils/Cargo.toml rename to misc/webrtc-utils/Cargo.toml diff --git a/transports/webrtc-utils/src/fingerprint.rs b/misc/webrtc-utils/src/fingerprint.rs similarity index 100% rename from transports/webrtc-utils/src/fingerprint.rs rename to misc/webrtc-utils/src/fingerprint.rs diff --git a/transports/webrtc-utils/src/generated/message.proto b/misc/webrtc-utils/src/generated/message.proto similarity index 100% rename from transports/webrtc-utils/src/generated/message.proto rename to misc/webrtc-utils/src/generated/message.proto diff --git a/transports/webrtc-utils/src/generated/mod.rs b/misc/webrtc-utils/src/generated/mod.rs similarity index 100% rename from transports/webrtc-utils/src/generated/mod.rs rename to misc/webrtc-utils/src/generated/mod.rs diff --git a/transports/webrtc-utils/src/generated/webrtc/mod.rs b/misc/webrtc-utils/src/generated/webrtc/mod.rs similarity index 100% rename from transports/webrtc-utils/src/generated/webrtc/mod.rs rename to misc/webrtc-utils/src/generated/webrtc/mod.rs diff --git a/transports/webrtc-utils/src/generated/webrtc/pb.rs b/misc/webrtc-utils/src/generated/webrtc/pb.rs similarity index 100% rename from transports/webrtc-utils/src/generated/webrtc/pb.rs rename to misc/webrtc-utils/src/generated/webrtc/pb.rs diff --git a/transports/webrtc-utils/src/lib.rs b/misc/webrtc-utils/src/lib.rs similarity index 100% rename from transports/webrtc-utils/src/lib.rs rename to misc/webrtc-utils/src/lib.rs diff --git a/transports/webrtc-utils/src/noise.rs b/misc/webrtc-utils/src/noise.rs similarity index 100% rename from transports/webrtc-utils/src/noise.rs rename to misc/webrtc-utils/src/noise.rs diff --git a/transports/webrtc-utils/src/sdp.rs b/misc/webrtc-utils/src/sdp.rs similarity index 100% rename from transports/webrtc-utils/src/sdp.rs rename to misc/webrtc-utils/src/sdp.rs diff --git a/transports/webrtc-utils/src/stream.rs b/misc/webrtc-utils/src/stream.rs similarity index 100% rename from transports/webrtc-utils/src/stream.rs rename to misc/webrtc-utils/src/stream.rs diff --git a/transports/webrtc-utils/src/stream/drop_listener.rs b/misc/webrtc-utils/src/stream/drop_listener.rs similarity index 100% rename from transports/webrtc-utils/src/stream/drop_listener.rs rename to misc/webrtc-utils/src/stream/drop_listener.rs diff --git a/transports/webrtc-utils/src/stream/framed_dc.rs b/misc/webrtc-utils/src/stream/framed_dc.rs similarity index 100% rename from transports/webrtc-utils/src/stream/framed_dc.rs rename to misc/webrtc-utils/src/stream/framed_dc.rs diff --git a/transports/webrtc-utils/src/stream/state.rs b/misc/webrtc-utils/src/stream/state.rs similarity index 100% rename from transports/webrtc-utils/src/stream/state.rs rename to misc/webrtc-utils/src/stream/state.rs diff --git a/transports/webrtc-utils/src/transport.rs b/misc/webrtc-utils/src/transport.rs similarity index 100% rename from transports/webrtc-utils/src/transport.rs rename to misc/webrtc-utils/src/transport.rs From 3bb4c5329f49388dde89501e33ff65d9483c8e8a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 6 Sep 2023 09:25:05 +1000 Subject: [PATCH 204/235] Update `Cargo.lock` --- Cargo.lock | 48 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00cdd2f686c..d8df7deb676 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -747,7 +747,7 @@ dependencies = [ "log", "mime_guess", "rand 0.8.5", - "rust-embed", + "rust-embed 6.8.1", "tokio", "tokio-util", "tower", @@ -2453,7 +2453,7 @@ dependencies = [ "rand 0.8.5", "redis", "reqwest", - "rust-embed", + "rust-embed 8.0.0", "serde", "serde_json", "thirtyfour", @@ -4942,14 +4942,39 @@ dependencies = [ "webrtc-util", ] +[[package]] +name = "rust-embed" +version = "6.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" +dependencies = [ + "rust-embed-impl 6.8.1", + "rust-embed-utils 7.8.1", + "walkdir", +] + [[package]] name = "rust-embed" version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" dependencies = [ - "rust-embed-impl", - "rust-embed-utils", + "rust-embed-impl 8.0.0", + "rust-embed-utils 8.0.0", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils 7.8.1", + "shellexpand", + "syn 2.0.31", "walkdir", ] @@ -4961,19 +4986,28 @@ checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29" dependencies = [ "proc-macro2", "quote", - "rust-embed-utils", - "shellexpand", + "rust-embed-utils 8.0.0", "syn 2.0.31", "walkdir", ] +[[package]] +name = "rust-embed-utils" +version = "7.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" +dependencies = [ + "globset", + "sha2 0.10.7", + "walkdir", +] + [[package]] name = "rust-embed-utils" version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada" dependencies = [ - "globset", "sha2 0.10.7", "walkdir", ] From 3d50096c910ed89d21d920c0d77b5d46149465e8 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 6 Sep 2023 09:28:29 +1000 Subject: [PATCH 205/235] Temporarily set `publish = false` --- misc/webrtc-utils/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/misc/webrtc-utils/Cargo.toml b/misc/webrtc-utils/Cargo.toml index 878f89d4614..9fa13284a5c 100644 --- a/misc/webrtc-utils/Cargo.toml +++ b/misc/webrtc-utils/Cargo.toml @@ -8,6 +8,7 @@ name = "libp2p-webrtc-utils" repository = "https://github.com/libp2p/rust-libp2p" rust-version = { workspace = true } version = "0.1.0" +publish = false # TEMP fix for https://github.com/obi1kenobi/cargo-semver-checks-action/issues/53. [dependencies] bytes = "1" From 8129cb766d66687d6c6803d23425ce8df07dae6c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 6 Sep 2023 09:30:50 +1000 Subject: [PATCH 206/235] Appease clippy --- .../src/stream/poll_data_channel.rs | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index 1c2700d7e13..22a7785d1fa 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -1,7 +1,8 @@ use std::cmp::min; use std::io; use std::pin::Pin; -use std::sync::{Arc, Mutex}; +use std::rc::Rc; +use std::sync::Mutex; use std::task::{Context, Poll}; use bytes::BytesMut; @@ -23,29 +24,29 @@ pub(crate) struct PollDataChannel { /// The [RtcDataChannel] being wrapped. inner: RtcDataChannel, - new_data_waker: Arc, - read_buffer: Arc>, + new_data_waker: Rc, + read_buffer: Rc>, /// Waker for when we are waiting for the DC to be opened. - open_waker: Arc, + open_waker: Rc, /// Waker for when we are waiting to write (again) to the DC because we previously exceeded the `MAX_BUFFERED_AMOUNT` threshold. - write_waker: Arc, + write_waker: Rc, /// Waker for when we are waiting for the DC to be closed. - close_waker: Arc, + close_waker: Rc, // Store the closures for proper garbage collection. - // These are wrapped in an `Arc` so we can implement `Clone`. - _on_open_closure: Arc>, - _on_write_closure: Arc>, - _on_close_closure: Arc>, - _on_message_closure: Arc>, + // These are wrapped in an `Rc` so we can implement `Clone`. + _on_open_closure: Rc>, + _on_write_closure: Rc>, + _on_close_closure: Rc>, + _on_message_closure: Rc>, } impl PollDataChannel { pub(crate) fn new(inner: RtcDataChannel) -> Self { - let open_waker = Arc::new(AtomicWaker::new()); + let open_waker = Rc::new(AtomicWaker::new()); let on_open_closure = Closure::new({ let open_waker = open_waker.clone(); @@ -56,7 +57,7 @@ impl PollDataChannel { }); inner.set_onopen(Some(on_open_closure.as_ref().unchecked_ref())); - let write_waker = Arc::new(AtomicWaker::new()); + let write_waker = Rc::new(AtomicWaker::new()); inner.set_buffered_amount_low_threshold(0); let on_write_closure = Closure::new({ let write_waker = write_waker.clone(); @@ -68,7 +69,7 @@ impl PollDataChannel { }); inner.set_onbufferedamountlow(Some(on_write_closure.as_ref().unchecked_ref())); - let close_waker = Arc::new(AtomicWaker::new()); + let close_waker = Rc::new(AtomicWaker::new()); let on_close_closure = Closure::new({ let close_waker = close_waker.clone(); @@ -79,8 +80,8 @@ impl PollDataChannel { }); inner.set_onclose(Some(on_close_closure.as_ref().unchecked_ref())); - let new_data_waker = Arc::new(AtomicWaker::new()); - let read_buffer = Arc::new(Mutex::new(BytesMut::new())); // We purposely don't use `with_capacity` so we don't eagerly allocate `MAX_READ_BUFFER` per stream. + let new_data_waker = Rc::new(AtomicWaker::new()); + let read_buffer = Rc::new(Mutex::new(BytesMut::new())); // We purposely don't use `with_capacity` so we don't eagerly allocate `MAX_READ_BUFFER` per stream. let on_message_closure = Closure::::new({ let new_data_waker = new_data_waker.clone(); @@ -112,10 +113,10 @@ impl PollDataChannel { open_waker, write_waker, close_waker, - _on_open_closure: Arc::new(on_open_closure), - _on_write_closure: Arc::new(on_write_closure), - _on_close_closure: Arc::new(on_close_closure), - _on_message_closure: Arc::new(on_message_closure), + _on_open_closure: Rc::new(on_open_closure), + _on_write_closure: Rc::new(on_write_closure), + _on_close_closure: Rc::new(on_close_closure), + _on_message_closure: Rc::new(on_message_closure), } } From 92d8f8966a4c05923f82a66bfdc8e07dc7ca936d Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 6 Sep 2023 09:48:17 +1000 Subject: [PATCH 207/235] Misc fixes to reduce diff / duplication --- misc/webrtc-utils/src/stream.rs | 22 +++++++++---------- misc/webrtc-utils/src/stream/drop_listener.rs | 4 ++-- misc/webrtc-utils/src/stream/state.rs | 2 +- .../src/stream/poll_data_channel.rs | 18 ++++++--------- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/misc/webrtc-utils/src/stream.rs b/misc/webrtc-utils/src/stream.rs index faa92369f69..77f46b7ae1d 100644 --- a/misc/webrtc-utils/src/stream.rs +++ b/misc/webrtc-utils/src/stream.rs @@ -35,8 +35,6 @@ use crate::{ stream::state::{Closing, State}, }; -pub use drop_listener::DropListener; - mod drop_listener; mod framed_dc; mod state; @@ -46,7 +44,7 @@ mod state; /// "As long as message interleaving is not supported, the sender SHOULD limit the maximum message /// size to 16 KB to avoid monopolization." /// Source: -pub const MAX_MSG_LEN: usize = 16384; // 16kiB +pub const MAX_MSG_LEN: usize = 16 * 1024; /// Length of varint, in bytes. const VARINT_LEN: usize = 2; /// Overhead of the protobuf encoding, in bytes. @@ -54,9 +52,11 @@ const PROTO_OVERHEAD: usize = 5; /// Maximum length of data, in bytes. const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; -/// A substream on top of a WebRTC data channel. +pub use drop_listener::DropListener; + +/// A stream backed by a WebRTC data channel. /// -/// To be a proper libp2p substream, we need to implement [`AsyncRead`] and [`AsyncWrite`] as well +/// To be a proper libp2p stream, we need to implement [`AsyncRead`] and [`AsyncWrite`] as well /// as support a half-closed state which we do by framing messages in a protobuf envelope. pub struct Stream { io: FramedDc, @@ -70,12 +70,11 @@ impl Stream where T: AsyncRead + AsyncWrite + Unpin + Clone, { - /// Returns a new `Substream` and a listener, which will notify the receiver when/if the substream - /// is dropped. + /// Returns a new [`Stream`] and a [`DropListener`], which will notify the receiver when/if the stream is dropped. pub fn new(data_channel: T) -> (Self, DropListener) { let (sender, receiver) = oneshot::channel(); - let substream = Self { + let stream = Self { io: framed_dc::new(data_channel.clone()), state: State::Open, read_buffer: Bytes::default(), @@ -83,10 +82,10 @@ where }; let listener = DropListener::new(framed_dc::new(data_channel), receiver); - (substream, listener) + (stream, listener) } - /// Gracefully closes the "read-half" of the substream. + /// Gracefully closes the "read-half" of the stream. pub fn poll_close_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { match self.state.close_read_barrier()? { @@ -261,13 +260,12 @@ where #[cfg(test)] mod tests { + use super::*; use asynchronous_codec::Encoder; use bytes::BytesMut; use quick_protobuf::{MessageWrite, Writer}; use unsigned_varint::codec::UviBytes; - use super::*; - #[test] fn max_data_len() { // Largest possible message. diff --git a/misc/webrtc-utils/src/stream/drop_listener.rs b/misc/webrtc-utils/src/stream/drop_listener.rs index b6c952af3fa..b638ea84b09 100644 --- a/misc/webrtc-utils/src/stream/drop_listener.rs +++ b/misc/webrtc-utils/src/stream/drop_listener.rs @@ -79,7 +79,7 @@ where return Poll::Ready(Ok(())); } Poll::Ready(Err(Canceled)) => { - log::info!("Substream dropped without graceful close, sending Reset"); + log::info!("Stream dropped without graceful close, sending Reset"); *state = State::SendingReset { stream }; continue; } @@ -117,5 +117,5 @@ where } } -/// Indicates that our substream got gracefully closed. +/// Indicates that our stream got gracefully closed. pub struct GracefullyClosed {} diff --git a/misc/webrtc-utils/src/stream/state.rs b/misc/webrtc-utils/src/stream/state.rs index e25171876d4..082325e4d47 100644 --- a/misc/webrtc-utils/src/stream/state.rs +++ b/misc/webrtc-utils/src/stream/state.rs @@ -277,7 +277,7 @@ impl State { } } - /// Acts as a "barrier" for `stream::poll_close_read`. + /// Acts as a "barrier" for [`Stream::poll_close_read`](super::Stream::poll_close_read). pub(crate) fn close_read_barrier(&mut self) -> io::Result> { loop { match self { diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index 22a7785d1fa..d64a1790501 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -8,20 +8,14 @@ use std::task::{Context, Poll}; use bytes::BytesMut; use futures::task::AtomicWaker; use futures::{AsyncRead, AsyncWrite}; +use libp2p_webrtc_utils::MAX_MSG_LEN; use wasm_bindgen::{prelude::*, JsCast}; use web_sys::{Event, MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelState}; -/// WebRTC data channels only support backpressure for reading in a limited way. -/// We have to check the `bufferedAmount` property and compare it to chosen constant. -/// Once we exceed the constant, we pause sending of data until we receive the `bufferedAmountLow` event. -/// -/// As per spec, we limit the maximum amount to 16KB, see . -const MAX_BUFFER: usize = 16 * 1024; - /// [`PollDataChannel`] is a wrapper around around [`RtcDataChannel`] which implements [`AsyncRead`] and [`AsyncWrite`]. #[derive(Debug, Clone)] pub(crate) struct PollDataChannel { - /// The [RtcDataChannel] being wrapped. + /// The [`RtcDataChannel`] being wrapped. inner: RtcDataChannel, new_data_waker: Rc, @@ -30,14 +24,14 @@ pub(crate) struct PollDataChannel { /// Waker for when we are waiting for the DC to be opened. open_waker: Rc, - /// Waker for when we are waiting to write (again) to the DC because we previously exceeded the `MAX_BUFFERED_AMOUNT` threshold. + /// Waker for when we are waiting to write (again) to the DC because we previously exceeded the [`MAX_MSG_LEN`] threshold. write_waker: Rc, /// Waker for when we are waiting for the DC to be closed. close_waker: Rc, // Store the closures for proper garbage collection. - // These are wrapped in an `Rc` so we can implement `Clone`. + // These are wrapped in an [`Rc`] so we can implement [`Clone`]. _on_open_closure: Rc>, _on_write_closure: Rc>, _on_close_closure: Rc>, @@ -92,7 +86,9 @@ impl PollDataChannel { let mut read_buffer = read_buffer.lock().unwrap(); - if read_buffer.len() + data.length() as usize >= MAX_BUFFER { + if read_buffer.len() + data.length() as usize > MAX_MSG_LEN { + // TODO: Should we take as much of the data as we can or drop the entire message? + log::warn!( "Remote is overloading us with messages, dropping {} bytes of data", data.length() From 30a002670e5fda789430ae430134c51d9b81b76b Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 6 Sep 2023 09:49:51 +1000 Subject: [PATCH 208/235] Reduce diff --- misc/webrtc-utils/src/stream.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/misc/webrtc-utils/src/stream.rs b/misc/webrtc-utils/src/stream.rs index 77f46b7ae1d..a6de759a412 100644 --- a/misc/webrtc-utils/src/stream.rs +++ b/misc/webrtc-utils/src/stream.rs @@ -53,7 +53,6 @@ const PROTO_OVERHEAD: usize = 5; const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; pub use drop_listener::DropListener; - /// A stream backed by a WebRTC data channel. /// /// To be a proper libp2p stream, we need to implement [`AsyncRead`] and [`AsyncWrite`] as well From 09055f41a1437abeb3d368ebb2ddf428f9e42ae0 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 6 Sep 2023 18:35:57 +1000 Subject: [PATCH 209/235] Fix compile error --- transports/webrtc-websys/src/stream/poll_data_channel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index d64a1790501..2e6abfca563 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -180,8 +180,8 @@ impl AsyncWrite for PollDataChannel { futures::ready!(this.poll_open(cx))?; - debug_assert!(this.buffered_amount() <= MAX_BUFFER); - let remaining_space = MAX_BUFFER - this.buffered_amount(); + debug_assert!(this.buffered_amount() <= MAX_MSG_LEN); + let remaining_space = MAX_MSG_LEN - this.buffered_amount(); if remaining_space == 0 { this.write_waker.register(cx.waker()); From 03edec6d870cc3b3d4f69138ff2541838b3c8814 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 6 Sep 2023 17:38:16 -0300 Subject: [PATCH 210/235] rm LICENSE fm example --- examples/browser-webrtc/LICENSE | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 examples/browser-webrtc/LICENSE diff --git a/examples/browser-webrtc/LICENSE b/examples/browser-webrtc/LICENSE deleted file mode 100644 index 89147dbb831..00000000000 --- a/examples/browser-webrtc/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2023 Doug Anderson - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. From 244a1574a250c6caf1ad254866fad075db87a8a5 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 6 Sep 2023 17:42:03 -0300 Subject: [PATCH 211/235] add 'description', 'repository' to Cargo.toml --- examples/browser-webrtc/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/browser-webrtc/Cargo.toml b/examples/browser-webrtc/Cargo.toml index cf02458ba3a..fb43413bc91 100644 --- a/examples/browser-webrtc/Cargo.toml +++ b/examples/browser-webrtc/Cargo.toml @@ -1,9 +1,11 @@ [package] authors = ["Doug Anderson "] +description = "Example use of the WebRTC transport in a browser wasm environment" edition = "2021" license = "MIT" name = "browser-webrtc-example" publish = false +repository = "https://github.com/libp2p/rust-libp2p" rust-version = { workspace = true } version = "0.1.0" From 7c56d573b7c27b23a681432490917c2f57251e13 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 6 Sep 2023 17:45:54 -0300 Subject: [PATCH 212/235] more loquacious comment about content serving --- examples/browser-webrtc/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/browser-webrtc/src/main.rs b/examples/browser-webrtc/src/main.rs index 8a2cf61b7f0..52eecd4fa47 100644 --- a/examples/browser-webrtc/src/main.rs +++ b/examples/browser-webrtc/src/main.rs @@ -60,7 +60,7 @@ async fn main() -> anyhow::Result<()> { let addr = address.with(Protocol::P2p(*swarm.local_peer_id())); - // Serve the multiaddress over HTTP + // Serve .wasm, .js and server multiaddress over HTTP on this address. tokio::spawn(serve(addr)); loop { From a77603eb1a12c03ce07573502eacb63a391abe29 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 6 Sep 2023 18:51:52 -0300 Subject: [PATCH 213/235] Swarm with a capital `S` in README --- transports/webrtc-websys/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc-websys/README.md b/transports/webrtc-websys/README.md index fab771c3770..b522f31ba65 100644 --- a/transports/webrtc-websys/README.md +++ b/transports/webrtc-websys/README.md @@ -4,6 +4,6 @@ Browser Transport made available through `web-sys` bindings. ## Usage -Use with `with_wasm_executor` to enable the `wasm-bindgen` executor for the swarm. +Use with `Swarm::with_wasm_executor` to enable the `wasm-bindgen` executor for the `Swarm`. See the [browser-webrtc](../../examples/browser-webrtc) example for a full example. From 9e41d0097a15c4007ccad33350f06c7bf1f3f385 Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 6 Sep 2023 19:13:57 -0300 Subject: [PATCH 214/235] use `.expect()` instead of `.unwrap()` --- transports/webrtc-websys/src/connection.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 8e847d60aca..aa8b40a3c50 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -232,9 +232,9 @@ impl RtcPeerConnection { let offer = JsFuture::from(self.inner.create_offer()).await?; let offer = Reflect::get(&offer, &JsValue::from_str("sdp")) - .unwrap() + .expect("sdp should be valid") .as_string() - .unwrap(); + .expect("sdp string should be valid string"); Ok(offer) } From 870045a3908389498b0cb53f8ceda97ad06f797b Mon Sep 17 00:00:00 2001 From: Doug A Date: Wed, 6 Sep 2023 19:14:25 -0300 Subject: [PATCH 215/235] allow `non_upper_case_globals` for rust_embed --- examples/browser-webrtc/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/browser-webrtc/src/main.rs b/examples/browser-webrtc/src/main.rs index 52eecd4fa47..7f2ecb5f444 100644 --- a/examples/browser-webrtc/src/main.rs +++ b/examples/browser-webrtc/src/main.rs @@ -1,3 +1,5 @@ +#![allow(non_upper_case_globals)] + use anyhow::Result; use axum::extract::{Path, State}; use axum::http::header::CONTENT_TYPE; From 4dd3bd132314780a3df222be539d1294bf5601a6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 8 Sep 2023 17:10:27 +1000 Subject: [PATCH 216/235] Fail connection on overload --- .../src/stream/poll_data_channel.rs | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index 2e6abfca563..ccd135e94bd 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -2,6 +2,7 @@ use std::cmp::min; use std::io; use std::pin::Pin; use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex; use std::task::{Context, Poll}; @@ -30,6 +31,9 @@ pub(crate) struct PollDataChannel { /// Waker for when we are waiting for the DC to be closed. close_waker: Rc, + /// Whether we've been overloaded with data by the remote. + overloaded: Rc, + // Store the closures for proper garbage collection. // These are wrapped in an [`Rc`] so we can implement [`Clone`]. _on_open_closure: Rc>, @@ -76,10 +80,12 @@ impl PollDataChannel { let new_data_waker = Rc::new(AtomicWaker::new()); let read_buffer = Rc::new(Mutex::new(BytesMut::new())); // We purposely don't use `with_capacity` so we don't eagerly allocate `MAX_READ_BUFFER` per stream. + let overloaded = Rc::new(AtomicBool::new(false)); let on_message_closure = Closure::::new({ let new_data_waker = new_data_waker.clone(); let read_buffer = read_buffer.clone(); + let overloaded = overloaded.clone(); move |ev: MessageEvent| { let data = js_sys::Uint8Array::new(&ev.data()); @@ -87,12 +93,8 @@ impl PollDataChannel { let mut read_buffer = read_buffer.lock().unwrap(); if read_buffer.len() + data.length() as usize > MAX_MSG_LEN { - // TODO: Should we take as much of the data as we can or drop the entire message? - - log::warn!( - "Remote is overloading us with messages, dropping {} bytes of data", - data.length() - ); + overloaded.store(true, Ordering::SeqCst); + log::warn!("Remote is overloading us with messages, resetting stream",); return; } @@ -109,6 +111,7 @@ impl PollDataChannel { open_waker, write_waker, close_waker, + overloaded, _on_open_closure: Rc::new(on_open_closure), _on_write_closure: Rc::new(on_write_closure), _on_close_closure: Rc::new(on_close_closure), @@ -130,13 +133,22 @@ impl PollDataChannel { match self.ready_state() { RtcDataChannelState::Connecting => { self.open_waker.register(cx.waker()); - Poll::Pending + return Poll::Pending; } RtcDataChannelState::Closing | RtcDataChannelState::Closed => { - Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())) + return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())) } - RtcDataChannelState::Open | RtcDataChannelState::__Nonexhaustive => Poll::Ready(Ok(())), + RtcDataChannelState::Open | RtcDataChannelState::__Nonexhaustive => {} } + + if self.overloaded.load(Ordering::SeqCst) { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::BrokenPipe, + "remote overloaded us with messages", + ))); + } + + Poll::Ready(Ok(())) } } From d26864f837879ed3c02152667b6b5adb9f96313e Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 8 Sep 2023 17:10:52 +1000 Subject: [PATCH 217/235] Rename function and add docs --- transports/webrtc-websys/src/stream/poll_data_channel.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index ccd135e94bd..8502a133930 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -129,7 +129,8 @@ impl PollDataChannel { self.inner.buffered_amount() as usize } - fn poll_open(&mut self, cx: &mut Context) -> Poll> { + /// Whether the data channel is ready for reading or writing. + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { match self.ready_state() { RtcDataChannelState::Connecting => { self.open_waker.register(cx.waker()); @@ -160,7 +161,7 @@ impl AsyncRead for PollDataChannel { ) -> Poll> { let this = self.get_mut(); - futures::ready!(this.poll_open(cx))?; + futures::ready!(this.poll_ready(cx))?; let mut read_buffer = this.read_buffer.lock().unwrap(); @@ -190,7 +191,7 @@ impl AsyncWrite for PollDataChannel { ) -> Poll> { let this = self.get_mut(); - futures::ready!(this.poll_open(cx))?; + futures::ready!(this.poll_ready(cx))?; debug_assert!(this.buffered_amount() <= MAX_MSG_LEN); let remaining_space = MAX_MSG_LEN - this.buffered_amount(); From 470fab7f8fb31637d9f1718c2507bfe651de20d8 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 8 Sep 2023 17:12:59 +1000 Subject: [PATCH 218/235] Add docs for `overloaded` --- transports/webrtc-websys/src/stream/poll_data_channel.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index 8502a133930..9c9b19cdb32 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -32,6 +32,10 @@ pub(crate) struct PollDataChannel { close_waker: Rc, /// Whether we've been overloaded with data by the remote. + /// + /// This is set to `true` in case `read_buffer` overflows, i.e. the remote is sending us messages faster than we can read them. + /// In that case, we return an [`std::io::Error`] from [`AsyncRead`] or [`AsyncWrite`], depending which one gets called earlier. + /// Failing these will (very likely), cause the application developer to drop the stream which resets it. overloaded: Rc, // Store the closures for proper garbage collection. From ed0138157f27fbb5d51ff686dcbaba31e9d91084 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 08:33:53 -0300 Subject: [PATCH 219/235] remove old comment about being "unimplemented" --- transports/webrtc-websys/src/connection.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index aa8b40a3c50..8c4b50caf53 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -34,9 +34,7 @@ pub struct Connection { /// Because the browser's WebRTC API is event-based, we need to use a channel to obtain all inbound data channels. inbound_data_channels: SendWrapper>, /// A list of futures, which, once completed, signal that a [`Stream`] has been dropped. - /// Currently unimplemented, will be implemented in a future PR. drop_listeners: FuturesUnordered, - /// Currently unimplemented, will be implemented in a future PR. no_drop_listeners_waker: Option, _ondatachannel_closure: SendWrapper>, From 8505d0d29f0a8a0aea01fb871340384b61d544ca Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 11:12:55 -0300 Subject: [PATCH 220/235] check if local firefox --- transports/webrtc-websys/Cargo.toml | 2 +- transports/webrtc-websys/src/transport.rs | 39 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index e7f2000c395..12d72ce1f06 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -27,7 +27,7 @@ serde = { version = "1.0", features = ["derive"] } thiserror = "1" wasm-bindgen = { version = "0.2.87" } wasm-bindgen-futures = { version = "0.4.37" } -web-sys = { version = "0.3.64", features = ["MessageEvent", "RtcCertificate", "RtcConfiguration", "RtcDataChannel", "RtcDataChannelEvent", "RtcDataChannelInit", "RtcDataChannelState", "RtcDataChannelType", "RtcPeerConnection", "RtcSdpType", "RtcSessionDescription", "RtcSessionDescriptionInit", ] } +web-sys = { version = "0.3.64", features = ["Document", "Location", "MessageEvent", "Navigator", "RtcCertificate", "RtcConfiguration", "RtcDataChannel", "RtcDataChannelEvent", "RtcDataChannelInit", "RtcDataChannelState", "RtcDataChannelType", "RtcPeerConnection", "RtcSdpType", "RtcSessionDescription", "RtcSessionDescriptionInit", "Window"] } [dev-dependencies] hex-literal = "0.4" diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs index 9b157937a66..e0553a2f482 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc-websys/src/transport.rs @@ -63,6 +63,14 @@ impl libp2p_core::Transport for Transport { } fn dial(&mut self, addr: Multiaddr) -> Result> { + if maybe_local_firefox() { + return Err(TransportError::Other( + "Firefox does not support WebRTC over localhost or 127.0.0.1" + .to_string() + .into(), + )); + } + let (sock_addr, server_fingerprint) = libp2p_webrtc_utils::parse_webrtc_dial_addr(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; @@ -99,3 +107,34 @@ impl libp2p_core::Transport for Transport { None } } + +/// Checks if local Firefox. +/// +/// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1659672 for more details +fn maybe_local_firefox() -> bool { + let window = &web_sys::window().expect("window should be available"); + let ua = match window.navigator().user_agent() { + Ok(agent) => agent.to_lowercase(), + Err(_) => return false, + }; + + let hostname = match window + .document() + .expect("should be valid document") + .location() + { + Some(location) => match location.hostname() { + Ok(hostname) => hostname, + Err(_) => return false, + }, + None => return false, + }; + + // check if web_sys::Navigator::user_agent() matches any of the following: + // - firefox + // - seamonkey + // - iceape + // AND hostname is either localhost or "127.0.0.1" + (ua.contains("firefox") || ua.contains("seamonkey") || ua.contains("iceape")) + && (hostname == "localhost" || hostname == "127.0.0.1") +} From 75e25b58ac2e599122c331283db2ebb71ad9c4f7 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 11:14:08 -0300 Subject: [PATCH 221/235] use `Ipv6Addr::UNSPECIFIED` instead of `LOCALHOST` --- examples/browser-webrtc/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/browser-webrtc/src/main.rs b/examples/browser-webrtc/src/main.rs index 7f2ecb5f444..6b22a58af1a 100644 --- a/examples/browser-webrtc/src/main.rs +++ b/examples/browser-webrtc/src/main.rs @@ -46,7 +46,7 @@ async fn main() -> anyhow::Result<()> { let mut swarm = SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build(); - let address_webrtc = Multiaddr::from(Ipv6Addr::LOCALHOST) + let address_webrtc = Multiaddr::from(Ipv6Addr::UNSPECIFIED) .with(Protocol::Udp(0)) .with(Protocol::WebRTCDirect); From a880251ae03655a29a1462bd5cfa8fc6686bbc9e Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 13:07:47 -0300 Subject: [PATCH 222/235] send reset signal when read Error occurs --- misc/webrtc-utils/src/stream.rs | 22 +++++++++++++------ misc/webrtc-utils/src/stream/drop_listener.rs | 20 ++++++++++++----- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/misc/webrtc-utils/src/stream.rs b/misc/webrtc-utils/src/stream.rs index a6de759a412..694b33faa20 100644 --- a/misc/webrtc-utils/src/stream.rs +++ b/misc/webrtc-utils/src/stream.rs @@ -30,7 +30,7 @@ use std::{ use crate::proto::{Flag, Message}; use crate::{ - stream::drop_listener::GracefullyClosed, + stream::drop_listener::DropMessage, stream::framed_dc::FramedDc, stream::state::{Closing, State}, }; @@ -62,7 +62,7 @@ pub struct Stream { state: State, read_buffer: Bytes, /// Dropping this will close the oneshot and notify the receiver by emitting `Canceled`. - drop_notifier: Option>, + drop_notifier: Option>, } impl Stream @@ -139,21 +139,29 @@ where .. } = &mut *self; - match ready!(io_poll_next(io, cx))? { - Some((flag, message)) => { + match ready!(io_poll_next(io, cx)) { + Ok(Some((flag, message))) => { if let Some(flag) = flag { state.handle_inbound_flag(flag, read_buffer); } - debug_assert!(read_buffer.is_empty()); if let Some(message) = message { *read_buffer = message.into(); } } - None => { + Ok(None) => { state.handle_inbound_flag(Flag::FIN, read_buffer); return Poll::Ready(Ok(0)); } + Err(e) => { + log::error!("Error while reading next message: {:?}", e); + let _ = self + .drop_notifier + .take() + .expect("should be able to take drop_notifier value") + .send(DropMessage::SendReset); + return Poll::Ready(Err(e)); + } } } } @@ -231,7 +239,7 @@ where .drop_notifier .take() .expect("to not close twice") - .send(GracefullyClosed {}); + .send(DropMessage::GracefullyClosed); return Poll::Ready(Ok(())); } diff --git a/misc/webrtc-utils/src/stream/drop_listener.rs b/misc/webrtc-utils/src/stream/drop_listener.rs index b638ea84b09..f57079d47ea 100644 --- a/misc/webrtc-utils/src/stream/drop_listener.rs +++ b/misc/webrtc-utils/src/stream/drop_listener.rs @@ -36,7 +36,7 @@ pub struct DropListener { } impl DropListener { - pub fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { + pub fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { Self { state: State::Idle { stream, receiver }, } @@ -47,7 +47,7 @@ enum State { /// The [`DropListener`] is idle and waiting to be activated. Idle { stream: FramedDc, - receiver: oneshot::Receiver, + receiver: oneshot::Receiver, }, /// The stream got dropped and we are sending a reset flag. SendingReset { @@ -75,9 +75,14 @@ where stream, mut receiver, } => match receiver.poll_unpin(cx) { - Poll::Ready(Ok(GracefullyClosed {})) => { + Poll::Ready(Ok(DropMessage::GracefullyClosed)) => { return Poll::Ready(Ok(())); } + Poll::Ready(Ok(DropMessage::SendReset)) => { + log::info!("Stream gracefully errored, sending Reset"); + *state = State::SendingReset { stream }; + continue; + } Poll::Ready(Err(Canceled)) => { log::info!("Stream dropped without graceful close, sending Reset"); *state = State::SendingReset { stream }; @@ -117,5 +122,10 @@ where } } -/// Indicates that our stream got gracefully closed. -pub struct GracefullyClosed {} +/// The reason we are dropping the Stream. +pub enum DropMessage { + /// The stream was closed gracefully. + GracefullyClosed, + /// The stream errored (such as receiving too much data) and we are sending a reset signal + SendReset, +} From fc26ded763657e2c48adc14c7dbf14ffe5aedbbf Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 14:01:32 -0300 Subject: [PATCH 223/235] set repo to lowercase in docker .yml --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 7bde2040e2d..5713b4f7b9a 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -32,4 +32,4 @@ jobs: context: . file: ./misc/server/Dockerfile push: true - tags: ghcr.io/${{ github.repository }}-server:${{ steps.ref-name.outputs.ref }} + tags: ghcr.io/${{ github.repository.lowercase }}-server:${{ steps.ref-name.outputs.ref }} From 8c23eb16a8e281b2a4ef2510185f81c67376ead4 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 14:12:28 -0300 Subject: [PATCH 224/235] set repo to lowercase in docker .yml --- .github/workflows/docker-image.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 7bde2040e2d..31f70037bcd 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -26,10 +26,14 @@ jobs: id: ref-name run: echo ::set-output name=ref::${GITHUB_REF#refs/*/} + - name: downcase REPO + run: | + echo "REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + - name: Build and push uses: docker/build-push-action@v4 with: context: . file: ./misc/server/Dockerfile push: true - tags: ghcr.io/${{ github.repository }}-server:${{ steps.ref-name.outputs.ref }} + tags: ghcr.io/${REPO}-server:${{ steps.ref-name.outputs.ref }} From a24d4ed9e4489cefb7718725986acebc05e20dbb Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 14:18:07 -0300 Subject: [PATCH 225/235] set REPO to lowercase in docker-image.yml Since my repo has uppercase letters, needs to be lowered. See https://github.com/orgs/community/discussions/25768 --- .github/workflows/docker-image.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 5713b4f7b9a..31f70037bcd 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -26,10 +26,14 @@ jobs: id: ref-name run: echo ::set-output name=ref::${GITHUB_REF#refs/*/} + - name: downcase REPO + run: | + echo "REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + - name: Build and push uses: docker/build-push-action@v4 with: context: . file: ./misc/server/Dockerfile push: true - tags: ghcr.io/${{ github.repository.lowercase }}-server:${{ steps.ref-name.outputs.ref }} + tags: ghcr.io/${REPO}-server:${{ steps.ref-name.outputs.ref }} From 4a14e748829ce7272704e0e41d03df273b15f60b Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 14:37:13 -0300 Subject: [PATCH 226/235] add coverage for hostname as IPv6 --- transports/webrtc-websys/src/transport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs index e0553a2f482..e341b1347d5 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc-websys/src/transport.rs @@ -136,5 +136,5 @@ fn maybe_local_firefox() -> bool { // - iceape // AND hostname is either localhost or "127.0.0.1" (ua.contains("firefox") || ua.contains("seamonkey") || ua.contains("iceape")) - && (hostname == "localhost" || hostname == "127.0.0.1") + && (hostname == "localhost" || hostname == "127.0.0.1" || hostname == "[::1]") } From e3896b8ec1cfb89a7a8b799c9fae87599e9cabbd Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 14:38:27 -0300 Subject: [PATCH 227/235] remove duplicate REPO naming in yml --- .github/workflows/docker-image.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 52811a563c4..ad48f277a73 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -30,10 +30,6 @@ jobs: run: | echo "REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} - - name: downcase REPO - run: | - echo "REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} - - name: Build and push uses: docker/build-push-action@v4 with: From c4693f02cbad4751d5c6ce9a0aa4346b02c14db2 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 14:41:36 -0300 Subject: [PATCH 228/235] fix rustdoc hyperlink --- transports/webrtc-websys/src/transport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs index e341b1347d5..ecf137eab8a 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc-websys/src/transport.rs @@ -110,7 +110,7 @@ impl libp2p_core::Transport for Transport { /// Checks if local Firefox. /// -/// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1659672 for more details +/// See: `` for more details fn maybe_local_firefox() -> bool { let window = &web_sys::window().expect("window should be available"); let ua = match window.navigator().user_agent() { From 65c027b27cdf6387ad8eda0d42ef66f706a2fe0c Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 14:58:27 -0300 Subject: [PATCH 229/235] update webpki 0.22.1 for RUSTSEC-2023-0052 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87bbe70c5aa..f3a1d4d2ba6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5063,7 +5063,7 @@ dependencies = [ "log", "ring", "sct 0.7.0", - "webpki 0.22.0", + "webpki 0.22.1", ] [[package]] @@ -5894,7 +5894,7 @@ checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls 0.20.8", "tokio", - "webpki 0.22.0", + "webpki 0.22.1", ] [[package]] @@ -6476,9 +6476,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" dependencies = [ "ring", "untrusted", From 5d30cbfd2de9ae84f1006bb1288c8e985b7e4729 Mon Sep 17 00:00:00 2001 From: Doug A Date: Fri, 8 Sep 2023 16:56:33 -0300 Subject: [PATCH 230/235] publish=false, temp fix to CI semver-checks-action --- transports/webrtc-websys/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index 12d72ce1f06..9c595071815 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -9,6 +9,7 @@ name = "libp2p-webrtc-websys" repository = "https://github.com/libp2p/rust-libp2p" rust-version = { workspace = true } version = "0.1.0-alpha" +publish = false # TEMP fix for https://github.com/obi1kenobi/cargo-semver-checks-action/issues/53. [dependencies] bytes = "1" From 952160ec27be0257f51b00775b4f1866bc74ed3e Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 11 Sep 2023 12:15:02 +1000 Subject: [PATCH 231/235] Make example work in Firefox --- examples/browser-webrtc/README.md | 4 +--- examples/browser-webrtc/src/main.rs | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/examples/browser-webrtc/README.md b/examples/browser-webrtc/README.md index 1688307034a..d44cf879905 100644 --- a/examples/browser-webrtc/README.md +++ b/examples/browser-webrtc/README.md @@ -15,6 +15,4 @@ wasm-pack build --target web --out-dir static cargo run ``` -3. Open the following in a Chromium-based[^1] browser: http://localhost:8080 - -[^1]: Support in other browsers has not been verified. +3. Open the URL printed in the terminal diff --git a/examples/browser-webrtc/src/main.rs b/examples/browser-webrtc/src/main.rs index 6b22a58af1a..f919f047af5 100644 --- a/examples/browser-webrtc/src/main.rs +++ b/examples/browser-webrtc/src/main.rs @@ -17,10 +17,7 @@ use libp2p::{ }; use libp2p_webrtc as webrtc; use rand::thread_rng; -use std::{ - net::Ipv6Addr, - net::{IpAddr, Ipv4Addr, SocketAddr}, -}; +use std::net::{Ipv4Addr, SocketAddr}; use tower_http::cors::{Any, CorsLayer}; #[tokio::main] @@ -46,7 +43,7 @@ async fn main() -> anyhow::Result<()> { let mut swarm = SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build(); - let address_webrtc = Multiaddr::from(Ipv6Addr::UNSPECIFIED) + let address_webrtc = Multiaddr::from(Ipv4Addr::UNSPECIFIED) .with(Protocol::Udp(0)) .with(Protocol::WebRTCDirect); @@ -54,6 +51,14 @@ async fn main() -> anyhow::Result<()> { let address = loop { if let SwarmEvent::NewListenAddr { address, .. } = swarm.select_next_some().await { + if address + .iter() + .any(|e| e == Protocol::Ip4(Ipv4Addr::LOCALHOST)) + { + log::debug!("Ignoring localhost address to make sure the example works in Firefox"); + continue; + } + log::info!("Listening on: {address}"); break address; @@ -91,6 +96,11 @@ struct StaticFiles; /// Serve the Multiaddr we are listening on and the host files. pub(crate) async fn serve(libp2p_transport: Multiaddr) { + let listen_addr = match libp2p_transport.iter().next() { + Some(Protocol::Ip4(addr)) => addr, + _ => panic!("Expected 1st protocol to be IP4"), + }; + let server = Router::new() .route("/", get(get_index)) .route("/index.html", get(get_index)) @@ -103,7 +113,7 @@ pub(crate) async fn serve(libp2p_transport: Multiaddr) { .allow_methods([Method::GET]), ); - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080); + let addr = SocketAddr::new(listen_addr.into(), 8080); log::info!("Serving client files at http://{addr}"); From 5d224793fdd0c1ac68655e5ad2aa13ef9587b95a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 11 Sep 2023 12:31:45 +1000 Subject: [PATCH 232/235] Don't set waker on connection shutdown --- transports/webrtc-websys/src/connection.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index 8c4b50caf53..ee162c60645 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -9,6 +9,7 @@ use js_sys::{Object, Reflect}; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; use libp2p_webrtc_utils::Fingerprint; use send_wrapper::SendWrapper; +use std::io; use std::pin::Pin; use std::task::Waker; use std::task::{ready, Context, Poll}; @@ -120,8 +121,9 @@ impl StreamMuxer for Connection { Poll::Ready(Ok(stream)) } None => { - self.no_drop_listeners_waker = Some(cx.waker().clone()); - Poll::Pending + // This only happens if the [`RtcPeerConnection::ondatachannel`] closure gets freed which means we are most likely shutting down the connection. + log::debug!("`Sender` for inbound data channels has been dropped"); + Poll::Ready(Err(Error::Connection("connection closed".to_owned()))) } } } From f4d2ecf6ad2e2fdef9a1bb69bc814f05970e8008 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 12 Sep 2023 09:54:33 +1000 Subject: [PATCH 233/235] Remove unused import --- transports/webrtc-websys/src/connection.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index ee162c60645..dfdebbc98c0 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -9,7 +9,6 @@ use js_sys::{Object, Reflect}; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; use libp2p_webrtc_utils::Fingerprint; use send_wrapper::SendWrapper; -use std::io; use std::pin::Pin; use std::task::Waker; use std::task::{ready, Context, Poll}; From 7a2eda1a9722e6b24da42dd9fcebfde338a0fc98 Mon Sep 17 00:00:00 2001 From: Doug A Date: Mon, 11 Sep 2023 21:29:56 -0300 Subject: [PATCH 234/235] revert docker.yml casing change --- .github/workflows/docker-image.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index ad48f277a73..7bde2040e2d 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -26,14 +26,10 @@ jobs: id: ref-name run: echo ::set-output name=ref::${GITHUB_REF#refs/*/} - - name: downcase REPO - run: | - echo "REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} - - name: Build and push uses: docker/build-push-action@v4 with: context: . file: ./misc/server/Dockerfile push: true - tags: ghcr.io/${{ env.REPO }}-server:${{ steps.ref-name.outputs.ref }} + tags: ghcr.io/${{ github.repository }}-server:${{ steps.ref-name.outputs.ref }} From 46876ad1fd855bb6f81677f46e945d78129ab959 Mon Sep 17 00:00:00 2001 From: Doug Date: Sun, 17 Sep 2023 10:34:47 -0300 Subject: [PATCH 235/235] revert commit a880251 (send Reset) --- misc/webrtc-utils/src/stream.rs | 22 ++++++------------- misc/webrtc-utils/src/stream/drop_listener.rs | 20 +++++------------ 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/misc/webrtc-utils/src/stream.rs b/misc/webrtc-utils/src/stream.rs index 694b33faa20..a6de759a412 100644 --- a/misc/webrtc-utils/src/stream.rs +++ b/misc/webrtc-utils/src/stream.rs @@ -30,7 +30,7 @@ use std::{ use crate::proto::{Flag, Message}; use crate::{ - stream::drop_listener::DropMessage, + stream::drop_listener::GracefullyClosed, stream::framed_dc::FramedDc, stream::state::{Closing, State}, }; @@ -62,7 +62,7 @@ pub struct Stream { state: State, read_buffer: Bytes, /// Dropping this will close the oneshot and notify the receiver by emitting `Canceled`. - drop_notifier: Option>, + drop_notifier: Option>, } impl Stream @@ -139,29 +139,21 @@ where .. } = &mut *self; - match ready!(io_poll_next(io, cx)) { - Ok(Some((flag, message))) => { + match ready!(io_poll_next(io, cx))? { + Some((flag, message)) => { if let Some(flag) = flag { state.handle_inbound_flag(flag, read_buffer); } + debug_assert!(read_buffer.is_empty()); if let Some(message) = message { *read_buffer = message.into(); } } - Ok(None) => { + None => { state.handle_inbound_flag(Flag::FIN, read_buffer); return Poll::Ready(Ok(0)); } - Err(e) => { - log::error!("Error while reading next message: {:?}", e); - let _ = self - .drop_notifier - .take() - .expect("should be able to take drop_notifier value") - .send(DropMessage::SendReset); - return Poll::Ready(Err(e)); - } } } } @@ -239,7 +231,7 @@ where .drop_notifier .take() .expect("to not close twice") - .send(DropMessage::GracefullyClosed); + .send(GracefullyClosed {}); return Poll::Ready(Ok(())); } diff --git a/misc/webrtc-utils/src/stream/drop_listener.rs b/misc/webrtc-utils/src/stream/drop_listener.rs index f57079d47ea..b638ea84b09 100644 --- a/misc/webrtc-utils/src/stream/drop_listener.rs +++ b/misc/webrtc-utils/src/stream/drop_listener.rs @@ -36,7 +36,7 @@ pub struct DropListener { } impl DropListener { - pub fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { + pub fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { Self { state: State::Idle { stream, receiver }, } @@ -47,7 +47,7 @@ enum State { /// The [`DropListener`] is idle and waiting to be activated. Idle { stream: FramedDc, - receiver: oneshot::Receiver, + receiver: oneshot::Receiver, }, /// The stream got dropped and we are sending a reset flag. SendingReset { @@ -75,14 +75,9 @@ where stream, mut receiver, } => match receiver.poll_unpin(cx) { - Poll::Ready(Ok(DropMessage::GracefullyClosed)) => { + Poll::Ready(Ok(GracefullyClosed {})) => { return Poll::Ready(Ok(())); } - Poll::Ready(Ok(DropMessage::SendReset)) => { - log::info!("Stream gracefully errored, sending Reset"); - *state = State::SendingReset { stream }; - continue; - } Poll::Ready(Err(Canceled)) => { log::info!("Stream dropped without graceful close, sending Reset"); *state = State::SendingReset { stream }; @@ -122,10 +117,5 @@ where } } -/// The reason we are dropping the Stream. -pub enum DropMessage { - /// The stream was closed gracefully. - GracefullyClosed, - /// The stream errored (such as receiving too much data) and we are sending a reset signal - SendReset, -} +/// Indicates that our stream got gracefully closed. +pub struct GracefullyClosed {}