mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-05-29 02:31:20 +00:00
feat(websocket): add WebSocket for WASM environments
Resolves https://github.com/libp2p/rust-libp2p/issues/3611. Pull-Request: #4102.
This commit is contained in:
parent
5efcb8ff6d
commit
19c5cf22ec
@ -32,10 +32,11 @@
|
|||||||
- [`libp2p-pnet` CHANGELOG](transports/pnet/CHANGELOG.md)
|
- [`libp2p-pnet` CHANGELOG](transports/pnet/CHANGELOG.md)
|
||||||
- [`libp2p-quic` CHANGELOG](transports/quic/CHANGELOG.md)
|
- [`libp2p-quic` CHANGELOG](transports/quic/CHANGELOG.md)
|
||||||
- [`libp2p-tcp` CHANGELOG](transports/tcp/CHANGELOG.md)
|
- [`libp2p-tcp` CHANGELOG](transports/tcp/CHANGELOG.md)
|
||||||
|
- [`libp2p-tls` CHANGELOG](transports/tls/CHANGELOG.md)
|
||||||
- [`libp2p-uds` CHANGELOG](transports/uds/CHANGELOG.md)
|
- [`libp2p-uds` CHANGELOG](transports/uds/CHANGELOG.md)
|
||||||
- [`libp2p-wasm-ext` CHANGELOG](transports/wasm-ext/CHANGELOG.md)
|
- [`libp2p-wasm-ext` CHANGELOG](transports/wasm-ext/CHANGELOG.md)
|
||||||
- [`libp2p-websocket` CHANGELOG](transports/websocket/CHANGELOG.md)
|
- [`libp2p-websocket` CHANGELOG](transports/websocket/CHANGELOG.md)
|
||||||
- [`libp2p-tls` CHANGELOG](transports/tls/CHANGELOG.md)
|
- [`libp2p-websocket-websys` CHANGELOG](transports/websocket-websys/CHANGELOG.md)
|
||||||
|
|
||||||
## Multiplexers
|
## Multiplexers
|
||||||
|
|
||||||
|
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -2359,7 +2359,7 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libp2p"
|
name = "libp2p"
|
||||||
version = "0.52.3"
|
version = "0.52.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -2401,6 +2401,7 @@ dependencies = [
|
|||||||
"libp2p-upnp",
|
"libp2p-upnp",
|
||||||
"libp2p-wasm-ext",
|
"libp2p-wasm-ext",
|
||||||
"libp2p-websocket",
|
"libp2p-websocket",
|
||||||
|
"libp2p-websocket-websys",
|
||||||
"libp2p-webtransport-websys",
|
"libp2p-webtransport-websys",
|
||||||
"libp2p-yamux",
|
"libp2p-yamux",
|
||||||
"multiaddr",
|
"multiaddr",
|
||||||
@ -3281,6 +3282,25 @@ dependencies = [
|
|||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libp2p-websocket-websys"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures",
|
||||||
|
"js-sys",
|
||||||
|
"libp2p-core",
|
||||||
|
"libp2p-identity",
|
||||||
|
"libp2p-noise",
|
||||||
|
"libp2p-yamux",
|
||||||
|
"log",
|
||||||
|
"parking_lot",
|
||||||
|
"send_wrapper 0.6.0",
|
||||||
|
"thiserror",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libp2p-webtransport-websys"
|
name = "libp2p-webtransport-websys"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -62,6 +62,7 @@ members = [
|
|||||||
"transports/webrtc-websys",
|
"transports/webrtc-websys",
|
||||||
"transports/websocket",
|
"transports/websocket",
|
||||||
"transports/webtransport-websys",
|
"transports/webtransport-websys",
|
||||||
|
"transports/websocket-websys",
|
||||||
"wasm-tests/webtransport-tests",
|
"wasm-tests/webtransport-tests",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
@ -71,7 +72,7 @@ rust-version = "1.65.0"
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
futures-bounded = { version = "0.1.0", path = "misc/futures-bounded" }
|
futures-bounded = { version = "0.1.0", path = "misc/futures-bounded" }
|
||||||
libp2p = { version = "0.52.3", path = "libp2p" }
|
libp2p = { version = "0.52.4", path = "libp2p" }
|
||||||
libp2p-allow-block-list = { version = "0.2.0", path = "misc/allow-block-list" }
|
libp2p-allow-block-list = { version = "0.2.0", path = "misc/allow-block-list" }
|
||||||
libp2p-autonat = { version = "0.11.0", path = "protocols/autonat" }
|
libp2p-autonat = { version = "0.11.0", path = "protocols/autonat" }
|
||||||
libp2p-connection-limits = { version = "0.2.1", path = "misc/connection-limits" }
|
libp2p-connection-limits = { version = "0.2.1", path = "misc/connection-limits" }
|
||||||
@ -111,6 +112,7 @@ libp2p-webrtc = { version = "0.6.1-alpha", path = "transports/webrtc" }
|
|||||||
libp2p-webrtc-utils = { version = "0.1.0", path = "misc/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-webrtc-websys = { version = "0.1.0-alpha", path = "transports/webrtc-websys" }
|
||||||
libp2p-websocket = { version = "0.42.1", path = "transports/websocket" }
|
libp2p-websocket = { version = "0.42.1", path = "transports/websocket" }
|
||||||
|
libp2p-websocket-websys = { version = "0.2.0", path = "transports/websocket-websys" }
|
||||||
libp2p-webtransport-websys = { version = "0.1.0", path = "transports/webtransport-websys" }
|
libp2p-webtransport-websys = { version = "0.1.0", path = "transports/webtransport-websys" }
|
||||||
libp2p-yamux = { version = "0.44.1", path = "muxers/yamux" }
|
libp2p-yamux = { version = "0.44.1", path = "muxers/yamux" }
|
||||||
multistream-select = { version = "0.13.0", path = "misc/multistream-select" }
|
multistream-select = { version = "0.13.0", path = "misc/multistream-select" }
|
||||||
|
@ -35,7 +35,8 @@ tracing = "0.1"
|
|||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
libp2p = { path = "../libp2p", features = [ "ping", "macros", "webtransport-websys", "wasm-bindgen", "identify"] }
|
libp2p = { path = "../libp2p", features = [ "ping", "macros", "webtransport-websys", "wasm-bindgen", "identify", "websocket-websys", "yamux", "noise"] }
|
||||||
|
libp2p-mplex = { path = "../muxers/mplex" }
|
||||||
libp2p-webrtc-websys = { workspace = true }
|
libp2p-webrtc-websys = { workspace = true }
|
||||||
wasm-bindgen = { version = "0.2" }
|
wasm-bindgen = { version = "0.2" }
|
||||||
wasm-bindgen-futures = { version = "0.4" }
|
wasm-bindgen-futures = { version = "0.4" }
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
"containerImageID": "chromium-rust-libp2p-head",
|
"containerImageID": "chromium-rust-libp2p-head",
|
||||||
"transports": [
|
"transports": [
|
||||||
{ "name": "webtransport", "onlyDial": true },
|
{ "name": "webtransport", "onlyDial": true },
|
||||||
{ "name": "webrtc-direct", "onlyDial": true }
|
{ "name": "webrtc-direct", "onlyDial": true },
|
||||||
|
{ "name": "ws", "onlyDial": true }
|
||||||
],
|
],
|
||||||
"secureChannels": [],
|
"secureChannels": ["noise"],
|
||||||
"muxers": []
|
"muxers": ["mplex", "yamux"]
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ pub(crate) mod native {
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use either::Either;
|
|
||||||
use env_logger::{Env, Target};
|
use env_logger::{Env, Target};
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
@ -31,7 +30,7 @@ pub(crate) mod native {
|
|||||||
use libp2p_webrtc as webrtc;
|
use libp2p_webrtc as webrtc;
|
||||||
use redis::AsyncCommands;
|
use redis::AsyncCommands;
|
||||||
|
|
||||||
use crate::{from_env, Muxer, SecProtocol, Transport};
|
use crate::{Muxer, SecProtocol, Transport};
|
||||||
|
|
||||||
use super::BoxedTransport;
|
use super::BoxedTransport;
|
||||||
|
|
||||||
@ -47,66 +46,103 @@ pub(crate) mod native {
|
|||||||
tokio::time::sleep(duration).boxed()
|
tokio::time::sleep(duration).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn muxer_protocol_from_env() -> Result<Either<yamux::Config, mplex::MplexConfig>> {
|
|
||||||
Ok(match from_env("muxer")? {
|
|
||||||
Muxer::Yamux => Either::Left(yamux::Config::default()),
|
|
||||||
Muxer::Mplex => Either::Right(mplex::MplexConfig::new()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn build_transport(
|
pub(crate) fn build_transport(
|
||||||
local_key: Keypair,
|
local_key: Keypair,
|
||||||
ip: &str,
|
ip: &str,
|
||||||
transport: Transport,
|
transport: Transport,
|
||||||
|
sec_protocol: Option<SecProtocol>,
|
||||||
|
muxer: Option<Muxer>,
|
||||||
) -> Result<(BoxedTransport, String)> {
|
) -> Result<(BoxedTransport, String)> {
|
||||||
let (transport, addr) = match (transport, from_env::<SecProtocol>("security")) {
|
let (transport, addr) = match (transport, sec_protocol, muxer) {
|
||||||
(Transport::QuicV1, _) => (
|
(Transport::QuicV1, _, _) => (
|
||||||
quic::tokio::Transport::new(quic::Config::new(&local_key))
|
quic::tokio::Transport::new(quic::Config::new(&local_key))
|
||||||
.map(|(p, c), _| (p, StreamMuxerBox::new(c)))
|
.map(|(p, c), _| (p, StreamMuxerBox::new(c)))
|
||||||
.boxed(),
|
.boxed(),
|
||||||
format!("/ip4/{ip}/udp/0/quic-v1"),
|
format!("/ip4/{ip}/udp/0/quic-v1"),
|
||||||
),
|
),
|
||||||
(Transport::Tcp, Ok(SecProtocol::Tls)) => (
|
(Transport::Tcp, Some(SecProtocol::Tls), Some(Muxer::Mplex)) => (
|
||||||
tcp::tokio::Transport::new(tcp::Config::new())
|
tcp::tokio::Transport::new(tcp::Config::new())
|
||||||
.upgrade(Version::V1Lazy)
|
.upgrade(Version::V1Lazy)
|
||||||
.authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?)
|
.authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?)
|
||||||
.multiplex(muxer_protocol_from_env()?)
|
.multiplex(mplex::MplexConfig::new())
|
||||||
.timeout(Duration::from_secs(5))
|
.timeout(Duration::from_secs(5))
|
||||||
.boxed(),
|
.boxed(),
|
||||||
format!("/ip4/{ip}/tcp/0"),
|
format!("/ip4/{ip}/tcp/0"),
|
||||||
),
|
),
|
||||||
(Transport::Tcp, Ok(SecProtocol::Noise)) => (
|
(Transport::Tcp, Some(SecProtocol::Tls), Some(Muxer::Yamux)) => (
|
||||||
|
tcp::tokio::Transport::new(tcp::Config::new())
|
||||||
|
.upgrade(Version::V1Lazy)
|
||||||
|
.authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?)
|
||||||
|
.multiplex(yamux::Config::default())
|
||||||
|
.timeout(Duration::from_secs(5))
|
||||||
|
.boxed(),
|
||||||
|
format!("/ip4/{ip}/tcp/0"),
|
||||||
|
),
|
||||||
|
(Transport::Tcp, Some(SecProtocol::Noise), Some(Muxer::Mplex)) => (
|
||||||
tcp::tokio::Transport::new(tcp::Config::new())
|
tcp::tokio::Transport::new(tcp::Config::new())
|
||||||
.upgrade(Version::V1Lazy)
|
.upgrade(Version::V1Lazy)
|
||||||
.authenticate(
|
.authenticate(
|
||||||
noise::Config::new(&local_key).context("failed to intialise noise")?,
|
noise::Config::new(&local_key).context("failed to intialise noise")?,
|
||||||
)
|
)
|
||||||
.multiplex(muxer_protocol_from_env()?)
|
.multiplex(mplex::MplexConfig::new())
|
||||||
.timeout(Duration::from_secs(5))
|
.timeout(Duration::from_secs(5))
|
||||||
.boxed(),
|
.boxed(),
|
||||||
format!("/ip4/{ip}/tcp/0"),
|
format!("/ip4/{ip}/tcp/0"),
|
||||||
),
|
),
|
||||||
(Transport::Ws, Ok(SecProtocol::Tls)) => (
|
(Transport::Tcp, Some(SecProtocol::Noise), Some(Muxer::Yamux)) => (
|
||||||
|
tcp::tokio::Transport::new(tcp::Config::new())
|
||||||
|
.upgrade(Version::V1Lazy)
|
||||||
|
.authenticate(
|
||||||
|
noise::Config::new(&local_key).context("failed to intialise noise")?,
|
||||||
|
)
|
||||||
|
.multiplex(yamux::Config::default())
|
||||||
|
.timeout(Duration::from_secs(5))
|
||||||
|
.boxed(),
|
||||||
|
format!("/ip4/{ip}/tcp/0"),
|
||||||
|
),
|
||||||
|
(Transport::Ws, Some(SecProtocol::Tls), Some(Muxer::Mplex)) => (
|
||||||
WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new()))
|
WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new()))
|
||||||
.upgrade(Version::V1Lazy)
|
.upgrade(Version::V1Lazy)
|
||||||
.authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?)
|
.authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?)
|
||||||
.multiplex(muxer_protocol_from_env()?)
|
.multiplex(mplex::MplexConfig::new())
|
||||||
.timeout(Duration::from_secs(5))
|
.timeout(Duration::from_secs(5))
|
||||||
.boxed(),
|
.boxed(),
|
||||||
format!("/ip4/{ip}/tcp/0/ws"),
|
format!("/ip4/{ip}/tcp/0/ws"),
|
||||||
),
|
),
|
||||||
(Transport::Ws, Ok(SecProtocol::Noise)) => (
|
(Transport::Ws, Some(SecProtocol::Tls), Some(Muxer::Yamux)) => (
|
||||||
|
WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new()))
|
||||||
|
.upgrade(Version::V1Lazy)
|
||||||
|
.authenticate(
|
||||||
|
tls::Config::new(&local_key).context("failed to intialise noise")?,
|
||||||
|
)
|
||||||
|
.multiplex(yamux::Config::default())
|
||||||
|
.timeout(Duration::from_secs(5))
|
||||||
|
.boxed(),
|
||||||
|
format!("/ip4/{ip}/tcp/0/ws"),
|
||||||
|
),
|
||||||
|
(Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Mplex)) => (
|
||||||
|
WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new()))
|
||||||
|
.upgrade(Version::V1Lazy)
|
||||||
|
.authenticate(
|
||||||
|
noise::Config::new(&local_key).context("failed to initialise tls")?,
|
||||||
|
)
|
||||||
|
.multiplex(mplex::MplexConfig::new())
|
||||||
|
.timeout(Duration::from_secs(5))
|
||||||
|
.boxed(),
|
||||||
|
format!("/ip4/{ip}/tcp/0/ws"),
|
||||||
|
),
|
||||||
|
(Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Yamux)) => (
|
||||||
WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new()))
|
WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new()))
|
||||||
.upgrade(Version::V1Lazy)
|
.upgrade(Version::V1Lazy)
|
||||||
.authenticate(
|
.authenticate(
|
||||||
noise::Config::new(&local_key).context("failed to intialise noise")?,
|
noise::Config::new(&local_key).context("failed to intialise noise")?,
|
||||||
)
|
)
|
||||||
.multiplex(muxer_protocol_from_env()?)
|
.multiplex(yamux::Config::default())
|
||||||
.timeout(Duration::from_secs(5))
|
.timeout(Duration::from_secs(5))
|
||||||
.boxed(),
|
.boxed(),
|
||||||
format!("/ip4/{ip}/tcp/0/ws"),
|
format!("/ip4/{ip}/tcp/0/ws"),
|
||||||
),
|
),
|
||||||
(Transport::WebRtcDirect, _) => (
|
(Transport::WebRtcDirect, _, _) => (
|
||||||
webrtc::tokio::Transport::new(
|
webrtc::tokio::Transport::new(
|
||||||
local_key,
|
local_key,
|
||||||
webrtc::tokio::Certificate::generate(&mut rand::thread_rng())?,
|
webrtc::tokio::Certificate::generate(&mut rand::thread_rng())?,
|
||||||
@ -115,9 +151,13 @@ pub(crate) mod native {
|
|||||||
.boxed(),
|
.boxed(),
|
||||||
format!("/ip4/{ip}/udp/0/webrtc-direct"),
|
format!("/ip4/{ip}/udp/0/webrtc-direct"),
|
||||||
),
|
),
|
||||||
(Transport::Tcp, Err(_)) => bail!("Missing security protocol for TCP transport"),
|
(Transport::Webtransport, _, _) => bail!("Webtransport can only be used with wasm"),
|
||||||
(Transport::Ws, Err(_)) => bail!("Missing security protocol for Websocket transport"),
|
(Transport::Tcp | Transport::Ws, None, _) => {
|
||||||
(Transport::Webtransport, _) => bail!("Webtransport can only be used with wasm"),
|
bail!("Missing security protocol for {transport:?}")
|
||||||
|
}
|
||||||
|
(Transport::Tcp | Transport::Ws, _, None) => {
|
||||||
|
bail!("Missing muxer protocol for {transport:?}")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok((transport, addr))
|
Ok((transport, addr))
|
||||||
}
|
}
|
||||||
@ -154,15 +194,17 @@ pub(crate) mod native {
|
|||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub(crate) mod wasm {
|
pub(crate) mod wasm {
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use futures::future::{BoxFuture, FutureExt};
|
use futures::future::{BoxFuture, FutureExt};
|
||||||
|
use libp2p::core::upgrade::Version;
|
||||||
use libp2p::identity::Keypair;
|
use libp2p::identity::Keypair;
|
||||||
use libp2p::swarm::{NetworkBehaviour, SwarmBuilder};
|
use libp2p::swarm::{NetworkBehaviour, SwarmBuilder};
|
||||||
use libp2p::PeerId;
|
use libp2p::{noise, yamux, PeerId, Transport as _};
|
||||||
|
use libp2p_mplex as mplex;
|
||||||
use libp2p_webrtc_websys as webrtc;
|
use libp2p_webrtc_websys as webrtc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::{BlpopRequest, Transport};
|
use crate::{BlpopRequest, Muxer, SecProtocol, Transport};
|
||||||
|
|
||||||
use super::BoxedTransport;
|
use super::BoxedTransport;
|
||||||
|
|
||||||
@ -181,21 +223,56 @@ pub(crate) mod wasm {
|
|||||||
local_key: Keypair,
|
local_key: Keypair,
|
||||||
ip: &str,
|
ip: &str,
|
||||||
transport: Transport,
|
transport: Transport,
|
||||||
|
sec_protocol: Option<SecProtocol>,
|
||||||
|
muxer: Option<Muxer>,
|
||||||
) -> Result<(BoxedTransport, String)> {
|
) -> Result<(BoxedTransport, String)> {
|
||||||
match transport {
|
Ok(match (transport, sec_protocol, muxer) {
|
||||||
Transport::Webtransport => Ok((
|
(Transport::Webtransport, _, _) => (
|
||||||
libp2p::webtransport_websys::Transport::new(
|
libp2p::webtransport_websys::Transport::new(
|
||||||
libp2p::webtransport_websys::Config::new(&local_key),
|
libp2p::webtransport_websys::Config::new(&local_key),
|
||||||
)
|
)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
format!("/ip4/{ip}/udp/0/quic/webtransport"),
|
format!("/ip4/{ip}/udp/0/quic/webtransport"),
|
||||||
)),
|
),
|
||||||
Transport::WebRtcDirect => Ok((
|
(Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Mplex)) => (
|
||||||
|
libp2p::websocket_websys::Transport::default()
|
||||||
|
.upgrade(Version::V1Lazy)
|
||||||
|
.authenticate(
|
||||||
|
noise::Config::new(&local_key).context("failed to initialise noise")?,
|
||||||
|
)
|
||||||
|
.multiplex(mplex::MplexConfig::new())
|
||||||
|
.timeout(Duration::from_secs(5))
|
||||||
|
.boxed(),
|
||||||
|
format!("/ip4/{ip}/tcp/0/wss"),
|
||||||
|
),
|
||||||
|
(Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Yamux)) => (
|
||||||
|
libp2p::websocket_websys::Transport::default()
|
||||||
|
.upgrade(Version::V1Lazy)
|
||||||
|
.authenticate(
|
||||||
|
noise::Config::new(&local_key).context("failed to initialise noise")?,
|
||||||
|
)
|
||||||
|
.multiplex(yamux::Config::default())
|
||||||
|
.timeout(Duration::from_secs(5))
|
||||||
|
.boxed(),
|
||||||
|
format!("/ip4/{ip}/tcp/0/wss"),
|
||||||
|
),
|
||||||
|
(Transport::Ws, None, _) => {
|
||||||
|
bail!("Missing security protocol for WS")
|
||||||
|
}
|
||||||
|
(Transport::Ws, Some(SecProtocol::Tls), _) => {
|
||||||
|
bail!("TLS not supported in WASM")
|
||||||
|
}
|
||||||
|
(Transport::Ws, _, None) => {
|
||||||
|
bail!("Missing muxer protocol for WS")
|
||||||
|
}
|
||||||
|
(Transport::WebRtcDirect, _, _) => (
|
||||||
webrtc::Transport::new(webrtc::Config::new(&local_key)).boxed(),
|
webrtc::Transport::new(webrtc::Config::new(&local_key)).boxed(),
|
||||||
format!("/ip4/{ip}/udp/0/webrtc-direct"),
|
format!("/ip4/{ip}/udp/0/webrtc-direct"),
|
||||||
)),
|
),
|
||||||
_ => bail!("Only webtransport and webrtc-direct are supported with wasm"),
|
(Transport::QuicV1 | Transport::Tcp, _, _) => {
|
||||||
}
|
bail!("{transport:?} is not supported in WASM")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn swarm_builder<TBehaviour: NetworkBehaviour>(
|
pub(crate) fn swarm_builder<TBehaviour: NetworkBehaviour>(
|
||||||
|
@ -5,6 +5,8 @@ use anyhow::{Context, Result};
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct Config {
|
pub(crate) struct Config {
|
||||||
pub(crate) transport: String,
|
pub(crate) transport: String,
|
||||||
|
pub(crate) sec_protocol: Option<String>,
|
||||||
|
pub(crate) muxer: Option<String>,
|
||||||
pub(crate) ip: String,
|
pub(crate) ip: String,
|
||||||
pub(crate) is_dialer: bool,
|
pub(crate) is_dialer: bool,
|
||||||
pub(crate) test_timeout: u64,
|
pub(crate) test_timeout: u64,
|
||||||
@ -26,8 +28,13 @@ impl Config {
|
|||||||
.map(|addr| format!("redis://{addr}"))
|
.map(|addr| format!("redis://{addr}"))
|
||||||
.unwrap_or_else(|_| "redis://redis:6379".into());
|
.unwrap_or_else(|_| "redis://redis:6379".into());
|
||||||
|
|
||||||
|
let sec_protocol = env::var("security").ok();
|
||||||
|
let muxer = env::var("muxer").ok();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
transport,
|
transport,
|
||||||
|
sec_protocol,
|
||||||
|
muxer,
|
||||||
ip,
|
ip,
|
||||||
is_dialer,
|
is_dialer,
|
||||||
test_timeout,
|
test_timeout,
|
||||||
|
@ -12,6 +12,8 @@ async fn main() -> Result<()> {
|
|||||||
config.is_dialer,
|
config.is_dialer,
|
||||||
config.test_timeout,
|
config.test_timeout,
|
||||||
&config.redis_addr,
|
&config.redis_addr,
|
||||||
|
config.sec_protocol,
|
||||||
|
config.muxer,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -179,8 +179,18 @@ async fn serve_index_html(state: State<TestState>) -> Result<impl IntoResponse,
|
|||||||
ip,
|
ip,
|
||||||
is_dialer,
|
is_dialer,
|
||||||
test_timeout,
|
test_timeout,
|
||||||
|
sec_protocol,
|
||||||
|
muxer,
|
||||||
..
|
..
|
||||||
} = state.0.config;
|
} = state.0.config;
|
||||||
|
|
||||||
|
let sec_protocol = sec_protocol
|
||||||
|
.map(|p| format!(r#""{p}""#))
|
||||||
|
.unwrap_or("null".to_owned());
|
||||||
|
let muxer = muxer
|
||||||
|
.map(|p| format!(r#""{p}""#))
|
||||||
|
.unwrap_or("null".to_owned());
|
||||||
|
|
||||||
Ok(Html(format!(
|
Ok(Html(format!(
|
||||||
r#"
|
r#"
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@ -200,7 +210,9 @@ async fn serve_index_html(state: State<TestState>) -> Result<impl IntoResponse,
|
|||||||
"{ip}",
|
"{ip}",
|
||||||
{is_dialer},
|
{is_dialer},
|
||||||
"{test_timeout}",
|
"{test_timeout}",
|
||||||
"{BIND_ADDR}"
|
"{BIND_ADDR}",
|
||||||
|
{sec_protocol},
|
||||||
|
{muxer}
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
@ -18,18 +18,35 @@ pub async fn run_test(
|
|||||||
is_dialer: bool,
|
is_dialer: bool,
|
||||||
test_timeout_seconds: u64,
|
test_timeout_seconds: u64,
|
||||||
redis_addr: &str,
|
redis_addr: &str,
|
||||||
|
sec_protocol: Option<String>,
|
||||||
|
muxer: Option<String>,
|
||||||
) -> Result<Report> {
|
) -> Result<Report> {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
let test_timeout = Duration::from_secs(test_timeout_seconds);
|
let test_timeout = Duration::from_secs(test_timeout_seconds);
|
||||||
let transport = transport.parse().context("Couldn't parse transport")?;
|
let transport = transport.parse().context("Couldn't parse transport")?;
|
||||||
|
let sec_protocol = sec_protocol
|
||||||
|
.map(|sec_protocol| {
|
||||||
|
sec_protocol
|
||||||
|
.parse()
|
||||||
|
.context("Couldn't parse security protocol")
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
let muxer = muxer
|
||||||
|
.map(|sec_protocol| {
|
||||||
|
sec_protocol
|
||||||
|
.parse()
|
||||||
|
.context("Couldn't parse muxer protocol")
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
let local_key = identity::Keypair::generate_ed25519();
|
let local_key = identity::Keypair::generate_ed25519();
|
||||||
let local_peer_id = PeerId::from(local_key.public());
|
let local_peer_id = PeerId::from(local_key.public());
|
||||||
let redis_client = RedisClient::new(redis_addr).context("Could not connect to redis")?;
|
let redis_client = RedisClient::new(redis_addr).context("Could not connect to redis")?;
|
||||||
|
|
||||||
// Build the transport from the passed ENV var.
|
// Build the transport from the passed ENV var.
|
||||||
let (boxed_transport, local_addr) = build_transport(local_key.clone(), ip, transport)?;
|
let (boxed_transport, local_addr) =
|
||||||
|
build_transport(local_key.clone(), ip, transport, sec_protocol, muxer)?;
|
||||||
let mut swarm = swarm_builder(
|
let mut swarm = swarm_builder(
|
||||||
boxed_transport,
|
boxed_transport,
|
||||||
Behaviour {
|
Behaviour {
|
||||||
@ -113,7 +130,8 @@ pub async fn run_test(
|
|||||||
}
|
}
|
||||||
if listener_id == id {
|
if listener_id == id {
|
||||||
let ma = format!("{address}/p2p/{local_peer_id}");
|
let ma = format!("{address}/p2p/{local_peer_id}");
|
||||||
redis_client.rpush("listenerAddr", ma).await?;
|
redis_client.rpush("listenerAddr", ma.clone()).await?;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +141,9 @@ pub async fn run_test(
|
|||||||
futures::future::select(
|
futures::future::select(
|
||||||
async move {
|
async move {
|
||||||
loop {
|
loop {
|
||||||
swarm.next().await;
|
let event = swarm.next().await.unwrap();
|
||||||
|
|
||||||
|
log::debug!("{event:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.boxed(),
|
.boxed(),
|
||||||
@ -145,8 +165,19 @@ pub async fn run_test_wasm(
|
|||||||
is_dialer: bool,
|
is_dialer: bool,
|
||||||
test_timeout_secs: u64,
|
test_timeout_secs: u64,
|
||||||
base_url: &str,
|
base_url: &str,
|
||||||
|
sec_protocol: Option<String>,
|
||||||
|
muxer: Option<String>,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
let result = run_test(transport, ip, is_dialer, test_timeout_secs, base_url).await;
|
let result = run_test(
|
||||||
|
transport,
|
||||||
|
ip,
|
||||||
|
is_dialer,
|
||||||
|
test_timeout_secs,
|
||||||
|
base_url,
|
||||||
|
sec_protocol,
|
||||||
|
muxer,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
log::info!("Sending test result: {result:?}");
|
log::info!("Sending test result: {result:?}");
|
||||||
reqwest::Client::new()
|
reqwest::Client::new()
|
||||||
.post(&format!("http://{}/results", base_url))
|
.post(&format!("http://{}/results", base_url))
|
||||||
@ -244,14 +275,3 @@ struct Behaviour {
|
|||||||
ping: ping::Behaviour,
|
ping: ping::Behaviour,
|
||||||
identify: identify::Behaviour,
|
identify: identify::Behaviour,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to get a ENV variable into an test parameter like `Transport`.
|
|
||||||
pub fn from_env<T>(env_var: &str) -> Result<T>
|
|
||||||
where
|
|
||||||
T: FromStr<Err = anyhow::Error>,
|
|
||||||
{
|
|
||||||
std::env::var(env_var)
|
|
||||||
.with_context(|| format!("{env_var} environment variable is not set"))?
|
|
||||||
.parse()
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
## 0.52.4 - unreleased
|
||||||
|
|
||||||
|
- Introduce `libp2p::websocket_websys` module behind `websocket-websys` feature flag.
|
||||||
|
This supersedes the existing `libp2p::wasm_ext` module which is now deprecated.
|
||||||
|
See [PR 3679].
|
||||||
|
|
||||||
|
[PR 3679]: https://github.com/libp2p/rust-libp2p/pull/3679
|
||||||
|
|
||||||
## 0.52.3
|
## 0.52.3
|
||||||
|
|
||||||
- Add `libp2p-quic` stable release.
|
- Add `libp2p-quic` stable release.
|
||||||
@ -64,8 +72,8 @@
|
|||||||
## 0.51.3
|
## 0.51.3
|
||||||
|
|
||||||
- Deprecate the `mplex` feature.
|
- Deprecate the `mplex` feature.
|
||||||
The recommended baseline stream multiplexer is `yamux`.
|
The recommended baseline stream multiplexer is `yamux`.
|
||||||
See [PR 3689].
|
See [PR 3689].
|
||||||
|
|
||||||
[PR 3689]: https://github.com/libp2p/rust-libp2p/pull/3689
|
[PR 3689]: https://github.com/libp2p/rust-libp2p/pull/3689
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ name = "libp2p"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = { workspace = true }
|
rust-version = { workspace = true }
|
||||||
description = "Peer-to-peer networking library"
|
description = "Peer-to-peer networking library"
|
||||||
version = "0.52.3"
|
version = "0.52.4"
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/libp2p/rust-libp2p"
|
repository = "https://github.com/libp2p/rust-libp2p"
|
||||||
@ -47,6 +47,7 @@ full = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-ext",
|
"wasm-ext",
|
||||||
"wasm-ext-websocket",
|
"wasm-ext-websocket",
|
||||||
|
"websocket-websys",
|
||||||
"websocket",
|
"websocket",
|
||||||
"webtransport-websys",
|
"webtransport-websys",
|
||||||
"yamux",
|
"yamux",
|
||||||
@ -88,6 +89,7 @@ uds = ["dep:libp2p-uds"]
|
|||||||
wasm-bindgen = [ "futures-timer/wasm-bindgen", "instant/wasm-bindgen", "getrandom/js", "libp2p-swarm/wasm-bindgen", "libp2p-gossipsub?/wasm-bindgen",]
|
wasm-bindgen = [ "futures-timer/wasm-bindgen", "instant/wasm-bindgen", "getrandom/js", "libp2p-swarm/wasm-bindgen", "libp2p-gossipsub?/wasm-bindgen",]
|
||||||
wasm-ext = ["dep:libp2p-wasm-ext"]
|
wasm-ext = ["dep:libp2p-wasm-ext"]
|
||||||
wasm-ext-websocket = ["wasm-ext", "libp2p-wasm-ext?/websocket"]
|
wasm-ext-websocket = ["wasm-ext", "libp2p-wasm-ext?/websocket"]
|
||||||
|
websocket-websys = ["dep:libp2p-websocket-websys"]
|
||||||
websocket = ["dep:libp2p-websocket"]
|
websocket = ["dep:libp2p-websocket"]
|
||||||
webtransport-websys = ["dep:libp2p-webtransport-websys"]
|
webtransport-websys = ["dep:libp2p-webtransport-websys"]
|
||||||
yamux = ["dep:libp2p-yamux"]
|
yamux = ["dep:libp2p-yamux"]
|
||||||
@ -120,9 +122,9 @@ libp2p-rendezvous = { workspace = true, optional = true }
|
|||||||
libp2p-request-response = { workspace = true, optional = true }
|
libp2p-request-response = { workspace = true, optional = true }
|
||||||
libp2p-swarm = { workspace = true }
|
libp2p-swarm = { workspace = true }
|
||||||
libp2p-wasm-ext = { workspace = true, optional = true }
|
libp2p-wasm-ext = { workspace = true, optional = true }
|
||||||
|
libp2p-websocket-websys = { workspace = true, optional = true }
|
||||||
libp2p-webtransport-websys = { workspace = true, optional = true }
|
libp2p-webtransport-websys = { workspace = true, optional = true }
|
||||||
libp2p-yamux = { workspace = true, optional = true }
|
libp2p-yamux = { workspace = true, optional = true }
|
||||||
|
|
||||||
multiaddr = { workspace = true }
|
multiaddr = { workspace = true }
|
||||||
pin-project = "1.0.0"
|
pin-project = "1.0.0"
|
||||||
|
|
||||||
|
@ -135,12 +135,20 @@ pub use libp2p_uds as uds;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use libp2p_upnp as upnp;
|
pub use libp2p_upnp as upnp;
|
||||||
#[cfg(feature = "wasm-ext")]
|
#[cfg(feature = "wasm-ext")]
|
||||||
#[doc(inline)]
|
#[deprecated(
|
||||||
pub use libp2p_wasm_ext as wasm_ext;
|
note = "`wasm-ext` is deprecated and will be removed in favor of `libp2p-websocket-websys`."
|
||||||
|
)]
|
||||||
|
pub mod wasm_ext {
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use libp2p_wasm_ext::*;
|
||||||
|
}
|
||||||
#[cfg(feature = "websocket")]
|
#[cfg(feature = "websocket")]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use libp2p_websocket as websocket;
|
pub use libp2p_websocket as websocket;
|
||||||
|
#[cfg(feature = "websocket-websys")]
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use libp2p_websocket_websys as websocket_websys;
|
||||||
#[cfg(feature = "webtransport-websys")]
|
#[cfg(feature = "webtransport-websys")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "webtransport-websys")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "webtransport-websys")))]
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
7
transports/websocket-websys/CHANGELOG.md
Normal file
7
transports/websocket-websys/CHANGELOG.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# 0.2.0 - unreleased
|
||||||
|
|
||||||
|
- Add Websys Websocket transport.
|
||||||
|
|
||||||
|
# 0.1.0
|
||||||
|
|
||||||
|
- Crate claimed.
|
35
transports/websocket-websys/Cargo.toml
Normal file
35
transports/websocket-websys/Cargo.toml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
[package]
|
||||||
|
name = "libp2p-websocket-websys"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.60.0"
|
||||||
|
description = "WebSocket for libp2p under WASM environment"
|
||||||
|
version = "0.2.0"
|
||||||
|
authors = ["Vince Vasta <vince.vasta@gmail.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/libp2p/rust-libp2p"
|
||||||
|
keywords = ["peer-to-peer", "libp2p", "networking"]
|
||||||
|
categories = ["network-programming", "asynchronous"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytes = "1.4.0"
|
||||||
|
futures = "0.3.26"
|
||||||
|
js-sys = "0.3.61"
|
||||||
|
libp2p-core = { workspace = true }
|
||||||
|
log = "0.4.19"
|
||||||
|
parking_lot = "0.12.1"
|
||||||
|
send_wrapper = "0.6.0"
|
||||||
|
thiserror = "1.0.38"
|
||||||
|
wasm-bindgen = "0.2.84"
|
||||||
|
web-sys = { version = "0.3.61", features = ["BinaryType", "CloseEvent", "MessageEvent", "WebSocket", "Window"] }
|
||||||
|
|
||||||
|
# Passing arguments to the docsrs builder in order to properly document cfg's.
|
||||||
|
# More information: https://docs.rs/about/builds#cross-compiling
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
rustc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
libp2p-yamux = { workspace = true }
|
||||||
|
libp2p-noise = { workspace = true }
|
||||||
|
libp2p-identity = { workspace = true }
|
446
transports/websocket-websys/src/lib.rs
Normal file
446
transports/websocket-websys/src/lib.rs
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
// Copyright (C) 2023 Vince Vasta
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Libp2p websocket transports built on [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html).
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use futures::task::AtomicWaker;
|
||||||
|
use futures::{future::Ready, io, prelude::*};
|
||||||
|
use js_sys::Array;
|
||||||
|
use libp2p_core::{
|
||||||
|
multiaddr::{Multiaddr, Protocol},
|
||||||
|
transport::{ListenerId, TransportError, TransportEvent},
|
||||||
|
};
|
||||||
|
use send_wrapper::SendWrapper;
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::{pin::Pin, task::Context, task::Poll};
|
||||||
|
use wasm_bindgen::{prelude::*, JsCast};
|
||||||
|
use web_sys::{window, CloseEvent, Event, MessageEvent, WebSocket};
|
||||||
|
|
||||||
|
/// A Websocket transport that can be used in a wasm environment.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// To create an authenticated transport instance with Noise protocol and Yamux:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use libp2p_core::{upgrade::Version, Transport};
|
||||||
|
/// # use libp2p_identity::Keypair;
|
||||||
|
/// # use libp2p_yamux as yamux;
|
||||||
|
/// # use libp2p_noise as noise;
|
||||||
|
/// let local_key = Keypair::generate_ed25519();
|
||||||
|
/// let transport = libp2p_websocket_websys::Transport::default()
|
||||||
|
/// .upgrade(Version::V1)
|
||||||
|
/// .authenticate(noise::Config::new(&local_key).unwrap())
|
||||||
|
/// .multiplex(yamux::Config::default())
|
||||||
|
/// .boxed();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Transport {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arbitrary, maximum amount we are willing to buffer before we throttle our user.
|
||||||
|
const MAX_BUFFER: usize = 1024 * 1024;
|
||||||
|
|
||||||
|
impl libp2p_core::Transport for Transport {
|
||||||
|
type Output = Connection;
|
||||||
|
type Error = Error;
|
||||||
|
type ListenerUpgrade = Ready<Result<Self::Output, Self::Error>>;
|
||||||
|
type Dial = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
|
||||||
|
|
||||||
|
fn listen_on(
|
||||||
|
&mut self,
|
||||||
|
_: ListenerId,
|
||||||
|
addr: Multiaddr,
|
||||||
|
) -> Result<(), TransportError<Self::Error>> {
|
||||||
|
Err(TransportError::MultiaddrNotSupported(addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_listener(&mut self, _id: ListenerId) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dial(&mut self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
|
||||||
|
let url = extract_websocket_url(&addr)
|
||||||
|
.ok_or_else(|| TransportError::MultiaddrNotSupported(addr))?;
|
||||||
|
|
||||||
|
Ok(async move {
|
||||||
|
let socket = match WebSocket::new(&url) {
|
||||||
|
Ok(ws) => ws,
|
||||||
|
Err(_) => return Err(Error::invalid_websocket_url(&url)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Connection::new(socket))
|
||||||
|
}
|
||||||
|
.boxed())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dial_as_listener(
|
||||||
|
&mut self,
|
||||||
|
addr: Multiaddr,
|
||||||
|
) -> Result<Self::Dial, TransportError<Self::Error>> {
|
||||||
|
Err(TransportError::MultiaddrNotSupported(addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> std::task::Poll<TransportEvent<Self::ListenerUpgrade, Self::Error>> {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
|
||||||
|
fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option<Multiaddr> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to convert Multiaddr to a Websocket url.
|
||||||
|
fn extract_websocket_url(addr: &Multiaddr) -> Option<String> {
|
||||||
|
let mut protocols = addr.iter();
|
||||||
|
let host_port = match (protocols.next(), protocols.next()) {
|
||||||
|
(Some(Protocol::Ip4(ip)), Some(Protocol::Tcp(port))) => {
|
||||||
|
format!("{ip}:{port}")
|
||||||
|
}
|
||||||
|
(Some(Protocol::Ip6(ip)), Some(Protocol::Tcp(port))) => {
|
||||||
|
format!("[{ip}]:{port}")
|
||||||
|
}
|
||||||
|
(Some(Protocol::Dns(h)), Some(Protocol::Tcp(port)))
|
||||||
|
| (Some(Protocol::Dns4(h)), Some(Protocol::Tcp(port)))
|
||||||
|
| (Some(Protocol::Dns6(h)), Some(Protocol::Tcp(port)))
|
||||||
|
| (Some(Protocol::Dnsaddr(h)), Some(Protocol::Tcp(port))) => {
|
||||||
|
format!("{}:{}", &h, port)
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (scheme, wspath) = match protocols.next() {
|
||||||
|
Some(Protocol::Ws(path)) => ("ws", path.into_owned()),
|
||||||
|
Some(Protocol::Wss(path)) => ("wss", path.into_owned()),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(format!("{scheme}://{host_port}{wspath}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
#[error("{msg}")]
|
||||||
|
pub struct Error {
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
fn invalid_websocket_url(url: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
msg: format!("Invalid websocket url: {url}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Websocket connection created by the [`Transport`].
|
||||||
|
pub struct Connection {
|
||||||
|
inner: SendWrapper<Inner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Inner {
|
||||||
|
socket: WebSocket,
|
||||||
|
|
||||||
|
new_data_waker: Rc<AtomicWaker>,
|
||||||
|
read_buffer: Rc<Mutex<BytesMut>>,
|
||||||
|
|
||||||
|
/// Waker for when we are waiting for the WebSocket to be opened.
|
||||||
|
open_waker: Rc<AtomicWaker>,
|
||||||
|
|
||||||
|
/// Waker for when we are waiting to write (again) to the WebSocket because we previously exceeded the [`MAX_BUFFER`] threshold.
|
||||||
|
write_waker: Rc<AtomicWaker>,
|
||||||
|
|
||||||
|
/// Waker for when we are waiting for the WebSocket to be closed.
|
||||||
|
close_waker: Rc<AtomicWaker>,
|
||||||
|
|
||||||
|
/// Whether the connection errored.
|
||||||
|
errored: Rc<AtomicBool>,
|
||||||
|
|
||||||
|
// Store the closures for proper garbage collection.
|
||||||
|
// These are wrapped in an [`Rc`] so we can implement [`Clone`].
|
||||||
|
_on_open_closure: Rc<Closure<dyn FnMut(Event)>>,
|
||||||
|
_on_buffered_amount_low_closure: Rc<Closure<dyn FnMut(Event)>>,
|
||||||
|
_on_close_closure: Rc<Closure<dyn FnMut(CloseEvent)>>,
|
||||||
|
_on_error_closure: Rc<Closure<dyn FnMut(CloseEvent)>>,
|
||||||
|
_on_message_closure: Rc<Closure<dyn FnMut(MessageEvent)>>,
|
||||||
|
buffered_amount_low_interval: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inner {
|
||||||
|
fn ready_state(&self) -> ReadyState {
|
||||||
|
match self.socket.ready_state() {
|
||||||
|
0 => ReadyState::Connecting,
|
||||||
|
1 => ReadyState::Open,
|
||||||
|
2 => ReadyState::Closing,
|
||||||
|
3 => ReadyState::Closed,
|
||||||
|
unknown => unreachable!("invalid `ReadyState` value: {unknown}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_open(&mut self, cx: &Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
match self.ready_state() {
|
||||||
|
ReadyState::Connecting => {
|
||||||
|
self.open_waker.register(cx.waker());
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
ReadyState::Open => Poll::Ready(Ok(())),
|
||||||
|
ReadyState::Closed | ReadyState::Closing => {
|
||||||
|
Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_barrier(&self) -> io::Result<()> {
|
||||||
|
if self.errored.load(Ordering::SeqCst) {
|
||||||
|
return Err(io::ErrorKind::BrokenPipe.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The state of the WebSocket.
|
||||||
|
///
|
||||||
|
/// See <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState>.
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum ReadyState {
|
||||||
|
Connecting,
|
||||||
|
Open,
|
||||||
|
Closing,
|
||||||
|
Closed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
fn new(socket: WebSocket) -> Self {
|
||||||
|
socket.set_binary_type(web_sys::BinaryType::Arraybuffer);
|
||||||
|
|
||||||
|
let open_waker = Rc::new(AtomicWaker::new());
|
||||||
|
let onopen_closure = Closure::<dyn FnMut(_)>::new({
|
||||||
|
let open_waker = open_waker.clone();
|
||||||
|
move |_| {
|
||||||
|
open_waker.wake();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.set_onopen(Some(onopen_closure.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
let close_waker = Rc::new(AtomicWaker::new());
|
||||||
|
let onclose_closure = Closure::<dyn FnMut(_)>::new({
|
||||||
|
let close_waker = close_waker.clone();
|
||||||
|
move |_| {
|
||||||
|
close_waker.wake();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.set_onclose(Some(onclose_closure.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
let errored = Rc::new(AtomicBool::new(false));
|
||||||
|
let onerror_closure = Closure::<dyn FnMut(_)>::new({
|
||||||
|
let errored = errored.clone();
|
||||||
|
move |_| {
|
||||||
|
errored.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.set_onerror(Some(onerror_closure.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
let read_buffer = Rc::new(Mutex::new(BytesMut::new()));
|
||||||
|
let new_data_waker = Rc::new(AtomicWaker::new());
|
||||||
|
let onmessage_closure = Closure::<dyn FnMut(_)>::new({
|
||||||
|
let read_buffer = read_buffer.clone();
|
||||||
|
let new_data_waker = new_data_waker.clone();
|
||||||
|
let errored = errored.clone();
|
||||||
|
move |e: MessageEvent| {
|
||||||
|
let data = js_sys::Uint8Array::new(&e.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, closing connection");
|
||||||
|
errored.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_buffer.extend_from_slice(&data.to_vec());
|
||||||
|
new_data_waker.wake();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.set_onmessage(Some(onmessage_closure.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
let write_waker = Rc::new(AtomicWaker::new());
|
||||||
|
let on_buffered_amount_low_closure = Closure::<dyn FnMut(_)>::new({
|
||||||
|
let write_waker = write_waker.clone();
|
||||||
|
let socket = socket.clone();
|
||||||
|
move |_| {
|
||||||
|
if socket.buffered_amount() == 0 {
|
||||||
|
write_waker.wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let buffered_amount_low_interval = window()
|
||||||
|
.expect("to have a window")
|
||||||
|
.set_interval_with_callback_and_timeout_and_arguments(
|
||||||
|
on_buffered_amount_low_closure.as_ref().unchecked_ref(),
|
||||||
|
100, // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws transport, no further effort was invested at the time.
|
||||||
|
&Array::new(),
|
||||||
|
)
|
||||||
|
.expect("to be able to set an interval");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner: SendWrapper::new(Inner {
|
||||||
|
socket,
|
||||||
|
new_data_waker,
|
||||||
|
read_buffer,
|
||||||
|
open_waker,
|
||||||
|
write_waker,
|
||||||
|
close_waker,
|
||||||
|
errored,
|
||||||
|
_on_open_closure: Rc::new(onopen_closure),
|
||||||
|
_on_buffered_amount_low_closure: Rc::new(on_buffered_amount_low_closure),
|
||||||
|
_on_close_closure: Rc::new(onclose_closure),
|
||||||
|
_on_error_closure: Rc::new(onerror_closure),
|
||||||
|
_on_message_closure: Rc::new(onmessage_closure),
|
||||||
|
buffered_amount_low_interval,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffered_amount(&self) -> usize {
|
||||||
|
self.inner.socket.buffered_amount() as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncRead for Connection {
|
||||||
|
fn poll_read(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Poll<Result<usize, io::Error>> {
|
||||||
|
let this = self.get_mut();
|
||||||
|
this.inner.error_barrier()?;
|
||||||
|
futures::ready!(this.inner.poll_open(cx))?;
|
||||||
|
|
||||||
|
let mut read_buffer = this.inner.read_buffer.lock().unwrap();
|
||||||
|
|
||||||
|
if read_buffer.is_empty() {
|
||||||
|
this.inner.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_to(split_index);
|
||||||
|
let len = bytes_to_return.len();
|
||||||
|
buf[..len].copy_from_slice(&bytes_to_return);
|
||||||
|
|
||||||
|
Poll::Ready(Ok(len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncWrite for Connection {
|
||||||
|
fn poll_write(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
let this = self.get_mut();
|
||||||
|
|
||||||
|
this.inner.error_barrier()?;
|
||||||
|
futures::ready!(this.inner.poll_open(cx))?;
|
||||||
|
|
||||||
|
debug_assert!(this.buffered_amount() <= MAX_BUFFER);
|
||||||
|
let remaining_space = MAX_BUFFER - this.buffered_amount();
|
||||||
|
|
||||||
|
if remaining_space == 0 {
|
||||||
|
this.inner.write_waker.register(cx.waker());
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes_to_send = min(buf.len(), remaining_space);
|
||||||
|
|
||||||
|
if this
|
||||||
|
.inner
|
||||||
|
.socket
|
||||||
|
.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<io::Result<()>> {
|
||||||
|
if self.buffered_amount() == 0 {
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.error_barrier()?;
|
||||||
|
|
||||||
|
self.inner.write_waker.register(cx.waker());
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
const REGULAR_CLOSE: u16 = 1000; // See https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1.
|
||||||
|
|
||||||
|
if self.inner.ready_state() == ReadyState::Closed {
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.error_barrier()?;
|
||||||
|
|
||||||
|
if self.inner.ready_state() != ReadyState::Closing {
|
||||||
|
let _ = self
|
||||||
|
.inner
|
||||||
|
.socket
|
||||||
|
.close_with_code_and_reason(REGULAR_CLOSE, "user initiated");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.close_waker.register(cx.waker());
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Connection {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
const GO_AWAY_STATUS_CODE: u16 = 1001; // See https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1.
|
||||||
|
|
||||||
|
if let ReadyState::Connecting | ReadyState::Open = self.inner.ready_state() {
|
||||||
|
let _ = self
|
||||||
|
.inner
|
||||||
|
.socket
|
||||||
|
.close_with_code_and_reason(GO_AWAY_STATUS_CODE, "connection dropped");
|
||||||
|
}
|
||||||
|
|
||||||
|
window()
|
||||||
|
.expect("to have a window")
|
||||||
|
.clear_interval_with_handle(self.inner.buffered_amount_low_interval)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user