diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd3737a49f4..ad180ef5f2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,12 +76,41 @@ jobs: run: | PACKAGE_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -e -r '.packages[] | select(.name == "'"$CRATE"'") | .version') SPECIFIED_VERSION=$(tomlq "workspace.dependencies.$CRATE.version" --file ./Cargo.toml) - + echo "Package version: $PACKAGE_VERSION"; echo "Specified version: $SPECIFIED_VERSION"; test "$PACKAGE_VERSION" = "$SPECIFIED_VERSION" + wasm_tests: + name: Run all WASM tests + runs-on: ubuntu-latest + env: + CHROMEDRIVER_VERSION: '114.0.5735.90' + steps: + - uses: actions/checkout@v3 + + - uses: dtolnay/rust-toolchain@stable + with: + target: wasm32-unknown-unknown + + - uses: taiki-e/cache-cargo-install-action@v1 + with: + tool: wasm-pack@0.12.0 + + - name: Install Google Chrome + run: | + curl -o /tmp/google-chrome-stable_amd64.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROMEDRIVER_VERSION}-1_amd64.deb + sudo dpkg -i /tmp/google-chrome-stable_amd64.deb + + - name: Install chromedriver + uses: nanasess/setup-chromedriver@v2 + with: + chromedriver-version: ${{ env.CHROMEDRIVER_VERSION }} + + - name: Run all tests + run: ./wasm-tests/run-all.sh + cross: name: Compile on ${{ matrix.target }} strategy: @@ -284,6 +313,8 @@ jobs: steps: - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - id: cargo-metadata run: | WORKSPACE_MEMBERS=$(cargo metadata --format-version=1 --no-deps | jq -c '.packages | map(select(.publish == null) | .name)') diff --git a/.github/workflows/interop-test.yml b/.github/workflows/interop-test.yml index e6527709aa3..5e9889c4067 100644 --- a/.github/workflows/interop-test.yml +++ b/.github/workflows/interop-test.yml @@ -13,15 +13,19 @@ jobs: run-multidim-interop: name: Run multidimensional interoperability tests runs-on: ${{ fromJSON(github.repository == 'libp2p/rust-libp2p' && '["self-hosted", "linux", "x64", "xlarge"]' || '"ubuntu-latest"') }} + strategy: + matrix: + flavour: [chromium, native] steps: - uses: actions/checkout@v3 - uses: docker/setup-buildx-action@v2 - - name: Build image - run: docker buildx build --load -t rust-libp2p-head . -f interop-tests/Dockerfile - - uses: libp2p/test-plans/.github/actions/run-interop-ping-test@master + - name: Build ${{ matrix.flavour }} image + run: docker buildx build --load -t ${{ matrix.flavour }}-rust-libp2p-head . -f interop-tests/Dockerfile.${{ matrix.flavour }} + - name: Run ${{ matrix.flavour }} tests + uses: libp2p/test-plans/.github/actions/run-interop-ping-test@master with: - test-filter: rust-libp2p-head - extra-versions: ${{ github.workspace }}/interop-tests/ping-version.json + test-filter: ${{ matrix.flavour }}-rust-libp2p-head + extra-versions: ${{ github.workspace }}/interop-tests/${{ matrix.flavour }}-ping-version.json s3-cache-bucket: libp2p-by-tf-aws-bootstrap s3-access-key-id: ${{ vars.TEST_PLANS_BUILD_CACHE_KEY_ID }} s3-secret-access-key: ${{ secrets.TEST_PLANS_BUILD_CACHE_KEY }} diff --git a/Cargo.lock b/Cargo.lock index 994c4da29cf..b3547bb78af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,7 +181,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -191,7 +191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -415,7 +415,7 @@ dependencies = [ "futures-lite", "rustix", "signal-hook", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -514,6 +514,56 @@ dependencies = [ "libp2p", ] +[[package]] +name = "axum" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite 0.2.9", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http 0.3.5", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "base-x" version = "0.2.11" @@ -866,12 +916,33 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -1417,6 +1488,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "enum-as-inner" version = "0.5.1" @@ -1460,7 +1540,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1479,6 +1559,28 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "fantoccini" +version = "0.20.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5eb32b0001134a1d3b9e16010eb4b119451edf68446963a30a8130a0d056e98" +dependencies = [ + "base64 0.13.1", + "cookie", + "futures-core", + "futures-util", + "http", + "hyper", + "hyper-rustls", + "mime", + "serde", + "serde_json", + "time", + "tokio", + "url", + "webdriver", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1544,6 +1646,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -1958,6 +2075,12 @@ dependencies = [ "pin-project-lite 0.2.9", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -1986,6 +2109,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "httparse", @@ -1999,6 +2123,34 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +dependencies = [ + "http", + "hyper", + "log", + "rustls 0.20.8", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2121,17 +2273,33 @@ name = "interop-tests" version = "0.1.0" dependencies = [ "anyhow", + "axum", + "console_error_panic_hook", "either", "env_logger 0.10.0", "futures", + "futures-timer", + "instant", "libp2p", "libp2p-mplex", "libp2p-quic", "libp2p-webrtc", "log", + "mime_guess", "rand 0.8.5", "redis", + "reqwest", + "rust-embed", + "serde", + "serde_json", + "thirtyfour", "tokio", + "tower-http 0.4.0", + "tracing", + "tracing-subscriber", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-logger", ] [[package]] @@ -2142,7 +2310,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2153,8 +2321,8 @@ checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2 0.5.3", "widestring", - "windows-sys", - "winreg", + "windows-sys 0.48.0", + "winreg 0.50.0", ] [[package]] @@ -2195,7 +2363,7 @@ dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2312,6 +2480,7 @@ dependencies = [ "libp2p-uds", "libp2p-wasm-ext", "libp2p-websocket", + "libp2p-webtransport-websys", "libp2p-yamux", "multiaddr", "pin-project", @@ -3089,7 +3258,27 @@ dependencies = [ "rw-stream-sink", "soketto", "url", - "webpki-roots 0.23.1", + "webpki-roots 0.23.0", +] + +[[package]] +name = "libp2p-webtransport-websys" +version = "0.1.0" +dependencies = [ + "futures", + "js-sys", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "log", + "multiaddr", + "multibase", + "multihash", + "send_wrapper 0.6.0", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] @@ -3208,12 +3397,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "md-5" version = "0.10.5" @@ -3260,6 +3464,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3283,7 +3503,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3348,6 +3568,24 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "netlink-packet-core" version = "0.4.2" @@ -3443,6 +3681,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -3519,6 +3767,56 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "p256" version = "0.11.1" @@ -3699,6 +3997,12 @@ dependencies = [ "spki 0.7.2", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "platforms" version = "3.0.2" @@ -3746,7 +4050,7 @@ dependencies = [ "libc", "log", "pin-project-lite 0.2.9", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3799,6 +4103,30 @@ dependencies = [ "elliptic-curve 0.13.5", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-warning" version = "0.4.1" @@ -4078,9 +4406,24 @@ checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.2" @@ -4113,6 +4456,43 @@ dependencies = [ "tokio", ] +[[package]] +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "base64 0.21.2", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite 0.2.9", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.10.1", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -4231,10 +4611,44 @@ dependencies = [ ] [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "rust-embed" +version = "6.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "b73e721f488c353141288f223b599b4ae9303ecf3e62923f40a492f0634a4dc3" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22ce362f5561923889196595504317a4372b84210e6e335da529a65ea5452b5" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.18", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +dependencies = [ + "sha2 0.10.7", + "walkdir", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" @@ -4265,7 +4679,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4305,6 +4719,18 @@ dependencies = [ "sct 0.7.0", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.2" @@ -4324,6 +4750,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + [[package]] name = "rw-stream-sink" version = "0.4.0" @@ -4358,6 +4790,21 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -4424,6 +4871,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.17" @@ -4441,6 +4911,9 @@ name = "send_wrapper" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] [[package]] name = "serde" @@ -4478,6 +4951,39 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", "itoa", "ryu", "serde", @@ -4541,6 +5047,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook" version = "0.3.15" @@ -4655,7 +5170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4705,6 +5220,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stringmatch" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aadc0801d92f0cdc26127c67c4b8766284f52a5ba22894f285e3101fa57d05d" +dependencies = [ + "regex", +] + [[package]] name = "strsim" version = "0.10.0" @@ -4767,6 +5291,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -4811,7 +5341,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4823,6 +5353,44 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thirtyfour" +version = "0.32.0-rc.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0fe180d5f1f7dd32bb5f1a8d19231bb63dc9bbb1985e1dbb6f07163b6a8578" +dependencies = [ + "async-trait", + "base64 0.21.2", + "cookie", + "fantoccini", + "futures", + "http", + "indexmap", + "log", + "parking_lot", + "paste", + "serde", + "serde_json", + "serde_repr", + "stringmatch", + "thirtyfour-macros", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "thirtyfour-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cae91d1c7c61ec65817f1064954640ee350a50ae6548ff9a1bdd2489d6ffbb0" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "thiserror" version = "1.0.40" @@ -4843,6 +5411,16 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", +] + [[package]] name = "time" version = "0.3.21" @@ -4911,7 +5489,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.4.9", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4925,6 +5503,16 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -4951,6 +5539,72 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite 0.2.9", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite 0.2.9", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite 0.2.9", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -4964,6 +5618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", + "log", "pin-project-lite 0.2.9", "tracing-attributes", "tracing-core", @@ -4987,6 +5642,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -5104,6 +5789,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -5125,6 +5819,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -5193,12 +5893,24 @@ dependencies = [ "getrandom 0.2.10", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "value-bag" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -5324,16 +6036,70 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "wasm-logger" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webdriver" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9973cb72c8587d5ad5efdb91e663d36177dc37725e6c90ca86c626b0cc45c93f" +dependencies = [ + "base64 0.13.1", + "bytes", + "cookie", + "http", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "unicode-segmentation", + "url", +] + [[package]] name = "webpki" version = "0.21.4" @@ -5365,9 +6131,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.23.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" +checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125" dependencies = [ "rustls-webpki", ] @@ -5584,6 +6350,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "webtransport-tests" +version = "0.1.0" +dependencies = [ + "futures", + "getrandom 0.2.10", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "libp2p-webtransport-websys", + "multiaddr", + "multihash", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + [[package]] name = "widestring" version = "1.0.2" @@ -5634,6 +6418,21 @@ 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" @@ -5649,15 +6448,21 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.48.0", "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" @@ -5670,6 +6475,12 @@ 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" @@ -5682,6 +6493,12 @@ 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" @@ -5694,6 +6511,12 @@ 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" @@ -5706,12 +6529,24 @@ 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" @@ -5724,12 +6559,27 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "winreg" version = "0.50.0" @@ -5737,7 +6587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if 1.0.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3d247cfa046..4915c1fb707 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] members = [ "core", - "examples/chat-example", "examples/autonat", + "examples/chat-example", "examples/dcutr", "examples/distributed-key-value-store", "examples/file-sharing", @@ -53,6 +53,8 @@ members = [ "transports/wasm-ext", "transports/webrtc", "transports/websocket", + "transports/webtransport-websys", + "wasm-tests/webtransport-tests", ] resolver = "2" @@ -94,6 +96,7 @@ libp2p-uds = { version = "0.39.0", path = "transports/uds" } libp2p-wasm-ext = { version = "0.40.0", path = "transports/wasm-ext" } libp2p-webrtc = { version = "0.5.0-alpha", path = "transports/webrtc" } libp2p-websocket = { version = "0.42.0", path = "transports/websocket" } +libp2p-webtransport-websys = { version = "0.1.0", path = "transports/webtransport-websys" } libp2p-yamux = { version = "0.44.0", path = "muxers/yamux" } multistream-select = { version = "0.13.0", path = "misc/multistream-select" } quick-protobuf-codec = { version = "0.2.0", path = "misc/quick-protobuf-codec" } diff --git a/interop-tests/Cargo.toml b/interop-tests/Cargo.toml index 6d65861c3c2..87bef038074 100644 --- a/interop-tests/Cargo.toml +++ b/interop-tests/Cargo.toml @@ -5,16 +5,40 @@ version = "0.1.0" publish = false license = "MIT" +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] anyhow = "1" either = "1.8.0" env_logger = "0.10.0" futures = "0.3.28" -libp2p = { path = "../libp2p", features = ["websocket", "yamux", "tcp", "tokio", "ping", "noise", "tls", "dns", "rsa", "macros"] } +log = "0.4" +serde = { version = "1", features = ["derive"] } +rand = "0.8.5" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +axum = "0.6" +libp2p = { path = "../libp2p", features = ["ping", "noise", "tls", "rsa", "macros", "websocket", "tokio", "yamux", "tcp", "dns"] } libp2p-quic = { workspace = true, features = ["tokio"] } libp2p-webrtc = { workspace = true, features = ["tokio"] } libp2p-mplex = { path = "../muxers/mplex" } -log = "0.4" -rand = "0.8.5" +mime_guess = "2.0" redis = { version = "0.23.0", default-features = false, features = ["tokio-comp"] } +rust-embed = "6.7" +serde_json = "1" +thirtyfour = "=0.32.0-rc.8" # https://github.com/stevepryde/thirtyfour/issues/169 tokio = { version = "1.28.2", features = ["full"] } +tower-http = { version = "0.4", features = ["cors", "fs", "trace"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +libp2p = { path = "../libp2p", features = ["ping", "macros", "webtransport-websys", "wasm-bindgen"] } +wasm-bindgen = { version = "0.2" } +wasm-bindgen-futures = { version = "0.4" } +wasm-logger = { version = "0.2.0" } +instant = "0.1.12" +reqwest = { version = "0.11", features = ["json"] } +console_error_panic_hook = { version = "0.1.7" } +futures-timer = "3.0.2" diff --git a/interop-tests/Dockerfile.chromium b/interop-tests/Dockerfile.chromium new file mode 100644 index 00000000000..37d730af91a --- /dev/null +++ b/interop-tests/Dockerfile.chromium @@ -0,0 +1,28 @@ +FROM rust:1.67.0 as builder + +# Run with access to the target cache to speed up builds +WORKDIR /workspace +ADD . . + +RUN rustup target add wasm32-unknown-unknown + +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + cargo install wasm-pack@0.11.1 --locked + +RUN --mount=type=cache,target=./target \ + --mount=type=cache,target=/usr/local/cargo/registry \ + wasm-pack build --target web interop-tests + +RUN --mount=type=cache,target=./target \ + --mount=type=cache,target=/usr/local/cargo/registry \ + cargo build --release --package interop-tests --bin wasm_ping + +RUN --mount=type=cache,target=./target \ + mv ./target/release/wasm_ping /usr/local/bin/testplan + +FROM selenium/standalone-chrome:112.0 +COPY --from=builder /usr/local/bin/testplan /usr/local/bin/testplan + +ENV RUST_BACKTRACE=1 + +ENTRYPOINT ["testplan"] diff --git a/interop-tests/Dockerfile b/interop-tests/Dockerfile.native similarity index 77% rename from interop-tests/Dockerfile rename to interop-tests/Dockerfile.native index 0d80a872f5c..f78b85e424c 100644 --- a/interop-tests/Dockerfile +++ b/interop-tests/Dockerfile.native @@ -6,10 +6,10 @@ WORKDIR /workspace ADD . . RUN --mount=type=cache,target=./target \ --mount=type=cache,target=/usr/local/cargo/registry \ - cargo build --release --package interop-tests + cargo build --release --package interop-tests --bin native_ping RUN --mount=type=cache,target=./target \ - mv ./target/release/ping /usr/local/bin/testplan + mv ./target/release/native_ping /usr/local/bin/testplan FROM gcr.io/distroless/cc COPY --from=builder /usr/local/bin/testplan /usr/local/bin/testplan diff --git a/interop-tests/README.md b/interop-tests/README.md index 6a933edf6c0..88cd7518833 100644 --- a/interop-tests/README.md +++ b/interop-tests/README.md @@ -10,15 +10,25 @@ can dial/listen for ourselves we can do the following: 1. Start redis (needed by the tests): `docker run --rm -it -p 6379:6379 redis/redis-stack`. -2. In one terminal run the dialer: `REDIS_ADDR=localhost:6379 ip="0.0.0.0" +2. In one terminal run the dialer: `redis_addr=localhost:6379 ip="0.0.0.0" transport=quic-v1 security=quic muxer=quic is_dialer="true" cargo run --bin ping` -3. In another terminal, run the listener: `REDIS_ADDR=localhost:6379 - ip="0.0.0.0" transport=quic-v1 security=quic muxer=quic is_dialer="false" cargo run --bin ping` +3. In another terminal, run the listener: `redis_addr=localhost:6379 + ip="0.0.0.0" transport=quic-v1 security=quic muxer=quic is_dialer="false" cargo run --bin native_ping` To test the interop with other versions do something similar, except replace one of these nodes with the other version's interop test. +# Running this test with webtransport dialer in browser + +To run the webtransport test from within the browser, you'll need the +`chromedriver` in your `$PATH`, compatible with your Chrome browser. +Firefox is not yet supported as it doesn't support all required features yet +(in v114 there is no support for certhashes). + +1. Build the wasm package: `wasm-pack build --target web` +2. Run the dialer: `redis_addr=127.0.0.1:6379 ip=0.0.0.0 transport=webtransport is_dialer=true cargo run --bin wasm_ping` + # Running all interop tests locally with Compose To run this test against all released libp2p versions you'll need to have the diff --git a/interop-tests/chromium-ping-version.json b/interop-tests/chromium-ping-version.json new file mode 100644 index 00000000000..9fb2cd2252c --- /dev/null +++ b/interop-tests/chromium-ping-version.json @@ -0,0 +1,7 @@ +{ + "id": "chromium-rust-libp2p-head", + "containerImageID": "chromium-rust-libp2p-head", + "transports": [{ "name": "webtransport", "onlyDial": true }], + "secureChannels": [], + "muxers": [] +} diff --git a/interop-tests/ping-version.json b/interop-tests/native-ping-version.json similarity index 67% rename from interop-tests/ping-version.json rename to interop-tests/native-ping-version.json index fbf858e85e8..c509f72bfd0 100644 --- a/interop-tests/ping-version.json +++ b/interop-tests/native-ping-version.json @@ -1,6 +1,6 @@ { - "id": "rust-libp2p-head", - "containerImageID": "rust-libp2p-head", + "id": "native-rust-libp2p-head", + "containerImageID": "native-rust-libp2p-head", "transports": [ "ws", "tcp", diff --git a/interop-tests/pkg/readme.md b/interop-tests/pkg/readme.md new file mode 100644 index 00000000000..28a771b5ac5 --- /dev/null +++ b/interop-tests/pkg/readme.md @@ -0,0 +1,6 @@ +# Wasm package directory + +Content of this directory should be generated with +``` +wasm pack build --target web +``` diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs new file mode 100644 index 00000000000..2f4a7495a04 --- /dev/null +++ b/interop-tests/src/arch.rs @@ -0,0 +1,231 @@ +use libp2p::core::muxing::StreamMuxerBox; +use libp2p::core::transport::Boxed; +use libp2p::PeerId; + +// Native re-exports +#[cfg(not(target_arch = "wasm32"))] +pub(crate) use native::{build_transport, init_logger, sleep, swarm_builder, Instant, RedisClient}; + +// Wasm re-exports +#[cfg(target_arch = "wasm32")] +pub(crate) use wasm::{build_transport, init_logger, sleep, swarm_builder, Instant, RedisClient}; + +type BoxedTransport = Boxed<(PeerId, StreamMuxerBox)>; + +#[cfg(not(target_arch = "wasm32"))] +pub(crate) mod native { + use std::time::Duration; + + use anyhow::{bail, Context, Result}; + use either::Either; + use env_logger::{Env, Target}; + use futures::future::BoxFuture; + use futures::FutureExt; + use libp2p::core::muxing::StreamMuxerBox; + use libp2p::core::upgrade::Version; + use libp2p::identity::Keypair; + use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; + use libp2p::websocket::WsConfig; + use libp2p::{noise, tcp, tls, yamux, PeerId, Transport as _}; + use libp2p_mplex as mplex; + use libp2p_quic as quic; + use libp2p_webrtc as webrtc; + use redis::AsyncCommands; + + use crate::{from_env, Muxer, SecProtocol, Transport}; + + use super::BoxedTransport; + + pub(crate) type Instant = std::time::Instant; + + pub(crate) fn init_logger() { + env_logger::Builder::from_env(Env::default().default_filter_or("info")) + .target(Target::Stdout) + .init(); + } + + pub(crate) fn sleep(duration: Duration) -> BoxFuture<'static, ()> { + tokio::time::sleep(duration).boxed() + } + + fn muxer_protocol_from_env() -> Result> { + Ok(match from_env("muxer")? { + Muxer::Yamux => Either::Left(yamux::Config::default()), + Muxer::Mplex => Either::Right(mplex::MplexConfig::new()), + }) + } + + pub(crate) fn build_transport( + local_key: Keypair, + ip: &str, + transport: Transport, + ) -> Result<(BoxedTransport, String)> { + let (transport, addr) = match (transport, from_env::("security")) { + (Transport::QuicV1, _) => ( + quic::tokio::Transport::new(quic::Config::new(&local_key)) + .map(|(p, c), _| (p, StreamMuxerBox::new(c))) + .boxed(), + format!("/ip4/{ip}/udp/0/quic-v1"), + ), + (Transport::Tcp, Ok(SecProtocol::Tls)) => ( + tcp::tokio::Transport::new(tcp::Config::new()) + .upgrade(Version::V1Lazy) + .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) + .multiplex(muxer_protocol_from_env()?) + .timeout(Duration::from_secs(5)) + .boxed(), + format!("/ip4/{ip}/tcp/0"), + ), + (Transport::Tcp, Ok(SecProtocol::Noise)) => ( + tcp::tokio::Transport::new(tcp::Config::new()) + .upgrade(Version::V1Lazy) + .authenticate( + noise::Config::new(&local_key).context("failed to intialise noise")?, + ) + .multiplex(muxer_protocol_from_env()?) + .timeout(Duration::from_secs(5)) + .boxed(), + format!("/ip4/{ip}/tcp/0"), + ), + (Transport::Ws, Ok(SecProtocol::Tls)) => ( + WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) + .upgrade(Version::V1Lazy) + .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) + .multiplex(muxer_protocol_from_env()?) + .timeout(Duration::from_secs(5)) + .boxed(), + format!("/ip4/{ip}/tcp/0/ws"), + ), + (Transport::Ws, Ok(SecProtocol::Noise)) => ( + WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) + .upgrade(Version::V1Lazy) + .authenticate( + noise::Config::new(&local_key).context("failed to intialise noise")?, + ) + .multiplex(muxer_protocol_from_env()?) + .timeout(Duration::from_secs(5)) + .boxed(), + format!("/ip4/{ip}/tcp/0/ws"), + ), + (Transport::WebRtcDirect, _) => ( + webrtc::tokio::Transport::new( + local_key, + webrtc::tokio::Certificate::generate(&mut rand::thread_rng())?, + ) + .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) + .boxed(), + format!("/ip4/{ip}/udp/0/webrtc-direct"), + ), + (Transport::Tcp, Err(_)) => bail!("Missing security protocol for TCP transport"), + (Transport::Ws, Err(_)) => bail!("Missing security protocol for Websocket transport"), + (Transport::Webtransport, _) => bail!("Webtransport can only be used with wasm"), + }; + Ok((transport, addr)) + } + + pub(crate) fn swarm_builder( + transport: BoxedTransport, + behaviour: TBehaviour, + peer_id: PeerId, + ) -> SwarmBuilder { + SwarmBuilder::with_tokio_executor(transport, behaviour, peer_id) + } + + pub(crate) struct RedisClient(redis::Client); + + impl RedisClient { + pub(crate) fn new(redis_addr: &str) -> Result { + Ok(Self( + redis::Client::open(redis_addr).context("Could not connect to redis")?, + )) + } + + pub(crate) async fn blpop(&self, key: &str, timeout: u64) -> Result> { + let mut conn = self.0.get_async_connection().await?; + Ok(conn.blpop(key, timeout as usize).await?) + } + + pub(crate) async fn rpush(&self, key: &str, value: String) -> Result<()> { + let mut conn = self.0.get_async_connection().await?; + conn.rpush(key, value).await?; + Ok(()) + } + } +} + +#[cfg(target_arch = "wasm32")] +pub(crate) mod wasm { + use anyhow::{bail, Result}; + use futures::future::{BoxFuture, FutureExt}; + use libp2p::identity::Keypair; + use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; + use libp2p::PeerId; + use std::time::Duration; + + use crate::{BlpopRequest, Transport}; + + use super::BoxedTransport; + + pub(crate) type Instant = instant::Instant; + + pub(crate) fn init_logger() { + console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::default()); + } + + pub(crate) fn sleep(duration: Duration) -> BoxFuture<'static, ()> { + futures_timer::Delay::new(duration).boxed() + } + + pub(crate) fn build_transport( + local_key: Keypair, + ip: &str, + transport: Transport, + ) -> Result<(BoxedTransport, String)> { + if let Transport::Webtransport = transport { + Ok(( + libp2p::webtransport_websys::Transport::new( + libp2p::webtransport_websys::Config::new(&local_key), + ) + .boxed(), + format!("/ip4/{ip}/udp/0/quic/webtransport"), + )) + } else { + bail!("Only webtransport supported with wasm") + } + } + + pub(crate) fn swarm_builder( + transport: BoxedTransport, + behaviour: TBehaviour, + peer_id: PeerId, + ) -> SwarmBuilder { + SwarmBuilder::with_wasm_executor(transport, behaviour, peer_id) + } + + pub(crate) struct RedisClient(String); + + impl RedisClient { + pub(crate) fn new(base_url: &str) -> Result { + Ok(Self(base_url.to_owned())) + } + + pub(crate) async fn blpop(&self, key: &str, timeout: u64) -> Result> { + let res = reqwest::Client::new() + .post(&format!("http://{}/blpop", self.0)) + .json(&BlpopRequest { + key: key.to_owned(), + timeout, + }) + .send() + .await? + .json() + .await?; + Ok(res) + } + + pub(crate) async fn rpush(&self, _: &str, _: String) -> Result<()> { + bail!("unimplemented") + } + } +} diff --git a/interop-tests/src/bin/config/mod.rs b/interop-tests/src/bin/config/mod.rs new file mode 100644 index 00000000000..82747e82802 --- /dev/null +++ b/interop-tests/src/bin/config/mod.rs @@ -0,0 +1,37 @@ +use std::env; + +use anyhow::{Context, Result}; + +#[derive(Debug, Clone)] +pub(crate) struct Config { + pub(crate) transport: String, + pub(crate) ip: String, + pub(crate) is_dialer: bool, + pub(crate) test_timeout: u64, + pub(crate) redis_addr: String, +} + +impl Config { + pub(crate) fn from_env() -> Result { + let transport = + env::var("transport").context("transport environment variable is not set")?; + let ip = env::var("ip").context("ip environment variable is not set")?; + let is_dialer = env::var("is_dialer") + .unwrap_or_else(|_| "true".into()) + .parse::()?; + let test_timeout = env::var("test_timeout_seconds") + .unwrap_or_else(|_| "180".into()) + .parse::()?; + let redis_addr = env::var("redis_addr") + .map(|addr| format!("redis://{addr}")) + .unwrap_or_else(|_| "redis://redis:6379".into()); + + Ok(Self { + transport, + ip, + is_dialer, + test_timeout, + redis_addr, + }) + } +} diff --git a/interop-tests/src/bin/native_ping.rs b/interop-tests/src/bin/native_ping.rs new file mode 100644 index 00000000000..88905803d26 --- /dev/null +++ b/interop-tests/src/bin/native_ping.rs @@ -0,0 +1,21 @@ +use anyhow::Result; + +mod config; + +#[tokio::main] +async fn main() -> Result<()> { + let config = config::Config::from_env()?; + + let report = interop_tests::run_test( + &config.transport, + &config.ip, + config.is_dialer, + config.test_timeout, + &config.redis_addr, + ) + .await?; + + println!("{}", serde_json::to_string(&report)?); + + Ok(()) +} diff --git a/interop-tests/src/bin/ping.rs b/interop-tests/src/bin/ping.rs deleted file mode 100644 index 23cc86f4571..00000000000 --- a/interop-tests/src/bin/ping.rs +++ /dev/null @@ -1,267 +0,0 @@ -use std::env; -use std::str::FromStr; -use std::time::{Duration, Instant}; - -use anyhow::{bail, Context, Result}; -use either::Either; -use env_logger::{Env, Target}; -use futures::StreamExt; -use libp2p::core::muxing::StreamMuxerBox; -use libp2p::core::upgrade::Version; -use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmEvent}; -use libp2p::websocket::WsConfig; -use libp2p::{ - identity, noise, ping, swarm::SwarmBuilder, tcp, tls, yamux, Multiaddr, PeerId, Transport as _, -}; -use libp2p_mplex as mplex; -use libp2p_quic as quic; -use libp2p_webrtc as webrtc; -use redis::AsyncCommands; - -#[tokio::main] -async fn main() -> Result<()> { - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); - - let transport_param: Transport = from_env("transport")?; - - let ip = env::var("ip").context("ip environment variable is not set")?; - - let is_dialer = env::var("is_dialer") - .unwrap_or_else(|_| "true".into()) - .parse::()?; - - let test_timeout = env::var("test_timeout_seconds") - .unwrap_or_else(|_| "180".into()) - .parse::()?; - - let redis_addr = env::var("redis_addr") - .map(|addr| format!("redis://{addr}")) - .unwrap_or_else(|_| "redis://redis:6379".into()); - - let client = redis::Client::open(redis_addr).context("Could not connect to redis")?; - - // Build the transport from the passed ENV var. - let (boxed_transport, local_addr) = match (transport_param, from_env("security")) { - (Transport::QuicV1, _) => ( - quic::tokio::Transport::new(quic::Config::new(&local_key)) - .map(|(p, c), _| (p, StreamMuxerBox::new(c))) - .boxed(), - format!("/ip4/{ip}/udp/0/quic-v1"), - ), - (Transport::Tcp, Ok(SecProtocol::Tls)) => ( - tcp::tokio::Transport::new(tcp::Config::new()) - .upgrade(Version::V1Lazy) - .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) - .multiplex(muxer_protocol_from_env()?) - .timeout(Duration::from_secs(5)) - .boxed(), - format!("/ip4/{ip}/tcp/0"), - ), - (Transport::Tcp, Ok(SecProtocol::Noise)) => ( - tcp::tokio::Transport::new(tcp::Config::new()) - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key).context("failed to intialise noise")?) - .multiplex(muxer_protocol_from_env()?) - .timeout(Duration::from_secs(5)) - .boxed(), - format!("/ip4/{ip}/tcp/0"), - ), - (Transport::Ws, Ok(SecProtocol::Tls)) => ( - WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) - .upgrade(Version::V1Lazy) - .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) - .multiplex(muxer_protocol_from_env()?) - .timeout(Duration::from_secs(5)) - .boxed(), - format!("/ip4/{ip}/tcp/0/ws"), - ), - (Transport::Ws, Ok(SecProtocol::Noise)) => ( - WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key).context("failed to intialise noise")?) - .multiplex(muxer_protocol_from_env()?) - .timeout(Duration::from_secs(5)) - .boxed(), - format!("/ip4/{ip}/tcp/0/ws"), - ), - (Transport::WebRtcDirect, _) => ( - webrtc::tokio::Transport::new( - local_key, - webrtc::tokio::Certificate::generate(&mut rand::thread_rng())?, - ) - .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) - .boxed(), - format!("/ip4/{ip}/udp/0/webrtc-direct"), - ), - (Transport::Tcp, Err(_)) => bail!("Missing security protocol for TCP transport"), - (Transport::Ws, Err(_)) => bail!("Missing security protocol for Websocket transport"), - }; - - let mut swarm = SwarmBuilder::with_tokio_executor( - boxed_transport, - Behaviour { - ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), - keep_alive: keep_alive::Behaviour, - }, - local_peer_id, - ) - .build(); - - let mut conn = client.get_async_connection().await?; - - log::info!("Running ping test: {}", swarm.local_peer_id()); - env_logger::Builder::from_env(Env::default().default_filter_or("info")) - .target(Target::Stdout) - .init(); - - log::info!( - "Test instance, listening for incoming connections on: {:?}.", - local_addr - ); - let id = swarm.listen_on(local_addr.parse()?)?; - - // Run a ping interop test. Based on `is_dialer`, either dial the address - // retrieved via `listenAddr` key over the redis connection. Or wait to be pinged and have - // `dialerDone` key ready on the redis connection. - if is_dialer { - let result: Vec = conn.blpop("listenerAddr", test_timeout as usize).await?; - let other = result - .get(1) - .context("Failed to wait for listener to be ready")?; - - let handshake_start = Instant::now(); - - swarm.dial(other.parse::()?)?; - log::info!("Test instance, dialing multiaddress on: {}.", other); - - let rtt = loop { - if let Some(SwarmEvent::Behaviour(BehaviourEvent::Ping(ping::Event { - result: Ok(rtt), - .. - }))) = swarm.next().await - { - log::info!("Ping successful: {rtt:?}"); - break rtt.as_millis() as f32; - } - }; - - let handshake_plus_ping = handshake_start.elapsed().as_millis() as f32; - println!( - r#"{{"handshakePlusOneRTTMillis": {handshake_plus_ping:.1}, "pingRTTMilllis": {rtt:.1}}}"# - ); - } else { - loop { - if let Some(SwarmEvent::NewListenAddr { - listener_id, - address, - }) = swarm.next().await - { - if address.to_string().contains("127.0.0.1") { - continue; - } - if listener_id == id { - let ma = format!("{address}/p2p/{local_peer_id}"); - conn.rpush("listenerAddr", ma).await?; - break; - } - } - } - - // Drive Swarm in the background while we await for `dialerDone` to be ready. - tokio::spawn(async move { - loop { - swarm.next().await; - } - }); - tokio::time::sleep(Duration::from_secs(test_timeout)).await; - bail!("Test should have been killed by the test runner!"); - } - - Ok(()) -} - -fn muxer_protocol_from_env() -> Result> { - Ok(match from_env("muxer")? { - Muxer::Yamux => Either::Left(yamux::Config::default()), - Muxer::Mplex => Either::Right(mplex::MplexConfig::new()), - }) -} - -/// Supported transports by rust-libp2p. -#[derive(Clone, Debug)] -pub enum Transport { - Tcp, - QuicV1, - WebRtcDirect, - Ws, -} - -impl FromStr for Transport { - type Err = anyhow::Error; - - fn from_str(s: &str) -> std::result::Result { - Ok(match s { - "tcp" => Self::Tcp, - "quic-v1" => Self::QuicV1, - "webrtc-direct" => Self::WebRtcDirect, - "ws" => Self::Ws, - other => bail!("unknown transport {other}"), - }) - } -} - -/// Supported stream multiplexers by rust-libp2p. -#[derive(Clone, Debug)] -pub enum Muxer { - Mplex, - Yamux, -} - -impl FromStr for Muxer { - type Err = anyhow::Error; - - fn from_str(s: &str) -> std::result::Result { - Ok(match s { - "mplex" => Self::Mplex, - "yamux" => Self::Yamux, - other => bail!("unknown muxer {other}"), - }) - } -} - -/// Supported security protocols by rust-libp2p. -#[derive(Clone, Debug)] -pub enum SecProtocol { - Noise, - Tls, -} - -impl FromStr for SecProtocol { - type Err = anyhow::Error; - - fn from_str(s: &str) -> std::result::Result { - Ok(match s { - "noise" => Self::Noise, - "tls" => Self::Tls, - other => bail!("unknown security protocol {other}"), - }) - } -} - -#[derive(NetworkBehaviour)] -struct Behaviour { - ping: ping::Behaviour, - keep_alive: keep_alive::Behaviour, -} - -/// Helper function to get a ENV variable into an test parameter like `Transport`. -pub fn from_env(env_var: &str) -> Result -where - T: FromStr, -{ - env::var(env_var) - .with_context(|| format!("{env_var} environment variable is not set"))? - .parse() - .map_err(Into::into) -} diff --git a/interop-tests/src/bin/wasm_ping.rs b/interop-tests/src/bin/wasm_ping.rs new file mode 100644 index 00000000000..20350170d59 --- /dev/null +++ b/interop-tests/src/bin/wasm_ping.rs @@ -0,0 +1,219 @@ +use std::process::Stdio; +use std::time::Duration; + +use anyhow::{bail, Context, Result}; +use axum::body; +use axum::http::{header, Uri}; +use axum::response::{Html, IntoResponse, Response}; +use axum::routing::get; +use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; +use redis::{AsyncCommands, Client}; +use thirtyfour::prelude::*; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::Child; +use tokio::sync::mpsc; +use tower_http::cors::CorsLayer; +use tower_http::trace::TraceLayer; +use tracing::{error, warn}; +use tracing_subscriber::{fmt, prelude::*, EnvFilter}; + +use interop_tests::{BlpopRequest, Report}; + +mod config; + +const BIND_ADDR: &str = "127.0.0.1:8080"; + +/// Embedded Wasm package +/// +/// Make sure to build the wasm with `wasm-pack build --target web` +#[derive(rust_embed::RustEmbed)] +#[folder = "pkg"] +struct WasmPackage; + +#[derive(Clone)] +struct TestState { + redis_client: Client, + config: config::Config, + results_tx: mpsc::Sender>, +} + +#[tokio::main] +async fn main() -> Result<()> { + // start logging + tracing_subscriber::registry() + .with(fmt::layer()) + .with(EnvFilter::from_default_env()) + .init(); + + // read env variables + let config = config::Config::from_env()?; + let test_timeout = Duration::from_secs(config.test_timeout); + + // create a redis client + let redis_client = + Client::open(config.redis_addr.as_str()).context("Could not connect to redis")?; + let (results_tx, mut results_rx) = mpsc::channel(1); + + let state = TestState { + redis_client, + config, + results_tx, + }; + + // create a wasm-app service + let app = Router::new() + // Redis proxy + .route("/blpop", post(redis_blpop)) + // Report tests status + .route("/results", post(post_results)) + // Wasm ping test trigger + .route("/", get(serve_index_html)) + // Wasm app static files + .fallback(serve_wasm_pkg) + // Middleware + .layer(CorsLayer::very_permissive()) + .layer(TraceLayer::new_for_http()) + .with_state(state); + + // Run the service in background + tokio::spawn(axum::Server::bind(&BIND_ADDR.parse()?).serve(app.into_make_service())); + + // Start executing the test in a browser + let (mut chrome, driver) = open_in_browser().await?; + + // Wait for the outcome to be reported + let test_result = match tokio::time::timeout(test_timeout, results_rx.recv()).await { + Ok(received) => received.unwrap_or(Err("Results channel closed".to_owned())), + Err(_) => Err("Test timed out".to_owned()), + }; + + // Close the browser after we got the results + driver.quit().await?; + chrome.kill().await?; + + match test_result { + Ok(report) => println!("{}", serde_json::to_string(&report)?), + Err(error) => bail!("Tests failed: {error}"), + } + + Ok(()) +} + +async fn open_in_browser() -> Result<(Child, WebDriver)> { + // start a webdriver process + // currently only the chromedriver is supported as firefox doesn't + // have support yet for the certhashes + let mut chrome = tokio::process::Command::new("chromedriver") + .arg("--port=45782") + .stdout(Stdio::piped()) + .spawn()?; + // read driver's stdout + let driver_out = chrome + .stdout + .take() + .context("No stdout found for webdriver")?; + // wait for the 'ready' message + let mut reader = BufReader::new(driver_out).lines(); + while let Some(line) = reader.next_line().await? { + if line.contains("ChromeDriver was started successfully.") { + break; + } + } + + // run a webdriver client + let mut caps = DesiredCapabilities::chrome(); + caps.set_headless()?; + let driver = WebDriver::new("http://localhost:45782", caps).await?; + // go to the wasm test service + driver.goto(format!("http://{BIND_ADDR}")).await?; + + Ok((chrome, driver)) +} + +/// Redis proxy handler. +/// `blpop` is currently the only redis client method used in a ping dialer. +async fn redis_blpop( + state: State, + request: Json, +) -> Result>, StatusCode> { + let client = state.0.redis_client; + let mut conn = client.get_async_connection().await.map_err(|e| { + warn!("Failed to connect to redis: {e}"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + let res = conn + .blpop(&request.key, request.timeout as usize) + .await + .map_err(|e| { + warn!( + "Failed to get list elem {} within timeout {}: {e}", + request.key, request.timeout + ); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + Ok(Json(res)) +} + +/// Receive test results +async fn post_results( + state: State, + request: Json>, +) -> Result<(), StatusCode> { + state.0.results_tx.send(request.0).await.map_err(|_| { + error!("Failed to send results"); + StatusCode::INTERNAL_SERVER_ERROR + }) +} + +/// Serve the main page which loads our javascript +async fn serve_index_html(state: State) -> Result { + let config::Config { + transport, + ip, + is_dialer, + test_timeout, + .. + } = state.0.config; + Ok(Html(format!( + r#" + + + + + libp2p ping test + + + + + + "# + ))) +} + +async fn serve_wasm_pkg(uri: Uri) -> Result { + let path = uri.path().trim_start_matches('/').to_string(); + if let Some(content) = WasmPackage::get(&path) { + let mime = mime_guess::from_path(&path).first_or_octet_stream(); + Ok(Response::builder() + .header(header::CONTENT_TYPE, mime.as_ref()) + .body(body::boxed(body::Full::from(content.data))) + .unwrap()) + } else { + Err(StatusCode::NOT_FOUND) + } +} diff --git a/interop-tests/src/lib.rs b/interop-tests/src/lib.rs new file mode 100644 index 00000000000..beb7c91c63d --- /dev/null +++ b/interop-tests/src/lib.rs @@ -0,0 +1,251 @@ +use std::str::FromStr; +use std::time::Duration; + +use anyhow::{bail, Context, Result}; +use futures::{FutureExt, StreamExt}; +use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmEvent}; +use libp2p::{identity, ping, Multiaddr, PeerId}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +mod arch; + +use arch::{build_transport, init_logger, swarm_builder, Instant, RedisClient}; + +pub async fn run_test( + transport: &str, + ip: &str, + is_dialer: bool, + test_timeout_seconds: u64, + redis_addr: &str, +) -> Result { + init_logger(); + + let test_timeout = Duration::from_secs(test_timeout_seconds); + let transport = transport.parse().context("Couldn't parse transport")?; + + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + let redis_client = RedisClient::new(redis_addr).context("Could not connect to redis")?; + + // Build the transport from the passed ENV var. + let (boxed_transport, local_addr) = build_transport(local_key, ip, transport)?; + let mut swarm = swarm_builder( + boxed_transport, + Behaviour { + ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), + keep_alive: keep_alive::Behaviour, + }, + local_peer_id, + ) + .build(); + + log::info!("Running ping test: {}", swarm.local_peer_id()); + + let mut maybe_id = None; + + // See https://github.com/libp2p/rust-libp2p/issues/4071. + if transport == Transport::WebRtcDirect { + maybe_id = Some(swarm.listen_on(local_addr.parse()?)?); + } + + // Run a ping interop test. Based on `is_dialer`, either dial the address + // retrieved via `listenAddr` key over the redis connection. Or wait to be pinged and have + // `dialerDone` key ready on the redis connection. + match is_dialer { + true => { + let result: Vec = redis_client + .blpop("listenerAddr", test_timeout.as_secs()) + .await?; + let other = result + .get(1) + .context("Failed to wait for listener to be ready")?; + + let handshake_start = Instant::now(); + + swarm.dial(other.parse::()?)?; + log::info!("Test instance, dialing multiaddress on: {}.", other); + + let rtt = loop { + if let Some(SwarmEvent::Behaviour(BehaviourEvent::Ping(ping::Event { + result: Ok(rtt), + .. + }))) = swarm.next().await + { + log::info!("Ping successful: {rtt:?}"); + break rtt.as_micros() as f32 / 1000.; + } + }; + + let handshake_plus_ping = handshake_start.elapsed().as_micros() as f32 / 1000.; + Ok(Report { + handshake_plus_one_rtt_millis: handshake_plus_ping, + ping_rtt_millis: rtt, + }) + } + false => { + // Listen if we haven't done so already. + // This is a hack until https://github.com/libp2p/rust-libp2p/issues/4071 is fixed at which point we can do this unconditionally here. + let id = match maybe_id { + None => swarm.listen_on(local_addr.parse()?)?, + Some(id) => id, + }; + + log::info!( + "Test instance, listening for incoming connections on: {:?}.", + local_addr + ); + + loop { + if let Some(SwarmEvent::NewListenAddr { + listener_id, + address, + }) = swarm.next().await + { + if address.to_string().contains("127.0.0.1") { + continue; + } + if listener_id == id { + let ma = format!("{address}/p2p/{local_peer_id}"); + redis_client.rpush("listenerAddr", ma).await?; + break; + } + } + } + + // Drive Swarm while we await for `dialerDone` to be ready. + futures::future::select( + async move { + loop { + swarm.next().await; + } + } + .boxed(), + arch::sleep(test_timeout), + ) + .await; + + // The loop never ends so if we get here, we hit the timeout. + bail!("Test should have been killed by the test runner!"); + } + } +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub async fn run_test_wasm( + transport: &str, + ip: &str, + is_dialer: bool, + test_timeout_secs: u64, + base_url: &str, +) -> Result<(), JsValue> { + let result = run_test(transport, ip, is_dialer, test_timeout_secs, base_url).await; + log::info!("Sending test result: {result:?}"); + reqwest::Client::new() + .post(&format!("http://{}/results", base_url)) + .json(&result.map_err(|e| e.to_string())) + .send() + .await? + .error_for_status() + .map_err(|e| format!("Sending test result failed: {e}"))?; + + Ok(()) +} + +/// A request to redis proxy that will pop the value from the list +/// and will wait for it being inserted until a timeout is reached. +#[derive(serde::Deserialize, serde::Serialize)] +pub struct BlpopRequest { + pub key: String, + pub timeout: u64, +} + +/// A report generated by the test +#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct Report { + #[serde(rename = "handshakePlusOneRTTMillis")] + handshake_plus_one_rtt_millis: f32, + #[serde(rename = "pingRTTMilllis")] + ping_rtt_millis: f32, +} + +/// Supported transports by rust-libp2p. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Transport { + Tcp, + QuicV1, + WebRtcDirect, + Ws, + Webtransport, +} + +impl FromStr for Transport { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + Ok(match s { + "tcp" => Self::Tcp, + "quic-v1" => Self::QuicV1, + "webrtc-direct" => Self::WebRtcDirect, + "ws" => Self::Ws, + "webtransport" => Self::Webtransport, + other => bail!("unknown transport {other}"), + }) + } +} + +/// Supported stream multiplexers by rust-libp2p. +#[derive(Clone, Debug)] +pub enum Muxer { + Mplex, + Yamux, +} + +impl FromStr for Muxer { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + Ok(match s { + "mplex" => Self::Mplex, + "yamux" => Self::Yamux, + other => bail!("unknown muxer {other}"), + }) + } +} + +/// Supported security protocols by rust-libp2p. +#[derive(Clone, Debug)] +pub enum SecProtocol { + Noise, + Tls, +} + +impl FromStr for SecProtocol { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + Ok(match s { + "noise" => Self::Noise, + "tls" => Self::Tls, + other => bail!("unknown security protocol {other}"), + }) + } +} + +#[derive(NetworkBehaviour)] +struct Behaviour { + ping: ping::Behaviour, + keep_alive: keep_alive::Behaviour, +} + +/// Helper function to get a ENV variable into an test parameter like `Transport`. +pub fn from_env(env_var: &str) -> Result +where + T: FromStr, +{ + std::env::var(env_var) + .with_context(|| format!("{env_var} environment variable is not set"))? + .parse() + .map_err(Into::into) +} diff --git a/libp2p/Cargo.toml b/libp2p/Cargo.toml index ddea2d3ea2d..c6f4e2a3e27 100644 --- a/libp2p/Cargo.toml +++ b/libp2p/Cargo.toml @@ -45,6 +45,7 @@ full = [ "wasm-ext", "wasm-ext-websocket", "websocket", + "webtransport-websys", "yamux", ] @@ -81,6 +82,7 @@ wasm-bindgen = ["futures-timer/wasm-bindgen", "instant/wasm-bindgen", "getrandom wasm-ext = ["dep:libp2p-wasm-ext"] wasm-ext-websocket = ["wasm-ext", "libp2p-wasm-ext?/websocket"] websocket = ["dep:libp2p-websocket"] +webtransport-websys = ["dep:libp2p-webtransport-websys"] yamux = ["dep:libp2p-yamux"] [dependencies] @@ -110,6 +112,7 @@ libp2p-rendezvous = { workspace = true, optional = true } libp2p-request-response = { workspace = true, optional = true } libp2p-swarm = { workspace = true } libp2p-wasm-ext = { workspace = true, optional = true } +libp2p-webtransport-websys = { workspace = true, optional = true } libp2p-yamux = { workspace = true, optional = true } multiaddr = { workspace = true } diff --git a/libp2p/src/lib.rs b/libp2p/src/lib.rs index 56b6adda490..059857d3b8a 100644 --- a/libp2p/src/lib.rs +++ b/libp2p/src/lib.rs @@ -127,6 +127,10 @@ pub use libp2p_wasm_ext as wasm_ext; #[cfg(not(target_arch = "wasm32"))] #[doc(inline)] pub use libp2p_websocket as websocket; +#[cfg(feature = "webtransport-websys")] +#[cfg_attr(docsrs, doc(cfg(feature = "webtransport-websys")))] +#[doc(inline)] +pub use libp2p_webtransport_websys as webtransport_websys; #[cfg(feature = "yamux")] #[doc(inline)] pub use libp2p_yamux as yamux; diff --git a/transports/webtransport-websys/CHANGELOG.md b/transports/webtransport-websys/CHANGELOG.md new file mode 100644 index 00000000000..509e9be5994 --- /dev/null +++ b/transports/webtransport-websys/CHANGELOG.md @@ -0,0 +1,5 @@ +## 0.1.0 - unreleased + +* Initial implementation of WebTranport transport using web-sys bindings. See [PR 4015]. + +[PR 4015]: https://github.com/libp2p/rust-libp2p/pull/4015 diff --git a/transports/webtransport-websys/Cargo.toml b/transports/webtransport-websys/Cargo.toml new file mode 100644 index 00000000000..db554bc1982 --- /dev/null +++ b/transports/webtransport-websys/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "libp2p-webtransport-websys" +edition = "2021" +rust-version = { workspace = true } +description = "WebTransport for libp2p under WASM environment" +version = "0.1.0" +authors = [ + "Yiannis Marangos ", + "oblique ", +] +license = "MIT" +repository = "https://github.com/libp2p/rust-libp2p" +keywords = ["peer-to-peer", "libp2p", "networking"] +categories = ["network-programming", "asynchronous"] + +[dependencies] +futures = "0.3.28" +js-sys = "0.3.64" +libp2p-core = { workspace = true } +libp2p-identity = { workspace = true } +libp2p-noise = { workspace = true } +log = "0.4.19" +multiaddr = { workspace = true } +multihash = { workspace = true } +send_wrapper = { version = "0.6.0", features = ["futures"] } +thiserror = "1.0.40" +wasm-bindgen = "0.2.87" +wasm-bindgen-futures = "0.4.37" +web-sys = { version = "0.3.64", features = [ + "ReadableStreamDefaultReader", + "WebTransport", + "WebTransportBidirectionalStream", + "WebTransportHash", + "WebTransportOptions", + "WebTransportReceiveStream", + "WebTransportSendStream", + "WritableStreamDefaultWriter", +] } + +[dev-dependencies] +multibase = "0.9.1" + +# Passing arguments to the docsrs builder in order to properly document cfg's. +# More information: https://docs.rs/about/builds#cross-compiling +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] +rustc-args = ["--cfg", "docsrs"] diff --git a/transports/webtransport-websys/src/bindings.rs b/transports/webtransport-websys/src/bindings.rs new file mode 100644 index 00000000000..a8a1469f8ad --- /dev/null +++ b/transports/webtransport-websys/src/bindings.rs @@ -0,0 +1,141 @@ +//! This file is an extract from `web-sys` crate. It is a temporary +//! solution until `web_sys::WebTransport` and related structs get stabilized. +//! +//! Only the methods that are used by this crate are extracted. + +#![allow(clippy::all)] +use js_sys::{Object, Promise, Reflect}; +use wasm_bindgen::prelude::*; +use web_sys::{ReadableStream, WritableStream}; + +// WebTransport bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Object, js_name = WebTransport, typescript_type = "WebTransport")] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransport; + + #[wasm_bindgen(structural, method, getter, js_class = "WebTransport", js_name = ready)] + pub fn ready(this: &WebTransport) -> Promise; + + #[wasm_bindgen(structural, method, getter, js_class = "WebTransport", js_name = closed)] + pub fn closed(this: &WebTransport) -> Promise; + + #[wasm_bindgen(structural, method, getter, js_class = "WebTransport" , js_name = incomingBidirectionalStreams)] + pub fn incoming_bidirectional_streams(this: &WebTransport) -> ReadableStream; + + #[wasm_bindgen(catch, constructor, js_class = "WebTransport")] + pub fn new(url: &str) -> Result; + + #[wasm_bindgen(catch, constructor, js_class = "WebTransport")] + pub fn new_with_options( + url: &str, + options: &WebTransportOptions, + ) -> Result; + + #[wasm_bindgen(method, structural, js_class = "WebTransport", js_name = close)] + pub fn close(this: &WebTransport); + + #[wasm_bindgen (method, structural, js_class = "WebTransport", js_name = createBidirectionalStream)] + pub fn create_bidirectional_stream(this: &WebTransport) -> Promise; +} + +// WebTransportBidirectionalStream bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Object, js_name = WebTransportBidirectionalStream, typescript_type = "WebTransportBidirectionalStream")] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransportBidirectionalStream; + + #[wasm_bindgen(structural, method, getter, js_class = "WebTransportBidirectionalStream", js_name = readable)] + pub fn readable(this: &WebTransportBidirectionalStream) -> WebTransportReceiveStream; + + #[wasm_bindgen(structural, method, getter, js_class = "WebTransportBidirectionalStream", js_name = writable)] + pub fn writable(this: &WebTransportBidirectionalStream) -> WebTransportSendStream; +} + +// WebTransportReceiveStream bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = ReadableStream, extends = Object, js_name = WebTransportReceiveStream, typescript_type = "WebTransportReceiveStream")] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransportReceiveStream; +} + +// WebTransportSendStream bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = WritableStream, extends = Object, js_name = WebTransportSendStream, typescript_type = "WebTransportSendStream")] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransportSendStream; +} + +// WebTransportOptions bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Object, js_name = WebTransportOptions)] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransportOptions; +} + +impl WebTransportOptions { + pub fn new() -> Self { + #[allow(unused_mut)] + let mut ret: Self = JsCast::unchecked_into(Object::new()); + ret + } + + pub fn server_certificate_hashes(&mut self, val: &JsValue) -> &mut Self { + let r = ::js_sys::Reflect::set( + self.as_ref(), + &JsValue::from("serverCertificateHashes"), + &JsValue::from(val), + ); + debug_assert!( + r.is_ok(), + "setting properties should never fail on our dictionary objects" + ); + let _ = r; + self + } +} + +// WebTransportHash bindings +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Object, js_name = WebTransportHash)] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type WebTransportHash; +} + +impl WebTransportHash { + pub fn new() -> Self { + #[allow(unused_mut)] + let mut ret: Self = JsCast::unchecked_into(Object::new()); + ret + } + + pub fn algorithm(&mut self, val: &str) -> &mut Self { + let r = Reflect::set( + self.as_ref(), + &JsValue::from("algorithm"), + &JsValue::from(val), + ); + debug_assert!( + r.is_ok(), + "setting properties should never fail on our dictionary objects" + ); + let _ = r; + self + } + + pub fn value(&mut self, val: &::js_sys::Object) -> &mut Self { + let r = Reflect::set(self.as_ref(), &JsValue::from("value"), &JsValue::from(val)); + debug_assert!( + r.is_ok(), + "setting properties should never fail on our dictionary objects" + ); + let _ = r; + self + } +} diff --git a/transports/webtransport-websys/src/connection.rs b/transports/webtransport-websys/src/connection.rs new file mode 100644 index 00000000000..9ea1dbefd1c --- /dev/null +++ b/transports/webtransport-websys/src/connection.rs @@ -0,0 +1,208 @@ +use futures::FutureExt; +use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; +use libp2p_core::{OutboundUpgrade, UpgradeInfo}; +use libp2p_identity::{Keypair, PeerId}; +use multihash::Multihash; +use send_wrapper::SendWrapper; +use std::collections::HashSet; +use std::future::poll_fn; +use std::pin::Pin; +use std::task::{ready, Context, Poll}; +use wasm_bindgen_futures::JsFuture; +use web_sys::ReadableStreamDefaultReader; + +use crate::bindings::{WebTransport, WebTransportBidirectionalStream}; +use crate::endpoint::Endpoint; +use crate::fused_js_promise::FusedJsPromise; +use crate::utils::{detach_promise, parse_reader_response, to_js_type}; +use crate::{Error, Stream}; + +/// An opened WebTransport connection. +#[derive(Debug)] +pub struct Connection { + // Swarm needs all types to be Send. WASM is single-threaded + // and it is safe to use SendWrapper. + inner: SendWrapper, +} + +#[derive(Debug)] +struct ConnectionInner { + session: WebTransport, + create_stream_promise: FusedJsPromise, + incoming_stream_promise: FusedJsPromise, + incoming_streams_reader: ReadableStreamDefaultReader, + closed: bool, +} + +impl Connection { + pub(crate) fn new(endpoint: &Endpoint) -> Result { + let url = endpoint.url(); + + let session = if endpoint.certhashes.is_empty() { + // Endpoint has CA-signed TLS certificate. + WebTransport::new(&url).map_err(Error::from_js_value)? + } else { + // Endpoint has self-signed TLS certificates. + let opts = endpoint.webtransport_opts(); + WebTransport::new_with_options(&url, &opts).map_err(Error::from_js_value)? + }; + + let incoming_streams = session.incoming_bidirectional_streams(); + let incoming_streams_reader = + to_js_type::(incoming_streams.get_reader())?; + + Ok(Connection { + inner: SendWrapper::new(ConnectionInner { + session, + create_stream_promise: FusedJsPromise::new(), + incoming_stream_promise: FusedJsPromise::new(), + incoming_streams_reader, + closed: false, + }), + }) + } + + pub(crate) async fn authenticate( + &mut self, + keypair: &Keypair, + remote_peer: Option, + certhashes: HashSet>, + ) -> Result { + let fut = SendWrapper::new(self.inner.authenticate(keypair, remote_peer, certhashes)); + fut.await + } +} + +impl ConnectionInner { + /// Authenticates with the server + /// + /// This methods runs the security handshake as descripted + /// in the [spec][1]. It validates the certhashes and peer ID + /// of the server. + /// + /// [1]: https://github.com/libp2p/specs/tree/master/webtransport#security-handshake + async fn authenticate( + &mut self, + keypair: &Keypair, + remote_peer: Option, + certhashes: HashSet>, + ) -> Result { + JsFuture::from(self.session.ready()) + .await + .map_err(Error::from_js_value)?; + + let stream = poll_fn(|cx| self.poll_create_bidirectional_stream(cx)).await?; + let mut noise = libp2p_noise::Config::new(keypair)?; + + if !certhashes.is_empty() { + noise = noise.with_webtransport_certhashes(certhashes); + } + + // We do not use `upgrade::apply_outbound` function because it uses + // `multistream_select` protocol, which is not used by WebTransport spec. + let info = noise.protocol_info().next().unwrap_or_default(); + let (peer_id, _io) = noise.upgrade_outbound(stream, info).await?; + + // TODO: This should be part libp2p-noise + if let Some(expected_peer_id) = remote_peer { + if peer_id != expected_peer_id { + return Err(Error::UnknownRemotePeerId); + } + } + + Ok(peer_id) + } + + /// Initiates and polls a promise from `create_bidirectional_stream`. + fn poll_create_bidirectional_stream( + &mut self, + cx: &mut Context, + ) -> Poll> { + // Create bidirectional stream + let val = ready!(self + .create_stream_promise + .maybe_init(|| self.session.create_bidirectional_stream()) + .poll_unpin(cx)) + .map_err(Error::from_js_value)?; + + let bidi_stream = to_js_type::(val)?; + let stream = Stream::new(bidi_stream)?; + + Poll::Ready(Ok(stream)) + } + + /// Polls for incoming stream from `incoming_bidirectional_streams` reader. + fn poll_incoming_bidirectional_streams( + &mut self, + cx: &mut Context, + ) -> Poll> { + // Read the next incoming stream from the JS channel + let val = ready!(self + .incoming_stream_promise + .maybe_init(|| self.incoming_streams_reader.read()) + .poll_unpin(cx)) + .map_err(Error::from_js_value)?; + + let val = parse_reader_response(&val) + .map_err(Error::from_js_value)? + .ok_or_else(|| Error::JsError("incoming_bidirectional_streams closed".to_string()))?; + + let bidi_stream = to_js_type::(val)?; + let stream = Stream::new(bidi_stream)?; + + Poll::Ready(Ok(stream)) + } + + /// Closes the session. + /// + /// This closes the streams also and they will return an error + /// when they will be used. + fn close_session(&mut self) { + if !self.closed { + detach_promise(self.incoming_streams_reader.cancel()); + self.session.close(); + self.closed = true; + } + } +} + +impl Drop for ConnectionInner { + fn drop(&mut self) { + self.close_session(); + } +} + +/// WebTransport native multiplexing +impl StreamMuxer for Connection { + type Substream = Stream; + type Error = Error; + + fn poll_inbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + self.inner.poll_incoming_bidirectional_streams(cx) + } + + fn poll_outbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + self.inner.poll_create_bidirectional_stream(cx) + } + + fn poll_close( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + self.inner.close_session(); + Poll::Ready(Ok(())) + } + + fn poll( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Pending + } +} diff --git a/transports/webtransport-websys/src/endpoint.rs b/transports/webtransport-websys/src/endpoint.rs new file mode 100644 index 00000000000..0bff1ed6186 --- /dev/null +++ b/transports/webtransport-websys/src/endpoint.rs @@ -0,0 +1,227 @@ +use js_sys::{Array, Uint8Array}; +use libp2p_identity::PeerId; +use multiaddr::{Multiaddr, Protocol}; +use multihash::Multihash; +use std::collections::HashSet; + +use crate::bindings::{WebTransportHash, WebTransportOptions}; +use crate::Error; + +pub(crate) struct Endpoint { + pub(crate) host: String, + pub(crate) port: u16, + pub(crate) is_ipv6: bool, + pub(crate) certhashes: HashSet>, + pub(crate) remote_peer: Option, +} + +impl Endpoint { + pub(crate) fn from_multiaddr(addr: &Multiaddr) -> Result { + let mut host = None; + let mut port = None; + let mut found_quic = false; + let mut found_webtransport = false; + let mut certhashes = HashSet::new(); + let mut remote_peer = None; + let mut is_ipv6 = false; + + for proto in addr.iter() { + match proto { + Protocol::Ip4(addr) => { + if host.is_some() { + return Err(Error::InvalidMultiaddr("More than one host definitions")); + } + + host = Some(addr.to_string()); + } + Protocol::Ip6(addr) => { + if host.is_some() { + return Err(Error::InvalidMultiaddr("More than one host definitions")); + } + + is_ipv6 = true; + host = Some(addr.to_string()); + } + Protocol::Dns(domain) | Protocol::Dns4(domain) | Protocol::Dns6(domain) => { + if port.is_some() { + return Err(Error::InvalidMultiaddr("More than one host definitions")); + } + + host = Some(domain.to_string()) + } + Protocol::Dnsaddr(_) => { + return Err(Error::InvalidMultiaddr( + "/dnsaddr not supported from within a browser", + )); + } + Protocol::Udp(p) => { + if port.is_some() { + return Err(Error::InvalidMultiaddr("More than one port definitions")); + } + + port = Some(p); + } + Protocol::Quic | Protocol::QuicV1 => { + if host.is_none() || port.is_none() { + return Err(Error::InvalidMultiaddr( + "No host and port definition before /quic/webtransport", + )); + } + + found_quic = true; + } + Protocol::WebTransport => { + if !found_quic { + return Err(Error::InvalidMultiaddr( + "/quic is not found before /webtransport", + )); + } + + found_webtransport = true; + } + Protocol::Certhash(hash) => { + if !found_webtransport { + return Err(Error::InvalidMultiaddr( + "/certhashes must be after /quic/found_webtransport", + )); + } + + certhashes.insert(hash); + } + Protocol::P2p(peer) => { + if remote_peer.is_some() { + return Err(Error::InvalidMultiaddr("More than one peer definitions")); + } + + remote_peer = Some(peer); + } + _ => {} + } + } + + if !found_quic || !found_webtransport { + return Err(Error::InvalidMultiaddr( + "Not a /quic/webtransport multiaddr", + )); + } + + let host = host.ok_or_else(|| Error::InvalidMultiaddr("Host is not defined"))?; + let port = port.ok_or_else(|| Error::InvalidMultiaddr("Port is not defined"))?; + + Ok(Endpoint { + host, + port, + is_ipv6, + certhashes, + remote_peer, + }) + } + + pub(crate) fn url(&self) -> String { + let host = &self.host; + let port = self.port; + + if self.is_ipv6 { + format!("https://[{host}]:{port}/.well-known/libp2p-webtransport?type=noise") + } else { + format!("https://{host}:{port}/.well-known/libp2p-webtransport?type=noise") + } + } + + pub(crate) fn webtransport_opts(&self) -> WebTransportOptions { + let mut opts = WebTransportOptions::new(); + let hashes = Array::new(); + + for hash in &self.certhashes { + let digest = Uint8Array::from(hash.digest()); + + let mut jshash = WebTransportHash::new(); + jshash.algorithm("sha-256").value(&digest); + + hashes.push(&jshash); + } + + opts.server_certificate_hashes(&hashes); + + opts + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + fn multihash_from_str(s: &str) -> Multihash<64> { + let (_base, bytes) = multibase::decode(s).unwrap(); + Multihash::from_bytes(&bytes).unwrap() + } + + #[test] + fn valid_webtransport_multiaddr() { + let addr = Multiaddr::from_str("/ip4/127.0.0.1/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); + let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); + + assert_eq!(endpoint.host, "127.0.0.1"); + assert_eq!(endpoint.port, 44874); + assert_eq!(endpoint.certhashes.len(), 2); + + assert!(endpoint.certhashes.contains(&multihash_from_str( + "uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng" + ))); + + assert!(endpoint.certhashes.contains(&multihash_from_str( + "uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ" + ))); + + assert_eq!( + endpoint.remote_peer.unwrap(), + PeerId::from_str("12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap() + ); + + assert_eq!( + endpoint.url(), + "https://127.0.0.1:44874/.well-known/libp2p-webtransport?type=noise" + ); + } + + #[test] + fn valid_webtransport_multiaddr_without_certhashes() { + let addr = Multiaddr::from_str("/ip4/127.0.0.1/udp/44874/quic-v1/webtransport/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); + let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); + + assert_eq!(endpoint.host, "127.0.0.1"); + assert_eq!(endpoint.port, 44874); + assert_eq!(endpoint.certhashes.len(), 0); + assert_eq!( + endpoint.remote_peer.unwrap(), + PeerId::from_str("12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap() + ); + } + + #[test] + fn ipv6_webtransport() { + let addr = Multiaddr::from_str("/ip6/::1/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); + let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); + + assert_eq!(endpoint.host, "::1"); + assert_eq!(endpoint.port, 44874); + assert_eq!( + endpoint.url(), + "https://[::1]:44874/.well-known/libp2p-webtransport?type=noise" + ); + } + + #[test] + fn dns_webtransport() { + let addr = Multiaddr::from_str("/dns/libp2p.io/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap(); + let endpoint = Endpoint::from_multiaddr(&addr).unwrap(); + + assert_eq!(endpoint.host, "libp2p.io"); + assert_eq!(endpoint.port, 44874); + assert_eq!( + endpoint.url(), + "https://libp2p.io:44874/.well-known/libp2p-webtransport?type=noise" + ); + } +} diff --git a/transports/webtransport-websys/src/error.rs b/transports/webtransport-websys/src/error.rs new file mode 100644 index 00000000000..ad85cab7537 --- /dev/null +++ b/transports/webtransport-websys/src/error.rs @@ -0,0 +1,36 @@ +use wasm_bindgen::{JsCast, JsValue}; + +/// Errors that may happen on the [`Transport`](crate::Transport) or the +/// [`Connection`](crate::Connection). +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Invalid multiaddr: {0}")] + InvalidMultiaddr(&'static str), + + #[error("Noise authentication failed")] + Noise(#[from] libp2p_noise::Error), + + #[error("JavaScript error: {0}")] + JsError(String), + + #[error("JavaScript typecasting failed")] + JsCastFailed, + + #[error("Unknown remote peer ID")] + UnknownRemotePeerId, +} + +impl Error { + pub(crate) fn from_js_value(value: JsValue) -> Self { + let s = if value.is_instance_of::() { + js_sys::Error::from(value) + .to_string() + .as_string() + .unwrap_or_else(|| "Unknown error".to_string()) + } else { + "Unknown error".to_string() + }; + + Error::JsError(s) + } +} diff --git a/transports/webtransport-websys/src/fused_js_promise.rs b/transports/webtransport-websys/src/fused_js_promise.rs new file mode 100644 index 00000000000..0ba846501c2 --- /dev/null +++ b/transports/webtransport-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) + } +} diff --git a/transports/webtransport-websys/src/lib.rs b/transports/webtransport-websys/src/lib.rs new file mode 100644 index 00000000000..f9c59694fa3 --- /dev/null +++ b/transports/webtransport-websys/src/lib.rs @@ -0,0 +1,15 @@ +//! Libp2p WebTransport built on [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html) + +mod bindings; +mod connection; +mod endpoint; +mod error; +mod fused_js_promise; +mod stream; +mod transport; +mod utils; + +pub use self::connection::Connection; +pub use self::error::Error; +pub use self::stream::Stream; +pub use self::transport::{Config, Transport}; diff --git a/transports/webtransport-websys/src/stream.rs b/transports/webtransport-websys/src/stream.rs new file mode 100644 index 00000000000..ba4238ac814 --- /dev/null +++ b/transports/webtransport-websys/src/stream.rs @@ -0,0 +1,228 @@ +use futures::{AsyncRead, AsyncWrite, FutureExt}; +use js_sys::Uint8Array; +use send_wrapper::SendWrapper; +use std::io; +use std::pin::Pin; +use std::task::ready; +use std::task::{Context, Poll}; +use web_sys::{ReadableStreamDefaultReader, WritableStreamDefaultWriter}; + +use crate::bindings::WebTransportBidirectionalStream; +use crate::fused_js_promise::FusedJsPromise; +use crate::utils::{detach_promise, parse_reader_response, to_io_error, to_js_type}; +use crate::Error; + +/// A stream on a connection. +#[derive(Debug)] +pub struct Stream { + // Swarm needs all types to be Send. WASM is single-threaded + // and it is safe to use SendWrapper. + inner: SendWrapper, +} + +#[derive(Debug)] +struct StreamInner { + reader: ReadableStreamDefaultReader, + reader_read_promise: FusedJsPromise, + read_leftovers: Option, + writer: WritableStreamDefaultWriter, + writer_state: StreamState, + writer_ready_promise: FusedJsPromise, + writer_closed_promise: FusedJsPromise, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum StreamState { + Open, + Closing, + Closed, +} + +impl Stream { + pub(crate) fn new(bidi_stream: WebTransportBidirectionalStream) -> Result { + let recv_stream = bidi_stream.readable(); + let send_stream = bidi_stream.writable(); + + let reader = to_js_type::(recv_stream.get_reader())?; + let writer = send_stream.get_writer().map_err(Error::from_js_value)?; + + Ok(Stream { + inner: SendWrapper::new(StreamInner { + reader, + reader_read_promise: FusedJsPromise::new(), + read_leftovers: None, + writer, + writer_state: StreamState::Open, + writer_ready_promise: FusedJsPromise::new(), + writer_closed_promise: FusedJsPromise::new(), + }), + }) + } +} + +impl StreamInner { + fn poll_reader_read(&mut self, cx: &mut Context) -> Poll>> { + let val = ready!(self + .reader_read_promise + .maybe_init(|| self.reader.read()) + .poll_unpin(cx)) + .map_err(to_io_error)?; + + let val = parse_reader_response(&val) + .map_err(to_io_error)? + .map(Uint8Array::from); + + Poll::Ready(Ok(val)) + } + + fn poll_read(&mut self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { + // If we have leftovers from a previous read, then use them. + // Otherwise read new data. + let data = match self.read_leftovers.take() { + Some(data) => data, + None => { + match ready!(self.poll_reader_read(cx))? { + Some(data) => data, + // EOF + None => return Poll::Ready(Ok(0)), + } + } + }; + + if data.byte_length() == 0 { + return Poll::Ready(Ok(0)); + } + + let out_len = data.byte_length().min(buf.len() as u32); + data.slice(0, out_len).copy_to(&mut buf[..out_len as usize]); + + let leftovers = data.slice(out_len, data.byte_length()); + + if leftovers.byte_length() > 0 { + self.read_leftovers = Some(leftovers); + } + + Poll::Ready(Ok(out_len as usize)) + } + + fn poll_writer_ready(&mut self, cx: &mut Context) -> Poll> { + if self.writer_state != StreamState::Open { + return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())); + } + + let desired_size = self + .writer + .desired_size() + .map_err(to_io_error)? + .map(|n| n.trunc() as i64) + .unwrap_or(0); + + // We need to poll if the queue is full or if the promise was already activated. + // + // NOTE: `desired_size` can be negative if we overcommit messages to the queue. + if desired_size <= 0 || self.writer_ready_promise.is_active() { + ready!(self + .writer_ready_promise + .maybe_init(|| self.writer.ready()) + .poll_unpin(cx)) + .map_err(to_io_error)?; + } + + Poll::Ready(Ok(())) + } + + fn poll_write(&mut self, cx: &mut Context, buf: &[u8]) -> Poll> { + ready!(self.poll_writer_ready(cx))?; + + let len = buf.len() as u32; + let data = Uint8Array::new_with_length(len); + data.copy_from(buf); + + detach_promise(self.writer.write_with_chunk(&data)); + + Poll::Ready(Ok(len as usize)) + } + + fn poll_flush(&mut self, cx: &mut Context) -> Poll> { + if self.writer_state == StreamState::Open { + // Writer has queue size of 1, so as soon it is ready, self means the + // messages were flushed. + self.poll_writer_ready(cx) + } else { + debug_assert!( + false, + "libp2p_webtransport_websys::Stream: poll_flush called after poll_close" + ); + Poll::Ready(Ok(())) + } + } + + fn poll_writer_close(&mut self, cx: &mut Context) -> Poll> { + match self.writer_state { + StreamState::Open => { + self.writer_state = StreamState::Closing; + + // Initiate close + detach_promise(self.writer.close()); + + // Assume closed on error + let _ = ready!(self + .writer_closed_promise + .maybe_init(|| self.writer.closed()) + .poll_unpin(cx)); + + self.writer_state = StreamState::Closed; + } + StreamState::Closing => { + // Assume closed on error + let _ = ready!(self.writer_closed_promise.poll_unpin(cx)); + self.writer_state = StreamState::Closed; + } + StreamState::Closed => {} + } + + Poll::Ready(Ok(())) + } +} + +impl Drop for StreamInner { + fn drop(&mut self) { + // Close writer. + // + // We choose to use `close()` instead of `abort()`, because + // abort was causing some side effects on the WebTransport + // layer and connection was lost. + detach_promise(self.writer.close()); + + // Cancel any ongoing reads. + detach_promise(self.reader.cancel()); + } +} + +impl AsyncRead for Stream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + self.inner.poll_read(cx, buf) + } +} + +impl AsyncWrite for Stream { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], + ) -> Poll> { + self.inner.poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.inner.poll_flush(cx) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.inner.poll_writer_close(cx) + } +} diff --git a/transports/webtransport-websys/src/transport.rs b/transports/webtransport-websys/src/transport.rs new file mode 100644 index 00000000000..dcb3639a194 --- /dev/null +++ b/transports/webtransport-websys/src/transport.rs @@ -0,0 +1,103 @@ +use futures::future::FutureExt; +use libp2p_core::muxing::StreamMuxerBox; +use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; +use libp2p_identity::{Keypair, PeerId}; +use multiaddr::Multiaddr; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use crate::endpoint::Endpoint; +use crate::Connection; +use crate::Error; + +/// Config for the [`Transport`]. +pub struct Config { + keypair: Keypair, +} + +/// A WebTransport [`Transport`](libp2p_core::Transport) that works with `web-sys`. +pub struct Transport { + config: Config, +} + +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 endpoint = Endpoint::from_multiaddr(&addr).map_err(|e| match e { + e @ Error::InvalidMultiaddr(_) => { + log::warn!("{}", e); + TransportError::MultiaddrNotSupported(addr) + } + e => TransportError::Other(e), + })?; + + let mut session = Connection::new(&endpoint).map_err(TransportError::Other)?; + let keypair = self.config.keypair.clone(); + + Ok(async move { + let peer_id = session + .authenticate(&keypair, endpoint.remote_peer, endpoint.certhashes) + .await?; + Ok((peer_id, session)) + } + .boxed()) + } + + 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 + } +} diff --git a/transports/webtransport-websys/src/utils.rs b/transports/webtransport-websys/src/utils.rs new file mode 100644 index 00000000000..fcde226be87 --- /dev/null +++ b/transports/webtransport-websys/src/utils.rs @@ -0,0 +1,76 @@ +use js_sys::{Promise, Reflect}; +use send_wrapper::SendWrapper; +use std::io; +use wasm_bindgen::{JsCast, JsValue}; + +use crate::Error; + +/// Properly detach a promise. +/// +/// A promise always runs in the background, however if you don't await it, +/// or specify a `catch` handler before you drop it, it might cause some side +/// effects. This function avoids any side effects. +// +// Ref: https://github.com/typescript-eslint/typescript-eslint/blob/391a6702c0a9b5b3874a7a27047f2a721f090fb6/packages/eslint-plugin/docs/rules/no-floating-promises.md +pub(crate) fn detach_promise(promise: Promise) { + type Closure = wasm_bindgen::closure::Closure; + static mut DO_NOTHING: Option> = None; + + // Allocate Closure only once and reuse it + let do_nothing = unsafe { + if DO_NOTHING.is_none() { + let cb = Closure::new(|_| {}); + DO_NOTHING = Some(SendWrapper::new(cb)); + } + + DO_NOTHING.as_deref().unwrap() + }; + + // Avoid having "floating" promise and ignore any errors. + // After `catch` promise is allowed to be dropped. + let _ = promise.catch(do_nothing); +} + +/// Typecasts a JavaScript type. +/// +/// Returns a `Ok(value)` casted to the requested type. +/// +/// If the underlying value is an error and the requested +/// type is not, then `Err(Error::JsError)` is returned. +/// +/// If the underlying value can not be casted to the requested type and +/// is not an error, then `Err(Error::JsCastFailed)` is returned. +pub(crate) fn to_js_type(value: impl Into) -> Result +where + T: JsCast + From, +{ + let value = value.into(); + + if value.has_type::() { + Ok(value.unchecked_into()) + } else if value.has_type::() { + Err(Error::from_js_value(value)) + } else { + Err(Error::JsCastFailed) + } +} + +/// Parse reponse from `ReadableStreamDefaultReader::read`. +// +// Ref: https://streams.spec.whatwg.org/#default-reader-prototype +pub(crate) fn parse_reader_response(resp: &JsValue) -> Result, JsValue> { + let value = Reflect::get(resp, &JsValue::from_str("value"))?; + let done = Reflect::get(resp, &JsValue::from_str("done"))? + .as_bool() + .unwrap_or_default(); + + if value.is_undefined() || done { + Ok(None) + } else { + Ok(Some(value)) + } +} + +pub(crate) fn to_io_error(value: JsValue) -> io::Error { + io::Error::new(io::ErrorKind::Other, Error::from_js_value(value)) +} diff --git a/wasm-tests/README.md b/wasm-tests/README.md new file mode 100644 index 00000000000..1d0902b106c --- /dev/null +++ b/wasm-tests/README.md @@ -0,0 +1,11 @@ +# Dependencies + +Before you run the tests you need to install the following: + +* Chrome or Chromium +* chromedriver (major version must be the same as Chrome's) +* wasm-pack + +# Run tests + +Just call `run-all.sh` or `run.sh` in the test directory you are interested. diff --git a/wasm-tests/run-all.sh b/wasm-tests/run-all.sh new file mode 100755 index 00000000000..77b896a167d --- /dev/null +++ b/wasm-tests/run-all.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +# cd to this script directory +cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 + +./webtransport-tests/run.sh diff --git a/wasm-tests/webtransport-tests/Cargo.toml b/wasm-tests/webtransport-tests/Cargo.toml new file mode 100644 index 00000000000..6c564281274 --- /dev/null +++ b/wasm-tests/webtransport-tests/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "webtransport-tests" +version = "0.1.0" +edition = "2021" +license = "MIT" +publish = false + +[dependencies] +futures = "0.3.28" +getrandom = { version = "0.2.9", features = ["js"] } +libp2p-core = { workspace = true } +libp2p-identity = { workspace = true } +libp2p-noise = { workspace = true } +libp2p-webtransport-websys = { workspace = true } +multiaddr = { workspace = true } +multihash = { workspace = true } +wasm-bindgen = "0.2.87" +wasm-bindgen-futures = "0.4.37" +wasm-bindgen-test = "0.3.37" +web-sys = { version = "0.3.64", features = ["Response", "Window"] } diff --git a/wasm-tests/webtransport-tests/README.md b/wasm-tests/webtransport-tests/README.md new file mode 100644 index 00000000000..b57a159176b --- /dev/null +++ b/wasm-tests/webtransport-tests/README.md @@ -0,0 +1,27 @@ +# Manually run tests + +First you need to build and start the echo-server: + +``` +docker build -t webtransport-echo-server echo-server +docker run -it --rm --network=host webtransport-echo-server +``` + +On another terminal run: + +``` +wasm-pack test --chrome +``` + +Navigate with your browser at http://127.0.0.1:8000. + +You can also run the tests on a headless browser: + +``` +wasm-pack test --chrome --headless +``` + +> **Note:** For headless tests your Chrome browser needs to be compatible +> with chromedriver (i.e. they must have the same major version). +> +> You may need to define the path of chromedriver with `--chromedriver=/path/to/chromedriver`. diff --git a/wasm-tests/webtransport-tests/echo-server/.gitignore b/wasm-tests/webtransport-tests/echo-server/.gitignore new file mode 100644 index 00000000000..e831a850ae0 --- /dev/null +++ b/wasm-tests/webtransport-tests/echo-server/.gitignore @@ -0,0 +1 @@ +/echo-server diff --git a/wasm-tests/webtransport-tests/echo-server/Dockerfile b/wasm-tests/webtransport-tests/echo-server/Dockerfile new file mode 100644 index 00000000000..f498e2baa1b --- /dev/null +++ b/wasm-tests/webtransport-tests/echo-server/Dockerfile @@ -0,0 +1,9 @@ +# syntax=docker/dockerfile:1.5-labs +FROM docker.io/library/golang:1.20 AS builder +WORKDIR /workspace +ADD . . +RUN CGO_ENABLED=0 go build . + +FROM scratch +COPY --from=builder /workspace/echo-server / +ENTRYPOINT ["/echo-server"] diff --git a/wasm-tests/webtransport-tests/echo-server/go.mod b/wasm-tests/webtransport-tests/echo-server/go.mod new file mode 100644 index 00000000000..5856827d798 --- /dev/null +++ b/wasm-tests/webtransport-tests/echo-server/go.mod @@ -0,0 +1,64 @@ +module echo-server + +go 1.20 + +require ( + github.com/libp2p/go-libp2p v0.27.5 + github.com/multiformats/go-multiaddr v0.9.0 +) + +require ( + github.com/benbjohnson/clock v1.3.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/flynn/noise v1.0.0 // indirect + github.com/francoispqt/gojay v1.2.13 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect + github.com/ipfs/go-cid v0.4.1 // indirect + github.com/ipfs/go-log/v2 v2.5.1 // indirect + github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect + github.com/klauspost/compress v1.16.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/libp2p/go-netroute v0.2.1 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multicodec v0.8.1 // indirect + github.com/multiformats/go-multihash v0.2.1 // indirect + github.com/multiformats/go-multistream v0.4.1 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect + github.com/onsi/ginkgo/v2 v2.9.2 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qtls-go1-19 v0.3.2 // indirect + github.com/quic-go/qtls-go1-20 v0.2.2 // indirect + github.com/quic-go/quic-go v0.33.0 // indirect + github.com/quic-go/webtransport-go v0.5.2 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/tools v0.7.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + lukechampine.com/blake3 v1.1.7 // indirect +) diff --git a/wasm-tests/webtransport-tests/echo-server/go.sum b/wasm-tests/webtransport-tests/echo-server/go.sum new file mode 100644 index 00000000000..ab2832bce75 --- /dev/null +++ b/wasm-tests/webtransport-tests/echo-server/go.sum @@ -0,0 +1,344 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= +github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b h1:Qcx5LM0fSiks9uCyFZwDBUasd3lxd1RM0GYpL+Li5o4= +github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= +github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= +github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= +github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= +github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/libp2p/go-libp2p v0.27.5 h1:KwA7pXKXpz8hG6Cr1fMA7UkgleogcwQj0sxl5qquWRg= +github.com/libp2p/go-libp2p v0.27.5/go.mod h1:oMfQGTb9CHnrOuSM6yMmyK2lXz3qIhnkn2+oK3B1Y2g= +github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= +github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= +github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= +github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= +github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= +github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108= +github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= +github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= +github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= +github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= +github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= +github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= +github.com/quic-go/webtransport-go v0.5.2 h1:GA6Bl6oZY+g/flt00Pnu0XtivSD8vukOu3lYhJjnGEk= +github.com/quic-go/webtransport-go v0.5.2/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= +lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/wasm-tests/webtransport-tests/echo-server/main.go b/wasm-tests/webtransport-tests/echo-server/main.go new file mode 100644 index 00000000000..def4151bd1b --- /dev/null +++ b/wasm-tests/webtransport-tests/echo-server/main.go @@ -0,0 +1,99 @@ +package main + +import ( + "context" + "crypto/rand" + "fmt" + "io" + "net/http" + + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/transport" + "github.com/libp2p/go-libp2p/p2p/transport/quicreuse" + webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" + "github.com/multiformats/go-multiaddr" +) + +// This provides a way for test cases to discover the WebTransport address +func addrReporter(ma multiaddr.Multiaddr) { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + h := w.Header() + h.Add("Access-Control-Allow-Origin", "*") + h.Add("Cross-Origin-Resource-Policy", "cross-origin") + h.Add("Content-Type", "text/plain; charset=utf-8") + + fmt.Fprint(w, ma.String()) + }) + + http.ListenAndServe(":4455", nil) +} + +func serveConn(conn transport.CapableConn) { + go func() { + for { + stream, err := conn.OpenStream(context.Background()) + if err != nil { + break; + } + + // Stream is a local operation until data is send + // on the stream. We send a single byte to fully + // initiate the stream. + // + // Ref: https://github.com/libp2p/go-libp2p/issues/2343 + stream.Write([]byte("1")) + + go io.Copy(stream, stream) + } + }() + + for { + stream, err := conn.AcceptStream() + if err != nil { + break + } + + go io.Copy(stream, stream) + } +} + +func main() { + priv, pub, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + panic(err) + } + + peerId, err := peer.IDFromPublicKey(pub) + if err != nil { + panic(err) + } + + connManager, err := quicreuse.NewConnManager([32]byte{}) + if err != nil { + panic(err) + } + + transport, err := webtransport.New(priv, nil, connManager, nil, nil); + if err != nil { + panic(err) + } + + listener, err := transport.Listen(multiaddr.StringCast("/ip4/127.0.0.1/udp/0/quic-v1/webtransport")) + if err != nil { + panic(err) + } + + addr := listener.Multiaddr().Encapsulate(multiaddr.StringCast("/p2p/" + peerId.String())) + + go addrReporter(addr) + + for { + conn, err := listener.Accept() + if err != nil { + panic(nil) + } + + go serveConn(conn) + } +} diff --git a/wasm-tests/webtransport-tests/run.sh b/wasm-tests/webtransport-tests/run.sh new file mode 100755 index 00000000000..1819fc97770 --- /dev/null +++ b/wasm-tests/webtransport-tests/run.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Prefer podman over docker since it doesn't require root privileges +if command -v podman > /dev/null; then + docker=podman +else + docker=docker +fi + +# cd to this script directory +cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 + +# Print the directory for debugging +echo "Tests: $PWD" + +# Build and run echo-server +$docker build -t webtransport-echo-server echo-server || exit 1 +id="$($docker run -d --network=host webtransport-echo-server)" || exit 1 + +# Run tests +wasm-pack test --chrome --headless +exit_code=$? + +# Remove echo-server container +$docker rm -f "$id" + +# Propagate wasm-pack's exit code +exit $exit_code diff --git a/wasm-tests/webtransport-tests/src/lib.rs b/wasm-tests/webtransport-tests/src/lib.rs new file mode 100644 index 00000000000..0ec2f0bcb03 --- /dev/null +++ b/wasm-tests/webtransport-tests/src/lib.rs @@ -0,0 +1,382 @@ +use futures::channel::oneshot; +use futures::{AsyncReadExt, AsyncWriteExt}; +use getrandom::getrandom; +use libp2p_core::{StreamMuxer, Transport as _}; +use libp2p_identity::{Keypair, PeerId}; +use libp2p_noise as noise; +use libp2p_webtransport_websys::{Config, Connection, Error, Stream, Transport}; +use multiaddr::{Multiaddr, Protocol}; +use multihash::Multihash; +use std::future::poll_fn; +use std::pin::Pin; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::{spawn_local, JsFuture}; +use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; +use web_sys::{window, Response}; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +async fn single_conn_single_stream() { + let mut conn = new_connection_to_echo_server().await; + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; +} + +#[wasm_bindgen_test] +async fn single_conn_single_stream_incoming() { + let mut conn = new_connection_to_echo_server().await; + let mut stream = incoming_stream(&mut conn).await; + + send_recv(&mut stream).await; +} + +#[wasm_bindgen_test] +async fn single_conn_multiple_streams() { + let mut conn = new_connection_to_echo_server().await; + let mut tasks = Vec::new(); + let mut streams = Vec::new(); + + for i in 0..30 { + let stream = if i % 2 == 0 { + create_stream(&mut conn).await + } else { + incoming_stream(&mut conn).await + }; + + streams.push(stream); + } + + for stream in streams { + tasks.push(send_recv_task(stream)); + } + + futures::future::try_join_all(tasks).await.unwrap(); +} + +#[wasm_bindgen_test] +async fn multiple_conn_multiple_streams() { + let mut tasks = Vec::new(); + let mut conns = Vec::new(); + + for _ in 0..10 { + let mut conn = new_connection_to_echo_server().await; + let mut streams = Vec::new(); + + for i in 0..10 { + let stream = if i % 2 == 0 { + create_stream(&mut conn).await + } else { + incoming_stream(&mut conn).await + }; + + streams.push(stream); + } + + // If `conn` gets drop then its streams will close. + // Keep it alive by moving it to the outer scope. + conns.push(conn); + + for stream in streams { + tasks.push(send_recv_task(stream)); + } + } + + futures::future::try_join_all(tasks).await.unwrap(); +} + +#[wasm_bindgen_test] +async fn multiple_conn_multiple_streams_sequential() { + for _ in 0..10 { + let mut conn = new_connection_to_echo_server().await; + + for i in 0..10 { + let mut stream = if i % 2 == 0 { + create_stream(&mut conn).await + } else { + incoming_stream(&mut conn).await + }; + + send_recv(&mut stream).await; + } + } +} + +#[wasm_bindgen_test] +async fn read_leftovers() { + let mut conn = new_connection_to_echo_server().await; + let mut stream = create_stream(&mut conn).await; + + // Test that stream works + send_recv(&mut stream).await; + + stream.write_all(b"hello").await.unwrap(); + + let mut buf = [0u8; 3]; + + // Read first half + let len = stream.read(&mut buf[..]).await.unwrap(); + assert_eq!(len, 3); + assert_eq!(&buf[..len], b"hel"); + + // Read second half + let len = stream.read(&mut buf[..]).await.unwrap(); + assert_eq!(len, 2); + assert_eq!(&buf[..len], b"lo"); +} + +#[wasm_bindgen_test] +async fn allow_read_after_closing_writer() { + let mut conn = new_connection_to_echo_server().await; + let mut stream = create_stream(&mut conn).await; + + // Test that stream works + send_recv(&mut stream).await; + + // Write random data + let mut send_buf = [0u8; 1024]; + getrandom(&mut send_buf).unwrap(); + stream.write_all(&send_buf).await.unwrap(); + + // Close writer by calling AsyncWrite::poll_close + stream.close().await.unwrap(); + + // Make sure writer is closed + stream.write_all(b"1").await.unwrap_err(); + + // We should be able to read + let mut recv_buf = [0u8; 1024]; + stream.read_exact(&mut recv_buf).await.unwrap(); + + assert_eq!(send_buf, recv_buf); +} + +#[wasm_bindgen_test] +async fn poll_outbound_error_after_connection_close() { + let mut conn = new_connection_to_echo_server().await; + + // Make sure that poll_outbound works well before closing the connection + let mut stream = create_stream(&mut conn).await; + send_recv(&mut stream).await; + drop(stream); + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + poll_fn(|cx| Pin::new(&mut conn).poll_outbound(cx)) + .await + .expect_err("poll_outbound error after conn closed"); +} + +#[wasm_bindgen_test] +async fn poll_inbound_error_after_connection_close() { + let mut conn = new_connection_to_echo_server().await; + + // Make sure that poll_inbound works well before closing the connection + let mut stream = incoming_stream(&mut conn).await; + send_recv(&mut stream).await; + drop(stream); + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + poll_fn(|cx| Pin::new(&mut conn).poll_inbound(cx)) + .await + .expect_err("poll_inbound error after conn closed"); +} + +#[wasm_bindgen_test] +async fn read_error_after_connection_drop() { + let mut conn = new_connection_to_echo_server().await; + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + drop(conn); + + let mut buf = [0u8; 16]; + stream + .read(&mut buf) + .await + .expect_err("read error after conn drop"); +} + +#[wasm_bindgen_test] +async fn read_error_after_connection_close() { + let mut conn = new_connection_to_echo_server().await; + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + let mut buf = [0u8; 16]; + stream + .read(&mut buf) + .await + .expect_err("read error after conn drop"); +} + +#[wasm_bindgen_test] +async fn write_error_after_connection_drop() { + let mut conn = new_connection_to_echo_server().await; + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + drop(conn); + + let buf = [0u8; 16]; + stream + .write(&buf) + .await + .expect_err("write error after conn drop"); +} + +#[wasm_bindgen_test] +async fn write_error_after_connection_close() { + let mut conn = new_connection_to_echo_server().await; + let mut stream = create_stream(&mut conn).await; + + send_recv(&mut stream).await; + + poll_fn(|cx| Pin::new(&mut conn).poll_close(cx)) + .await + .unwrap(); + + let buf = [0u8; 16]; + stream + .write(&buf) + .await + .expect_err("write error after conn drop"); +} + +#[wasm_bindgen_test] +async fn connect_without_peer_id() { + let mut addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + // Remove peer id + addr.pop(); + + let mut transport = Transport::new(Config::new(&keypair)); + transport.dial(addr).unwrap().await.unwrap(); +} + +#[wasm_bindgen_test] +async fn error_on_unknown_peer_id() { + let mut addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + // Remove peer id + addr.pop(); + + // Add an unknown one + addr.push(Protocol::P2p(PeerId::random())); + + let mut transport = Transport::new(Config::new(&keypair)); + let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); + assert!(matches!(e, Error::UnknownRemotePeerId)); +} + +#[wasm_bindgen_test] +async fn error_on_unknown_certhash() { + let mut addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + // Remove peer id + let peer_id = addr.pop().unwrap(); + + // Add unknown certhash + addr.push(Protocol::Certhash(Multihash::wrap(1, b"1").unwrap())); + + // Add peer id back + addr.push(peer_id); + + let mut transport = Transport::new(Config::new(&keypair)); + let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); + assert!(matches!( + e, + Error::Noise(noise::Error::UnknownWebTransportCerthashes(..)) + )); +} + +async fn new_connection_to_echo_server() -> Connection { + let addr = fetch_server_addr().await; + let keypair = Keypair::generate_ed25519(); + + let mut transport = Transport::new(Config::new(&keypair)); + + let (_peer_id, conn) = transport.dial(addr).unwrap().await.unwrap(); + + conn +} + +/// Helper that returns the multiaddress of echo-server +/// +/// It fetches the multiaddress via HTTP request to +/// 127.0.0.1:4455. +async fn fetch_server_addr() -> Multiaddr { + let url = "http://127.0.0.1:4455/"; + let window = window().expect("failed to get browser window"); + + let value = JsFuture::from(window.fetch_with_str(url)) + .await + .expect("fetch failed"); + let resp = value.dyn_into::().expect("cast failed"); + + let text = resp.text().expect("text failed"); + let text = JsFuture::from(text).await.expect("text promise failed"); + + text.as_string() + .filter(|s| !s.is_empty()) + .expect("response not a text") + .parse() + .unwrap() +} + +async fn create_stream(conn: &mut Connection) -> Stream { + poll_fn(|cx| Pin::new(&mut *conn).poll_outbound(cx)) + .await + .unwrap() +} + +async fn incoming_stream(conn: &mut Connection) -> Stream { + let mut stream = poll_fn(|cx| Pin::new(&mut *conn).poll_inbound(cx)) + .await + .unwrap(); + + // For the stream to be initiated `echo-server` sends a single byte + let mut buf = [0u8; 1]; + stream.read_exact(&mut buf).await.unwrap(); + + stream +} + +fn send_recv_task(mut steam: Stream) -> oneshot::Receiver<()> { + let (tx, rx) = oneshot::channel(); + + spawn_local(async move { + send_recv(&mut steam).await; + tx.send(()).unwrap(); + }); + + rx +} + +async fn send_recv(stream: &mut Stream) { + let mut send_buf = [0u8; 1024]; + let mut recv_buf = [0u8; 1024]; + + for _ in 0..30 { + getrandom(&mut send_buf).unwrap(); + + stream.write_all(&send_buf).await.unwrap(); + stream.read_exact(&mut recv_buf).await.unwrap(); + + assert_eq!(send_buf, recv_buf); + } +}