From 08d4975e99b91471d8cc62a44f8a897e006e6d8a Mon Sep 17 00:00:00 2001 From: Caio Date: Wed, 27 Sep 2023 17:35:58 -0300 Subject: [PATCH] Add support for compression --- .github/workflows/ci.yaml | 63 +- .gitignore | 1 - .scripts/autobahn-fuzzingclient.sh | 26 + .scripts/autobahn-fuzzingserver.sh | 25 + .scripts/autobahn.sh | 47 - .scripts/autobahn/fuzzingclient-min.json | 35 + .scripts/autobahn/fuzzingclient.json | 2 +- .scripts/autobahn/fuzzingserver-min.json | 30 + .scripts/autobahn/fuzzingserver.json | 2 +- .scripts/fuzz.sh | 4 +- .scripts/gen-certs.sh | 16 + .scripts/{wtx.sh => internal-tests.sh} | 14 +- .scripts/wtx-bench.sh | 32 +- Cargo.lock | 2161 +++++++++++++++++ Cargo.toml | 2 +- rustfmt.toml | 7 + wtx-bench/Cargo.toml | 1 - wtx-bench/src/main.rs | 304 +-- wtx-fuzz/Cargo.toml | 4 +- wtx-fuzz/parse_frame.rs | 36 +- wtx-fuzz/unmask.rs | 4 +- wtx-ui/Cargo.toml | 22 + wtx-ui/README.md | 3 + wtx-ui/src/clap.rs | 51 + wtx-ui/src/main.rs | 10 + wtx-ui/src/misc.rs | 88 + wtx/Cargo.toml | 77 +- wtx/README.md | 25 +- wtx/benches/simple.rs | 18 +- wtx/examples/cert.pem | 20 + wtx/examples/common/mod.rs | 119 +- wtx/examples/key.pem | 28 + wtx/examples/localhost.crt | 28 - wtx/examples/localhost.key | 52 - wtx/examples/root-ca.crt | 19 + wtx/examples/root-ca.key | 28 + .../web-socket-client-cli-raw-tokio-rustls.rs | 81 + .../web-socket-server-echo-raw-async-std.rs | 29 + .../web-socket-server-echo-raw-glommio.rs | 26 + .../web-socket-server-echo-raw-monoio.rs | 22 + .../web-socket-server-echo-raw-smol.rs | 25 + ...web-socket-server-echo-raw-tokio-rustls.rs | 55 + .../web-socket-server-echo-raw-tokio-uring.rs | 23 + .../web-socket-server-echo-raw-tokio.rs | 26 + .../web_socket_client_autobahn_raw_tokio.rs | 95 - .../web_socket_client_cli_raw_tokio_rustls.rs | 62 - wtx/examples/web_socket_server_echo_hyper.rs | 48 - .../web_socket_server_echo_raw_async_std.rs | 25 - .../web_socket_server_echo_raw_glommio.rs | 47 - .../web_socket_server_echo_raw_tokio.rs | 24 - ...web_socket_server_echo_raw_tokio_rustls.rs | 52 - wtx/profiling/web_socket.rs | 62 - wtx/src/bin/autobahn-client.rs | 100 + wtx/src/bin/autobahn-server.rs | 27 + wtx/src/bin/web-socket-profiling.rs | 62 + wtx/src/buffer.rs | 62 + wtx/src/cache.rs | 46 +- wtx/src/error.rs | 275 ++- wtx/src/expected_header.rs | 16 +- wtx/src/http_structs.rs | 12 + wtx/src/http_structs/header.rs | 58 + wtx/src/http_structs/parse_status.rs | 18 + wtx/src/http_structs/request.rs | 86 + wtx/src/http_structs/response.rs | 90 + wtx/src/lib.rs | 31 +- wtx/src/macros.rs | 38 + wtx/src/misc.rs | 118 +- wtx/src/misc/incomplete_utf8_char.rs | 115 +- wtx/src/misc/rng.rs | 26 - wtx/src/misc/traits.rs | 60 +- wtx/src/misc/uri_parts.rs | 63 +- wtx/src/misc/utf8_errors.rs | 10 +- wtx/src/partitioned_buffer.rs | 177 ++ wtx/src/read_buffer.rs | 103 - wtx/src/request.rs | 75 - wtx/src/response.rs | 82 - wtx/src/rng.rs | 133 + wtx/src/role.rs | 24 + wtx/src/stream.rs | 428 ++-- wtx/src/web_socket.rs | 1573 +++++++----- wtx/src/web_socket/close_code.rs | 171 +- wtx/src/web_socket/compression.rs | 202 ++ .../compression/compression_level.rs | 43 + .../web_socket/compression/deflate_config.rs | 23 + wtx/src/web_socket/compression/flate2.rs | 265 ++ wtx/src/web_socket/compression/window_bits.rs | 47 + wtx/src/web_socket/frame.rs | 272 +-- wtx/src/web_socket/frame_buffer.rs | 376 +-- wtx/src/web_socket/handshake.rs | 84 +- wtx/src/web_socket/handshake/hyper.rs | 191 -- wtx/src/web_socket/handshake/misc.rs | 64 +- wtx/src/web_socket/handshake/raw.rs | 435 ++-- wtx/src/web_socket/handshake/tests.rs | 506 ++-- wtx/src/web_socket/mask.rs | 123 +- wtx/src/web_socket/misc.rs | 119 + wtx/src/web_socket/op_code.rs | 54 +- wtx/src/web_socket/web_socket_error.rs | 62 +- 97 files changed, 7371 insertions(+), 3680 deletions(-) create mode 100755 .scripts/autobahn-fuzzingclient.sh create mode 100755 .scripts/autobahn-fuzzingserver.sh delete mode 100755 .scripts/autobahn.sh create mode 100644 .scripts/autobahn/fuzzingclient-min.json create mode 100644 .scripts/autobahn/fuzzingserver-min.json create mode 100644 .scripts/gen-certs.sh rename .scripts/{wtx.sh => internal-tests.sh} (69%) create mode 100644 Cargo.lock create mode 100644 rustfmt.toml create mode 100644 wtx-ui/Cargo.toml create mode 100644 wtx-ui/README.md create mode 100644 wtx-ui/src/clap.rs create mode 100644 wtx-ui/src/main.rs create mode 100644 wtx-ui/src/misc.rs create mode 100644 wtx/examples/cert.pem create mode 100644 wtx/examples/key.pem delete mode 100644 wtx/examples/localhost.crt delete mode 100644 wtx/examples/localhost.key create mode 100644 wtx/examples/root-ca.crt create mode 100644 wtx/examples/root-ca.key create mode 100644 wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs create mode 100644 wtx/examples/web-socket-server-echo-raw-async-std.rs create mode 100644 wtx/examples/web-socket-server-echo-raw-glommio.rs create mode 100644 wtx/examples/web-socket-server-echo-raw-monoio.rs create mode 100644 wtx/examples/web-socket-server-echo-raw-smol.rs create mode 100644 wtx/examples/web-socket-server-echo-raw-tokio-rustls.rs create mode 100644 wtx/examples/web-socket-server-echo-raw-tokio-uring.rs create mode 100644 wtx/examples/web-socket-server-echo-raw-tokio.rs delete mode 100644 wtx/examples/web_socket_client_autobahn_raw_tokio.rs delete mode 100644 wtx/examples/web_socket_client_cli_raw_tokio_rustls.rs delete mode 100644 wtx/examples/web_socket_server_echo_hyper.rs delete mode 100644 wtx/examples/web_socket_server_echo_raw_async_std.rs delete mode 100644 wtx/examples/web_socket_server_echo_raw_glommio.rs delete mode 100644 wtx/examples/web_socket_server_echo_raw_tokio.rs delete mode 100644 wtx/examples/web_socket_server_echo_raw_tokio_rustls.rs delete mode 100644 wtx/profiling/web_socket.rs create mode 100644 wtx/src/bin/autobahn-client.rs create mode 100644 wtx/src/bin/autobahn-server.rs create mode 100644 wtx/src/bin/web-socket-profiling.rs create mode 100644 wtx/src/buffer.rs create mode 100644 wtx/src/http_structs.rs create mode 100644 wtx/src/http_structs/header.rs create mode 100644 wtx/src/http_structs/parse_status.rs create mode 100644 wtx/src/http_structs/request.rs create mode 100644 wtx/src/http_structs/response.rs create mode 100644 wtx/src/macros.rs delete mode 100644 wtx/src/misc/rng.rs create mode 100644 wtx/src/partitioned_buffer.rs delete mode 100644 wtx/src/read_buffer.rs delete mode 100644 wtx/src/request.rs delete mode 100644 wtx/src/response.rs create mode 100644 wtx/src/rng.rs create mode 100644 wtx/src/role.rs create mode 100644 wtx/src/web_socket/compression.rs create mode 100644 wtx/src/web_socket/compression/compression_level.rs create mode 100644 wtx/src/web_socket/compression/deflate_config.rs create mode 100644 wtx/src/web_socket/compression/flate2.rs create mode 100644 wtx/src/web_socket/compression/window_bits.rs delete mode 100644 wtx/src/web_socket/handshake/hyper.rs create mode 100644 wtx/src/web_socket/misc.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6387d3a8..e78f72aa 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,10 +6,10 @@ on: - main jobs: - autobahn: + autobahn-fuzzingclient: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: override: true @@ -17,28 +17,47 @@ jobs: toolchain: nightly-2023-08-01 - uses: Swatinem/rust-cache@v2 - - run: .scripts/autobahn.sh ci - -# fuzz: -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v3 -# - uses: actions-rs/toolchain@v1 -# with: -# override: true -# profile: minimal -# toolchain: nightly-2023-08-01 -# - uses: actions-rs/install@v0.1 -# with: -# crate: cargo-fuzz -# use-tool-cache: true -# -# - run: .scripts/fuzz.sh + - run: .scripts/autobahn-fuzzingclient.sh ci + strategy: + fail-fast: true + + autobahn-fuzzingserver: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + override: true + profile: minimal + toolchain: nightly-2023-08-01 + - uses: Swatinem/rust-cache@v2 + + - run: .scripts/autobahn-fuzzingserver.sh ci + strategy: + fail-fast: true + + fuzz: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + override: true + profile: minimal + toolchain: nightly-2023-08-01 + - uses: actions-rs/install@v0.1 + with: + crate: cargo-fuzz + use-tool-cache: true + + - run: .scripts/fuzz.sh + strategy: + fail-fast: true tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: components: clippy, rustfmt @@ -47,4 +66,6 @@ jobs: toolchain: nightly-2023-08-01 - uses: Swatinem/rust-cache@v2 - - run: .scripts/wtx.sh \ No newline at end of file + - run: .scripts/internal-tests.sh + strategy: + fail-fast: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index f6e49f12..2aef59e4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ .vscode **/*.rs.bk **/artifacts -**/Cargo.lock **/corpus **/target **/target \ No newline at end of file diff --git a/.scripts/autobahn-fuzzingclient.sh b/.scripts/autobahn-fuzzingclient.sh new file mode 100755 index 00000000..158cfb4a --- /dev/null +++ b/.scripts/autobahn-fuzzingclient.sh @@ -0,0 +1,26 @@ +set -euxo pipefail + +ARG=${1:-""} +if [ "$ARG" != "ci" ]; then + trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT +fi; + +## fuzzingclient + +RUSTFLAGS='-C target-cpu=native' cargo build --bin autobahn-server --features flate2,tokio,web-socket-handshake --release +RUSTFLAGS='-C target-cpu=native' cargo run --bin autobahn-server --features flate2,tokio,web-socket-handshake --release & cargo_pid=$! +mkdir -p .scripts/autobahn/reports/fuzzingclient +podman run \ + -v .scripts/autobahn/fuzzingclient-min.json:/fuzzingclient.json:ro \ + -v .scripts/autobahn:/autobahn \ + --name fuzzingclient \ + --net=host \ + --rm \ + docker.io/crossbario/autobahn-testsuite:0.8.2 wstest -m fuzzingclient -s fuzzingclient.json +podman rm --force --ignore fuzzingclient +kill -9 $cargo_pid + +if [ $(grep -ci "failed" .scripts/autobahn/reports/fuzzingclient/index.json) -gt 0 ] +then + exit 1 +fi \ No newline at end of file diff --git a/.scripts/autobahn-fuzzingserver.sh b/.scripts/autobahn-fuzzingserver.sh new file mode 100755 index 00000000..9be9624d --- /dev/null +++ b/.scripts/autobahn-fuzzingserver.sh @@ -0,0 +1,25 @@ +set -euxo pipefail + +ARG=${1:-""} +if [ "$ARG" != "ci" ]; then + trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT +fi; + +RUSTFLAGS='-C target-cpu=native' cargo build --bin autobahn-client --features flate2,tokio,web-socket-handshake --release +mkdir -p .scripts/autobahn/reports/fuzzingserver +podman run \ + -d \ + -p 9080:9080 \ + -v .scripts/autobahn/fuzzingserver-min.json:/fuzzingserver.json:ro \ + -v .scripts/autobahn:/autobahn \ + --name fuzzingserver \ + --net=host \ + docker.io/crossbario/autobahn-testsuite:0.8.2 wstest -m fuzzingserver -s fuzzingserver.json +sleep 5 +RUSTFLAGS='-C target-cpu=native' cargo run --bin autobahn-client --features flate2,tokio,web-socket-handshake --release -- 127.0.0.1:9080 +podman rm --force --ignore fuzzingserver + +if [ $(grep -ci "failed" .scripts/autobahn/reports/fuzzingserver/index.json) -gt 0 ] +then + exit 1 +fi diff --git a/.scripts/autobahn.sh b/.scripts/autobahn.sh deleted file mode 100755 index 30b6b75a..00000000 --- a/.scripts/autobahn.sh +++ /dev/null @@ -1,47 +0,0 @@ -set -euxo pipefail - -ARG=${1:-""} -if [ "$ARG" != "ci" ]; then - trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT -fi; - -# fuzzingclient - -cargo build --example web_socket_server_echo_raw_tokio --features tokio,web-socket-handshake --release -cargo run --example web_socket_server_echo_raw_tokio --features tokio,web-socket-handshake --release & cargo_pid=$! -mkdir -p .scripts/autobahn/reports/fuzzingclient -podman run \ - -v .scripts/autobahn/fuzzingclient.json:/fuzzingclient.json:ro \ - -v .scripts/autobahn:/autobahn \ - --name fuzzingclient \ - --net=host \ - --rm \ - docker.io/crossbario/autobahn-testsuite:0.8.2 wstest -m fuzzingclient -s fuzzingclient.json -podman rm --force --ignore fuzzingclient -kill -9 $cargo_pid - -if [ $(grep -ci "failed" .scripts/autobahn/reports/fuzzingclient/index.json) -gt 0 ] -then - exit 1 -fi - -## fuzzingserver - -cargo build --example web_socket_client_autobahn_raw_tokio --features tokio,web-socket-handshake --release -mkdir -p .scripts/autobahn/reports/fuzzingserver -podman run \ - -d \ - -p 9080:9080 \ - -v .scripts/autobahn/fuzzingserver.json:/fuzzingserver.json:ro \ - -v .scripts/autobahn:/autobahn \ - --name fuzzingserver \ - --net=host \ - docker.io/crossbario/autobahn-testsuite:0.8.2 wstest -m fuzzingserver -s fuzzingserver.json -sleep 5 -cargo run --example web_socket_client_autobahn_raw_tokio --features tokio,web-socket-handshake --release -- 127.0.0.1:9080 -podman rm --force --ignore fuzzingserver - -if [ $(grep -ci "failed" .scripts/autobahn/reports/fuzzingserver/index.json) -gt 0 ] -then - exit 1 -fi diff --git a/.scripts/autobahn/fuzzingclient-min.json b/.scripts/autobahn/fuzzingclient-min.json new file mode 100644 index 00000000..7b65ec3a --- /dev/null +++ b/.scripts/autobahn/fuzzingclient-min.json @@ -0,0 +1,35 @@ +{ + "cases": [ + "1.*", + "2.*", + "3.*", + "4.*", + "5.*", + "6.*", + "7.*", + "8.*", + "9.*", + "10.*", + "12.1.1", + "12.2.1", + "12.3.1", + "12.4.1", + "12.5.1", + "13.1.1", + "13.2.1", + "13.3.1", + "13.4.1", + "13.5.1", + "13.6.1", + "13.7.1" + ], + "exclude-agent-cases": {}, + "exclude-cases": [], + "outdir": "/autobahn/reports/fuzzingclient", + "servers": [ + { + "agent": "wtx", + "url": "ws://127.0.0.1:8080" + } + ] +} diff --git a/.scripts/autobahn/fuzzingclient.json b/.scripts/autobahn/fuzzingclient.json index f2567006..42201b3d 100644 --- a/.scripts/autobahn/fuzzingclient.json +++ b/.scripts/autobahn/fuzzingclient.json @@ -1,5 +1,5 @@ { - "cases": ["1.*", "2.*", "3.*", "4.*", "5.*", "6.*", "7.*", "9.*", "10.*"], + "cases": ["*"], "exclude-agent-cases": {}, "exclude-cases": [], "outdir": "/autobahn/reports/fuzzingclient", diff --git a/.scripts/autobahn/fuzzingserver-min.json b/.scripts/autobahn/fuzzingserver-min.json new file mode 100644 index 00000000..26740937 --- /dev/null +++ b/.scripts/autobahn/fuzzingserver-min.json @@ -0,0 +1,30 @@ +{ + "cases": [ + "1.*", + "2.*", + "3.*", + "4.*", + "5.*", + "6.*", + "7.*", + "8.*", + "9.*", + "10.*", + "12.1.1", + "12.2.1", + "12.3.1", + "12.4.1", + "12.5.1", + "13.1.1", + "13.2.1", + "13.3.1", + "13.4.1", + "13.5.1", + "13.6.1", + "13.7.1" + ], + "exclude-agent-cases": {}, + "exclude-cases": [], + "outdir": "/autobahn/reports/fuzzingserver", + "url": "ws://127.0.0.1:9080" +} \ No newline at end of file diff --git a/.scripts/autobahn/fuzzingserver.json b/.scripts/autobahn/fuzzingserver.json index 96fde560..973cc8c0 100644 --- a/.scripts/autobahn/fuzzingserver.json +++ b/.scripts/autobahn/fuzzingserver.json @@ -1,5 +1,5 @@ { - "cases": ["1.*", "2.*", "3.*", "4.*", "5.*", "6.*", "7.*", "9.*", "10.*"], + "cases": ["*"], "exclude-agent-cases": {}, "exclude-cases": [], "outdir": "/autobahn/reports/fuzzingserver", diff --git a/.scripts/fuzz.sh b/.scripts/fuzz.sh index 781efd83..c796b72a 100755 --- a/.scripts/fuzz.sh +++ b/.scripts/fuzz.sh @@ -2,5 +2,5 @@ set -euxo pipefail -cargo fuzz run --fuzz-dir wtx-fuzz unmask -- -runs=100000 -cargo fuzz run --fuzz-dir wtx-fuzz parse-frame -- -runs=100000 +cargo fuzz run --features="libfuzzer-sys/link_libfuzzer" --fuzz-dir wtx-fuzz parse-frame -- -max_total_time=30 +cargo fuzz run --features="libfuzzer-sys/link_libfuzzer" --fuzz-dir wtx-fuzz unmask -- -max_total_time=30 diff --git a/.scripts/gen-certs.sh b/.scripts/gen-certs.sh new file mode 100644 index 00000000..8804bf44 --- /dev/null +++ b/.scripts/gen-certs.sh @@ -0,0 +1,16 @@ +# https://stackoverflow.com/questions/76049656/unexpected-notvalidforname-with-rusts-tonic-with-tls + +openssl req -newkey rsa:2048 -nodes -subj "/C=FI/CN=vahid" -keyout key.pem -out key.csr +openssl x509 -signkey key.pem -in key.csr -req -days 365 -out cert.pem +openssl req -x509 -sha256 -nodes -subj "/C=FI/CN=vahid" -days 1825 -newkey rsa:2048 -keyout root-ca.key -out root-ca.crt +cat <<'EOF' >> localhost.ext +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +subjectAltName = @alt_names +[alt_names] +DNS.1 = server +IP.1 = 127.0.0.1 +EOF +openssl x509 -req -CA root-ca.crt -CAkey root-ca.key -in key.csr -out cert.pem -days 365 -CAcreateserial -extfile localhost.ext +rm localhost.ext +rm key.csr \ No newline at end of file diff --git a/.scripts/wtx.sh b/.scripts/internal-tests.sh similarity index 69% rename from .scripts/wtx.sh rename to .scripts/internal-tests.sh index 81229800..d2e2fbc1 100755 --- a/.scripts/wtx.sh +++ b/.scripts/internal-tests.sh @@ -6,25 +6,29 @@ cargo install rust-tools --git https://github.com/c410-f3r/regular-crates rt='rust-tools --template you-rust' -CARGO_TARGET_DIR="$($rt target-dir)" -RUST_BACKTRACE=1 RUSTFLAGS="$($rt rust-flags)" +export CARGO_TARGET_DIR="$($rt target-dir)" +export RUST_BACKTRACE=1 +export RUST_LOG=debug + $rt rustfmt $rt clippy $rt test-generic wtx $rt test-with-features wtx async-std -$rt test-with-features wtx async-trait $rt test-with-features wtx base64 +$rt test-with-features wtx flate2 $rt test-with-features wtx futures-lite $rt test-with-features wtx glommio $rt test-with-features wtx http $rt test-with-features wtx httparse -$rt test-with-features wtx hyper +$rt test-with-features wtx rand +$rt test-with-features wtx rustls-pemfile $rt test-with-features wtx sha1 $rt test-with-features wtx simdutf8 +$rt test-with-features wtx smol $rt test-with-features wtx std $rt test-with-features wtx tokio +$rt test-with-features wtx tokio-rustls $rt test-with-features wtx web-socket-handshake -$rt test-with-features wtx web-socket-hyper \ No newline at end of file diff --git a/.scripts/wtx-bench.sh b/.scripts/wtx-bench.sh index 04b74fbb..3a6c8cc9 100755 --- a/.scripts/wtx-bench.sh +++ b/.scripts/wtx-bench.sh @@ -35,17 +35,23 @@ fi ./EchoServer 8083 & popd -RUSTFLAGS='-C target-cpu=native' cargo build --example web_socket_server_echo_hyper --features simdutf8,web-socket-hyper --release -RUSTFLAGS='-C target-cpu=native' cargo run --example web_socket_server_echo_hyper --features simdutf8,web-socket-hyper --release 127.0.0.1:8084 & +RUSTFLAGS='-C target-cpu=native' cargo build --example web-socket-server-echo-raw-async-std --features async-std/attributes,simdutf8,web-socket-handshake --release +RUSTFLAGS='-C target-cpu=native' cargo run --example web-socket-server-echo-raw-async-std --features async-std/attributes,simdutf8,web-socket-handshake --release 127.0.0.1:8084 & -RUSTFLAGS='-C target-cpu=native' cargo build --example web_socket_server_echo_raw_async_std --features async-std,simdutf8,web-socket-handshake --release -RUSTFLAGS='-C target-cpu=native' cargo run --example web_socket_server_echo_raw_async_std --features async-std,simdutf8,web-socket-handshake --release 127.0.0.1:8085 & +RUSTFLAGS='-C target-cpu=native' cargo build --example web-socket-server-echo-raw-glommio --features glommio,simdutf8,web-socket-handshake --release +RUSTFLAGS='-C target-cpu=native' cargo run --example web-socket-server-echo-raw-glommio --features glommio,simdutf8,web-socket-handshake --release 127.0.0.1:8085 & -RUSTFLAGS='-C target-cpu=native' cargo build --example web_socket_server_echo_raw_glommio --features glommio,simdutf8,web-socket-handshake --release -RUSTFLAGS='-C target-cpu=native' cargo run --example web_socket_server_echo_raw_glommio --features glommio,simdutf8,web-socket-handshake --release 127.0.0.1:8086 & +RUSTFLAGS='-C target-cpu=native' cargo build --example web-socket-server-echo-raw-monoio --features monoio/iouring,monoio/legacy,monoio/macros,simdutf8,web-socket-handshake --release +RUSTFLAGS='-C target-cpu=native' cargo run --example web-socket-server-echo-raw-monoio --features monoio/iouring,monoio/legacy,monoio/macros,simdutf8,web-socket-handshake --release 127.0.0.1:8086 & -RUSTFLAGS='-C target-cpu=native' cargo build --example web_socket_server_echo_raw_tokio --features simdutf8,tokio,web-socket-handshake --release -RUSTFLAGS='-C target-cpu=native' cargo run --example web_socket_server_echo_raw_tokio --features simdutf8,tokio,web-socket-handshake --release 127.0.0.1:8087 & +RUSTFLAGS='-C target-cpu=native' cargo build --example web-socket-server-echo-raw-smol --features simdutf8,smol,web-socket-handshake --release +RUSTFLAGS='-C target-cpu=native' cargo run --example web-socket-server-echo-raw-smol --features simdutf8,smol,web-socket-handshake --release 127.0.0.1:8087 & + +RUSTFLAGS='-C target-cpu=native' cargo build --example web-socket-server-echo-raw-tokio --features simdutf8,tokio,web-socket-handshake --release +RUSTFLAGS='-C target-cpu=native' cargo run --example web-socket-server-echo-raw-tokio --features simdutf8,tokio,web-socket-handshake --release 127.0.0.1:8088 & + +RUSTFLAGS='-C target-cpu=native' cargo build --example web-socket-server-echo-raw-tokio-uring --features simdutf8,tokio-uring,web-socket-handshake --release +RUSTFLAGS='-C target-cpu=native' cargo run --example web-socket-server-echo-raw-tokio-uring --features simdutf8,tokio-uring,web-socket-handshake --release 127.0.0.1:8089 & sleep 1 @@ -54,7 +60,9 @@ RUSTFLAGS='-C target-cpu=native' cargo run --bin wtx-bench --release -- \ http://127.0.0.1:8081/gorilla-websocket \ http://127.0.0.1:8082/tokio-tungstenite \ http://127.0.0.1:8083/uWebSockets \ - http://127.0.0.1:8084/wtx-hyper \ - http://127.0.0.1:8085/wtx-raw-async-std \ - http://127.0.0.1:8086/wtx-raw-glommio \ - http://127.0.0.1:8087/wtx-raw-tokio + http://127.0.0.1:8084/wtx-raw-async-std \ + http://127.0.0.1:8085/wtx-raw-glommio \ + http://127.0.0.1:8086/wtx-raw-monoio \ + http://127.0.0.1:8087/wtx-raw-smol \ + http://127.0.0.1:8088/wtx-raw-tokio \ + http://127.0.0.1:8089/wtx-raw-tokio-uring \ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..1f9d10f8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2161 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstyle" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" + +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "as-slice" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" +dependencies = [ + "generic-array 0.12.4", + "generic-array 0.13.3", + "generic-array 0.14.7", + "stable_deref_trait", +] + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue 2.3.0", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f2db9467baa66a700abce2a18c5ad793f6f83310aca1284796fc3921d113fd" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue 2.3.0", + "fastrand 2.0.1", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock", + "autocfg", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue 2.3.0", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.23", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-net" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf012553ce51eb7aa6dc2143804cc8252bd1cb681a1c5cb7fa94ca88682dee1d" +dependencies = [ + "async-io", + "async-lock", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.0.0", + "futures-lite", + "rustix 0.38.14", + "windows-sys", +] + +[[package]] +name = "async-signal" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4af361a844928cb7d36590d406709473a1b574f443094422ef166daa3b493208" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "concurrent-queue 2.3.0", + "futures-core", + "futures-io", + "libc", + "signal-hook-registry", + "slab", + "windows-sys", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9441c6b2fe128a7c2bf680a44c34d0df31ce09e5b7e401fcca3faa483dbc921" + +[[package]] +name = "atomic-polyfill" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +dependencies = [ + "critical-section", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "atomic-pool" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c5fc22e05ec2884db458bf307dc7b278c9428888d2b6e6fad9c0ae7804f5f6" +dependencies = [ + "as-slice 0.1.5", + "as-slice 0.2.1", + "atomic-polyfill 1.0.3", + "stable_deref_trait", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "auto-const-array" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f7df18977a1ee03650ee4b31b4aefed6d56bac188760b6e37610400fe8d4bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bitmaps" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "blocking" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c4ef1f913d78636d78d538eec1f18de81e481f44b1be0a81060090530846e1" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "fastrand 2.0.1", + "futures-io", + "futures-lite", + "piper", + "tracing", +] + +[[package]] +name = "buddy-alloc" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3240a4cb09cf0da6a51641bd40ce90e96ea6065e3a1adc46434029254bcc2d09" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cache-padded" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824956d0dca8334758a5b7f7e50518d66ea319330cbceedcf76905c2f6ab30e3" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122ec64120a49b4563ccaedcbea7818d069ed8e9aa6d829b82d8a4128936b2ab" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "concurrent-queue" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "crossbeam" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.9.0", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.7", + "typenum", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "embassy-net" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d812646c1c50452d77293e05bf2eca13c23803447cdf9bbbcf820588f4c9879" +dependencies = [ + "as-slice 0.2.1", + "atomic-polyfill 1.0.3", + "atomic-pool", + "embassy-net-driver", + "embassy-sync", + "embassy-time", + "futures", + "generic-array 0.14.7", + "heapless", + "managed", + "smoltcp", + "stable_deref_trait", +] + +[[package]] +name = "embassy-net-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6a4985a5dab4cb55d09703bfdd7d74f58c12c6e889fd3cbb40ea40462a976e" + +[[package]] +name = "embassy-sync" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0dad296a6f70bfdc32ef52442a31f98c28e1608893c1cecc9b6f419bab005a0" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io", + "futures-util", + "heapless", +] + +[[package]] +name = "embassy-time" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a9532d29ec2949c49079d85fd77422e3f4cdc133a71674743cd3b8d5a56a20" +dependencies = [ + "atomic-polyfill 1.0.3", + "cfg-if", + "critical-section", + "embedded-hal", + "futures-util", + "heapless", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "enclose" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1056f553da426e9c025a662efa48b52e62e0a3a7648aa2d15aeaaf7f0d329357" + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e56284f00d94c1bc7fd3c77027b4623c88c1f53d8d2394c6199f2921dea325" +dependencies = [ + "concurrent-queue 2.3.0", + "parking", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "flate2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "libz-ng-sys", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "glommio" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f09bf53139d5680da6325b4e79c6bc1518e94a65ab74df14b7e3693a8c78b" +dependencies = [ + "ahash", + "backtrace", + "bitflags 1.3.2", + "bitmaps", + "buddy-alloc", + "cc", + "concurrent-queue 1.2.4", + "crossbeam", + "enclose", + "flume", + "futures-lite", + "intrusive-collections", + "lazy_static", + "libc", + "lockfree", + "log", + "nix", + "pin-project-lite", + "rlimit", + "scoped-tls", + "scopeguard", + "signal-hook", + "sketches-ddsketch", + "smallvec", + "socket2 0.4.9", + "tracing", + "typenum", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +dependencies = [ + "atomic-polyfill 0.1.11", + "hash32", + "rustc_version", + "spin 0.9.8", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "intrusive-collections" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b694dc9f70c3bda874626d2aed13b780f137aab435f4e9814121955cf706122e" +dependencies = [ + "memoffset 0.9.0", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "io-uring" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd1e1a01cfb924fd8c5c43b6827965db394f5a3a16c599ce03452266e1cf984c" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "io-uring" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460648e47a07a43110fbfa2e0b14afb2be920093c31e5dccc50e49568e099762" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix 0.38.14", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "libz-ng-sys" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd9f43e75536a46ee0f92b758f6b63846e594e86638c61a9251338a65baea63" +dependencies = [ + "cmake", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "lockfree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ee94b5ad113c7cb98c5a040f783d0952ee4fe100993881d1673c2cb002dd23" +dependencies = [ + "owned-alloc", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +dependencies = [ + "value-bag", +] + +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "monoio" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91e8e1600b215e4878b5c236e211f3365415407a2b3891c62dbcfd22f778860" +dependencies = [ + "auto-const-array", + "fxhash", + "io-uring 0.6.2", + "libc", + "pin-project-lite", + "socket2 0.5.4", + "windows-sys", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[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-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owned-alloc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30fceb411f9a12ff9222c5f824026be368ff15dc2f13468d850c7d3f502205d6" + +[[package]] +name = "parking" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue 2.3.0", + "libc", + "log", + "pin-project-lite", + "windows-sys", +] + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.3.8", + "regex-syntax 0.7.5", +] + +[[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-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rlimit" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0bf25554376fd362f54332b8410a625c71f15445bca32ffdfdf4ec9ac91726" +dependencies = [ + "libc", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys", +] + +[[package]] +name = "rustix" +version = "0.38.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.7", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +dependencies = [ + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +dependencies = [ + "ring", + "untrusted", +] + +[[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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "sketches-ddsketch" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d2ecae5fcf33b122e2e6bd520a57ccf152d2dde3b38c71039df1a6867264ee" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "smol" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + +[[package]] +name = "smoltcp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2e3a36ac8fea7b94e666dfa3871063d6e0a5c9d5d4fec9a1a6b7b6760f0229" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "heapless", + "managed", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2 0.5.4", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-uring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d5e02bb137e030b3a547c65a3bd2f1836d66a97369fdcc69034002b10e155ef" +dependencies = [ + "io-uring 0.5.13", + "libc", + "scoped-tls", + "slab", + "socket2 0.4.9", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "tracing-tree" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d6b63348fad3ae0439b8bebf8d38fb5bda0b115d7a8a7e6f165f12790c58c3" +dependencies = [ + "is-terminal", + "nu-ansi-term", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "wtx" +version = "0.5.2" +dependencies = [ + "arbitrary", + "async-std", + "base64", + "embassy-net", + "flate2", + "futures-lite", + "glommio", + "http", + "httparse", + "monoio", + "rand", + "rustls-pemfile", + "sha1", + "simdutf8", + "smol", + "tokio", + "tokio-rustls", + "tokio-uring", + "tracing", + "tracing-subscriber", + "tracing-tree", + "webpki-roots", + "wtx", +] + +[[package]] +name = "wtx-bench" +version = "0.0.1" +dependencies = [ + "plotters", + "tokio", + "wtx", +] + +[[package]] +name = "wtx-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "tokio", + "wtx", +] + +[[package]] +name = "wtx-ui" +version = "0.1.0" +dependencies = [ + "clap", + "tokio", + "wtx", +] diff --git a/Cargo.toml b/Cargo.toml index e1a89c36..968b4c9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,5 +15,5 @@ rpath = false strip = "debuginfo" [workspace] -members = ["wtx-bench", "wtx", "wtx-fuzz"] +members = ["wtx-bench", "wtx", "wtx-fuzz", "wtx-ui"] resolver = "2" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..224e3a03 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,7 @@ +edition="2021" +imports_granularity = "Crate" +newline_style = "Unix" +reorder_imports = true +tab_spaces=2 +use_field_init_shorthand=true +use_small_heuristics="Max" diff --git a/wtx-bench/Cargo.toml b/wtx-bench/Cargo.toml index 98f9ddb4..6ba3a672 100644 --- a/wtx-bench/Cargo.toml +++ b/wtx-bench/Cargo.toml @@ -1,5 +1,4 @@ [dependencies] -hyper = { default-features = false, features = ["client", "http1", "server", "tcp"], version = "0.14" } plotters = { default-features = false, features = ["histogram", "svg_backend"], version = "0.3" } tokio = { default-features = false, features = ["macros", "rt-multi-thread"], version = "1.0" } wtx = { features = ["tokio", "web-socket-handshake"], path = "../wtx" } diff --git a/wtx-bench/src/main.rs b/wtx-bench/src/main.rs index 5970dc53..acf50030 100644 --- a/wtx-bench/src/main.rs +++ b/wtx-bench/src/main.rs @@ -8,168 +8,142 @@ )] use plotters::{ - prelude::{ - ChartBuilder, IntoDrawingArea, IntoSegmentedCoord, LabelAreaPosition, PathElement, - SVGBackend, SeriesLabelPosition, - }, - series::Histogram, - style::{AsRelative, Color, Palette99, PaletteColor, BLACK, WHITE}, + prelude::{ + ChartBuilder, IntoDrawingArea, IntoSegmentedCoord, LabelAreaPosition, PathElement, SVGBackend, + SeriesLabelPosition, + }, + series::Histogram, + style::{AsRelative, Color, Palette99, PaletteColor, BLACK, WHITE}, }; use std::time::Instant; use tokio::{net::TcpStream, task::JoinSet}; use wtx::{ - web_socket::{ - handshake::{WebSocketHandshake, WebSocketHandshakeRaw}, - FrameBufferVec, FrameVecMut, OpCode, WebSocketClientOwned, - }, - UriParts, + rng::StaticRng, + web_socket::{ + handshake::WebSocketConnectRaw, FrameBufferVec, FrameMutVec, OpCode, WebSocketClientOwned, + }, + UriParts, }; // Verifies the handling of concurrent calls. -const CONNECTIONS: usize = 1; +const CONNECTIONS: usize = 2048; // Some applications use WebSocket to perform streaming so the length of a frame can be quite large // but statistically it is generally low. -const FRAME_LEN: usize = 1; +const FRAME_LEN: usize = 64 * 1024; // For each message, the client always verifies the content sent back from a server and this // leads to a sequential-like behavior. // // If this is the only high metric, all different servers end-up performing similarly effectively // making this criteria an "augmenting factor" when combined with other parameters. -const NUM_MESSAGES: usize = 1; +const NUM_MESSAGES: usize = 16; // Automatically calculated. const NUM_FRAMES: usize = { - let n = NUM_MESSAGES / 4; - if n == 0 { - 1 - } else { - n - } + let n = NUM_MESSAGES / 4; + if n == 0 { + 1 + } else { + n + } }; static FRAME_DATA: &[u8; FRAME_LEN] = &[53; FRAME_LEN]; #[tokio::main] async fn main() { - let uris: Vec<_> = std::env::args().skip(1).collect(); - let mut agents = Vec::new(); - for uri in uris { - let uri_parts = UriParts::from(uri.as_str()); - let mut agent = Agent { - result: 0, - name: uri_parts.href.to_owned(), - }; - bench(uri_parts.authority, &mut agent, &uri).await; - agents.push(agent); - } - flush(&agents); + let uris: Vec<_> = std::env::args().skip(1).collect(); + let mut agents = Vec::new(); + for uri in uris { + let uri_parts = UriParts::from(uri.as_str()); + let mut agent = Agent { result: 0, name: uri_parts.href.to_owned() }; + bench(uri_parts.authority, &mut agent, &uri).await; + agents.push(agent); + } + flush(&agents); } async fn bench(addr: &str, agent: &mut Agent, uri: &str) { - let instant = Instant::now(); - let mut set = JoinSet::new(); - for _ in 0..CONNECTIONS { - let _handle = set.spawn({ - let local_addr: String = addr.to_owned(); - let local_uri = uri.to_owned(); - async move { - let fb = &mut FrameBufferVec::default(); - let mut ws = ws(&local_addr, fb, &local_uri).await; - for _ in 0..NUM_MESSAGES { - match NUM_FRAMES { - 0 => break, - 1 => { - ws.write_frame( - &mut FrameVecMut::new_fin(fb.into(), OpCode::Text, FRAME_DATA) - .unwrap(), - ) - .await - .unwrap(); - } - 2 => { - ws.write_frame( - &mut FrameVecMut::new_unfin(fb.into(), OpCode::Text, FRAME_DATA) - .unwrap(), - ) - .await - .unwrap(); - ws.write_frame( - &mut FrameVecMut::new_fin( - fb.into(), - OpCode::Continuation, - FRAME_DATA, - ) - .unwrap(), - ) - .await - .unwrap(); - } - _ => { - ws.write_frame( - &mut FrameVecMut::new_unfin(fb.into(), OpCode::Text, FRAME_DATA) - .unwrap(), - ) - .await - .unwrap(); - for _ in (0..NUM_FRAMES).skip(2) { - ws.write_frame( - &mut FrameVecMut::new_unfin( - fb.into(), - OpCode::Continuation, - FRAME_DATA, - ) - .unwrap(), - ) - .await - .unwrap(); - } - ws.write_frame( - &mut FrameVecMut::new_fin( - fb.into(), - OpCode::Continuation, - FRAME_DATA, - ) - .unwrap(), - ) - .await - .unwrap(); - } - } - assert_eq!( - ws.read_frame(fb).await.unwrap().fb().payload().len(), - FRAME_LEN * NUM_FRAMES - ); - } - ws.write_frame(&mut FrameVecMut::new_fin(fb.into(), OpCode::Close, &[]).unwrap()) - .await - .unwrap(); + let instant = Instant::now(); + let mut set = JoinSet::new(); + for _ in 0..CONNECTIONS { + let _handle = set.spawn({ + let local_addr: String = addr.to_owned(); + let local_uri = uri.to_owned(); + async move { + let fb = &mut FrameBufferVec::default(); + let (_, mut ws) = WebSocketClientOwned::connect(WebSocketConnectRaw { + compression: (), + fb, + headers_buffer: &mut <_>::default(), + pb: <_>::default(), + rng: StaticRng::default(), + stream: TcpStream::connect(&local_addr).await.unwrap(), + uri: &local_uri, + }) + .await + .unwrap(); + for _ in 0..NUM_MESSAGES { + match NUM_FRAMES { + 0 => break, + 1 => { + ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Text, FRAME_DATA).unwrap()) + .await + .unwrap(); + } + 2 => { + ws.write_frame(&mut FrameMutVec::new_unfin(fb, OpCode::Text, FRAME_DATA).unwrap()) + .await + .unwrap(); + ws.write_frame( + &mut FrameMutVec::new_fin(fb, OpCode::Continuation, FRAME_DATA).unwrap(), + ) + .await + .unwrap(); + } + _ => { + ws.write_frame(&mut FrameMutVec::new_unfin(fb, OpCode::Text, FRAME_DATA).unwrap()) + .await + .unwrap(); + for _ in (0..NUM_FRAMES).skip(2) { + ws.write_frame( + &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, FRAME_DATA).unwrap(), + ) + .await + .unwrap(); + } + ws.write_frame( + &mut FrameMutVec::new_fin(fb, OpCode::Continuation, FRAME_DATA).unwrap(), + ) + .await + .unwrap(); } - }); - } - while let Some(rslt) = set.join_next().await { - rslt.unwrap(); - } - agent.result = instant.elapsed().as_millis(); + } + assert_eq!(ws.read_frame(fb).await.unwrap().fb().payload().len(), FRAME_LEN * NUM_FRAMES); + } + ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Close, &[]).unwrap()).await.unwrap(); + } + }); + } + while let Some(rslt) = set.join_next().await { + rslt.unwrap(); + } + agent.result = instant.elapsed().as_millis(); } fn flush(agents: &[Agent]) { - if agents.is_empty() { - return; - } - let x_spec = agents - .iter() - .map(|el| &el.name) - .cloned() - .collect::>(); - let root = SVGBackend::new("/tmp/wtx-bench.png", (1000, 500)).into_drawing_area(); - root.fill(&WHITE).unwrap(); - let mut ctx = ChartBuilder::on(&root) + if agents.is_empty() { + return; + } + let x_spec = agents.iter().map(|el| &el.name).cloned().collect::>(); + let root = SVGBackend::new("/tmp/wtx-bench.png", (1280, 780)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let mut ctx = ChartBuilder::on(&root) .caption( format!("{CONNECTIONS} connection(s) sending {NUM_MESSAGES} message(s) composed by {NUM_FRAMES} frame(s) of {FRAME_LEN} byte(s)"), ("sans-serif", (4).percent_height()), ) .margin((1).percent()) - .set_label_area_size(LabelAreaPosition::Left, (15).percent()) + .set_label_area_size(LabelAreaPosition::Left, (10).percent()) .set_label_area_size(LabelAreaPosition::Bottom, (5).percent()) .build_cartesian_2d(x_spec.into_segmented(), { let start = 0u128; @@ -178,54 +152,38 @@ fn flush(agents: &[Agent]) { start..surplus_end }) .unwrap(); - ctx.configure_mesh() - .axis_desc_style(("sans-serif", 15)) - .bold_line_style(WHITE.mix(0.3)) - .y_desc("Time (ms)") - .draw() - .unwrap(); - for (idx, agent) in agents.iter().enumerate() { - let _ = ctx - .draw_series( - Histogram::vertical(&ctx) - .style(PaletteColor::::pick(idx).mix(0.5).filled()) - .data([(&agent.name, agent.result)]), - ) - .unwrap() - .label(format!("{} ({}ms)", &agent.name, agent.result)) - .legend(move |(x, y)| { - PathElement::new([(x, y), (x + 20, y)], PaletteColor::::pick(idx)) - }); - } - ctx.configure_series_labels() - .border_style(BLACK) - .background_style(WHITE.mix(0.8)) - .position(SeriesLabelPosition::UpperRight) - .draw() - .unwrap(); - root.present().unwrap(); -} - -async fn ws( - authority: &str, - fb: &mut FrameBufferVec, - uri: &str, -) -> WebSocketClientOwned { - WebSocketHandshakeRaw { - fb, - headers_buffer: &mut <_>::default(), - rb: <_>::default(), - stream: TcpStream::connect(authority).await.unwrap(), - uri, - } - .handshake() - .await - .unwrap() - .1 + ctx + .configure_mesh() + .axis_desc_style(("sans-serif", 15)) + .bold_line_style(WHITE.mix(0.3)) + .y_desc("Time (ms)") + .draw() + .unwrap(); + for (idx, agent) in agents.iter().enumerate() { + let _ = ctx + .draw_series( + Histogram::vertical(&ctx) + .style(PaletteColor::::pick(idx).mix(0.5).filled()) + .data([(&agent.name, agent.result)]), + ) + .unwrap() + .label(format!("{} ({}ms)", &agent.name, agent.result)) + .legend(move |(x, y)| { + PathElement::new([(x, y), (x + 20, y)], PaletteColor::::pick(idx)) + }); + } + ctx + .configure_series_labels() + .border_style(BLACK) + .background_style(WHITE.mix(0.8)) + .position(SeriesLabelPosition::UpperRight) + .draw() + .unwrap(); + root.present().unwrap(); } #[derive(Debug)] struct Agent { - result: u128, - name: String, + result: u128, + name: String, } diff --git a/wtx-fuzz/Cargo.toml b/wtx-fuzz/Cargo.toml index 5173305b..54887fdc 100644 --- a/wtx-fuzz/Cargo.toml +++ b/wtx-fuzz/Cargo.toml @@ -1,15 +1,17 @@ [[bin]] name = "parse-frame" path = "parse_frame.rs" +required-features = ["libfuzzer-sys/link_libfuzzer"] [[bin]] name = "unmask" path = "unmask.rs" +required-features = ["libfuzzer-sys/link_libfuzzer"] [dependencies] libfuzzer-sys = { default-features = false, version = "0.4" } tokio = { default-features = false, features = ["rt"], version = "1.0" } -wtx = { default-features = false, path = "../wtx" } +wtx = { default-features = false, features = ["arbitrary"], path = "../wtx" } [package] name = "wtx-fuzz" diff --git a/wtx-fuzz/parse_frame.rs b/wtx-fuzz/parse_frame.rs index 66edc2fa..b3b5cd49 100644 --- a/wtx-fuzz/parse_frame.rs +++ b/wtx-fuzz/parse_frame.rs @@ -6,20 +6,30 @@ )] #![no_main] -use tokio::runtime::Handle; +use tokio::runtime::Builder; use wtx::{ - web_socket::{FrameBufferVec, FrameVecMut, OpCode, WebSocketServer}, - BytesStream, ReadBuffer, + rng::StaticRng, + web_socket::{FrameBufferVec, FrameMutVec, OpCode, WebSocketServerOwned}, + BytesStream, PartitionedBuffer, }; -libfuzzer_sys::fuzz_target!(|data: &[u8]| { - let mut ws = WebSocketServer::new(ReadBuffer::default(), BytesStream::default()); - ws.set_max_payload_len(u16::MAX.into()); - let fb = &mut FrameBufferVec::default(); - Handle::current().block_on(async move { - ws.write_frame(&mut FrameVecMut::new_fin(fb.into(), OpCode::Text, data).unwrap()) - .await - .unwrap(); - let _frame = ws.read_frame(fb).await.unwrap(); - }); +libfuzzer_sys::fuzz_target!(|data: (OpCode, &[u8])| { + let (op_code, payload) = data; + let mut ws = WebSocketServerOwned::new( + (), + PartitionedBuffer::default(), + StaticRng::default(), + BytesStream::default(), + ); + ws.set_max_payload_len(u16::MAX.into()); + let fb = &mut FrameBufferVec::default(); + Builder::new_current_thread().enable_all().build().unwrap().block_on(async move { + let Ok(mut frame) = FrameMutVec::new_fin(fb, op_code, payload) else { + return; + }; + if ws.write_frame(&mut frame).await.is_err() { + return; + }; + let _rslt = ws.read_frame(fb).await; + }); }); diff --git a/wtx-fuzz/unmask.rs b/wtx-fuzz/unmask.rs index c4163d01..ab3a5ab7 100644 --- a/wtx-fuzz/unmask.rs +++ b/wtx-fuzz/unmask.rs @@ -3,6 +3,6 @@ #![no_main] libfuzzer_sys::fuzz_target!(|data: &[u8]| { - let mut data = data.to_vec(); - wtx::web_socket::unmask(&mut data, [1, 2, 3, 4]); + let mut data = data.to_vec(); + wtx::web_socket::unmask(&mut data, [1, 2, 3, 4]); }); diff --git a/wtx-ui/Cargo.toml b/wtx-ui/Cargo.toml new file mode 100644 index 00000000..82e96815 --- /dev/null +++ b/wtx-ui/Cargo.toml @@ -0,0 +1,22 @@ +[dependencies] +clap = { default-features = false, features = ["derive", "help", "std", "usage"], optional = true, version = "4.0" } +tokio = { default-features = false, features = ["io-std", "macros", "rt"], version = "1.0" } +wtx = { default-features = false, path = "../wtx" } + +[features] +default = [] + +[package] +authors = ["Caio Fernandes "] +categories = ["asynchronous", "command-line-interface", "gui"] +description = "Different user interfaces for WTX" +edition = "2021" +keywords = ["io", "network", "websocket"] +license = "Apache-2.0" +name = "wtx-ui" +readme = "README.md" +repository = "https://github.com/c410-f3r/wtx" +version = "0.1.0" + +[package.metadata.docs.rs] +all-features = true diff --git a/wtx-ui/README.md b/wtx-ui/README.md new file mode 100644 index 00000000..aa65924f --- /dev/null +++ b/wtx-ui/README.md @@ -0,0 +1,3 @@ +# WTX UI + +A collection of different UI implementations for `wtx`. \ No newline at end of file diff --git a/wtx-ui/src/clap.rs b/wtx-ui/src/clap.rs new file mode 100644 index 00000000..7477ef23 --- /dev/null +++ b/wtx-ui/src/clap.rs @@ -0,0 +1,51 @@ +use crate::misc::{_connect, _serve}; +use clap::Parser; + +pub(crate) async fn init() -> wtx::Result<()> { + let args = Cli::parse(); + match args.commands { + Commands::Ws(ws_args) => match (ws_args.connect, ws_args.serve) { + (None, None) | (Some(_), Some(_)) => { + panic!("Please connect to a server using `-c` or listen to requests using `-s`"); + } + (None, Some(uri)) => { + _serve( + &uri, + |payload| println!("{payload:?}"), + |err| println!("{err}"), + |payload| println!("{payload}"), + ) + .await?; + } + (Some(uri), None) => { + _connect(&uri, |payload| println!("{payload}")).await?; + } + }, + } + Ok(()) +} + +/// Command-line interface for different web transport implementations +#[derive(Debug, clap::Parser)] +#[command(author, long_about = None, name = "wtx", version)] +struct Cli { + #[command(subcommand)] + commands: Commands, +} + +#[derive(Debug, clap::Subcommand)] +enum Commands { + Ws(WsArgs), +} + +/// WebSocket subcommands +#[derive(Debug, clap::Args)] +#[command(args_conflicts_with_subcommands = true)] +struct WsArgs { + /// Connects to a server + #[arg(short = 'c', value_name = "URL")] + connect: Option, + /// Listens external requests + #[arg(short = 's', value_name = "URL")] + serve: Option, +} diff --git a/wtx-ui/src/main.rs b/wtx-ui/src/main.rs new file mode 100644 index 00000000..d0235da7 --- /dev/null +++ b/wtx-ui/src/main.rs @@ -0,0 +1,10 @@ +#[cfg(feature = "clap")] +mod clap; +mod misc; + +#[tokio::main] +async fn main() -> wtx::Result<()> { + #[cfg(feature = "clap")] + clap::init().await?; + Ok(()) +} diff --git a/wtx-ui/src/misc.rs b/wtx-ui/src/misc.rs new file mode 100644 index 00000000..cd1d9784 --- /dev/null +++ b/wtx-ui/src/misc.rs @@ -0,0 +1,88 @@ +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + net::{TcpListener, TcpStream}, +}; +use wtx::{ + rng::StdRng, + web_socket::{ + handshake::{WebSocketAcceptRaw, WebSocketConnectRaw}, + FrameBufferVec, FrameMutVec, OpCode, WebSocketClient, WebSocketServer, + }, + PartitionedBuffer, UriParts, +}; + +pub(crate) async fn _connect(uri: &str, cb: impl Fn(&str)) -> wtx::Result<()> { + let uri_parts = UriParts::from(uri); + let fb = &mut FrameBufferVec::default(); + let pb = &mut <_>::default(); + let (_, mut ws) = WebSocketClient::connect(WebSocketConnectRaw { + fb, + headers_buffer: &mut <_>::default(), + pb, + rng: StdRng::default(), + stream: TcpStream::connect(uri_parts.host).await?, + uri, + compression: (), + }) + .await?; + let mut buffer = String::new(); + let mut reader = BufReader::new(tokio::io::stdin()); + loop { + tokio::select! { + frame_rslt = ws.read_frame(fb) => { + let frame = frame_rslt?; + match (frame.op_code(), frame.text_payload()) { + (_, Some(elem)) => cb(elem), + (OpCode::Close, _) => break, + _ => {} + } + } + read_rslt = reader.read_line(&mut buffer) => { + let _ = read_rslt?; + ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Text, buffer.as_bytes())?).await?; + } + } + } + Ok(()) +} + +pub(crate) async fn _serve( + uri: &str, + binary: fn(&[u8]), + error: fn(wtx::Error), + str: fn(&str), +) -> wtx::Result<()> { + let uri_parts = UriParts::from(uri); + let listener = TcpListener::bind(uri_parts.host).await?; + loop { + let (stream, _) = listener.accept().await?; + let _jh = tokio::spawn(async move { + let sun = || async move { + let pb = PartitionedBuffer::default(); + let (_, mut ws) = WebSocketServer::accept(WebSocketAcceptRaw { + compression: (), + headers_buffer: &mut <_>::default(), + key_buffer: &mut <_>::default(), + pb, + rng: StdRng::default(), + stream, + }) + .await?; + let mut fb = FrameBufferVec::default(); + loop { + let frame = ws.read_frame(&mut fb).await?; + match (frame.op_code(), frame.text_payload()) { + (_, Some(elem)) => str(elem), + (OpCode::Binary, _) => binary(frame.fb().payload()), + (OpCode::Close, _) => break, + _ => {} + } + } + wtx::Result::Ok(()) + }; + if let Err(err) = sun().await { + error(err); + } + }); + } +} diff --git a/wtx/Cargo.toml b/wtx/Cargo.toml index 685621a4..fce6450a 100644 --- a/wtx/Cargo.toml +++ b/wtx/Cargo.toml @@ -1,68 +1,91 @@ +[[bin]] +name = "autobahn-client" +required-features = ["flate2", "tokio/macros", "tokio/rt", "web-socket-handshake"] + +[[bin]] +name = "autobahn-server" +required-features = ["flate2", "tokio/macros", "tokio/rt", "web-socket-handshake"] + +[[bin]] +name = "web-socket-profiling" +required-features = ["tokio/macros", "tokio/rt"] + [[example]] -name = "web_socket_client_autobahn_raw_tokio" -required-features = ["tokio", "web-socket-handshake"] +name = "web-socket-client-cli-raw-tokio-rustls" +required-features = ["rustls-pemfile", "tokio/io-std", "tokio-rustls/tls12", "web-socket-handshake", "webpki-roots"] [[example]] -name = "web_socket_client_cli_raw_tokio_rustls" -required-features = ["tokio-rustls", "web-socket-handshake"] +name = "web-socket-server-echo-raw-async-std" +required-features = ["async-std/attributes", "web-socket-handshake"] [[example]] -name = "web_socket_server_echo_hyper" -required-features = ["web-socket-hyper"] +name = "web-socket-server-echo-raw-glommio" +required-features = ["glommio", "web-socket-handshake"] [[example]] -name = "web_socket_server_echo_raw_async_std" -required-features = ["async-std", "web-socket-handshake"] +name = "web-socket-server-echo-raw-monoio" +required-features = ["monoio/iouring", "monoio/legacy", "monoio/macros", "web-socket-handshake"] [[example]] -name = "web_socket_server_echo_raw_glommio" -required-features = ["glommio", "web-socket-handshake"] +name = "web-socket-server-echo-raw-smol" +required-features = ["smol", "web-socket-handshake"] [[example]] -name = "web_socket_server_echo_raw_tokio" -required-features = ["tokio", "web-socket-handshake"] +name = "web-socket-server-echo-raw-tokio" +required-features = ["tokio/macros", "web-socket-handshake"] [[example]] -name = "web_socket_server_echo_raw_tokio_rustls" -required-features = ["tokio-rustls", "web-socket-handshake"] +name = "web-socket-server-echo-raw-tokio-uring" +required-features = ["tokio-uring", "web-socket-handshake"] [[example]] -name = "web_socket" -path = "profiling/web_socket.rs" +name = "web-socket-server-echo-raw-tokio-rustls" +required-features = ["rustls-pemfile", "tokio-rustls", "web-socket-handshake"] [dependencies] +arbitrary = { default-features = false, features = ["derive_arbitrary"], optional = true, version = "1.0" } async-std = { default-features = false, features = ["default"], optional = true, version = "1.0" } -async-trait = { default-features = false, optional = true, version = "0.1" } base64 = { default-features = false, features = ["alloc"], optional = true, version = "0.21" } +embassy-net = { default-features = false, features = ["tcp"], optional = true, version = "0.1" } +flate2 = { default-features = false, features = ["zlib-ng"], optional = true, version = "1.0" } futures-lite = { default-features = false, optional = true, version = "1.0" } glommio = { default-features = false, optional = true, version = "0.8" } http = { default-features = false, optional = true, version = "0.2" } httparse = { default-features = false, optional = true, version = "1.0" } -hyper = { default-features = false, features = ["client", "http1", "server"], optional = true, version = "0.14" } -rand = { default-features = false, features = ["getrandom", "small_rng"], version = "0.8" } +monoio = { default-features = false, optional = true, version = "0.1" } +rand = { default-features = false, features = ["small_rng"], optional = true, version = "0.8" } +rustls-pemfile = { default-features = false, optional = true, version = "1.0" } sha1 = { default-features = false, optional = true, version = "0.10" } simdutf8 = { default-features = false, features = ["aarch64_neon"], optional = true, version = "0.1" } +smol = { default-features = false, optional = true, version = "1.0" } tokio = { default-features = false, features = ["io-util", "net"], optional = true, version = "1.0" } tokio-rustls = { default-features = false, optional = true, version = "0.24" } +tokio-uring = { default-features = false, optional = true, version = "0.4" } +tracing = { default-features = false, features = ["attributes"], optional = true, version = "0.1" } +webpki-roots = { default-features = false, optional = true, version = "0.25" } + +tracing-subscriber = { default-features = false, features = ["env-filter", "fmt"], version = "0.3" } +tracing-tree = { default-features = false, version = "0.2" } [dev-dependencies] -async-std = { default-features = false, features = ["attributes"], version = "1.0" } -tokio = { default-features = false, features = ["macros", "rt-multi-thread", "time"], version = "1.0" } -tokio-rustls = { default-features = false, features = ["tls12"], version = "0.24" } -rustls-pemfile = { default-features = false, version = "1.0" } -webpki-roots = { default-features = false, version = "0.25" } -wtx = { default-features = false, features = ["std", "tokio"], path = "." } +tokio = { default-features = false, features = ["macros", "rt", "time"], version = "1.0" } +wtx = { default-features = false, features = ["flate2", "std", "tokio"], path = "." } [features] +arbitrary = ["dep:arbitrary", "std"] async-std = ["dep:async-std", "std"] default = [] glommio = ["futures-lite", "dep:glommio"] -hyper = ["http", "dep:hyper", "tokio"] +nightly = [] std = [] tokio = ["std", "dep:tokio"] tokio-rustls = ["tokio", "dep:tokio-rustls"] web-socket-handshake = ["base64", "httparse", "sha1"] -web-socket-hyper = ["hyper", "web-socket-handshake"] + +# Dependencies that don't support `no-default-features`. +# +# Used internally to avoid unnecessary poll of unused dependencies or unused features for downstream. +_hack = ["embassy-net/medium-ethernet", "embassy-net/proto-ipv4"] [package] authors = ["Caio Fernandes "] diff --git a/wtx/README.md b/wtx/README.md index b34513e7..1630cef4 100644 --- a/wtx/README.md +++ b/wtx/README.md @@ -6,25 +6,29 @@ [![License](https://img.shields.io/badge/license-APACHE2-blue.svg)](./LICENSE) [![Rustc](https://img.shields.io/badge/rustc-1.71-lightgray")](https://blog.rust-lang.org/2020/03/12/Rust-1.71.html) -Different web transport implementations. +A collection of different web transport implementations. ## WebSocket Provides low and high level abstractions to dispatch frames, as such, it is up to you to implement [Stream](https://docs.rs/wtx/latest/wtx/trait.Stream.html) with any desired logic or use any of the built-in strategies through the selection of features. -[fastwebsockets](https://github.com/denoland/fastwebsockets) served as an initial inspiration for the skeleton of this implementation so thanks to the authors. ```rust use wtx::{ - Stream, web_socket::{FrameBufferVec, FrameMutVec, FrameVecMut, OpCode, WebSocketClientOwned} + Stream, + rng::Rng, + web_socket::{ + FrameBufferVec, FrameMutVec, FrameVecMut, compression::NegotiatedCompression, OpCode, + WebSocketClientOwned + } }; pub async fn handle_client_frames( fb: &mut FrameBufferVec, - ws: &mut WebSocketClientOwned + ws: &mut WebSocketClientOwned ) -> wtx::Result<()> { loop { - let frame = match ws.read_msg(fb).await { + let frame = match ws.read_frame(fb).await { Err(err) => { println!("Error: {err}"); ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Close, &[])?).await?; @@ -44,8 +48,17 @@ pub async fn handle_client_frames( See the `examples` directory for more suggestions. +### Compression + +The "permessage-deflate" extension, described in [RFC-7692](https://datatracker.ietf.org/doc/html/rfc7692), is the only supported compression format and is backed by the fastest compression library available at the current time, which is "zlib-ng". It also means that a C compiler is required to use such a feature. + ### Performance There are mainly 2 things that impact performance, the chosen runtime and the number of pre-allocated bytes. Specially for servers that have to create a new `WebSocket` instance for each handshake, pre-allocating a high number of bytes for short-lived or low-transfer connections can have a negative impact. -![Benchmark](https://i.imgur.com/ZZU3Hay.jpeg) \ No newline at end of file +![Benchmark](https://i.imgur.com/Iv2WzJV.jpg) + +If you disagree with any of the above numbers, feel free to checkout `wtx-bench` to point any misunderstandings or misconfigurations. A more insightful analysis is available at https://c410-f3r.github.io/thoughts/the-fastest-websocket-implementation/. + +¹ `monoio` and `tokio-uring` are slower because owned vectors need to be created on each read/write operation.
+² `embassy` is supported but there isn't a `std` example for measurement purposes. \ No newline at end of file diff --git a/wtx/benches/simple.rs b/wtx/benches/simple.rs index 6c79655f..bf34e37f 100644 --- a/wtx/benches/simple.rs +++ b/wtx/benches/simple.rs @@ -6,13 +6,13 @@ use test::Bencher; #[bench] fn unmask(b: &mut Bencher) { - const DATA_LEN: usize = 64 << 20; - let mut data: Vec = (0..DATA_LEN) - .map(|el| { - let n = el % usize::try_from(u8::MAX).unwrap(); - n.try_into().unwrap() - }) - .collect(); - let mask = [3, 5, 7, 11]; - b.iter(|| wtx::web_socket::unmask(&mut data, mask)); + const DATA_LEN: usize = 64 << 20; + let mut data: Vec = (0..DATA_LEN) + .map(|el| { + let n = el % usize::try_from(u8::MAX).unwrap(); + n.try_into().unwrap() + }) + .collect(); + let mask = [3, 5, 7, 11]; + b.iter(|| wtx::web_socket::unmask(&mut data, mask)); } diff --git a/wtx/examples/cert.pem b/wtx/examples/cert.pem new file mode 100644 index 00000000..20b567ed --- /dev/null +++ b/wtx/examples/cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDLjCCAhagAwIBAgIUEOBbAM3XZH/fykExICF/YYitxH4wDQYJKoZIhvcNAQEL +BQAwHTELMAkGA1UEBhMCRkkxDjAMBgNVBAMMBXZhaGlkMB4XDTIzMDgzMDIyNTY1 +MloXDTI0MDgyOTIyNTY1MlowHTELMAkGA1UEBhMCRkkxDjAMBgNVBAMMBXZhaGlk +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rFzJNq41iWC4fKYR9df +lTdvUvFw4jeGM/ScaEyNqV7S7FYVjjOPq2mAwYMuVLy6ppPLUmrOOSiuHCv1lWDJ +utGKbyny+vEwHorW1gVoLXwa+HOhSvfpBx7KqgVL2AwNnIxibPqvVbnUw9Wy/s5A +UVXOtrcCFyXF6ksA8+HcIMLzrc4JVo38bpLlQv7OIwpIV3jI3XPIBkGXb1kc9ay+ +bjFDsKni4Qd4p310dGmVTkHD0+pz3fcqXVsHKXYRf9MaTGKhneQSo+J90xDyjI47 +Jux1MhOtwymaldvJht7ivWscARVtNSAGMxD4mpNDQKD83s2/rBqkX4ACLrkRTuGI +tQIDAQABo2YwZDAfBgNVHSMEGDAWgBQffPUM+g3DgRehzfZFUj/FjHmAGjAJBgNV +HRMEAjAAMBcGA1UdEQQQMA6CBnNlcnZlcocEfwAAATAdBgNVHQ4EFgQUdKpZNmdT +a6PuZJAz1tzDJeoJhgcwDQYJKoZIhvcNAQELBQADggEBADsoI3M4bmxZpjCgHaIi +EpTRwuf5SA/4zxvfQU1Mv6IRLm4kBVlDjxKfKP6qWoJHTX4t/dVnRfcWcCE6COxG +rOoEuvpKqbq4nzfxc1+4N2QC6JhzmigeJBxs2ztaun/fvxxQRe8/ObHC1bU5Kuvk +aw86UN4WZEr5evudTlfKJsc1doTdiRMdBmaJ9iAGeLjUu1ZzMw1UrHqPoxj9kmt1 +5Zt39Abqn67d4+MwaHVWovNXk8xvyDmgmbxd6DN6fF5P+vmt/A5DDbfRcmctS5a1 +ma9R+aMdZCquSFHyQCMynKxr6Y8MK2JXFPsGrcY9nc6K/R7tVgUlnCrNlV8dlLap +oA4= +-----END CERTIFICATE----- diff --git a/wtx/examples/common/mod.rs b/wtx/examples/common/mod.rs index d3e7f74e..f261e3f0 100644 --- a/wtx/examples/common/mod.rs +++ b/wtx/examples/common/mod.rs @@ -1,94 +1,63 @@ use std::borrow::BorrowMut; use wtx::{ - web_socket::{ - handshake::{ - WebSocketAccept, WebSocketAcceptRaw, WebSocketHandshake, WebSocketHandshakeRaw, - }, - FrameBufferVec, OpCode, WebSocketClient, WebSocketServer, - }, - ReadBuffer, Stream, + rng::StdRng, + web_socket::{ + compression::NegotiatedCompression, handshake::WebSocketAcceptRaw, Compression, FrameBufferVec, + OpCode, WebSocketServer, + }, + PartitionedBuffer, Stream, }; -#[cfg(not(feature = "async-trait"))] -pub(crate) trait AsyncBounds {} - -#[cfg(not(feature = "async-trait"))] -impl AsyncBounds for T where T: ?Sized {} - -#[cfg(feature = "async-trait")] -pub(crate) trait AsyncBounds: Send + Sync {} - -#[cfg(feature = "async-trait")] -impl AsyncBounds for T where T: Send + Sync + ?Sized {} - -pub(crate) async fn _accept_conn_and_echo_frames( - fb: &mut FrameBufferVec, - rb: &mut ReadBuffer, - stream: impl AsyncBounds + Stream, -) -> wtx::Result<()> { - let (_, mut ws) = WebSocketAcceptRaw { - fb, - headers_buffer: &mut <_>::default(), - key_buffer: &mut <_>::default(), - rb, - stream, - } - .accept() - .await?; - _handle_frames(fb, &mut ws).await?; - Ok(()) -} - -pub(crate) async fn _connect( - fb: &mut FrameBufferVec, - uri: &str, - rb: RB, - stream: S, -) -> wtx::Result> +pub(crate) async fn _accept_conn_and_echo_frames( + compression: C, + fb: &mut FrameBufferVec, + pb: PB, + stream: S, +) -> wtx::Result<()> where - RB: AsyncBounds + BorrowMut, - S: AsyncBounds + Stream, + C: Compression, + PB: BorrowMut, + S: Stream, { - Ok(WebSocketHandshakeRaw { - fb, - headers_buffer: &mut <_>::default(), - rb, - uri, - stream, - } - .handshake() - .await? - .1) + let (_, mut ws) = WebSocketServer::accept(WebSocketAcceptRaw { + compression, + headers_buffer: &mut <_>::default(), + key_buffer: &mut <_>::default(), + pb, + rng: <_>::default(), + stream, + }) + .await?; + _handle_frames(fb, &mut ws).await?; + Ok(()) } -pub(crate) async fn _handle_frames( - fb: &mut FrameBufferVec, - ws: &mut WebSocketServer, +pub(crate) async fn _handle_frames( + fb: &mut FrameBufferVec, + ws: &mut WebSocketServer, ) -> wtx::Result<()> where - RB: BorrowMut, + NC: NegotiatedCompression, + PB: BorrowMut, + S: Stream, { - loop { - let mut frame = ws.read_msg(fb).await?; - match frame.op_code() { - OpCode::Binary | OpCode::Text => { - ws.write_frame(&mut frame).await?; - } - OpCode::Close => break, - _ => {} - } + loop { + let mut frame = ws.read_frame(fb).await?; + match frame.op_code() { + OpCode::Binary | OpCode::Text => { + ws.write_frame(&mut frame).await?; + } + OpCode::Close => break, + _ => {} } - Ok(()) + } + Ok(()) } pub(crate) fn _host_from_args() -> String { - std::env::args() - .nth(1) - .unwrap_or_else(|| "127.0.0.1:8080".to_owned()) + std::env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8080".to_owned()) } pub(crate) fn _uri_from_args() -> String { - std::env::args() - .nth(1) - .unwrap_or_else(|| "http://127.0.0.1:8080".to_owned()) + std::env::args().nth(1).unwrap_or_else(|| "http://127.0.0.1:8080".to_owned()) } diff --git a/wtx/examples/key.pem b/wtx/examples/key.pem new file mode 100644 index 00000000..21360fe8 --- /dev/null +++ b/wtx/examples/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDesXMk2rjWJYLh +8phH11+VN29S8XDiN4Yz9JxoTI2pXtLsVhWOM4+raYDBgy5UvLqmk8tSas45KK4c +K/WVYMm60YpvKfL68TAeitbWBWgtfBr4c6FK9+kHHsqqBUvYDA2cjGJs+q9VudTD +1bL+zkBRVc62twIXJcXqSwDz4dwgwvOtzglWjfxukuVC/s4jCkhXeMjdc8gGQZdv +WRz1rL5uMUOwqeLhB3infXR0aZVOQcPT6nPd9ypdWwcpdhF/0xpMYqGd5BKj4n3T +EPKMjjsm7HUyE63DKZqV28mG3uK9axwBFW01IAYzEPiak0NAoPzezb+sGqRfgAIu +uRFO4Yi1AgMBAAECggEAax92MZzLPCwoNRNzIF/gmVFkykPdVtx5wKVi1aM5o+c6 +1Dgmv1RbQIwMRUoar+Vnbfj7XeaF1CYW9vNIe/Zmo+jnTEkkGoWJ7a9A4AwSzIp+ +GNTamkr9/BBaUgYhMUi/BXbcY+sFC9pMGHZEV+EUPGvn96m6JjNj7KvuxeJ4dwMa +hrECw3JmzLn9mWSRw34sEQLYq8CvO1z+cYsgIjRgME6le6qmhHXemLqE8FrRI8lY +VRq8d0dj02qi/ygowMlkR7UYKxUIEkV5cRFhcMia9bt2HXcX2+bdWEH8EI9zQ2Vp +A3xGhQzCB9ebotKCUL4VDdRS9xsMED3G7JOsaNqK4wKBgQDhUKp5tRoxZSmSUQPA +BFmtaIK3oQOfZNpUtc2i8fwfoBIZSqCEFxom8OdWULDGKheaNJ+ppstnlRWipyCE +vMahDbl7aB4i/pvpFj6g1jAOsm610vJaG6bwn5ktYnEbA1WgNRbZ7aAKmLEF62dV +lqRyBpIvERd8iSiy/m3qz9szJwKBgQD9BV+K90Fa8Qs0r3s0xcSQj+uuClgv+I/c +NmEcandAILhHFNXhnaW4mA6IsD9iezD2ZBb5Fh4z91ZXMoeSgvgp9uKs3YXjohBP +gbXhpj2IHIcJ3WJ3UK5cpYMgxtMh6MuQcJWuC6upb5M2Wip8YuSkPvsxJYe6Ykww +7uUhNJ0ewwKBgQDVM/18WTBF2VHzEBIaWoN5IVG/7O5+n3Ixgi7pp01Jp7A4mnM1 +Sp8GhiKBfK0pdp8K3gzO/fOvOlVUqgyNTgwuTcxbx/PsbTye3Nu6WbLz1UgJaWjG +dzAKtRq+5U3dn9QgWtwar04neMDZtxz60icddrZRLz0oLKmEZlkgPjtPNQKBgQDg +OEvD3XtIOQXCKaQYTw3zQecMM1SVJkNuVa5XPS3yYUyMiTVEm6zJWc6/aNiy0BLC +tk35bJVVXeFgooGr325F2ehkYmcNI4gqvPrOPcCXhxhQMKTasV/i8DW07R0bFq6W +kH7EXsk//IVLjDnMQuL1fm6hze1qyUNVtc7NT0/RewKBgAaY6gjqEzkAv1k/MPMv +dTPKS0yIHQIQ0ERYsa02/Lw2JWk5V626HT1jeuJGcFXvAzH1XaXvBw+hyYOWsKxG +cXgpv+6k0+PF1s4QKa5J1nmIGDCD5VCE8XiLwpgxzZH01k9XV/HTGor/0qszUb/b +Ntk2+3KNd/M8edbjsCubazt6 +-----END PRIVATE KEY----- diff --git a/wtx/examples/localhost.crt b/wtx/examples/localhost.crt deleted file mode 100644 index 9d259427..00000000 --- a/wtx/examples/localhost.crt +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEtDCCApwCCQDtm3HlNW4u5TANBgkqhkiG9w0BAQsFADAcMQswCQYDVQQGEwJJ -TjENMAsGA1UEAwwERGl2eTAeFw0yMzA0MjIxMjAzMjZaFw0yODA0MjAxMjAzMjZa -MBwxCzAJBgNVBAYTAklOMQ0wCwYDVQQDDAREaXZ5MIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEAvqQG1wPW3F53JjydkfDJSnHMJYtvqjsVIHbJWVV/Aes8 -OKp/JvdpzlP8YRLu6KI/mutya6iuGt+xHLXJdRJYAThoke5QML27s9raxOfl3+wO -AwUtGYP9G0KcwVFVbxOD/edJ84NSwSL6o0MqfiHReydi7Gc6xyRa6R8PPpJ2ckWV -nx8r/m/LCG5TxAPCU1GbGx3sWhvDJyzL7Yj/X2y7wqIVsJy/lMz765ND01LtvmlJ -IG7N9hnmcoVgxCxrWmBQ+x4YIAJx7OWcs/vuvSjsxJuxlRl+YeiZqilvm5u3Fopn -x5xzE1oN+vBU5ncDVqfoidsh5w7BkPHgHbZWE7Ba1wlp9mJqBMBe1ko9/xVJjmlb -ot+EinTDYGxhUfngh7tGt45bJjNHFINPf3WSCRPUancF/lJjHoTvlVAGYMZUMwNz -ENo+chYCg2Bb5c8+/OuYgtfqtSCttdw+Eo0V49zue/i7leGD1IQ+pgskdGvCa1UG -bwHkSbC61U9tDwHyjju8oi0wMEYsVBMyjy25wuS/iYCte5J1pfrCIuziR/xAiYfF -+oC0Hd828Tujtbii5YtXXr3Bjb+A78lnkadecXUBfIe8yqtPkgiMOPSWUM0KEuJr -EYvnZX4wdhfz9AD0NmgZrIvTlXE0s0hjVHvBzgJLzmIBHLMHGcZVeDoCe9oPF0sC -AwEAATANBgkqhkiG9w0BAQsFAAOCAgEAAAQNpXGAqng6YNy1AOVsPSXecVrcqxfp -l28SSfDzrvhtLs5FaK9MxER5Lt3c4Fmlq+fC5mg7eAHQcVQvpvnCufMDH17yZF04 -X/EzIniQ4fVkhyWtukDm18orpCQfDIyJ0iRSsFDzDyi/UZFIsPkD0lumNjGZY4m/ -VoELlwIAeoDgDplSDBqJ2N0dgxZYUKDjqS0hLBnp8RfETLTbXtpQCVN3Q35gJApB -gGRtwOKYf5GZPIcp0iDNumRLPLtqYanT/cD8nd54Bil13925l5dqy0/ozfm2I+NT -TYAd3b2q8Mexs/rJD8naCE7BM+zfbUkoUPOy1Q1y9A/5CfhjCAdJlnDnK0B+isW8 -HNl9U4pySDQRg66oUUZDboRGh+G7qQuPA2ewAz42KvtfS3XX6zaYxpQlrrjut7db -Df0y7fYumdmQtqZQJ2MtJHI9pZQ3+zxGq5RN/xZNh53XPIurCiqBypfHj4nSFNIq -VADjJEITr+oiFabDjp5jiwoewtEGCdT0PuzaY/iADvlxTOMdy6AUdRTkhLob930F -1QtKU45rwHTbaPxLdjvnKMI2ElwqVyFS5H5YNgM2xSWkRMmqPlvihh3a7M+Ux/Ri -C878EKTdkCNTXUpCCJhMGhrXTzYkJ5G+Nh9ERcTGuLkw6uzkUdbAmYgOn2GN3xvl -Q26ks4m/6Fs= ------END CERTIFICATE----- diff --git a/wtx/examples/localhost.key b/wtx/examples/localhost.key deleted file mode 100644 index e13c8349..00000000 --- a/wtx/examples/localhost.key +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC+pAbXA9bcXncm -PJ2R8MlKccwli2+qOxUgdslZVX8B6zw4qn8m92nOU/xhEu7ooj+a63JrqK4a37Ec -tcl1ElgBOGiR7lAwvbuz2trE5+Xf7A4DBS0Zg/0bQpzBUVVvE4P950nzg1LBIvqj -Qyp+IdF7J2LsZzrHJFrpHw8+knZyRZWfHyv+b8sIblPEA8JTUZsbHexaG8MnLMvt -iP9fbLvCohWwnL+UzPvrk0PTUu2+aUkgbs32GeZyhWDELGtaYFD7HhggAnHs5Zyz -++69KOzEm7GVGX5h6JmqKW+bm7cWimfHnHMTWg368FTmdwNWp+iJ2yHnDsGQ8eAd -tlYTsFrXCWn2YmoEwF7WSj3/FUmOaVui34SKdMNgbGFR+eCHu0a3jlsmM0cUg09/ -dZIJE9RqdwX+UmMehO+VUAZgxlQzA3MQ2j5yFgKDYFvlzz7865iC1+q1IK213D4S -jRXj3O57+LuV4YPUhD6mCyR0a8JrVQZvAeRJsLrVT20PAfKOO7yiLTAwRixUEzKP -LbnC5L+JgK17knWl+sIi7OJH/ECJh8X6gLQd3zbxO6O1uKLli1devcGNv4DvyWeR -p15xdQF8h7zKq0+SCIw49JZQzQoS4msRi+dlfjB2F/P0APQ2aBmsi9OVcTSzSGNU -e8HOAkvOYgEcswcZxlV4OgJ72g8XSwIDAQABAoICACH5GxrwHTcSQot22+GpFkYE -94ttSM3+T2qEoKch3EtcP1Qd1iD8kEdrohsug5LDbzBNawuSeMxjNq3WG3uYdERr -Z/8xh+rXtP59LuVOKiH4cBrLrljQs6dK/KJauy3bPXde40fZDENM13uGuajWn/0h -bLiSQOBCM0098rqE4UTF7772kCF8jKMI/jZ9MQEmFs0DTR5VujZd/k1rT48S0ncB -6XmaxW1gBjjZ+olLSwDWxGhaNqv3u6CG8lKjU9I8PdIyb7wsk17TIFTWvZnKFD+J -O2FFtMb/63pufewuGLeUnJ/u2ncFYl5ou8iCRv8HVyJSAb2qXIZXBEhnOPmzQMyn -+NEkX/3w8LpWhbd0RCsYeIQqYWTBX97kUXgbDtMCOCPH/DdDcJBQWRm5TUJt062K -dDOwqg1jWOQ8F8nr3OwJ9E7NPoBQO5zWVfG6i3opxcFhoWGdTF0oIEhT42aX20ip -AqQGwrW72j+qZdLt8nHK38kvnq8RinS7I2mw8QVhGFqN84x2WIUSiny9+lVD4lfT -ckcSblKL9BpjYVkDvQ3s6BS92RCYqFaBwES4s5oFd2V+Ffrz3cCqp59k/5bZ6x7h -ia+Hw2/Mmtp4TaAVcGrHOvAmnZcnfV9jvsdRzBeihom/gtnVrA8ctsipxhE+Ylkx -q4g/xHHuBpRLMxusD3cBAoIBAQD8fR27xKLYw6aCgLW5pYMDUp8YaFW3uhsA3VPu -UlErdF7QJwGHq7e4eiWp9W1lsc7Wq99PnetRxrs1HJNjExVcLSpCOdXOA0BGO8hI -VgnmFr2doAmjLAXZzSDWnt7jl2FP4Casyv01/EmtpfFdnsXtdJa4s8ITZ5k9cRaV -z28YkpMSXkoY83E4YOK2TrwzvgK7mwFjjV1x3xZZu6QXeMppCGxYQoCooNUvO6uO -r4npHXJVBUdz+mMxci08v0sqhmakk4YRPq0A9A5b55zlSZwwE51d8wSgwfqdFygx -EULkLCs881tM5jfIXcnHSxOQ23jd7bUHy8Hdll1U741AYXPLAoIBAQDBSrnORhzQ -urN33OUvdYBIQT8pFDPNC7hoEPhxrk1V3N47Kvmxgc88pzrscqNI0e93XixRqjc5 -bCeR07qWl/xWlD8/MVu2MGHxtdjoXEDUtynUoiVe6D1hea4hcF4BkBWAhLKIz81R -5fU9p/RzZmWy8Fbc+ZV8GvX64LS/orWGmQvJx9Q0byZui6JUUrChRrYZWeFX+ehB -5Y/5BHsOL15HntqBfF3v6zK7vzQ03Aqd9vWxR3xUlbpUvxiax5Kg+fuiksmwNKIh -P3/nhoP3LBtBZUx4h/Jdt0e/NFHtDXdIbxaaHO/jbTfy1tg4/wCp9OneMZF2kMVj -PpU7wetwr3qBAoIBAFOEV0d63Zrx7KwSQworc1CwDawXJvNk/fWlQFP+qpbDIXGc -1Wa5KEY/MSIs6ojO7eoYY/+D7wjXwajp0N7euxwIXIgXdV91t9cDg1ZaD2AqeYIg -I8/ziePndEtJtdR2iFvRezmA04z97Kkh0Nr03+eRvyFNZI7in8+xDpVzTf5EzZ0v -zza9n9/UPGmtVZeP7Ht95FG3uwclkdEQvlB9RgbEIIJ5TPF6ccnz5OWHrwiLEvyI -iIAWfKUobUpAxG5GksExgxFFOBiuoelIjZ9SX/WPJ2iiMA+02l8H/+VrHkM3UP4S -SUsAg8clLs9bSBeMYUiXjmALyA6x5CFqM8Dt+00CggEAPnVgDvh27Te3MF8vq6ND -XZW/zA1cI8DKyM3bChjxonIpWWMspiA1D/tVvfvZKXm08JR8q7Ld/28kZinNnEXm -Yy+qNEhFw1xk+c7yFTtiM5owKSZv/vf6hZnlG6cMqWKeoBXA/xZu2Sz+jvrLsdJ/ -wE+LMgJwPFcV7whXP6lbEPA5b+1jc8IK4CO8w5SowKRxyUVS3LPDSi/c0vGQtee2 -hlwdbUP7ssAEd8h0HTSRNbQMdkmMMmTjfej2EWW1ytCccE8QXyDS1v2G3hCIagFV -mU8bY8NCHOhRhcZpRrlYNw62dfwtxAaR0qV73wb/duvN+l94CqEDN2uMm2+xHYuG -gQKCAQEA76Lce+5FCR31hD3Gm0/RbA4jFDzkV5dY8JxOwzZHGzOUJpVd2sZ3a+rQ -BIGC5EiSh9cdIKPqfuxjf+ufzhrA1rzxJVLPWHiUT+37ionXsUpIQQLeeaEpeHFb -Dqg+vu2y+Fg9vYDXTKVZWXADp9kH+KtgpvrcaL2k4UkY2q+jKVLvTt+ezwWTWZFF -QSFSMpTiAAo/kSEryG9DGnyvC5UZsgKsV9eQe7rkMg8p6TjFANcx6oDR6M6fchtn -YmrKkFivZU2bhmGM1HJCIcmAIXtqsf6gb8CqqqX0NQb5m23OJU3NC7N9g34ofhCm -GPx3/+N92+2q031KtpGtHOvcSrHFMA== ------END PRIVATE KEY----- diff --git a/wtx/examples/root-ca.crt b/wtx/examples/root-ca.crt new file mode 100644 index 00000000..20e539e8 --- /dev/null +++ b/wtx/examples/root-ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIUdBpU1oceQzlPnPFSSwUBlZTKbHswDQYJKoZIhvcNAQEL +BQAwHTELMAkGA1UEBhMCRkkxDjAMBgNVBAMMBXZhaGlkMB4XDTIzMDgzMDIyNTY1 +MloXDTI4MDgyODIyNTY1MlowHTELMAkGA1UEBhMCRkkxDjAMBgNVBAMMBXZhaGlk +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6OvvXlNgiYC0mw4ZVzWa +O211WzZUsdqP+E4Ac66cAzzRxbLVTCKBY4HCvfexbfTXkzha6JxOCk+OF7IWLyTM +hcrsDx7tSw5HgYAeSTDFGQJpAeJKk5MhP5jxhXQ8MOHj2zGULfRTx45HK7lq4OQZ +IYkCehpeglSiPuVdWSpVEf1UgFcdLX4L1YZP9MfWD6YDpseA1CkyoTwf4Q11Pnz0 +u0cDo4gEjXDKde9U8Wgr2HVVHg+2hj4k8cjsoacXQwojK2/gE8p8GyAEsTPEQBs9 +u2uJNlYvhytikRk3/DEd+h9s1qtkC5wWKW6GxLcMcRNqtsVyAhCal+c3hSpBvQOw +vwIDAQABo1MwUTAdBgNVHQ4EFgQUH3z1DPoNw4EXoc32RVI/xYx5gBowHwYDVR0j +BBgwFoAUH3z1DPoNw4EXoc32RVI/xYx5gBowDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAQEAzxgKqjeGZnL0LrDUE7Cn5AAoeSifIR6drN/9wVykLsDI ++U+ls8Dnw9TMVFhXs1naEDuj5IXBeaPZoz5Ygc/8ytwIx5EkVPf6/nUJ64nD5B2/ +vd9/Nf8zqTP3puj8yjLwzUgJJETp8vsiaD1dtkqpNmA31lwDdsHfG6+dqdT4pazh +yGDLkZMpwU9lZp5tonIaAdVqSTbEREKRoaY1XrdPEDOGLJ3JHmSi/nGtDTLgLEwH +MsOdppJypF1AbuIWcfOZwHmoUpLCnjqAAfEc61sOlrGeXr8BtOPFY/rRuOY1U8RL +VDiziJtft2duSTXMzk7gKG4ThFQyFdyWH5XtpeppcQ== +-----END CERTIFICATE----- diff --git a/wtx/examples/root-ca.key b/wtx/examples/root-ca.key new file mode 100644 index 00000000..44cf8495 --- /dev/null +++ b/wtx/examples/root-ca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDo6+9eU2CJgLSb +DhlXNZo7bXVbNlSx2o/4TgBzrpwDPNHFstVMIoFjgcK997Ft9NeTOFronE4KT44X +shYvJMyFyuwPHu1LDkeBgB5JMMUZAmkB4kqTkyE/mPGFdDww4ePbMZQt9FPHjkcr +uWrg5BkhiQJ6Gl6CVKI+5V1ZKlUR/VSAVx0tfgvVhk/0x9YPpgOmx4DUKTKhPB/h +DXU+fPS7RwOjiASNcMp171TxaCvYdVUeD7aGPiTxyOyhpxdDCiMrb+ATynwbIASx +M8RAGz27a4k2Vi+HK2KRGTf8MR36H2zWq2QLnBYpbobEtwxxE2q2xXICEJqX5zeF +KkG9A7C/AgMBAAECggEANbrnVu0I4e9MulAATHvS/k57ufTIiNaQS4lC1Wy8FIsd +IOvmBFAZZOj0EXpn9aN7bzyay/nyjrJXZuPv9iQZAewTlFFgD+ZVXGw/kEat0N0b +eBHJfk18Z/1SCaUMlYImW9QOCKPKB8isZku5efNkULYYWi5nCs2XN8cVR5JaWXGU +NZIXgteut8vutojT7pXyK3MWfTQ9F64cLD+thkpvp7DYiPqO29zgwKE50uUJtD1s +hw0X3cGGE8ocHEqcxGIma/HPZxPGauXIDIFDQa0RNw3Y1fxl1blZOlRsL9+7pOBu +c/37DjPz+nfqnjdRoDNX1wNFxoiFLxA2AoxzhYIwAQKBgQD4WdgQjokyOcpFU23H +4K5N3X0pq8XXecoAguW8mF2yiGbu9sXveMcXQiDw9VUWhi0YTg/JDPE792S2DVF/ +IhOAbtq2GLXwPCTAwKXt/JhtxOJH+z19RlgnvYOb3wLWW5Ghvu3mMJYfTMhiUVpV +PRvO82n3kzR7BjixFORZzm9cvwKBgQDwGG+5H4dmTOGgCw7llkOGfATHY0U1oYQB +umlB8idr4wI2/ChUTZj+QoE76wZfgI8kUZS5Kz9dup3sksxQUGU7PN3aeuhtJHBt +y1iRf3vaw7fBhYPasDF/yPTEYHEbev4KJVBAylf8pWFFxf+eQE/uw9AI6hi3TMXX +XhzTALysAQKBgBIKrNifEjZ03k3k/q+rkd4UPhh94xSBQ3yABeKKixwCLsAja0O3 +WXdGFZCLsg+91Z7TZPAtIYVCtq1HEGmU9ye4ZekeqHD4XY8nL6a3V/d5exrRlKj6 +KENS0DHNpK4f3teKQEwdsXo6oMALuu1AUDnbkxIqPoDFde7fXqtrhMmPAoGBAI/3 +Bx7pAeZcmTm/B7qwxGKigLcSFlDXPXFP1oedbPfrEcvonUIXmXJ0bEuCXLrtOmeb +p2L0xLUuDj3ptCtAWcMQdzLnWfD/1Y5wTPZJi/mcO9YFeg+qcLPfyqzp60iAEk4B +G8MN0X6Dp/UPiXvZslRIA7kkrZdTqJnAK+Z13awBAoGBAOTfaufM5z/nYwbjCtuW +Yn91wRKUrEUmTUSEUlReapKepnJ9CvbbhnketUysN5Pq+SnH7nLD+Im+xDO3ni4b +9L/X8/C7m+d254E2JVvVm2hN1aHl9aAHfvuL+5LK3H3XXr5inF3rKubDoO4KGgbo +0qgg+bjY/oPI7eR6PA0gVCLi +-----END PRIVATE KEY----- diff --git a/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs b/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs new file mode 100644 index 00000000..27a82deb --- /dev/null +++ b/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs @@ -0,0 +1,81 @@ +//! WebSocket CLI client. + +#[path = "./common/mod.rs"] +mod common; + +use std::{io::Cursor, sync::Arc}; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + net::TcpStream, +}; +use tokio_rustls::{ + rustls::{ClientConfig, OwnedTrustAnchor, RootCertStore, ServerName}, + TlsConnector, +}; +use webpki_roots::TLS_SERVER_ROOTS; +use wtx::{ + rng::StdRng, + web_socket::{ + handshake::WebSocketConnectRaw, Compression, FrameBufferVec, FrameMutVec, OpCode, + WebSocketClient, + }, + UriParts, +}; + +static ROOT_CA: &[u8] = include_bytes!("./root-ca.crt"); + +#[tokio::main(flavor = "current_thread")] +async fn main() -> wtx::Result<()> { + let fb = &mut FrameBufferVec::default(); + let pb = &mut <_>::default(); + let uri = common::_uri_from_args(); + let uri_parts = UriParts::from(uri.as_str()); + let (_, mut ws) = WebSocketClient::connect(WebSocketConnectRaw { + fb, + headers_buffer: &mut <_>::default(), + pb, + rng: StdRng::default(), + stream: tls_connector()? + .connect( + ServerName::try_from(uri_parts.hostname).map_err(|_err| wtx::Error::MissingHost)?, + TcpStream::connect(uri_parts.host).await?, + ) + .await?, + uri: &uri, + compression: Compression::None, + }) + .await?; + let mut buffer = String::new(); + let mut reader = BufReader::new(tokio::io::stdin()); + loop { + tokio::select! { + frame_rslt = ws.read_frame(fb) => { + let frame = frame_rslt?; + match (frame.op_code(), frame.text_payload()) { + (_, Some(elem)) => println!("{elem}"), + (OpCode::Close, _) => break, + _ => {} + } + } + read_rslt = reader.read_line(&mut buffer) => { + let _ = read_rslt?; + ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Text, buffer.as_bytes())?).await?; + } + } + } + Ok(()) +} + +// You probably shouldn't use self-signed root authorities in a production environment. +fn tls_connector() -> wtx::Result { + let mut root_store = RootCertStore::empty(); + root_store.add_trust_anchors(TLS_SERVER_ROOTS.iter().map(|ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints(ta.subject, ta.spki, ta.name_constraints) + })); + let _ = root_store.add_parsable_certificates(&rustls_pemfile::certs(&mut Cursor::new(ROOT_CA))?); + let config = ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth(); + Ok(TlsConnector::from(Arc::new(config))) +} diff --git a/wtx/examples/web-socket-server-echo-raw-async-std.rs b/wtx/examples/web-socket-server-echo-raw-async-std.rs new file mode 100644 index 00000000..7a286698 --- /dev/null +++ b/wtx/examples/web-socket-server-echo-raw-async-std.rs @@ -0,0 +1,29 @@ +//! WebSocket echo server. + +#[path = "./common/mod.rs"] +mod common; + +use async_std::net::TcpListener; +use wtx::{web_socket::FrameBufferVec, PartitionedBuffer}; + +fn main() -> wtx::Result<()> { + async_std::task::block_on::<_, wtx::Result<_>>(async { + let listener = TcpListener::bind(common::_host_from_args()).await?; + loop { + let (stream, _) = listener.accept().await?; + let _jh = async_std::task::spawn(async move { + if let Err(err) = common::_accept_conn_and_echo_frames( + (), + &mut FrameBufferVec::default(), + &mut PartitionedBuffer::default(), + stream, + ) + .await + { + println!("{err}"); + } + }); + } + })?; + Ok(()) +} diff --git a/wtx/examples/web-socket-server-echo-raw-glommio.rs b/wtx/examples/web-socket-server-echo-raw-glommio.rs new file mode 100644 index 00000000..46acd6f8 --- /dev/null +++ b/wtx/examples/web-socket-server-echo-raw-glommio.rs @@ -0,0 +1,26 @@ +//! WebSocket echo server. + +#[path = "./common/mod.rs"] +mod common; + +fn main() -> wtx::Result<()> { + use glommio::{net::TcpListener, LocalExecutorBuilder}; + + LocalExecutorBuilder::default() + .spawn::<_, _, wtx::Result<_>>(|| async { + let listener = TcpListener::bind(crate::common::_host_from_args())?; + loop { + let stream = listener.accept().await?; + let _jh = glommio::spawn_local(async move { + let fb = &mut <_>::default(); + let pb = &mut <_>::default(); + if let Err(err) = crate::common::_accept_conn_and_echo_frames((), fb, pb, stream).await { + println!("{err}"); + } + }) + .detach(); + } + })? + .join()??; + Ok(()) +} diff --git a/wtx/examples/web-socket-server-echo-raw-monoio.rs b/wtx/examples/web-socket-server-echo-raw-monoio.rs new file mode 100644 index 00000000..5d0dde99 --- /dev/null +++ b/wtx/examples/web-socket-server-echo-raw-monoio.rs @@ -0,0 +1,22 @@ +//! WebSocket echo server. + +#[path = "./common/mod.rs"] +mod common; + +use monoio::net::TcpListener; + +#[monoio::main] +async fn main() -> wtx::Result<()> { + let listener = TcpListener::bind(common::_host_from_args())?; + loop { + let (stream, _) = listener.accept().await?; + let _jh = monoio::spawn(async move { + if let Err(err) = + common::_accept_conn_and_echo_frames((), &mut <_>::default(), &mut <_>::default(), stream) + .await + { + println!("{err}"); + } + }); + } +} diff --git a/wtx/examples/web-socket-server-echo-raw-smol.rs b/wtx/examples/web-socket-server-echo-raw-smol.rs new file mode 100644 index 00000000..489704bc --- /dev/null +++ b/wtx/examples/web-socket-server-echo-raw-smol.rs @@ -0,0 +1,25 @@ +//! WebSocket echo server. + +#[path = "./common/mod.rs"] +mod common; + +use smol::net::TcpListener; + +fn main() -> wtx::Result<()> { + smol::block_on::>(async { + let listener = TcpListener::bind(common::_host_from_args()).await?; + loop { + let (stream, _) = listener.accept().await?; + smol::spawn(async move { + if let Err(err) = + common::_accept_conn_and_echo_frames((), &mut <_>::default(), &mut <_>::default(), stream) + .await + { + println!("{err}"); + } + }) + .detach(); + } + })?; + Ok(()) +} diff --git a/wtx/examples/web-socket-server-echo-raw-tokio-rustls.rs b/wtx/examples/web-socket-server-echo-raw-tokio-rustls.rs new file mode 100644 index 00000000..95eea741 --- /dev/null +++ b/wtx/examples/web-socket-server-echo-raw-tokio-rustls.rs @@ -0,0 +1,55 @@ +//! WebSocket echo server. + +#[path = "./common/mod.rs"] +mod common; + +use rustls_pemfile::{certs, pkcs8_private_keys}; +use std::{io, sync::Arc}; +use tokio::net::TcpListener; +use tokio_rustls::{ + rustls::{Certificate, PrivateKey, ServerConfig}, + TlsAcceptor, +}; + +static CERT: &[u8] = include_bytes!("./cert.pem"); +static KEY: &[u8] = include_bytes!("./key.pem"); + +#[tokio::main(flavor = "current_thread")] +async fn main() -> wtx::Result<()> { + let listener = TcpListener::bind(common::_host_from_args()).await?; + let tls_acceptor = tls_acceptor()?; + loop { + let (stream, _) = listener.accept().await?; + let local_tls_acceptor = tls_acceptor.clone(); + let _jh = tokio::spawn(async move { + let fun = || async move { + let stream = local_tls_acceptor.accept(stream).await?; + tokio::task::unconstrained(common::_accept_conn_and_echo_frames( + (), + &mut <_>::default(), + &mut <_>::default(), + stream, + )) + .await + }; + if let Err(err) = fun().await { + println!("{err}"); + } + }); + } +} + +// You probably shouldn't use self-signed certificates in a production environment. +fn tls_acceptor() -> wtx::Result { + let key = pkcs8_private_keys(&mut &*KEY)? + .into_iter() + .map(PrivateKey) + .next() + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "No private key"))?; + let certs: Vec<_> = certs(&mut &*CERT)?.into_iter().map(Certificate).collect(); + let config = ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(certs, key)?; + Ok(TlsAcceptor::from(Arc::new(config))) +} diff --git a/wtx/examples/web-socket-server-echo-raw-tokio-uring.rs b/wtx/examples/web-socket-server-echo-raw-tokio-uring.rs new file mode 100644 index 00000000..36bb8714 --- /dev/null +++ b/wtx/examples/web-socket-server-echo-raw-tokio-uring.rs @@ -0,0 +1,23 @@ +//! WebSocket echo server. + +#[path = "./common/mod.rs"] +mod common; + +use tokio_uring::net::TcpListener; + +fn main() -> wtx::Result<()> { + tokio_uring::start(async { + let listener = TcpListener::bind(common::_host_from_args().as_str().parse().unwrap())?; + loop { + let (stream, _) = listener.accept().await?; + let _jh = tokio_uring::spawn(async move { + if let Err(err) = + common::_accept_conn_and_echo_frames((), &mut <_>::default(), &mut <_>::default(), stream) + .await + { + println!("{err}"); + } + }); + } + }) +} diff --git a/wtx/examples/web-socket-server-echo-raw-tokio.rs b/wtx/examples/web-socket-server-echo-raw-tokio.rs new file mode 100644 index 00000000..59403428 --- /dev/null +++ b/wtx/examples/web-socket-server-echo-raw-tokio.rs @@ -0,0 +1,26 @@ +//! WebSocket echo server. + +#[path = "./common/mod.rs"] +mod common; + +use tokio::net::TcpListener; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> wtx::Result<()> { + let listener = TcpListener::bind(common::_host_from_args()).await?; + loop { + let (stream, _) = listener.accept().await?; + let _jh = tokio::spawn(async move { + if let Err(err) = tokio::task::unconstrained(common::_accept_conn_and_echo_frames( + (), + &mut <_>::default(), + &mut <_>::default(), + stream, + )) + .await + { + println!("{err}"); + } + }); + } +} diff --git a/wtx/examples/web_socket_client_autobahn_raw_tokio.rs b/wtx/examples/web_socket_client_autobahn_raw_tokio.rs deleted file mode 100644 index e187b325..00000000 --- a/wtx/examples/web_socket_client_autobahn_raw_tokio.rs +++ /dev/null @@ -1,95 +0,0 @@ -//! WebSocket autobahn client. - -mod common; - -use tokio::net::TcpStream; -use wtx::{ - web_socket::{FrameBufferVec, FrameMutVec, OpCode}, - ReadBuffer, -}; - -#[tokio::main] -async fn main() -> Result<(), Error> { - let fb = &mut <_>::default(); - let host = &common::_host_from_args(); - let rb = &mut <_>::default(); - for case in 1..=get_case_count(fb, &host, rb).await? { - let mut ws = common::_connect( - fb, - &format!("http://{host}/runCase?case={case}&agent=wtx"), - &mut *rb, - TcpStream::connect(host).await.map_err(wtx::Error::from)?, - ) - .await?; - loop { - let mut frame = match ws.read_msg(fb).await { - Err(err) => { - println!("Error: {err}"); - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Close, &[])?) - .await?; - break; - } - Ok(elem) => elem, - }; - match frame.op_code() { - OpCode::Binary | OpCode::Text => ws.write_frame(&mut frame).await?, - OpCode::Close => break, - _ => {} - } - } - } - common::_connect( - fb, - &format!("http://{host}/updateReports?agent=wtx"), - rb, - TcpStream::connect(host).await.map_err(wtx::Error::from)?, - ) - .await? - .write_frame(&mut FrameMutVec::close_from_params(1000, fb, &[])?) - .await?; - Ok(()) -} - -/// Error -#[derive(Debug)] -pub enum Error { - /// ParseIntError - ParseIntError(std::num::ParseIntError), - /// Wtx - Wtx(wtx::Error), -} - -impl From for Error { - fn from(from: std::num::ParseIntError) -> Self { - Self::ParseIntError(from) - } -} - -impl From for Error { - fn from(from: wtx::Error) -> Self { - Self::Wtx(from) - } -} - -async fn get_case_count( - fb: &mut FrameBufferVec, - host: &str, - rb: &mut ReadBuffer, -) -> Result { - let mut ws = common::_connect( - fb, - &format!("http://{host}/getCaseCount"), - rb, - TcpStream::connect(host).await.map_err(wtx::Error::from)?, - ) - .await?; - let rslt = ws - .read_msg(fb) - .await? - .text_payload() - .unwrap_or_default() - .parse()?; - ws.write_frame(&mut FrameMutVec::close_from_params(1000, fb, &[])?) - .await?; - Ok(rslt) -} diff --git a/wtx/examples/web_socket_client_cli_raw_tokio_rustls.rs b/wtx/examples/web_socket_client_cli_raw_tokio_rustls.rs deleted file mode 100644 index 3c029b84..00000000 --- a/wtx/examples/web_socket_client_cli_raw_tokio_rustls.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! WebSocket CLI client. - -mod common; - -use std::{ - io::{self, ErrorKind}, - sync::Arc, -}; -use tokio::net::TcpStream; -use tokio_rustls::{ - rustls::{ClientConfig, OwnedTrustAnchor, RootCertStore, ServerName}, - TlsConnector, -}; -use webpki_roots::TLS_SERVER_ROOTS; -use wtx::{web_socket::OpCode, UriParts}; - -#[tokio::main] -async fn main() -> wtx::Result<()> { - let fb = &mut <_>::default(); - let map_err = |_err| io::Error::new(ErrorKind::InvalidInput, "invalid dnsname"); - let rb = &mut <_>::default(); - let uri = common::_uri_from_args(); - let uri_parts = UriParts::from(uri.as_str()); - let mut ws = common::_connect( - fb, - &uri, - rb, - tls_connector() - .connect( - ServerName::try_from(uri_parts.hostname).map_err(map_err)?, - TcpStream::connect(uri_parts.host).await?, - ) - .await?, - ) - .await?; - - loop { - let frame = ws.read_msg(fb).await?; - match (frame.op_code(), frame.text_payload()) { - (_, Some(elem)) => println!("{elem}"), - (OpCode::Close, _) => break, - _ => {} - } - } - Ok(()) -} - -fn tls_connector() -> TlsConnector { - let mut root_store = RootCertStore::empty(); - root_store.add_trust_anchors(TLS_SERVER_ROOTS.iter().map(|ta| { - OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) - })); - let config = ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(root_store) - .with_no_client_auth(); - TlsConnector::from(Arc::new(config)) -} diff --git a/wtx/examples/web_socket_server_echo_hyper.rs b/wtx/examples/web_socket_server_echo_hyper.rs deleted file mode 100644 index 9e5270c1..00000000 --- a/wtx/examples/web_socket_server_echo_hyper.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! WebSocket echo server. - -mod common; - -use hyper::{server::conn::Http, service::service_fn, Body, Request, Response}; -use tokio::{net::TcpListener, task}; -use wtx::{ - web_socket::{ - handshake::{WebSocketUpgrade, WebSocketUpgradeHyper}, - WebSocketServer, - }, - ReadBuffer, -}; - -#[tokio::main] -async fn main() -> wtx::Result<()> { - let listener = TcpListener::bind(common::_host_from_args()).await?; - loop { - let (stream, _) = listener.accept().await?; - let _jh = tokio::spawn(async move { - let service = service_fn(server_upgrade); - if let Err(err) = Http::new() - .serve_connection(stream, service) - .with_upgrades() - .await - { - println!("An error occurred: {err}"); - } - }); - } -} - -async fn server_upgrade(req: Request) -> wtx::Result> { - let (res, fut) = WebSocketUpgradeHyper { req }.upgrade()?; - let _jh = task::spawn(async move { - let fut = async move { - common::_handle_frames( - &mut <_>::default(), - &mut WebSocketServer::new(ReadBuffer::default(), fut.await?), - ) - .await - }; - if let Err(err) = tokio::task::unconstrained(fut).await { - eprintln!("Error in WebSocket connection: {err}"); - } - }); - Ok(res) -} diff --git a/wtx/examples/web_socket_server_echo_raw_async_std.rs b/wtx/examples/web_socket_server_echo_raw_async_std.rs deleted file mode 100644 index c5c361d3..00000000 --- a/wtx/examples/web_socket_server_echo_raw_async_std.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! WebSocket echo server. - -mod common; - -use async_std::net::TcpListener; -use wtx::{web_socket::FrameBufferVec, ReadBuffer}; - -#[async_std::main] -async fn main() -> wtx::Result<()> { - let listener = TcpListener::bind(common::_host_from_args()).await?; - loop { - let (stream, _) = listener.accept().await?; - let _jh = async_std::task::spawn(async move { - if let Err(err) = common::_accept_conn_and_echo_frames( - &mut FrameBufferVec::default(), - &mut ReadBuffer::default(), - stream, - ) - .await - { - println!("{err}"); - } - }); - } -} diff --git a/wtx/examples/web_socket_server_echo_raw_glommio.rs b/wtx/examples/web_socket_server_echo_raw_glommio.rs deleted file mode 100644 index 3a072eab..00000000 --- a/wtx/examples/web_socket_server_echo_raw_glommio.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! WebSocket echo server. - -mod common; - -#[cfg(feature = "async-trait")] -mod cfg_hack { - pub(crate) fn hack() -> wtx::Result<()> { - Ok(()) - } -} - -#[cfg(not(feature = "async-trait"))] -mod cfg_hack { - use glommio::{net::TcpListener, CpuSet, LocalExecutorPoolBuilder, PoolPlacement}; - use std::thread::available_parallelism; - - pub(crate) fn hack() -> wtx::Result<()> { - let builder = LocalExecutorPoolBuilder::new(PoolPlacement::MaxSpread( - available_parallelism()?.into(), - CpuSet::online().ok(), - )); - for result in builder.on_all_shards(exec)?.join_all() { - result??; - } - Ok(()) - } - - async fn exec() -> wtx::Result<()> { - let listener = TcpListener::bind(crate::common::_host_from_args())?; - loop { - let stream = listener.accept().await?; - let _jh = glommio::spawn_local(async move { - let fb = &mut <_>::default(); - let rb = &mut <_>::default(); - if let Err(err) = crate::common::_accept_conn_and_echo_frames(fb, rb, stream).await - { - println!("{err}"); - } - }) - .detach(); - } - } -} - -fn main() -> wtx::Result<()> { - cfg_hack::hack() -} diff --git a/wtx/examples/web_socket_server_echo_raw_tokio.rs b/wtx/examples/web_socket_server_echo_raw_tokio.rs deleted file mode 100644 index 8c7ca32b..00000000 --- a/wtx/examples/web_socket_server_echo_raw_tokio.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! WebSocket echo server. - -mod common; - -use tokio::net::TcpListener; - -#[tokio::main] -async fn main() -> wtx::Result<()> { - let listener = TcpListener::bind(common::_host_from_args()).await?; - loop { - let (stream, _) = listener.accept().await?; - let _jh = tokio::spawn(async move { - if let Err(err) = tokio::task::unconstrained(common::_accept_conn_and_echo_frames( - &mut <_>::default(), - &mut <_>::default(), - stream, - )) - .await - { - println!("{err}"); - } - }); - } -} diff --git a/wtx/examples/web_socket_server_echo_raw_tokio_rustls.rs b/wtx/examples/web_socket_server_echo_raw_tokio_rustls.rs deleted file mode 100644 index e1ede742..00000000 --- a/wtx/examples/web_socket_server_echo_raw_tokio_rustls.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! WebSocket echo server. - -mod common; - -use rustls_pemfile::{certs, pkcs8_private_keys}; -use std::sync::Arc; -use tokio::net::TcpListener; -use tokio_rustls::{ - rustls::{Certificate, PrivateKey, ServerConfig}, - TlsAcceptor, -}; - -static CERT: &[u8] = include_bytes!("./localhost.crt"); -static KEY: &[u8] = include_bytes!("./localhost.key"); - -#[tokio::main] -async fn main() -> wtx::Result<()> { - let listener = TcpListener::bind(common::_host_from_args()).await?; - let tls_acceptor = tls_acceptor()?; - loop { - let (stream, _) = listener.accept().await?; - let local_tls_acceptor = tls_acceptor.clone(); - let _jh = tokio::spawn(async move { - let fun = || async move { - let stream = local_tls_acceptor.accept(stream).await?; - tokio::task::unconstrained(common::_accept_conn_and_echo_frames( - &mut <_>::default(), - &mut <_>::default(), - stream, - )) - .await - }; - if let Err(err) = fun().await { - println!("{err}"); - } - }); - } -} - -fn tls_acceptor() -> wtx::Result { - let mut keys: Vec = pkcs8_private_keys(&mut &*KEY) - .map(|certs| certs.into_iter().map(PrivateKey).collect()) - .map_err(wtx::Error::from)?; - let certs = certs(&mut &*CERT) - .map(|certs| certs.into_iter().map(Certificate).collect()) - .map_err(wtx::Error::from)?; - let config = ServerConfig::builder() - .with_safe_defaults() - .with_no_client_auth() - .with_single_cert(certs, keys.remove(0))?; - Ok(TlsAcceptor::from(Arc::new(config))) -} diff --git a/wtx/profiling/web_socket.rs b/wtx/profiling/web_socket.rs deleted file mode 100644 index ab50566e..00000000 --- a/wtx/profiling/web_socket.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Profiling - -use std::hint::black_box; -use wtx::{ - web_socket::{ - FrameBufferVec, FrameMutVec, OpCode, WebSocket, WebSocketClient, WebSocketServer, - }, - BytesStream, ReadBuffer, -}; - -#[tokio::main(flavor = "current_thread")] -async fn main() -> wtx::Result<()> { - let data = vec![52; 16 * 1024 * 1024]; - let mut fb = FrameBufferVec::default(); - let mut stream = BytesStream::default(); - black_box(from_client_to_server(&data, &mut fb, &mut stream).await?); - stream.clear(); - black_box(from_server_to_client(&data, &mut fb, &mut stream).await?); - Ok(()) -} - -async fn from_client_to_server( - data: &[u8], - fb: &mut FrameBufferVec, - stream: &mut BytesStream, -) -> wtx::Result<()> { - write(data, fb, WebSocketClient::new(<_>::default(), stream)).await?; - read(fb, WebSocketServer::new(<_>::default(), stream)).await?; - Ok(()) -} - -async fn from_server_to_client( - data: &[u8], - fb: &mut FrameBufferVec, - stream: &mut BytesStream, -) -> wtx::Result<()> { - write(data, fb, WebSocketServer::new(<_>::default(), stream)).await?; - read(fb, WebSocketClient::new(<_>::default(), stream)).await?; - Ok(()) -} - -async fn read( - fb: &mut FrameBufferVec, - mut ws: WebSocket, -) -> wtx::Result<()> { - let _frame = ws.read_msg(fb).await?; - Ok(()) -} - -async fn write( - data: &[u8], - fb: &mut FrameBufferVec, - mut ws: WebSocket, -) -> wtx::Result<()> { - ws.write_frame(&mut FrameMutVec::new_unfin(fb, OpCode::Text, data)?) - .await?; - ws.write_frame(&mut FrameMutVec::new_unfin(fb, OpCode::Continuation, data)?) - .await?; - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Continuation, data)?) - .await?; - Ok(()) -} diff --git a/wtx/src/bin/autobahn-client.rs b/wtx/src/bin/autobahn-client.rs new file mode 100644 index 00000000..713f2f42 --- /dev/null +++ b/wtx/src/bin/autobahn-client.rs @@ -0,0 +1,100 @@ +//! WebSocket autobahn client. + +use tokio::net::TcpStream; +use wtx::{ + rng::StdRng, + web_socket::{ + compression::Flate2, handshake::WebSocketConnectRaw, CloseCode, FrameBufferVec, FrameMutVec, + OpCode, WebSocketClient, + }, + PartitionedBuffer, +}; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), Error> { + let fb = &mut <_>::default(); + let host = "127.0.0.1:9080"; + let pb = &mut PartitionedBuffer::default(); + for case in 1..=get_case_count(fb, &host, pb).await? { + let (_, mut ws) = WebSocketClient::connect(WebSocketConnectRaw { + compression: Flate2::default(), + fb, + headers_buffer: &mut <_>::default(), + pb: &mut *pb, + rng: StdRng::default(), + stream: TcpStream::connect(host).await.map_err(wtx::Error::from)?, + uri: &format!("http://{host}/runCase?case={case}&agent=wtx"), + }) + .await?; + loop { + let mut frame = match ws.read_frame(fb).await { + Err(err) => { + println!("Error: {err}"); + ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Close, &[])?).await?; + break; + } + Ok(elem) => elem, + }; + match frame.op_code() { + OpCode::Binary | OpCode::Text => ws.write_frame(&mut frame).await?, + OpCode::Close => break, + _ => {} + } + } + } + WebSocketClient::connect(WebSocketConnectRaw { + compression: (), + fb, + headers_buffer: &mut <_>::default(), + pb, + rng: StdRng::default(), + stream: TcpStream::connect(host).await.map_err(wtx::Error::from)?, + uri: &format!("http://{host}/updateReports?agent=wtx"), + }) + .await? + .1 + .write_frame(&mut FrameMutVec::close_from_params(CloseCode::Normal, fb, &[])?) + .await?; + Ok(()) +} + +/// Error +#[derive(Debug)] +pub enum Error { + /// ParseIntError + ParseIntError(std::num::ParseIntError), + /// Wtx + Wtx(wtx::Error), +} + +impl From for Error { + fn from(from: std::num::ParseIntError) -> Self { + Self::ParseIntError(from) + } +} + +impl From for Error { + fn from(from: wtx::Error) -> Self { + Self::Wtx(from) + } +} + +async fn get_case_count( + fb: &mut FrameBufferVec, + host: &str, + pb: &mut PartitionedBuffer, +) -> Result { + let (_, mut ws) = WebSocketClient::connect(WebSocketConnectRaw { + compression: (), + fb, + headers_buffer: &mut <_>::default(), + pb, + rng: StdRng::default(), + stream: TcpStream::connect(host).await.map_err(wtx::Error::from)?, + uri: &&format!("http://{host}/getCaseCount"), + }) + .await?; + let rslt = ws.read_frame(fb).await?.text_payload().unwrap_or_default().parse()?; + ws.write_frame(&mut FrameMutVec::close_from_params(CloseCode::Normal, fb, &[])?).await?; + Ok(rslt) +} diff --git a/wtx/src/bin/autobahn-server.rs b/wtx/src/bin/autobahn-server.rs new file mode 100644 index 00000000..696d8efd --- /dev/null +++ b/wtx/src/bin/autobahn-server.rs @@ -0,0 +1,27 @@ +//! WebSocket autobahn server. + +#[path = "../../examples/common/mod.rs"] +mod common; + +use tokio::net::TcpListener; +use wtx::web_socket::compression::Flate2; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> wtx::Result<()> { + let listener = TcpListener::bind("127.0.0.1:8080").await?; + loop { + let (stream, _) = listener.accept().await?; + let _jh = tokio::spawn(async move { + if let Err(err) = tokio::task::unconstrained(common::_accept_conn_and_echo_frames( + Flate2::default(), + &mut <_>::default(), + &mut <_>::default(), + stream, + )) + .await + { + println!("{err}"); + } + }); + } +} diff --git a/wtx/src/bin/web-socket-profiling.rs b/wtx/src/bin/web-socket-profiling.rs new file mode 100644 index 00000000..09a1e772 --- /dev/null +++ b/wtx/src/bin/web-socket-profiling.rs @@ -0,0 +1,62 @@ +//! Profiling + +use std::hint::black_box; +use wtx::{ + rng::StaticRng, + web_socket::{FrameBufferVec, FrameMutVec, OpCode, WebSocket, WebSocketClient, WebSocketServer}, + BytesStream, PartitionedBuffer, +}; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> wtx::Result<()> { + let data = vec![52; 16 * 1024 * 1024]; + let mut fb = FrameBufferVec::default(); + let mut stream = BytesStream::default(); + black_box(from_client_to_server(&data, &mut fb, &mut stream).await?); + stream.clear(); + black_box(from_server_to_client(&data, &mut fb, &mut stream).await?); + Ok(()) +} + +async fn from_client_to_server( + data: &[u8], + fb: &mut FrameBufferVec, + stream: &mut BytesStream, +) -> wtx::Result<()> { + write(data, fb, WebSocketClient::new((), <_>::default(), StaticRng::default(), stream)).await?; + read(fb, WebSocketServer::new((), <_>::default(), StaticRng::default(), stream)).await?; + Ok(()) +} + +async fn from_server_to_client( + data: &[u8], + fb: &mut FrameBufferVec, + stream: &mut BytesStream, +) -> wtx::Result<()> { + write(data, fb, WebSocketServer::new((), <_>::default(), StaticRng::default(), stream)).await?; + read(fb, WebSocketClient::new((), <_>::default(), StaticRng::default(), stream)).await?; + Ok(()) +} + +#[allow( + // False positive + clippy::needless_pass_by_ref_mut +)] +async fn read( + fb: &mut FrameBufferVec, + mut ws: WebSocket<(), PartitionedBuffer, StaticRng, &mut BytesStream, IS_CLIENT>, +) -> wtx::Result<()> { + let _frame = ws.read_frame(fb).await?; + Ok(()) +} + +async fn write( + data: &[u8], + fb: &mut FrameBufferVec, + mut ws: WebSocket<(), PartitionedBuffer, StaticRng, &mut BytesStream, IS_CLIENT>, +) -> wtx::Result<()> { + ws.write_frame(&mut FrameMutVec::new_unfin(fb, OpCode::Text, data)?).await?; + ws.write_frame(&mut FrameMutVec::new_unfin(fb, OpCode::Continuation, data)?).await?; + ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Continuation, data)?).await?; + Ok(()) +} diff --git a/wtx/src/buffer.rs b/wtx/src/buffer.rs new file mode 100644 index 00000000..ff7bec16 --- /dev/null +++ b/wtx/src/buffer.rs @@ -0,0 +1,62 @@ +#![allow( + // Indices point to valid memory + clippy::unreachable +)] + +use alloc::vec::Vec; +use core::ops::{Deref, DerefMut}; + +/// Internal buffer not intended for public usage +#[derive(Debug, Default)] +pub struct Buffer { + buffer: Vec, + len: usize, +} + +impl Buffer { + pub(crate) fn clear(&mut self) { + self.len = 0; + } + + pub(crate) fn push_bytes(&mut self, bytes: &[u8]) { + let prev = self.len; + let curr = prev.wrapping_add(bytes.len()); + self.set_idx_through_expansion(curr); + self.get_mut(prev..curr).unwrap_or_default().copy_from_slice(bytes); + } + + pub(crate) fn set_idx_through_expansion(&mut self, len: usize) { + self.len = len; + self.expand(len); + } + + fn expand(&mut self, new_len: usize) { + if new_len > self.buffer.len() { + self.buffer.resize(new_len, 0); + } + } +} + +impl Deref for Buffer { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &Self::Target { + if let Some(el) = self.buffer.get(..self.len) { + el + } else { + unreachable!() + } + } +} + +impl DerefMut for Buffer { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + if let Some(el) = self.buffer.get_mut(..self.len) { + el + } else { + unreachable!() + } + } +} diff --git a/wtx/src/cache.rs b/wtx/src/cache.rs index bbd33b4d..3815a621 100644 --- a/wtx/src/cache.rs +++ b/wtx/src/cache.rs @@ -6,8 +6,8 @@ )] use core::{ - array, - sync::atomic::{AtomicUsize, Ordering}, + array, + sync::atomic::{AtomicUsize, Ordering}, }; /// Helper intended to avoid excessive allocations between multiple tasks/threads through @@ -17,33 +17,27 @@ use core::{ /// it is not desirable, you can create your own strategy or always allocate a new instance. #[derive(Debug)] pub struct Cache { - array: [T; N], - idx: AtomicUsize, + array: [T; N], + idx: AtomicUsize, } impl Cache { - /// It is up to the caller to provide all elements. - #[inline] - pub const fn new(array: [T; N]) -> Self { - Self { - array, - idx: AtomicUsize::new(0), - } - } + /// It is up to the caller to provide all elements. + #[inline] + pub const fn new(array: [T; N]) -> Self { + Self { array, idx: AtomicUsize::new(0) } + } - /// Each array element is constructed using `cb`. - #[inline] - pub fn from_cb(cb: impl FnMut(usize) -> T) -> Self { - Self { - array: array::from_fn(cb), - idx: AtomicUsize::new(0), - } - } + /// Each array element is constructed using `cb`. + #[inline] + pub fn from_cb(cb: impl FnMut(usize) -> T) -> Self { + Self { array: array::from_fn(cb), idx: AtomicUsize::new(0) } + } - /// Provides the next available element returning back to the begging when the internal - /// counter overflows `N`. - #[inline] - pub fn next(&self) -> &T { - &self.array[self.idx.fetch_add(1, Ordering::Relaxed) & (N - 1)] - } + /// Provides the next available element returning back to the begging when the internal + /// counter overflows `N`. + #[inline] + pub fn next(&self) -> &T { + &self.array[self.idx.fetch_add(1, Ordering::Relaxed) & (N - 1)] + } } diff --git a/wtx/src/error.rs b/wtx/src/error.rs index 0ed058f5..4a6dd8ab 100644 --- a/wtx/src/error.rs +++ b/wtx/src/error.rs @@ -1,7 +1,7 @@ use crate::ExpectedHeader; use core::{ - fmt::{Debug, Display, Formatter}, - num::TryFromIntError, + fmt::{Debug, Display, Formatter}, + num::TryFromIntError, }; /// Grouped individual errors @@ -11,164 +11,201 @@ use core::{ // * `Unexpected`: Received something that was not intended. #[derive(Debug)] pub enum Error { - /// Invalid UTF-8. - InvalidUTF8, - - /// Missing Header - MissingHeader { - /// See [ExpectedHeader]. - expected: ExpectedHeader, - }, - /// Url does not contain a host. - MissingHost, - - /// HTTP version does not match the expected method. - UnexpectedHttpMethod, - /// HTTP version does not match the expected value. - UnexpectedHttpVersion, - /// Unexpected end of file when reading. - UnexpectedEOF, - - /// The system does not process HTTP messages greater than 2048 bytes. - VeryLargeHttp, - - // External - // - /// See [glommio::GlommioError]. - #[cfg(all(feature = "glommio", feature = "hyper"))] - Glommio(std::sync::Mutex>), - /// See [glommio::GlommioError]. - #[cfg(all(feature = "glommio", not(feature = "hyper")))] - Glommio(Box>), - #[cfg(feature = "http")] - /// See [hyper::Error] - HttpError(http::Error), - /// See [http::header::InvalidHeaderName] - #[cfg(feature = "http")] - HttpInvalidHeaderName(http::header::InvalidHeaderName), - /// See [http::header::InvalidHeaderValue] - #[cfg(feature = "http")] - HttpInvalidHeaderValue(http::header::InvalidHeaderValue), - /// See [http::status::InvalidStatusCode] - #[cfg(feature = "http")] - HttpInvalidStatusCode(http::status::InvalidStatusCode), - #[cfg(feature = "web-socket-handshake")] - /// See [httparse::Error]. - HttpParse(httparse::Error), - #[cfg(feature = "hyper")] - /// See [hyper::Error] - HyperError(hyper::Error), - #[cfg(feature = "std")] - /// See [std::io::Error] - IoError(std::io::Error), - #[cfg(feature = "tokio-rustls")] - /// See [tokio_rustls::rustls::Error]. - TokioRustLsError(Box), - /// See [TryFromIntError] - TryFromIntError(TryFromIntError), - /// See [crate::web_socket::WebSocketError]. - WebSocketError(crate::web_socket::WebSocketError), + /// Invalid UTF-8. + InvalidUTF8, + /// Indices are out-of-bounds or the number of bytes are too small. + InvalidPartitionedBufferBounds, + + /// Missing Header + MissingHeader { + /// See [ExpectedHeader]. + expected: ExpectedHeader, + }, + /// Url does not contain a host. + MissingHost, + /// A value from an expected `key=value` structure was not found + MissingValue, + + /// A buffer was partially read or write but should in fact be fully processed. + UnexpectedBufferState, + /// HTTP version does not match the expected method. + UnexpectedHttpMethod, + /// HTTP version does not match the expected value. + UnexpectedHttpVersion, + /// Unexpected end of file when reading. + UnexpectedEOF, + + /// HTTP headers must be unique. + DuplicatedHeader, + /// The system does not process HTTP messages greater than 2048 bytes. + VeryLargeHttp, + + // External + // + #[cfg(feature = "embassy-net")] + /// See [embassy_net::tcp::Error]. + EmbassyNetTcp(embassy_net::tcp::Error), + #[cfg(feature = "flate2")] + /// See [flate2::CompressError]. + Flate2CompressError(flate2::CompressError), + #[cfg(feature = "flate2")] + /// See [flate2::DecompressError]. + Flate2DecompressError(Box), + /// See [glommio::GlommioError]. + #[cfg(feature = "glommio")] + Glommio(Box>), + #[cfg(feature = "http")] + /// See [http::Error] + HttpError(http::Error), + /// See [http::header::InvalidHeaderName] + #[cfg(feature = "http")] + HttpInvalidHeaderName(http::header::InvalidHeaderName), + /// See [http::header::InvalidHeaderValue] + #[cfg(feature = "http")] + HttpInvalidHeaderValue(http::header::InvalidHeaderValue), + /// See [http::status::InvalidStatusCode] + #[cfg(feature = "http")] + HttpInvalidStatusCode(http::status::InvalidStatusCode), + #[cfg(feature = "web-socket-handshake")] + /// See [httparse::Error]. + HttpParse(httparse::Error), + #[cfg(feature = "std")] + /// See [std::io::Error] + IoError(std::io::Error), + /// See [core::num::ParseIntError]. + ParseIntError(core::num::ParseIntError), + #[cfg(feature = "tokio-rustls")] + /// See [tokio_rustls::rustls::Error]. + TokioRustLsError(Box), + /// See [TryFromIntError] + TryFromIntError(TryFromIntError), + /// See [crate::web_socket::WebSocketError]. + WebSocketError(crate::web_socket::WebSocketError), } impl Display for Error { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - ::fmt(self, f) - } + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + ::fmt(self, f) + } } #[cfg(feature = "std")] impl std::error::Error for Error {} -#[cfg(feature = "glommio")] -impl From> for Error { - #[inline] - fn from(from: glommio::GlommioError<()>) -> Self { - Self::Glommio(from.into()) - } +#[cfg(feature = "embassy-net")] +impl From for Error { + #[inline] + fn from(from: embassy_net::tcp::Error) -> Self { + Self::EmbassyNetTcp(from) + } +} + +#[cfg(feature = "flate2")] +impl From for Error { + #[inline] + fn from(from: flate2::CompressError) -> Self { + Self::Flate2CompressError(from) + } +} + +#[cfg(feature = "flate2")] +impl From for Error { + #[inline] + fn from(from: flate2::DecompressError) -> Self { + Self::Flate2DecompressError(from.into()) + } } -#[cfg(feature = "hyper")] -impl From for Error { - #[inline] - fn from(from: hyper::Error) -> Self { - Self::HyperError(from) - } +#[cfg(feature = "glommio")] +impl From> for Error { + #[inline] + fn from(from: glommio::GlommioError<()>) -> Self { + Self::Glommio(from.into()) + } } #[cfg(feature = "http")] impl From for Error { - #[inline] - fn from(from: http::Error) -> Self { - Self::HttpError(from) - } + #[inline] + fn from(from: http::Error) -> Self { + Self::HttpError(from) + } } #[cfg(feature = "http")] impl From for Error { - #[inline] - fn from(from: http::header::InvalidHeaderName) -> Self { - Self::HttpInvalidHeaderName(from) - } + #[inline] + fn from(from: http::header::InvalidHeaderName) -> Self { + Self::HttpInvalidHeaderName(from) + } } #[cfg(feature = "http")] impl From for Error { - #[inline] - fn from(from: http::header::InvalidHeaderValue) -> Self { - Self::HttpInvalidHeaderValue(from) - } + #[inline] + fn from(from: http::header::InvalidHeaderValue) -> Self { + Self::HttpInvalidHeaderValue(from) + } } #[cfg(feature = "http")] impl From for Error { - #[inline] - fn from(from: http::status::InvalidStatusCode) -> Self { - Self::HttpInvalidStatusCode(from) - } + #[inline] + fn from(from: http::status::InvalidStatusCode) -> Self { + Self::HttpInvalidStatusCode(from) + } } #[cfg(feature = "web-socket-handshake")] impl From for Error { - #[inline] - fn from(from: httparse::Error) -> Self { - Self::HttpParse(from) - } + #[inline] + fn from(from: httparse::Error) -> Self { + Self::HttpParse(from) + } +} + +#[cfg(feature = "std")] +impl From for Error { + #[inline] + fn from(from: std::io::Error) -> Self { + Self::IoError(from) + } +} + +#[cfg(feature = "std")] +impl From for Error { + #[inline] + fn from(from: core::num::ParseIntError) -> Self { + Self::ParseIntError(from) + } } impl From for Error { - #[inline] - fn from(_: core::str::Utf8Error) -> Self { - Self::InvalidUTF8 - } + #[inline] + fn from(_: core::str::Utf8Error) -> Self { + Self::InvalidUTF8 + } } #[cfg(feature = "tokio-rustls")] impl From for Error { - #[inline] - fn from(from: tokio_rustls::rustls::Error) -> Self { - Self::TokioRustLsError(from.into()) - } + #[inline] + fn from(from: tokio_rustls::rustls::Error) -> Self { + Self::TokioRustLsError(from.into()) + } } impl From for Error { - #[inline] - fn from(from: TryFromIntError) -> Self { - Self::TryFromIntError(from) - } + #[inline] + fn from(from: TryFromIntError) -> Self { + Self::TryFromIntError(from) + } } impl From for Error { - #[inline] - fn from(from: crate::web_socket::WebSocketError) -> Self { - Self::WebSocketError(from) - } -} - -#[cfg(feature = "std")] -impl From for Error { - #[inline] - fn from(from: std::io::Error) -> Self { - Self::IoError(from) - } + #[inline] + fn from(from: crate::web_socket::WebSocketError) -> Self { + Self::WebSocketError(from) + } } diff --git a/wtx/src/expected_header.rs b/wtx/src/expected_header.rs index a89ad9c1..23e65e69 100644 --- a/wtx/src/expected_header.rs +++ b/wtx/src/expected_header.rs @@ -2,12 +2,12 @@ #[allow(non_camel_case_types)] #[derive(Debug)] pub enum ExpectedHeader { - /// `connection` key with `upgrade` value. - Connection_Upgrade, - /// `sec-websocket-key` key. - SecWebSocketKey, - /// `sec-websocket-version` key with `13` value. - SecWebSocketVersion_13, - /// `upgrade` key with `websocket` value. - Upgrade_WebSocket, + /// `connection` key with `upgrade` value. + Connection_Upgrade, + /// `sec-websocket-key` key. + SecWebSocketKey, + /// `sec-websocket-version` key with `13` value. + SecWebSocketVersion_13, + /// `upgrade` key with `websocket` value. + Upgrade_WebSocket, } diff --git a/wtx/src/http_structs.rs b/wtx/src/http_structs.rs new file mode 100644 index 00000000..bdfbc7f1 --- /dev/null +++ b/wtx/src/http_structs.rs @@ -0,0 +1,12 @@ +//! Essential HTTP structures + +mod header; +mod parse_status; +mod request; +mod response; + +pub use header::Header; +pub(crate) use header::HeaderSlice; +pub use parse_status::ParseStatus; +pub use request::Request; +pub use response::Response; diff --git a/wtx/src/http_structs/header.rs b/wtx/src/http_structs/header.rs new file mode 100644 index 00000000..8db2c0f0 --- /dev/null +++ b/wtx/src/http_structs/header.rs @@ -0,0 +1,58 @@ +use core::{mem, slice}; + +#[derive(Debug)] +#[repr(transparent)] +pub struct Header<'buffer>(httparse::Header<'buffer>); + +impl<'buffer> Header<'buffer> { + pub(crate) const EMPTY: Header<'static> = Header(httparse::EMPTY_HEADER); + + pub(crate) fn new(name: &'buffer str, value: &'buffer [u8]) -> Self { + Self(httparse::Header { name, value }) + } + + pub(crate) fn name(&self) -> &str { + self.0.name + } + + pub(crate) fn value(&self) -> &[u8] { + self.0.value + } +} + +pub(crate) struct HeaderSlice(pub(crate) T); + +impl<'buffer, 'headers> From<&'headers mut [Header<'buffer>]> + for HeaderSlice<&'headers mut [httparse::Header<'buffer>]> +{ + #[inline] + fn from(from: &'headers mut [Header<'buffer>]) -> Self { + assert!(mem::size_of::
() == mem::size_of::()); + assert!(mem::align_of::
() == mem::align_of::()); + let len = from.len(); + // SAFETY: `Header` and `httparse::Header` have the same size and alignment due + // to `#[transparent]` + Self(unsafe { slice::from_raw_parts_mut(from.as_mut_ptr().cast(), len) }) + } +} + +impl<'buffer, 'headers> From<&'headers [httparse::Header<'buffer>]> + for HeaderSlice<&'headers [Header<'buffer>]> +{ + #[inline] + fn from(from: &'headers [httparse::Header<'buffer>]) -> Self { + assert!(mem::size_of::
() == mem::size_of::()); + assert!(mem::align_of::
() == mem::align_of::()); + let len = from.len(); + // SAFETY: `Header` and `httparse::Header` have the same size and alignment due + // to `#[transparent]` + Self(unsafe { slice::from_raw_parts(from.as_ptr().cast(), len) }) + } +} + +#[test] +fn does_not_trigger_a_panic() { + let inner_header0 = httparse::Header { name: "foo", value: &[1, 2, 3] }; + let inner_header1 = httparse::Header { name: "bar", value: &[4, 5, 6] }; + let _headers = HeaderSlice::from(&[inner_header0, inner_header1][..]); +} diff --git a/wtx/src/http_structs/parse_status.rs b/wtx/src/http_structs/parse_status.rs new file mode 100644 index 00000000..2a8d060f --- /dev/null +++ b/wtx/src/http_structs/parse_status.rs @@ -0,0 +1,18 @@ +/// The result of a HTTP request or response frame parse +#[derive(Debug)] +pub enum ParseStatus { + /// The completed result. + Complete(usize), + /// A partial result. + Partial, +} + +impl From> for ParseStatus { + #[inline] + fn from(from: httparse::Status) -> Self { + match from { + httparse::Status::Complete(elem) => Self::Complete(elem), + httparse::Status::Partial => Self::Partial, + } + } +} diff --git a/wtx/src/http_structs/request.rs b/wtx/src/http_structs/request.rs new file mode 100644 index 00000000..a050703b --- /dev/null +++ b/wtx/src/http_structs/request.rs @@ -0,0 +1,86 @@ +use crate::http_structs::{Header, HeaderSlice, ParseStatus}; + +/// Raw request that can be converted to other high-level requests. +#[derive(Debug)] +pub struct Request<'buffer, 'headers> { + req: httparse::Request<'headers, 'buffer>, +} + +impl<'buffer, 'headers> Request<'buffer, 'headers> { + pub(crate) fn new(headers: &'headers mut [Header<'buffer>]) -> Self { + Self { req: httparse::Request::new(HeaderSlice::from(headers).0) } + } + + /// Method + #[inline] + pub fn method(&self) -> Option<&'buffer str> { + self.req.method + } + + /// Version + #[inline] + pub fn version(&self) -> Option { + self.req.version + } + + pub(crate) fn headers(&self) -> &[Header<'buffer>] { + HeaderSlice::from(&*self.req.headers).0 + } + + pub(crate) fn parse(&mut self, buffer: &'buffer [u8]) -> crate::Result { + Ok(self.req.parse(buffer)?.into()) + } +} + +#[cfg(feature = "http")] +mod http { + use crate::http_structs::Request; + use http::{HeaderMap, HeaderName, HeaderValue, Method}; + + impl<'buffer, 'headers> TryFrom> for http::Request<&'buffer [u8]> { + type Error = crate::Error; + + #[inline] + fn try_from(from: Request<'buffer, 'headers>) -> Result { + let method = + Method::try_from(from.req.method.ok_or(crate::Error::UnexpectedHttpVersion)?).unwrap(); + let version = if let Some(1) = from.req.version { + http::Version::HTTP_11 + } else { + return Err(crate::Error::UnexpectedHttpVersion); + }; + let mut headers = HeaderMap::with_capacity(from.req.headers.len()); + for h in from.req.headers { + let key = HeaderName::from_bytes(h.name.as_bytes())?; + let value = HeaderValue::from_bytes(h.value)?; + let _ = headers.append(key, value); + } + let mut req = http::Request::new(&[][..]); + *req.headers_mut() = headers; + *req.method_mut() = method; + *req.uri_mut() = from.req.path.unwrap().parse().unwrap(); + *req.version_mut() = version; + Ok(req) + } + } + + impl<'buffer, 'headers> TryFrom> for http::Request<()> { + type Error = crate::Error; + + #[inline] + fn try_from(from: Request<'buffer, 'headers>) -> Result { + let (parts, _) = http::Request::<&'buffer [u8]>::try_from(from)?.into_parts(); + Ok(http::Request::from_parts(parts, ())) + } + } + + impl<'buffer, 'headers> TryFrom> for http::Request> { + type Error = crate::Error; + + #[inline] + fn try_from(from: Request<'buffer, 'headers>) -> Result { + let (parts, body) = http::Request::<&'buffer [u8]>::try_from(from)?.into_parts(); + Ok(http::Request::from_parts(parts, body.to_vec())) + } + } +} diff --git a/wtx/src/http_structs/response.rs b/wtx/src/http_structs/response.rs new file mode 100644 index 00000000..4b38d7e3 --- /dev/null +++ b/wtx/src/http_structs/response.rs @@ -0,0 +1,90 @@ +use crate::http_structs::{Header, HeaderSlice, ParseStatus}; + +/// Raw response that can be converted to other high-level responses. +#[derive(Debug)] +pub struct Response<'buffer, 'headers> { + res: httparse::Response<'headers, 'buffer>, +} + +impl<'buffer, 'headers> Response<'buffer, 'headers> +where + 'buffer: 'headers, +{ + pub(crate) fn new(headers: &'headers mut [Header<'buffer>]) -> Self { + Self { res: httparse::Response::new(HeaderSlice::from(headers).0) } + } + + /// Status code + #[inline] + pub fn code(&self) -> Option { + self.res.code + } + + #[inline] + pub(crate) fn code_mut(&mut self) -> &mut Option { + &mut self.res.code + } + + pub(crate) fn headers(&self) -> &[Header<'buffer>] { + HeaderSlice::from(&*self.res.headers).0 + } + + pub(crate) fn parse(&mut self, buffer: &'buffer [u8]) -> crate::Result { + Ok(self.res.parse(buffer)?.into()) + } + + pub(crate) fn version_mut(&mut self) -> &mut Option { + &mut self.res.version + } +} + +#[cfg(feature = "http")] +mod http { + use crate::http_structs::Response; + use http::{HeaderMap, HeaderName, HeaderValue, StatusCode}; + + impl<'buffer, 'headers> TryFrom> for http::Response<&'buffer [u8]> { + type Error = crate::Error; + + #[inline] + fn try_from(from: Response<'buffer, 'headers>) -> Result { + let status = StatusCode::from_u16(from.res.code.ok_or(crate::Error::UnexpectedHttpVersion)?)?; + let version = if let Some(1) = from.res.version { + http::Version::HTTP_11 + } else { + return Err(crate::Error::UnexpectedHttpVersion); + }; + let mut headers = HeaderMap::with_capacity(from.res.headers.len()); + for h in from.res.headers { + let key = HeaderName::from_bytes(h.name.as_bytes())?; + let value = HeaderValue::from_bytes(h.value)?; + let _ = headers.append(key, value); + } + let mut res = http::Response::new(&[][..]); + *res.headers_mut() = headers; + *res.status_mut() = status; + *res.version_mut() = version; + Ok(res) + } + } + + impl<'buffer, 'headers> TryFrom> for http::Response<()> { + type Error = crate::Error; + + #[inline] + fn try_from(from: Response<'buffer, 'headers>) -> Result { + let (parts, _) = http::Response::<&'buffer [u8]>::try_from(from)?.into_parts(); + Ok(http::Response::from_parts(parts, ())) + } + } + + impl<'buffer, 'headers> TryFrom> for http::Response> { + type Error = crate::Error; + + #[inline] + fn try_from(from: Response<'buffer, 'headers>) -> Result { + let (parts, body) = http::Response::<&'buffer [u8]>::try_from(from)?.into_parts(); + Ok(http::Response::from_parts(parts, body.to_vec())) + } + } +} diff --git a/wtx/src/lib.rs b/wtx/src/lib.rs index 32686321..923e8fbf 100644 --- a/wtx/src/lib.rs +++ b/wtx/src/lib.rs @@ -1,34 +1,35 @@ -#![cfg_attr( - not(feature = "async-trait"), - feature(array_chunks, async_fn_in_trait, impl_trait_projections, inline_const) -)] #![cfg_attr(not(feature = "std"), no_std)] #![doc = include_str!("../README.md")] +#![feature(array_chunks, async_fn_in_trait, impl_trait_projections)] extern crate alloc; +#[macro_use] +mod macros; + +mod buffer; mod cache; mod error; mod expected_header; -mod misc; -mod read_buffer; #[cfg(feature = "web-socket-handshake")] -mod request; -#[cfg(feature = "web-socket-handshake")] -mod response; +pub mod http_structs; +mod misc; +mod partitioned_buffer; +pub mod rng; +#[cfg(feature = "tracing")] +mod role; mod stream; pub mod web_socket; -pub use crate::stream::{BytesStream, DummyStream, Stream}; pub use cache::Cache; pub use error::Error; pub use expected_header::ExpectedHeader; pub use misc::uri_parts::UriParts; -pub use read_buffer::ReadBuffer; -#[cfg(feature = "web-socket-handshake")] -pub use request::Request; -#[cfg(feature = "web-socket-handshake")] -pub use response::Response; +pub use partitioned_buffer::PartitionedBuffer; +pub use stream::{BytesStream, Stream}; + +pub(crate) const DFLT_PARTITIONED_BUFFER_LEN: usize = 32 * 1024; +pub(crate) const MAX_PAYLOAD_LEN: usize = 64 * 1024 * 1024; /// Shortcut of [core::result::Result]. pub type Result = core::result::Result; diff --git a/wtx/src/macros.rs b/wtx/src/macros.rs new file mode 100644 index 00000000..bfe4087a --- /dev/null +++ b/wtx/src/macros.rs @@ -0,0 +1,38 @@ +macro_rules! create_enum { + ($(#[$meta:meta])* $vis:vis enum $name:ident { + $($(#[$variant_meta:meta])* $variant_ident:ident = $variant_value:literal,)* + }) => { + $(#[$meta])* + $vis enum $name { + $($(#[$variant_meta])* $variant_ident = $variant_value,)* + } + + impl From<$name> for u8 { + #[inline] + fn from(from: $name) -> Self { + match from { + $($name::$variant_ident => $variant_value,)* + } + } + } + + impl TryFrom for $name { + type Error = crate::Error; + + #[inline] + fn try_from(from: u8) -> Result { + match from { + $(x if x == u8::from($name::$variant_ident) => Ok($name::$variant_ident),)* + _ => Err(crate::web_socket::WebSocketError::InvalidFromByte { provided: from }.into()), + } + } + } + } +} + +macro_rules! debug { + ($($tt:tt)+) => { + #[cfg(feature = "tracing")] + tracing::debug!($($tt)+); + }; +} diff --git a/wtx/src/misc.rs b/wtx/src/misc.rs index 3d93a985..fd2cec43 100644 --- a/wtx/src/misc.rs +++ b/wtx/src/misc.rs @@ -1,65 +1,95 @@ mod incomplete_utf8_char; -mod rng; mod traits; pub(crate) mod uri_parts; mod utf8_errors; +use core::ops::Range; pub(crate) use incomplete_utf8_char::{CompleteErr, IncompleteUtf8Char}; -pub(crate) use rng::Rng; -pub(crate) use traits::{AsyncBounds, Expand, SingleTypeStorage}; +pub(crate) use traits::{Expand, SingleTypeStorage}; pub(crate) use utf8_errors::{ExtUtf8Error, StdUtf8Error}; pub(crate) fn from_utf8_opt(bytes: &[u8]) -> Option<&str> { - #[cfg(feature = "simdutf8")] - return simdutf8::basic::from_utf8(bytes).ok(); - #[cfg(not(feature = "simdutf8"))] - return core::str::from_utf8(bytes).ok(); + #[cfg(feature = "simdutf8")] + return simdutf8::basic::from_utf8(bytes).ok(); + #[cfg(not(feature = "simdutf8"))] + return core::str::from_utf8(bytes).ok(); } pub(crate) fn from_utf8_ext_rslt(bytes: &[u8]) -> Result<&str, ExtUtf8Error> { - let err = match from_utf8_std_rslt(bytes) { - Ok(elem) => return Ok(elem), - Err(error) => error, - }; - let (_valid_bytes, after_valid) = bytes.split_at(err.valid_up_to); - match err.error_len { - None => Err(ExtUtf8Error::Incomplete { - incomplete_ending_char: { - let opt = IncompleteUtf8Char::new(after_valid); - opt.ok_or(ExtUtf8Error::Invalid)? - }, - }), - Some(_) => Err(ExtUtf8Error::Invalid), - } + let err = match from_utf8_std_rslt(bytes) { + Ok(elem) => return Ok(elem), + Err(error) => error, + }; + let (_valid_bytes, after_valid) = bytes.split_at(err.valid_up_to); + match err.error_len { + None => Err(ExtUtf8Error::Incomplete { + incomplete_ending_char: { + let opt = IncompleteUtf8Char::new(after_valid); + opt.ok_or(ExtUtf8Error::Invalid)? + }, + }), + Some(_) => Err(ExtUtf8Error::Invalid), + } } pub(crate) fn from_utf8_std_rslt(bytes: &[u8]) -> Result<&str, StdUtf8Error> { - #[cfg(feature = "simdutf8")] - return simdutf8::compat::from_utf8(bytes).map_err(|element| StdUtf8Error { - valid_up_to: element.valid_up_to(), - error_len: element.error_len(), - }); - #[cfg(not(feature = "simdutf8"))] - return core::str::from_utf8(bytes).map_err(|element| StdUtf8Error { - valid_up_to: element.valid_up_to(), - error_len: element.error_len(), - }); + #[cfg(feature = "simdutf8")] + return simdutf8::compat::from_utf8(bytes).map_err(|element| StdUtf8Error { + valid_up_to: element.valid_up_to(), + error_len: element.error_len(), + }); + #[cfg(not(feature = "simdutf8"))] + return core::str::from_utf8(bytes).map_err(|element| StdUtf8Error { + valid_up_to: element.valid_up_to(), + error_len: element.error_len(), + }); } #[cfg(test)] -mod tests { - use crate::UriParts; +pub(crate) fn _tracing() -> crate::Result { + use tracing_subscriber::{prelude::__tracing_subscriber_SubscriberExt, EnvFilter}; + let env_filter = EnvFilter::from_default_env(); + let tracing_tree = tracing_tree::HierarchicalLayer::default() + .with_writer(std::io::stdout) + .with_indent_lines(true) + .with_indent_amount(2) + .with_thread_names(false) + .with_thread_ids(true) + .with_verbose_exit(false) + .with_verbose_entry(false) + .with_targets(true); + let subscriber = tracing_subscriber::Registry::default().with(env_filter).with(tracing_tree); + Ok(subscriber) +} + +pub(crate) fn _trim(bytes: &[u8]) -> &[u8] { + _trim_end(_trim_begin(bytes)) +} + +pub(crate) fn _truncated_slice(slice: &[T], range: Range) -> &[T] { + let start = range.start; + let end = range.end.min(slice.len()); + slice.get(start..end).unwrap_or_default() +} + +fn _trim_begin(mut bytes: &[u8]) -> &[u8] { + while let [first, rest @ ..] = bytes { + if first.is_ascii_whitespace() { + bytes = rest; + } else { + break; + } + } + bytes +} - #[test] - fn uri_parts_generates_correct_output() { - assert_eq!( - UriParts::from("foo://user:pass@sub.domain.com:80/pa/th?query=value#hash"), - UriParts { - authority: "user:pass@sub.domain.com:80", - host: "sub.domain.com:80", - hostname: "sub.domain.com", - href: "/pa/th?query=value#hash" - } - ); +fn _trim_end(mut bytes: &[u8]) -> &[u8] { + while let [rest @ .., last] = bytes { + if last.is_ascii_whitespace() { + bytes = rest; + } else { + break; } + } + bytes } diff --git a/wtx/src/misc/incomplete_utf8_char.rs b/wtx/src/misc/incomplete_utf8_char.rs index cacc795d..0399f9bf 100644 --- a/wtx/src/misc/incomplete_utf8_char.rs +++ b/wtx/src/misc/incomplete_utf8_char.rs @@ -1,76 +1,71 @@ use crate::misc::from_utf8_std_rslt; +#[derive(Debug)] pub(crate) struct IncompleteUtf8Char { - buffer: [u8; 4], - len: usize, + buffer: [u8; 4], + len: usize, } impl IncompleteUtf8Char { - pub(crate) fn new(bytes: &[u8]) -> Option { - let len = bytes.len().min(4); - let bytes_slice = bytes.get(..len)?; - let mut buffer = [0, 0, 0, 0]; - buffer.get_mut(..len)?.copy_from_slice(bytes_slice); - Some(Self { - buffer, - len: bytes.len(), - }) - } + pub(crate) fn new(bytes: &[u8]) -> Option { + let len = bytes.len().min(4); + let bytes_slice = bytes.get(..len)?; + let mut buffer = [0, 0, 0, 0]; + buffer.get_mut(..len)?.copy_from_slice(bytes_slice); + Some(Self { buffer, len: bytes.len() }) + } - pub(crate) fn complete<'bytes>( - &mut self, - bytes: &'bytes [u8], - ) -> (Result<(), CompleteErr>, &'bytes [u8]) { - let (consumed, tce) = self.push_to_build_valid_char(bytes); - let remaining = bytes.get(consumed..).unwrap_or_default(); - match tce { - None => (Ok(()), remaining), - Some(elem) => (Err(elem), remaining), - } + pub(crate) fn complete<'bytes>( + &mut self, + bytes: &'bytes [u8], + ) -> (Result<(), CompleteErr>, &'bytes [u8]) { + let (consumed, complete_err) = self.push_to_build_valid_char(bytes); + let remaining = bytes.get(consumed..).unwrap_or_default(); + match complete_err { + None => (Ok(()), remaining), + Some(elem) => (Err(elem), remaining), } + } - fn push_to_build_valid_char(&mut self, bytes: &[u8]) -> (usize, Option) { - let initial_len = self.len; - let to_write_len; - { - let unwritten = self.buffer.get_mut(initial_len..).unwrap_or_default(); - to_write_len = unwritten.len().min(bytes.len()); - unwritten - .get_mut(..to_write_len) - .unwrap_or_default() - .copy_from_slice(bytes.get(..to_write_len).unwrap_or_default()); - }; - let new_bytes = { - let len = initial_len.wrapping_add(to_write_len); - self.buffer.get(..len).unwrap_or_default() - }; - if let Err(err) = from_utf8_std_rslt(new_bytes) { - if err.valid_up_to > 0 { - self.len = err.valid_up_to; - (err.valid_up_to.wrapping_sub(initial_len), None) - } else { - match err.error_len { - None => { - self.len = new_bytes.len(); - (to_write_len, Some(CompleteErr::InsufficientInput)) - } - Some(invalid_seq_len) => { - self.len = invalid_seq_len; - ( - invalid_seq_len.wrapping_sub(initial_len), - Some(CompleteErr::HasInvalidBytes), - ) - } - } - } - } else { + fn push_to_build_valid_char(&mut self, bytes: &[u8]) -> (usize, Option) { + let initial_len = self.len; + let to_write_len = { + let unwritten = self.buffer.get_mut(initial_len..).unwrap_or_default(); + let to_write_len = unwritten.len().min(bytes.len()); + unwritten + .get_mut(..to_write_len) + .unwrap_or_default() + .copy_from_slice(bytes.get(..to_write_len).unwrap_or_default()); + to_write_len + }; + let new_bytes = { + let len = initial_len.wrapping_add(to_write_len); + self.buffer.get(..len).unwrap_or_default() + }; + if let Err(err) = from_utf8_std_rslt(new_bytes) { + if err.valid_up_to > 0 { + self.len = err.valid_up_to; + (err.valid_up_to.wrapping_sub(initial_len), None) + } else { + match err.error_len { + None => { self.len = new_bytes.len(); - (to_write_len, None) + (to_write_len, Some(CompleteErr::InsufficientInput)) + } + Some(invalid_seq_len) => { + self.len = invalid_seq_len; + (invalid_seq_len.wrapping_sub(initial_len), Some(CompleteErr::HasInvalidBytes)) + } } + } + } else { + self.len = new_bytes.len(); + (to_write_len, None) } + } } pub(crate) enum CompleteErr { - HasInvalidBytes, - InsufficientInput, + HasInvalidBytes, + InsufficientInput, } diff --git a/wtx/src/misc/rng.rs b/wtx/src/misc/rng.rs deleted file mode 100644 index 5771e807..00000000 --- a/wtx/src/misc/rng.rs +++ /dev/null @@ -1,26 +0,0 @@ -use rand::{rngs::SmallRng, Rng as _, SeedableRng}; - -// Used for compatibility reasons -#[derive(Debug)] -pub(crate) struct Rng { - rng: SmallRng, -} - -impl Rng { - pub(crate) fn random_u8_4(&mut self) -> [u8; 4] { - self.rng.gen() - } - - pub(crate) fn _random_u8_16(&mut self) -> [u8; 16] { - self.rng.gen() - } -} - -impl Default for Rng { - #[inline] - fn default() -> Self { - Self { - rng: SmallRng::from_entropy(), - } - } -} diff --git a/wtx/src/misc/traits.rs b/wtx/src/misc/traits.rs index 12de4d40..5343caa7 100644 --- a/wtx/src/misc/traits.rs +++ b/wtx/src/misc/traits.rs @@ -1,84 +1,70 @@ use alloc::vec::Vec; -/// Internal trait not intended for public usage -#[cfg(not(feature = "async-trait"))] -pub trait AsyncBounds {} - -#[cfg(not(feature = "async-trait"))] -impl AsyncBounds for T where T: ?Sized {} - -/// Internal trait not intended for public usage -#[cfg(feature = "async-trait")] -pub trait AsyncBounds: Send + Sync {} - -#[cfg(feature = "async-trait")] -impl AsyncBounds for T where T: Send + Sync + ?Sized {} - /// Internal trait not intended for public usage pub trait Expand { - /// Internal method not intended for public usage - fn expand(&mut self, len: usize); + /// Internal method not intended for public usage + fn expand(&mut self, len: usize); } impl Expand for &mut T where - T: Expand, + T: Expand, { - fn expand(&mut self, len: usize) { - (*self).expand(len); - } + fn expand(&mut self, len: usize) { + (*self).expand(len); + } } impl Expand for Vec where - T: Clone + Default, + T: Clone + Default, { - fn expand(&mut self, len: usize) { - if len > self.len() { - self.resize(len, <_>::default()); - } + fn expand(&mut self, len: usize) { + if len > self.len() { + self.resize(len, <_>::default()); } + } } impl Expand for &mut [T] { - fn expand(&mut self, _: usize) {} + fn expand(&mut self, _: usize) {} } impl Expand for [T; N] { - fn expand(&mut self, _: usize) {} + fn expand(&mut self, _: usize) {} } /// Internal trait not intended for public usage pub trait SingleTypeStorage { - /// Internal method not intended for public usage - type Item; + /// Internal method not intended for public usage + type Item; } impl SingleTypeStorage for &T where - T: SingleTypeStorage, + T: SingleTypeStorage, { - type Item = T::Item; + type Item = T::Item; } impl SingleTypeStorage for &mut T where - T: SingleTypeStorage, + T: SingleTypeStorage, { - type Item = T::Item; + type Item = T::Item; } impl SingleTypeStorage for [T; N] { - type Item = T; + type Item = T; } impl SingleTypeStorage for &'_ [T] { - type Item = T; + type Item = T; } impl SingleTypeStorage for &'_ mut [T] { - type Item = T; + type Item = T; } impl SingleTypeStorage for Vec { - type Item = T; + type Item = T; } diff --git a/wtx/src/misc/uri_parts.rs b/wtx/src/misc/uri_parts.rs index 39ff8f8d..1c459659 100644 --- a/wtx/src/misc/uri_parts.rs +++ b/wtx/src/misc/uri_parts.rs @@ -5,32 +5,45 @@ /// ``` #[derive(Debug, Eq, PartialEq)] pub struct UriParts<'uri> { - /// `user:pass@sub.domain.com:80` - pub authority: &'uri str, - /// `sub.domain.com:80` - pub host: &'uri str, - /// `sub.domain.com` - pub hostname: &'uri str, - /// `/pa/th?query=value#hash` - pub href: &'uri str, + /// `user:pass@sub.domain.com:80` + pub authority: &'uri str, + /// `sub.domain.com:80` + pub host: &'uri str, + /// `sub.domain.com` + pub hostname: &'uri str, + /// `/pa/th?query=value#hash` + pub href: &'uri str, } impl<'str> From<&'str str> for UriParts<'str> { - #[inline] - fn from(from: &'str str) -> Self { - let after_schema = from.split("://").nth(1).unwrap_or(from); - let (authority, href) = after_schema - .as_bytes() - .iter() - .position(|el| el == &b'/') - .map_or((after_schema, "/"), |el| after_schema.split_at(el)); - let host = authority.split('@').nth(1).unwrap_or(authority); - let hostname = host.rsplit(':').nth(1).unwrap_or(host); - Self { - authority, - host, - hostname, - href, - } - } + #[inline] + fn from(from: &'str str) -> Self { + let after_schema = from.split("://").nth(1).unwrap_or(from); + let (authority, href) = after_schema + .as_bytes() + .iter() + .position(|el| el == &b'/') + .map_or((after_schema, "/"), |el| after_schema.split_at(el)); + let host = authority.split('@').nth(1).unwrap_or(authority); + let hostname = host.rsplit(':').nth(1).unwrap_or(host); + Self { authority, host, hostname, href } + } +} + +#[cfg(test)] +mod tests { + use crate::UriParts; + + #[test] + fn uri_parts_generates_correct_output() { + assert_eq!( + UriParts::from("foo://user:pass@sub.domain.com:80/pa/th?query=value#hash"), + UriParts { + authority: "user:pass@sub.domain.com:80", + host: "sub.domain.com:80", + hostname: "sub.domain.com", + href: "/pa/th?query=value#hash" + } + ); + } } diff --git a/wtx/src/misc/utf8_errors.rs b/wtx/src/misc/utf8_errors.rs index d2219570..cb5ac1cf 100644 --- a/wtx/src/misc/utf8_errors.rs +++ b/wtx/src/misc/utf8_errors.rs @@ -1,13 +1,11 @@ use crate::misc::IncompleteUtf8Char; pub(crate) enum ExtUtf8Error { - Incomplete { - incomplete_ending_char: IncompleteUtf8Char, - }, - Invalid, + Incomplete { incomplete_ending_char: IncompleteUtf8Char }, + Invalid, } pub(crate) struct StdUtf8Error { - pub(crate) error_len: Option, - pub(crate) valid_up_to: usize, + pub(crate) error_len: Option, + pub(crate) valid_up_to: usize, } diff --git a/wtx/src/partitioned_buffer.rs b/wtx/src/partitioned_buffer.rs new file mode 100644 index 00000000..91fd0dbc --- /dev/null +++ b/wtx/src/partitioned_buffer.rs @@ -0,0 +1,177 @@ +#![allow( + // Indices point to valid memory + clippy::unreachable +)] + +use crate::DFLT_PARTITIONED_BUFFER_LEN; +use alloc::{vec, vec::Vec}; + +/// Internal buffer not intended for public usage +#[derive(Debug)] +pub struct PartitionedBuffer { + antecedent_end_idx: usize, + buffer: Vec, + current_end_idx: usize, + following_end_idx: usize, +} + +impl PartitionedBuffer { + pub(crate) fn with_capacity(len: usize) -> Self { + Self { antecedent_end_idx: 0, buffer: vec![0; len], current_end_idx: 0, following_end_idx: 0 } + } + + pub(crate) fn _buffer(&self) -> &[u8] { + &self.buffer + } + + pub(crate) fn clear(&mut self) { + self.antecedent_end_idx = 0; + self.current_end_idx = 0; + self.following_end_idx = 0; + } + + pub(crate) fn clear_if_following_is_empty(&mut self) { + if !self.has_following() { + self.clear(); + } + } + + /// Current along side any trailing bytes + pub(crate) fn current_trail_mut(&mut self) -> &mut [u8] { + if let Some(el) = self.buffer.get_mut(self.antecedent_end_idx..) { + el + } else { + unreachable!() + } + } + + pub(crate) fn current(&self) -> &[u8] { + if let Some(el) = self.buffer.get(self.antecedent_end_idx..self.current_end_idx) { + el + } else { + unreachable!() + } + } + + pub(crate) fn current_end_idx(&self) -> usize { + self.current_end_idx + } + + pub(crate) fn current_mut(&mut self) -> &mut [u8] { + if let Some(el) = self.buffer.get_mut(self.antecedent_end_idx..self.current_end_idx) { + el + } else { + unreachable!() + } + } + + pub(crate) fn expand_buffer(&mut self, new_len: usize) { + if new_len > self.buffer.len() { + self.buffer.resize(new_len, 0); + } + } + + /// Expands the buffer that can accommodate "following" but doesn't set its length. + pub(crate) fn expand_following(&mut self, new_len: usize) { + self.expand_buffer(self.following_end_idx.wrapping_add(new_len)); + } + + pub(crate) fn _following(&self) -> &[u8] { + if let Some(el) = self.buffer.get(self.current_end_idx..self.following_end_idx) { + el + } else { + unreachable!() + } + } + + pub(crate) fn _following_mut(&mut self) -> &mut [u8] { + if let Some(el) = self.buffer.get_mut(self.current_end_idx..self.following_end_idx) { + el + } else { + unreachable!() + } + } + + pub(crate) fn following_len(&self) -> usize { + self.following_end_idx.wrapping_sub(self.current_end_idx) + } + + /// Following along side any trailing bytes + pub(crate) fn following_trail_mut(&mut self) -> &mut [u8] { + if let Some(el) = self.buffer.get_mut(self.current_end_idx..) { + el + } else { + unreachable!() + } + } + + pub(crate) fn has_following(&self) -> bool { + self.following_end_idx > self.current_end_idx + } + + pub(crate) fn set_indices( + &mut self, + antecedent_len: usize, + current_len: usize, + following_len: usize, + ) -> crate::Result<()> { + let [ant, cur, fol] = Self::indcs_from_lengths(antecedent_len, current_len, following_len); + if fol > self.buffer.len() { + return Err(crate::Error::InvalidPartitionedBufferBounds); + } + self.antecedent_end_idx = ant; + self.current_end_idx = cur; + self.following_end_idx = fol; + Ok(()) + } + + pub(crate) fn _set_indices_through_expansion( + &mut self, + antecedent_len: usize, + current_len: usize, + following_len: usize, + ) { + let [ant, cur, fol] = Self::indcs_from_lengths(antecedent_len, current_len, following_len); + self.antecedent_end_idx = ant; + self.current_end_idx = cur; + self.following_end_idx = fol; + self.expand_buffer(fol); + } + + fn indcs_from_lengths( + antecedent_len: usize, + current_len: usize, + following_len: usize, + ) -> [usize; 3] { + let current_end_idx = antecedent_len.saturating_add(current_len); + let following_end_idx = current_end_idx.saturating_add(following_len); + [antecedent_len, current_end_idx, following_end_idx] + } +} + +impl Default for PartitionedBuffer { + #[inline] + fn default() -> Self { + Self::with_capacity(DFLT_PARTITIONED_BUFFER_LEN) + } +} + +impl Extend for PartitionedBuffer { + #[inline] + fn extend(&mut self, iter: T) + where + T: IntoIterator, + { + self.buffer.extend(iter); + } +} + +impl<'item> Extend<&'item u8> for PartitionedBuffer { + #[inline] + fn extend(&mut self, iter: T) + where + T: IntoIterator, + { + self.buffer.extend(iter); + } +} diff --git a/wtx/src/read_buffer.rs b/wtx/src/read_buffer.rs deleted file mode 100644 index 0f55761e..00000000 --- a/wtx/src/read_buffer.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::web_socket::DFLT_READ_BUFFER_LEN; -use alloc::{vec, vec::Vec}; - -/// Internal buffer used to read external data. -// -// This structure isn't strictly necessary but it tries to optimize two things: -// -// 1. Avoid syscalls by reading the maximum possible number of bytes at once. -// 2. The transposition of **payloads** of frames that compose a message into the `FrameBuffer` -// of the same message. Frames are composed by headers and payloads as such it is necessary to -// have some transfer strategy. -#[derive(Debug)] -pub struct ReadBuffer { - antecedent_end_idx: usize, - buffer: Vec, - current_end_idx: usize, - following_end_idx: usize, -} - -impl ReadBuffer { - pub(crate) fn with_capacity(len: usize) -> Self { - Self { - antecedent_end_idx: 0, - buffer: vec![0; len], - current_end_idx: 0, - following_end_idx: 0, - } - } - - pub(crate) fn antecedent_end_idx(&self) -> usize { - self.antecedent_end_idx - } - - pub(crate) fn after_current_mut(&mut self) -> &mut [u8] { - self.buffer - .get_mut(self.current_end_idx..) - .unwrap_or_default() - } - - pub(crate) fn clear_if_following_is_empty(&mut self) { - if !self.has_following() { - self.antecedent_end_idx = 0; - self.current_end_idx = 0; - self.following_end_idx = 0; - } - } - - pub(crate) fn current(&self) -> &[u8] { - self.buffer - .get(self.antecedent_end_idx..self.current_end_idx) - .unwrap_or_default() - } - - pub(crate) fn current_mut(&mut self) -> &mut [u8] { - self.buffer - .get_mut(self.antecedent_end_idx..self.current_end_idx) - .unwrap_or_default() - } - - pub(crate) fn expand_after_current(&mut self, mut new_len: usize) { - new_len = self.current_end_idx.wrapping_add(new_len); - if new_len > self.buffer.len() { - self.buffer.resize(new_len, 0); - } - } - - pub(crate) fn expand_buffer(&mut self, new_len: usize) { - if new_len > self.buffer.len() { - self.buffer.resize(new_len, 0); - } - } - - pub(crate) fn following_len(&self) -> usize { - self.following_end_idx.wrapping_sub(self.current_end_idx) - } - - pub(crate) fn has_following(&self) -> bool { - self.following_end_idx > self.current_end_idx - } - - pub(crate) fn merge_current_with_antecedent(&mut self) { - self.antecedent_end_idx = self.current_end_idx; - } - - pub(crate) fn set_indices_through_expansion( - &mut self, - antecedent_end_idx: usize, - current_end_idx: usize, - following_end_idx: usize, - ) { - self.antecedent_end_idx = antecedent_end_idx; - self.current_end_idx = self.antecedent_end_idx.max(current_end_idx); - self.following_end_idx = self.current_end_idx.max(following_end_idx); - self.expand_buffer(self.following_end_idx); - } -} - -impl Default for ReadBuffer { - #[inline] - fn default() -> Self { - Self::with_capacity(DFLT_READ_BUFFER_LEN) - } -} diff --git a/wtx/src/request.rs b/wtx/src/request.rs deleted file mode 100644 index a52bbc43..00000000 --- a/wtx/src/request.rs +++ /dev/null @@ -1,75 +0,0 @@ -/// Raw request that can be converted to other high-level requests. -#[derive(Debug)] -pub struct Request<'buffer, 'headers> { - body: &'buffer [u8], - req: httparse::Request<'headers, 'buffer>, -} - -impl<'buffer> Request<'buffer, '_> { - /// Body - #[inline] - pub fn body(&self) -> &'buffer [u8] { - self.body - } - - /// Method - #[inline] - pub fn method(&self) -> Option<&'buffer str> { - self.req.method - } -} - -#[cfg(feature = "http")] -mod http { - use crate::Request; - use http::{HeaderMap, HeaderName, HeaderValue, Method}; - - impl<'buffer, 'headers> TryFrom> for http::Request<&'buffer [u8]> { - type Error = crate::Error; - - #[inline] - fn try_from(from: Request<'buffer, 'headers>) -> Result { - let method = - Method::try_from(from.req.method.ok_or(crate::Error::UnexpectedHttpVersion)?) - .unwrap(); - let version = if let Some(1) = from.req.version { - http::Version::HTTP_11 - } else { - return Err(crate::Error::UnexpectedHttpVersion); - }; - let mut headers = HeaderMap::with_capacity(from.req.headers.len()); - for h in from.req.headers { - let key = HeaderName::from_bytes(h.name.as_bytes())?; - let value = HeaderValue::from_bytes(h.value)?; - let _ = headers.append(key, value); - } - let mut req = http::Request::new(from.body); - *req.headers_mut() = headers; - *req.method_mut() = method; - *req.uri_mut() = from.req.path.unwrap().parse().unwrap(); - *req.version_mut() = version; - //*req.status_mut() = status; - Ok(req) - } - } - - impl<'buffer, 'headers> TryFrom> for http::Request<()> { - type Error = crate::Error; - - #[inline] - fn try_from(from: Request<'buffer, 'headers>) -> Result { - let (parts, _) = http::Request::<&'buffer [u8]>::try_from(from)?.into_parts(); - Ok(http::Request::from_parts(parts, ())) - } - } - - impl<'buffer, 'headers> TryFrom> for http::Request> { - type Error = crate::Error; - - #[inline] - fn try_from(from: Request<'buffer, 'headers>) -> Result { - let (parts, body) = http::Request::<&'buffer [u8]>::try_from(from)?.into_parts(); - Ok(http::Request::from_parts(parts, body.to_vec())) - } - } -} diff --git a/wtx/src/response.rs b/wtx/src/response.rs deleted file mode 100644 index 0c3f1a73..00000000 --- a/wtx/src/response.rs +++ /dev/null @@ -1,82 +0,0 @@ -use httparse::Header; - -/// Raw response that can be converted to other high-level responses. -#[derive(Debug)] -pub struct Response<'buffer, 'headers> { - body: &'buffer [u8], - res: httparse::Response<'headers, 'buffer>, -} - -impl<'buffer, 'headers> Response<'buffer, 'headers> { - pub(crate) fn new(body: &'buffer [u8], res: httparse::Response<'buffer, 'headers>) -> Self { - Self { body, res } - } - - /// Body - #[inline] - pub fn body(&self) -> &'buffer [u8] { - self.body - } - - /// Status code - #[inline] - pub fn code(&self) -> Option { - self.res.code - } - - pub(crate) fn headers(&self) -> &&'headers mut [Header<'buffer>] { - &self.res.headers - } -} - -#[cfg(feature = "http")] -mod http { - use crate::Response; - use http::{HeaderMap, HeaderName, HeaderValue, StatusCode}; - - impl<'buffer, 'headers> TryFrom> for http::Response<&'buffer [u8]> { - type Error = crate::Error; - - #[inline] - fn try_from(from: Response<'buffer, 'headers>) -> Result { - let status = - StatusCode::from_u16(from.res.code.ok_or(crate::Error::UnexpectedHttpVersion)?)?; - let version = if let Some(1) = from.res.version { - http::Version::HTTP_11 - } else { - return Err(crate::Error::UnexpectedHttpVersion); - }; - let mut headers = HeaderMap::with_capacity(from.res.headers.len()); - for h in from.res.headers { - let key = HeaderName::from_bytes(h.name.as_bytes())?; - let value = HeaderValue::from_bytes(h.value)?; - let _ = headers.append(key, value); - } - let mut res = http::Response::new(from.body); - *res.headers_mut() = headers; - *res.status_mut() = status; - *res.version_mut() = version; - Ok(res) - } - } - - impl<'buffer, 'headers> TryFrom> for http::Response<()> { - type Error = crate::Error; - - #[inline] - fn try_from(from: Response<'buffer, 'headers>) -> Result { - let (parts, _) = http::Response::<&'buffer [u8]>::try_from(from)?.into_parts(); - Ok(http::Response::from_parts(parts, ())) - } - } - - impl<'buffer, 'headers> TryFrom> for http::Response> { - type Error = crate::Error; - - #[inline] - fn try_from(from: Response<'buffer, 'headers>) -> Result { - let (parts, body) = http::Response::<&'buffer [u8]>::try_from(from)?.into_parts(); - Ok(http::Response::from_parts(parts, body.to_vec())) - } - } -} diff --git a/wtx/src/rng.rs b/wtx/src/rng.rs new file mode 100644 index 00000000..c33a71ec --- /dev/null +++ b/wtx/src/rng.rs @@ -0,0 +1,133 @@ +//! Random Number Generators + +#[cfg(feature = "std")] +pub use self::std::StdRng; + +/// Abstraction tailored for the needs of this project. Each implementation should manage how +/// seeds are retrieved as well as how numbers are generated. +pub trait Rng { + /// Creates an array of 4 bytes. + fn u8_4(&mut self) -> [u8; 4]; + + /// Creates an array of 16 bytes. + fn u8_16(&mut self) -> [u8; 16]; +} + +impl Rng for &mut T +where + T: Rng, +{ + #[inline] + fn u8_4(&mut self) -> [u8; 4] { + (*self).u8_4() + } + + #[inline] + fn u8_16(&mut self) -> [u8; 16] { + (*self).u8_16() + } +} + +/// Dummy that uses a pre-fixed seed, i.e., it doesn't generate randomness at all. +/// +/// The number generation is done using a simple XOR strategy. +/// +/// You probably shouldn't use this structure in a production environment. +#[derive(Debug)] +pub struct StaticRng(u64); + +impl Rng for StaticRng { + #[inline] + fn u8_4(&mut self) -> [u8; 4] { + xor_u8_4(&mut self.0) + } + + #[inline] + fn u8_16(&mut self) -> [u8; 16] { + xor_u8_16(&mut self.0) + } +} + +impl Default for StaticRng { + #[inline] + fn default() -> Self { + Self(u64::from_be_bytes([55, 120, 216, 218, 191, 63, 200, 169])) + } +} + +#[cfg(feature = "rand")] +mod rand { + use crate::rng::Rng; + use rand::Rng as _; + + macro_rules! implement { + ($struct:ty) => { + impl Rng for $struct { + #[inline] + fn u8_4(&mut self) -> [u8; 4] { + self.gen() + } + + #[inline] + fn u8_16(&mut self) -> [u8; 16] { + self.gen() + } + } + }; + } + + implement!(rand::rngs::mock::StepRng); + implement!(rand::rngs::SmallRng); +} + +#[cfg(feature = "std")] +mod std { + use crate::rng::{xor_u8_16, xor_u8_4, Rng}; + use std::{ + collections::hash_map::RandomState, + hash::{BuildHasher, Hasher}, + }; + + /// Derived from the tools provided by the standard library, uses a simple XOR strategy. + pub struct StdRng(u64); + + impl Rng for StdRng { + #[inline] + fn u8_4(&mut self) -> [u8; 4] { + xor_u8_4(&mut self.0) + } + + #[inline] + fn u8_16(&mut self) -> [u8; 16] { + xor_u8_16(&mut self.0) + } + } + + impl Default for StdRng { + #[inline] + fn default() -> Self { + Self(Hasher::finish(&BuildHasher::build_hasher(&RandomState::new()))) + } + } +} + +fn xor_numbers(seed: &mut u64) -> impl Iterator + '_ { + core::iter::repeat_with(move || { + *seed ^= *seed << 13; + *seed ^= *seed >> 17; + *seed ^= *seed << 5; + *seed + }) +} + +fn xor_u8_4(seed: &mut u64) -> [u8; 4] { + let [a, b, c, d, ..] = xor_numbers(seed).next().unwrap_or_default().to_be_bytes(); + [a, b, c, d] +} + +fn xor_u8_16(seed: &mut u64) -> [u8; 16] { + let mut iter = xor_numbers(seed); + let [a, b, c, d, e, f, g, h] = iter.next().unwrap_or_default().to_be_bytes(); + let [i, j, k, l, m, n, o, p] = iter.next().unwrap_or_default().to_be_bytes(); + [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] +} diff --git a/wtx/src/role.rs b/wtx/src/role.rs new file mode 100644 index 00000000..6cc64600 --- /dev/null +++ b/wtx/src/role.rs @@ -0,0 +1,24 @@ +#[derive(Clone, Copy, Debug)] +pub(crate) enum Role { + Client, + Server, +} + +impl Role { + pub(crate) fn from_is_client(is_client: bool) -> Self { + match is_client { + true => Self::Client, + false => Self::Server, + } + } +} + +impl From for &'static str { + #[inline] + fn from(from: Role) -> Self { + match from { + Role::Client => "Client", + Role::Server => "Server", + } + } +} diff --git a/wtx/src/stream.rs b/wtx/src/stream.rs index 06dd6bf6..d60feeca 100644 --- a/wtx/src/stream.rs +++ b/wtx/src/stream.rs @@ -1,260 +1,298 @@ -use crate::misc::AsyncBounds; -#[cfg(feature = "async-trait")] -use alloc::boxed::Box; use alloc::vec::Vec; use core::cmp::Ordering; /// A stream of values produced asynchronously. -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] pub trait Stream { - /// Pulls some bytes from this source into the specified buffer, returning how many bytes - /// were read. - async fn read(&mut self, bytes: &mut [u8]) -> crate::Result; + /// Pulls some bytes from this source into the specified buffer, returning how many bytes + /// were read. + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result; - /// Attempts to write all elements of `bytes`. - async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()>; + /// Attempts to write all elements of `bytes`. + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()>; } -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] impl Stream for &mut T where - T: AsyncBounds + Stream, + T: Stream, { - #[inline] - async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { - (*self).read(bytes).await - } + #[inline] + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + (*self).read(bytes).await + } - #[inline] - async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { - (*self).write_all(bytes).await - } + #[inline] + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + (*self).write_all(bytes).await + } } /// Stores written data to transfer when read. #[derive(Debug, Default)] pub struct BytesStream { - buffer: Vec, - idx: usize, + buffer: Vec, + idx: usize, } impl BytesStream { - /// Empties the internal buffer. - #[inline] - pub fn clear(&mut self) { - self.buffer.clear(); - self.idx = 0; - } + /// Empties the internal buffer. + #[inline] + pub fn clear(&mut self) { + self.buffer.clear(); + self.idx = 0; + } } -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] impl Stream for BytesStream { + #[inline] + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + let working_buffer = self.buffer.get(self.idx..).unwrap_or_default(); + let working_buffer_len = working_buffer.len(); + Ok(match working_buffer_len.cmp(&bytes.len()) { + Ordering::Less => { + bytes.get_mut(..working_buffer_len).unwrap_or_default().copy_from_slice(working_buffer); + self.clear(); + working_buffer_len + } + Ordering::Equal => { + bytes.copy_from_slice(working_buffer); + self.clear(); + working_buffer_len + } + Ordering::Greater => { + bytes.copy_from_slice(working_buffer.get(..bytes.len()).unwrap_or_default()); + self.idx = self.idx.wrapping_add(bytes.len()); + bytes.len() + } + }) + } + + #[inline] + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + self.buffer.extend_from_slice(bytes); + Ok(()) + } +} + +impl Stream for () { + #[inline] + async fn read(&mut self, _: &mut [u8]) -> crate::Result { + Ok(0) + } + + #[inline] + async fn write_all(&mut self, _: &[u8]) -> crate::Result<()> { + Ok(()) + } +} + +#[cfg(feature = "async-std")] +mod async_std { + use crate::Stream; + use async_std::{ + io::{ReadExt, WriteExt}, + net::TcpStream, + }; + + impl Stream for TcpStream { #[inline] async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { - let working_buffer = self.buffer.get(self.idx..).unwrap_or_default(); - let working_buffer_len = working_buffer.len(); - Ok(match working_buffer_len.cmp(&bytes.len()) { - Ordering::Less => { - bytes - .get_mut(..working_buffer_len) - .unwrap_or_default() - .copy_from_slice(working_buffer); - self.clear(); - working_buffer_len - } - Ordering::Equal => { - bytes.copy_from_slice(working_buffer); - self.clear(); - working_buffer_len - } - Ordering::Greater => { - bytes.copy_from_slice(working_buffer.get(..bytes.len()).unwrap_or_default()); - self.idx = self.idx.wrapping_add(bytes.len()); - bytes.len() - } - }) + Ok(::read(self, bytes).await?) } #[inline] async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { - self.buffer.extend_from_slice(bytes); - Ok(()) + ::write_all(self, bytes).await?; + Ok(()) } + } } -/// Does nothing. -#[derive(Debug)] -pub struct DummyStream; +#[cfg(feature = "embassy-net")] +mod embassy { + use crate::Stream; + use embassy_net::tcp::TcpSocket; -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl Stream for DummyStream { + impl<'any> Stream for TcpSocket<'any> { #[inline] - async fn read(&mut self, _: &mut [u8]) -> crate::Result { - Ok(0) + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + Ok((*self).read(bytes).await?) } #[inline] - async fn write_all(&mut self, _: &[u8]) -> crate::Result<()> { - Ok(()) + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + (*self).write(bytes).await?; + Ok(()) } + } } -#[cfg(feature = "async-std")] -mod async_std { - use crate::Stream; - #[cfg(feature = "async-trait")] - use alloc::boxed::Box; - use async_std::{ - io::{ReadExt, WriteExt}, - net::TcpStream, - }; - - #[cfg_attr(feature = "async-trait", async_trait::async_trait)] - impl Stream for TcpStream { - #[inline] - async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { - Ok(::read(self, bytes).await?) - } - - #[inline] - async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { - ::write_all(self, bytes).await?; - Ok(()) - } +#[cfg(feature = "glommio")] +mod glommio { + use crate::Stream; + use futures_lite::io::{AsyncReadExt, AsyncWriteExt}; + use glommio::net::TcpStream; + + impl Stream for TcpStream { + #[inline] + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + Ok(::read(self, bytes).await?) + } + + #[inline] + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + ::write_all(self, bytes).await?; + Ok(()) } + } } -#[cfg(all(feature = "glommio", not(feature = "async-trait")))] -mod glommio { - use crate::Stream; - use futures_lite::io::{AsyncReadExt, AsyncWriteExt}; - use glommio::net::TcpStream; - - impl Stream for TcpStream { - #[inline] - async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { - Ok(::read(self, bytes).await?) - } - - #[inline] - async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { - ::write_all(self, bytes).await?; - Ok(()) - } +#[cfg(feature = "monoio")] +mod monoio { + use crate::Stream; + use monoio::{ + io::{AsyncReadRent, AsyncWriteRentExt}, + net::TcpStream, + }; + + impl Stream for TcpStream { + #[inline] + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + let (rslt, read) = AsyncReadRent::read(self, bytes.to_vec()).await; + bytes.get_mut(..read.len()).unwrap_or_default().copy_from_slice(&read); + Ok(rslt?) + } + + #[inline] + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + let (rslt, _) = AsyncWriteRentExt::write_all(self, bytes.to_vec()).await; + rslt?; + Ok(()) } + } } -#[cfg(feature = "hyper")] -mod hyper { - use crate::Stream; - #[cfg(feature = "async-trait")] - use alloc::boxed::Box; - use hyper::upgrade::Upgraded; - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - - #[cfg_attr(feature = "async-trait", async_trait::async_trait)] - impl Stream for Upgraded { - #[inline] - async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { - Ok(::read(self, bytes).await?) - } - - #[inline] - async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { - ::write_all(self, bytes).await?; - Ok(()) - } +#[cfg(feature = "smol")] +mod smol { + use crate::Stream; + use smol::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, + }; + + impl Stream for TcpStream { + #[inline] + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + Ok(::read(self, bytes).await?) } + + #[inline] + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + ::write_all(self, bytes).await?; + Ok(()) + } + } } #[cfg(feature = "std")] mod std { - use crate::Stream; - #[cfg(feature = "async-trait")] - use alloc::boxed::Box; - use std::{ - io::{Read, Write}, - net::TcpStream, - }; - - #[cfg_attr(feature = "async-trait", async_trait::async_trait)] - impl Stream for TcpStream { - #[inline] - async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { - Ok(::read(self, bytes)?) - } - - #[inline] - async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { - ::write_all(self, bytes)?; - Ok(()) - } + use crate::Stream; + use std::{ + io::{Read, Write}, + net::TcpStream, + }; + + impl Stream for TcpStream { + #[inline] + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + Ok(::read(self, bytes)?) + } + + #[inline] + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + ::write_all(self, bytes)?; + Ok(()) } + } } #[cfg(feature = "tokio")] mod tokio { - use crate::Stream; - #[cfg(feature = "async-trait")] - use alloc::boxed::Box; - use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::TcpStream, - }; - - #[cfg_attr(feature = "async-trait", async_trait::async_trait)] - impl Stream for TcpStream { - #[inline] - async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { - Ok(::read(self, bytes).await?) - } - - #[inline] - async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { - ::write_all(self, bytes).await?; - Ok(()) - } + use crate::Stream; + use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, + }; + + impl Stream for TcpStream { + #[inline] + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + Ok(::read(self, bytes).await?) + } + + #[inline] + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + ::write_all(self, bytes).await?; + Ok(()) + } + } +} + +#[cfg(feature = "tokio-uring")] +mod tokio_uring { + use crate::Stream; + use tokio_uring::net::TcpStream; + + impl Stream for TcpStream { + #[inline] + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + let (rslt, read) = TcpStream::read(self, bytes.to_vec()).await; + bytes.get_mut(..read.len()).unwrap_or_default().copy_from_slice(&read); + Ok(rslt?) + } + + #[inline] + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + let (rslt, _) = TcpStream::write_all(self, bytes.to_vec()).await; + rslt?; + Ok(()) } + } } #[cfg(feature = "tokio-rustls")] mod tokio_rustls { - use crate::{misc::AsyncBounds, Stream}; - #[cfg(feature = "async-trait")] - use alloc::boxed::Box; - use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; - - #[cfg_attr(feature = "async-trait", async_trait::async_trait)] - impl Stream for tokio_rustls::client::TlsStream - where - T: AsyncBounds + AsyncRead + AsyncWrite + Unpin, - { - #[inline] - async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { - Ok(::read(self, bytes).await?) - } - - #[inline] - async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { - ::write_all(self, bytes).await?; - Ok(()) - } + use crate::Stream; + use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; + + impl Stream for tokio_rustls::client::TlsStream + where + T: AsyncRead + AsyncWrite + Unpin, + { + #[inline] + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + Ok(::read(self, bytes).await?) } - #[cfg_attr(feature = "async-trait", async_trait::async_trait)] - impl Stream for tokio_rustls::server::TlsStream - where - T: AsyncBounds + AsyncRead + AsyncWrite + Unpin, - { - #[inline] - async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { - Ok(::read(self, bytes).await?) - } - - #[inline] - async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { - ::write_all(self, bytes).await?; - Ok(()) - } + #[inline] + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + ::write_all(self, bytes).await?; + Ok(()) + } + } + + impl Stream for tokio_rustls::server::TlsStream + where + T: AsyncRead + AsyncWrite + Unpin, + { + #[inline] + async fn read(&mut self, bytes: &mut [u8]) -> crate::Result { + Ok(::read(self, bytes).await?) + } + + #[inline] + async fn write_all(&mut self, bytes: &[u8]) -> crate::Result<()> { + ::write_all(self, bytes).await?; + Ok(()) } + } } diff --git a/wtx/src/web_socket.rs b/wtx/src/web_socket.rs index dbaafb12..f8011a2d 100644 --- a/wtx/src/web_socket.rs +++ b/wtx/src/web_socket.rs @@ -1,723 +1,1032 @@ //! A computer communications protocol, providing full-duplex communication channels over a single //! TCP connection. +// # Reading copy +// +// | Frame | With Decompression | Without Decompression | +// |------------|-----------------------|-----------------------| +// |Single |(PB -> FB)¹ |(PB -> FB)¹ | +// |Continuation|(PB -> DB)* (DB -> FB)¹|(PB -> FB)* | + mod close_code; +pub mod compression; mod frame; mod frame_buffer; #[cfg(feature = "web-socket-handshake")] pub mod handshake; mod mask; +mod misc; mod op_code; mod web_socket_error; +#[cfg(feature = "tracing")] +use crate::role::Role; use crate::{ - misc::{from_utf8_ext_rslt, from_utf8_opt, CompleteErr, ExtUtf8Error, Rng}, - web_socket::close_code::CloseCode, - ReadBuffer, Stream, + buffer::Buffer, + misc::{from_utf8_ext_rslt, from_utf8_opt, CompleteErr, ExtUtf8Error, IncompleteUtf8Char}, + rng::Rng, + web_socket::{ + compression::NegotiatedCompression, + misc::{define_fb_from_header_params, header_placeholder, op_code}, + }, + PartitionedBuffer, Stream, MAX_PAYLOAD_LEN, }; use alloc::vec::Vec; -use core::borrow::BorrowMut; +pub use close_code::CloseCode; +pub use compression::{Compression, CompressionLevel, DeflateConfig}; +use core::{borrow::BorrowMut, ops::Range}; pub use frame::{ - Frame, FrameControlArray, FrameControlArrayMut, FrameMut, FrameMutControlArray, - FrameMutControlArrayMut, FrameMutMut, FrameMutVec, FrameMutVecMut, FrameVec, FrameVecMut, + Frame, FrameControlArray, FrameControlArrayMut, FrameMut, FrameMutControlArray, + FrameMutControlArrayMut, FrameMutMut, FrameMutVec, FrameMutVecMut, FrameVec, FrameVecMut, }; pub use frame_buffer::{ - FrameBuffer, FrameBufferControlArray, FrameBufferControlArrayMut, FrameBufferMut, - FrameBufferVec, FrameBufferVecMut, + FrameBuffer, FrameBufferControlArray, FrameBufferControlArrayMut, FrameBufferMut, FrameBufferVec, + FrameBufferVecMut, }; pub use mask::unmask; pub use op_code::OpCode; pub use web_socket_error::WebSocketError; -pub(crate) const DFLT_FRAME_BUFFER_VEC_LEN: usize = 16 * 1024; -pub(crate) const DFLT_READ_BUFFER_LEN: usize = 2 * DFLT_FRAME_BUFFER_VEC_LEN; +pub(crate) const DFLT_FRAME_BUFFER_VEC_LEN: usize = 32 * 1024; pub(crate) const MAX_CONTROL_FRAME_LEN: usize = MAX_HDR_LEN_USIZE + MAX_CONTROL_FRAME_PAYLOAD_LEN; pub(crate) const MAX_CONTROL_FRAME_PAYLOAD_LEN: usize = 125; pub(crate) const MAX_HDR_LEN_U8: u8 = 14; pub(crate) const MAX_HDR_LEN_USIZE: usize = 14; -pub(crate) const MAX_PAYLOAD_LEN: usize = 64 * 1024 * 1024; pub(crate) const MIN_HEADER_LEN_USIZE: usize = 2; +pub(crate) const DECOMPRESSION_SUFFIX: &[u8; 4] = &[0, 0, 255, 255]; /// Always masks the payload before sending. -pub type WebSocketClient = WebSocket; -/// [WebSocketClient] with a mutable reference of [ReadBuffer]. -pub type WebSocketClientMut<'rb, S> = WebSocketClient<&'rb mut ReadBuffer, S>; -/// [WebSocketClient] with an owned [ReadBuffer]. -pub type WebSocketClientOwned = WebSocketClient; -/// Always decode the payload after receiving. -pub type WebSocketServer = WebSocket; -/// [WebSocketServer] with a mutable reference of [ReadBuffer]. -pub type WebSocketServerMut<'rb, S> = WebSocketServer<&'rb mut ReadBuffer, S>; -/// [WebSocketServer] with an owned [ReadBuffer]. -pub type WebSocketServerOwned = WebSocketServer; +pub type WebSocketClient = WebSocket; +/// [WebSocketClient] with a mutable reference of [PartitionedBuffer]. +pub type WebSocketClientMut<'pb, NC, RNG, S> = + WebSocketClient; +/// [WebSocketClient] with an owned [PartitionedBuffer]. +pub type WebSocketClientOwned = WebSocketClient; +/// Always unmasks the payload after receiving. +pub type WebSocketServer = WebSocket; +/// [WebSocketServer] with a mutable reference of [PartitionedBuffer]. +pub type WebSocketServerMut<'pb, NC, RNG, S> = + WebSocketServer; +/// [WebSocketServer] with an owned [PartitionedBuffer]. +pub type WebSocketServerOwned = WebSocketServer; + +type ReadContinuationFramesCbs = ( + fn(&[u8]) -> crate::Result>, + fn(&[u8], &mut Option) -> crate::Result<()>, + fn( + &mut Buffer, + &mut FrameBuffer, + &mut PartitionedBuffer, + &ReadFrameInfo, + usize, + ) -> crate::Result<(bool, usize)>, +); /// WebSocket protocol implementation over an asynchronous stream. #[derive(Debug)] -pub struct WebSocket { - auto_close: bool, - auto_pong: bool, - is_stream_closed: bool, - max_payload_len: usize, - rb: RB, - rng: Rng, - stream: S, +pub struct WebSocket { + auto_close: bool, + auto_pong: bool, + decompression_buffer: Buffer, + is_stream_closed: bool, + max_payload_len: usize, + nc: NC, + pb: PB, + rng: RNG, + stream: S, } -impl WebSocket { - /// Sets whether to automatically close the connection when a close frame is received. Defaults - /// to `true`. - #[inline] - pub fn set_auto_close(&mut self, auto_close: bool) { - self.auto_close = auto_close; - } +impl WebSocket { + /// Sets whether to automatically close the connection when a close frame is received. Defaults + /// to `true`. + #[inline] + pub fn set_auto_close(&mut self, auto_close: bool) { + self.auto_close = auto_close; + } - /// Sets whether to automatically send a pong frame when a ping frame is received. Defaults - /// to `true`. - #[inline] - pub fn set_auto_pong(&mut self, auto_pong: bool) { - self.auto_pong = auto_pong; - } + /// Sets whether to automatically send a pong frame when a ping frame is received. Defaults + /// to `true`. + #[inline] + pub fn set_auto_pong(&mut self, auto_pong: bool) { + self.auto_pong = auto_pong; + } - /// Sets whether to automatically close the connection when a received frame payload length - /// exceeds `max_payload_len`. Defaults to `64 * 1024 * 1024` bytes (64 MiB). - #[inline] - pub fn set_max_payload_len(&mut self, max_payload_len: usize) { - self.max_payload_len = max_payload_len; - } + /// Sets whether to automatically close the connection when a received frame payload length + /// exceeds `max_payload_len`. Defaults to `64 * 1024 * 1024` bytes (64 MiB). + #[inline] + pub fn set_max_payload_len(&mut self, max_payload_len: usize) { + self.max_payload_len = max_payload_len; + } } -impl WebSocket +impl WebSocket where - RB: BorrowMut, - S: Stream, + NC: NegotiatedCompression, + PB: BorrowMut, + RNG: Rng, + S: Stream, { - /// Creates a new instance from a stream that supposedly has already completed the WebSocket - /// handshake. - #[inline] - pub fn new(mut rb: RB, stream: S) -> Self { - rb.borrow_mut().clear_if_following_is_empty(); - Self { - auto_close: true, - auto_pong: true, - is_stream_closed: false, - max_payload_len: MAX_PAYLOAD_LEN, - rb, - rng: Rng::default(), - stream, - } - } - - /// Reads a frame from the stream unmasking and validating its payload. - #[inline] - pub async fn read_frame<'fb, B>( - &mut self, - fb: &'fb mut FrameBuffer, - ) -> crate::Result, IS_CLIENT>> - where - B: AsMut> + AsRef<[u8]>, - { - let rbfi = self.do_read_frame::().await?; - Self::copy_from_rb_to_fb(CopyType::Normal, fb, self.rb.borrow(), &rbfi); - self.rb.borrow_mut().clear_if_following_is_empty(); - Frame::from_fb(fb) + /// Creates a new instance from a stream that supposedly has already completed the WebSocket + /// handshake. + #[inline] + pub fn new(nc: NC, mut pb: PB, rng: RNG, stream: S) -> Self { + pb.borrow_mut().clear_if_following_is_empty(); + pb.borrow_mut().expand_following(MAX_HDR_LEN_USIZE); + Self { + auto_close: true, + auto_pong: true, + decompression_buffer: Buffer::default(), + is_stream_closed: false, + max_payload_len: MAX_PAYLOAD_LEN, + nc, + pb, + rng, + stream, } + } - /// Collects frames and returns the completed message once all fragments have been received. - #[inline] - pub async fn read_msg<'fb, B>( - &mut self, - fb: &'fb mut FrameBuffer, - ) -> crate::Result, IS_CLIENT>> - where - B: AsMut<[u8]> + AsMut> + AsRef<[u8]>, - { - let mut iuc_opt = None; - let mut is_binary = true; - let rbfi = self.do_read_frame::().await?; - if rbfi.op_code.is_continuation() { - return Err(WebSocketError::UnexpectedMessageFrame.into()); - } - let should_stop_at_the_first_frame = match rbfi.op_code { - OpCode::Binary => rbfi.fin, - OpCode::Text => { - let range = rbfi.header_end_idx..; - let curr_payload = self.rb.borrow().current().get(range).unwrap_or_default(); - if rbfi.fin { - if from_utf8_opt(curr_payload).is_none() { - return Err(crate::Error::InvalidUTF8); - } - true - } else { - is_binary = false; - match from_utf8_ext_rslt(curr_payload) { - Err(ExtUtf8Error::Incomplete { - incomplete_ending_char, - .. - }) => { - iuc_opt = Some(incomplete_ending_char); - false - } - Err(ExtUtf8Error::Invalid { .. }) => { - return Err(crate::Error::InvalidUTF8); - } - Ok(_) => false, - } + /// Reads a frame from the stream. + /// + /// If a frame is made up of other frames, everything is collected until all fragments are + /// received. + #[inline] + pub async fn read_frame<'fb, B>( + &mut self, + fb: &'fb mut FrameBuffer, + ) -> crate::Result, IS_CLIENT>> + where + B: AsMut<[u8]> + AsMut> + AsRef<[u8]>, + { + fb.clear(); + let header_buffer_len = header_placeholder::(); + let payload_start_idx = header_buffer_len.into(); + let Some(first_rfi) = self.read_first_frame(fb, header_buffer_len, payload_start_idx).await? + else { + return Frame::from_fb(fb); + }; + let mut total_frame_len = payload_start_idx; + let payload_len = if first_rfi.should_decompress { + self + .read_continuation_frames( + fb, + &first_rfi, + payload_start_idx, + &mut total_frame_len, + ( + |_| Ok(None), + |_, _| Ok(()), + |local_db, _, local_pb, rfi, local_tfl| { + Ok((true, Self::copy_from_compressed_pb_to_db(local_db, local_tfl, local_pb, rfi)?)) + }, + ), + ) + .await?; + let payload_len = Self::copy_from_compressed_db_to_fb( + &mut self.decompression_buffer, + fb, + &mut self.nc, + payload_start_idx, + )?; + let payload = fb + .buffer() + .as_ref() + .get(payload_start_idx..payload_start_idx.wrapping_add(payload_len)) + .unwrap_or_default(); + if matches!(first_rfi.op_code, OpCode::Text) && from_utf8_opt(payload).is_none() { + return Err(crate::Error::InvalidUTF8); + } + payload_len + } else { + self + .read_continuation_frames( + fb, + &first_rfi, + payload_start_idx, + &mut total_frame_len, + ( + |curr_payload| { + Ok(match from_utf8_ext_rslt(curr_payload) { + Err(ExtUtf8Error::Incomplete { incomplete_ending_char, .. }) => { + Some(incomplete_ending_char) } - } - OpCode::Continuation | OpCode::Close | OpCode::Ping | OpCode::Pong => true, - }; - if should_stop_at_the_first_frame { - Self::copy_from_rb_to_fb(CopyType::Normal, fb, self.rb.borrow(), &rbfi); - self.rb.borrow_mut().clear_if_following_is_empty(); - return Frame::from_fb(fb); - } - let mut total_frame_len = msg_header_placeholder::().into(); - Self::copy_from_rb_to_fb( - CopyType::Msg(&mut total_frame_len), - fb, - self.rb.borrow(), - &rbfi, - ); - if is_binary { - self.manage_read_msg_loop(fb, rbfi.op_code, &mut total_frame_len, |_| Ok(())) - .await?; - } else { - self.manage_read_msg_loop(fb, rbfi.op_code, &mut total_frame_len, |payload| { - let tail = if let Some(mut incomplete) = iuc_opt.take() { - let (rslt, remaining) = incomplete.complete(payload); - match rslt { - Err(CompleteErr::HasInvalidBytes) => { - return Err(crate::Error::InvalidUTF8); - } - Err(CompleteErr::InsufficientInput) => { - let _ = iuc_opt.replace(incomplete); - &[] - } - Ok(_) => remaining, - } - } else { - payload - }; - match from_utf8_ext_rslt(tail) { - Err(ExtUtf8Error::Incomplete { - incomplete_ending_char, - .. - }) => { - iuc_opt = Some(incomplete_ending_char); - } - Err(ExtUtf8Error::Invalid { .. }) => { - return Err(crate::Error::InvalidUTF8); - } - Ok(_) => {} + Err(ExtUtf8Error::Invalid { .. }) => { + return Err(crate::Error::InvalidUTF8); } - Ok(()) - }) - .await?; - }; - Frame::from_fb(fb) - } + Ok(_) => None, + }) + }, + if matches!(first_rfi.op_code, OpCode::Binary) { + |_, _| Ok(()) + } else { + Self::manage_continuation_text + }, + |_, local_fb, local_pb, rfi, local_tfl| { + Ok(( + false, + Self::copy_from_uncompressed_pb_to_fb(local_fb, local_tfl, local_pb, rfi)?, + )) + }, + ), + ) + .await?; + total_frame_len.wrapping_sub(payload_start_idx) + }; + define_fb_from_header_params::<_, IS_CLIENT>( + fb, + true, + Some(header_buffer_len), + first_rfi.op_code, + payload_len, + self.nc.rsv1(), + )?; + Frame::from_fb(fb) + } - /// Writes a frame to the stream without masking its payload. - #[inline] - pub async fn write_frame( - &mut self, - frame: &mut Frame, - ) -> crate::Result<()> + /// Writes a frame to the stream. + #[inline] + pub async fn write_frame(&mut self, frame: &mut Frame) -> crate::Result<()> + where + B: AsMut<[u8]> + AsRef<[u8]>, + FB: BorrowMut>, + { + Self::do_write_frame( + frame, + &mut self.is_stream_closed, + &mut self.nc, + self.pb.borrow_mut(), + &mut self.rng, + &mut self.stream, + ) + .await?; + Ok(()) + } + + fn begin_fb_bytes_mut(fb: &mut FrameBuffer, payload_start_idx: usize) -> &mut [u8] + where + B: AsMut<[u8]> + AsMut>, + { + AsMut::<[u8]>::as_mut(fb.buffer_mut()).get_mut(payload_start_idx..).unwrap_or_default() + } + + fn compress_frame<'pb, B, FB>( + frame: &Frame, + nc: &mut NC, + pb: &'pb mut PartitionedBuffer, + ) -> crate::Result> + where + B: AsMut<[u8]> + AsRef<[u8]>, + FB: BorrowMut>, + { + fn expand_pb<'pb, B>( + len_with_header: usize, + local_fb: &FrameBuffer, + local_pb: &'pb mut PartitionedBuffer, + written: usize, + ) -> &'pb mut [u8] where - B: AsMut<[u8]> + AsRef<[u8]>, - FB: BorrowMut>, + B: AsRef<[u8]>, { - Self::do_write_frame( - frame, - &mut self.is_stream_closed, - &mut self.rng, - &mut self.stream, - ) - .await + let start = len_with_header.wrapping_add(written); + local_pb.expand_following(start.wrapping_add(local_fb.frame().len()).wrapping_add(128)); + local_pb.following_trail_mut().get_mut(start..).unwrap_or_default() } - fn copy_from_rb_to_fb( - ct: CopyType<'_>, - fb: &mut FrameBuffer, - rb: &ReadBuffer, - rbfi: &ReadBufferFrameInfo, - ) where - B: AsMut>, - { - let current_frame = rb.current(); - let range = match ct { - CopyType::Msg(total_frame_len) => { - let prev = *total_frame_len; - *total_frame_len = total_frame_len.wrapping_add(rbfi.payload_len); - fb.set_params_through_expansion( - 0, - msg_header_placeholder::(), - *total_frame_len, - ); - prev..*total_frame_len - } - CopyType::Normal => { - let mask_placeholder = if IS_CLIENT { 4 } else { 0 }; - let header_len_total = rbfi.header_len.wrapping_add(mask_placeholder); - let header_len_total_usize = rbfi.header_len.wrapping_add(mask_placeholder).into(); - fb.set_params_through_expansion( - 0, - header_len_total, - rbfi.payload_len.wrapping_add(header_len_total_usize), - ); - fb.buffer_mut() - .as_mut() - .get_mut(..rbfi.header_len.into()) - .unwrap_or_default() - .copy_from_slice( - current_frame - .get(rbfi.header_begin_idx..rbfi.header_end_idx) - .unwrap_or_default(), - ); - let start = header_len_total_usize; - let end = current_frame - .len() - .wrapping_sub(rbfi.header_begin_idx) - .wrapping_add(mask_placeholder.into()); - start..end - } - }; - fb.buffer_mut() - .as_mut() - .get_mut(range) - .unwrap_or_default() - .copy_from_slice(current_frame.get(rbfi.header_end_idx..).unwrap_or_default()); + let fb = frame.fb().borrow(); + let len = pb.following_trail_mut().len(); + let len_with_header = len.wrapping_add(fb.header().len()); + let mut payload_len = nc.compress( + fb.payload(), + pb, + |local_pb| expand_pb(len_with_header, fb, local_pb, 0), + |local_pb, written| expand_pb(len_with_header, fb, local_pb, written), + )?; + if frame.fin() { + payload_len = payload_len.saturating_sub(4); } + let mut compressed_fb = FrameBufferMut::new( + pb.following_trail_mut() + .get_mut(len..len_with_header.wrapping_add(payload_len)) + .unwrap_or_default(), + ); + define_fb_from_header_params::<_, IS_CLIENT>( + &mut compressed_fb, + frame.fin(), + Some(fb.header_len()), + frame.op_code(), + payload_len, + nc.rsv1(), + )?; + FrameMut::from_fb(compressed_fb) + } - #[inline] - async fn do_read_frame( - &mut self, - ) -> crate::Result { - loop { - let mut rbfi = self.fill_rb_from_stream().await?; - let curr_frame = self.rb.borrow_mut().current_mut(); - if !IS_CLIENT { - unmask( - curr_frame - .get_mut(rbfi.header_end_idx..) - .unwrap_or_default(), - rbfi.mask.ok_or(WebSocketError::MissingFrameMask)?, - ); - let n = remove_mask( - curr_frame - .get_mut(rbfi.header_begin_idx..rbfi.header_end_idx) - .unwrap_or_default(), - ); - let n_usize = n.into(); - rbfi.frame_len = rbfi.frame_len.wrapping_sub(n_usize); - rbfi.header_begin_idx = rbfi.header_begin_idx.wrapping_add(n_usize); - rbfi.header_len = rbfi.header_len.wrapping_sub(n); - } - let payload: &[u8] = curr_frame.get(rbfi.header_end_idx..).unwrap_or_default(); - match rbfi.op_code { - OpCode::Close if self.auto_close && !self.is_stream_closed => { - match payload { - [] => {} - [_] => return Err(WebSocketError::InvalidCloseFrame.into()), - [a, b, rest @ ..] => { - if from_utf8_opt(rest).is_none() { - return Err(crate::Error::InvalidUTF8); - }; - let is_not_allowed = - !CloseCode::from(u16::from_be_bytes([*a, *b])).is_allowed(); - if is_not_allowed || rest.len() > MAX_CONTROL_FRAME_PAYLOAD_LEN - 2 { - Self::write_control_frame( - &mut FrameControlArray::close_from_params( - 1002, - <_>::default(), - rest, - )?, - &mut self.is_stream_closed, - &mut self.rng, - &mut self.stream, - ) - .await?; - return Err(WebSocketError::InvalidCloseFrame.into()); - } - } - } - Self::write_control_frame( - &mut FrameControlArray::new_fin(<_>::default(), OpCode::Close, payload)?, - &mut self.is_stream_closed, - &mut self.rng, - &mut self.stream, - ) - .await?; - break Ok(rbfi); - } - OpCode::Ping if self.auto_pong => { - Self::write_control_frame( - &mut FrameControlArray::new_fin(<_>::default(), OpCode::Pong, payload)?, - &mut self.is_stream_closed, - &mut self.rng, - &mut self.stream, - ) - .await?; - } - OpCode::Text => { - if CHECK_TEXT_UTF8 && from_utf8_opt(payload).is_none() { - return Err(crate::Error::InvalidUTF8); - } - break Ok(rbfi); - } - OpCode::Continuation - | OpCode::Binary - | OpCode::Close - | OpCode::Ping - | OpCode::Pong => { - break Ok(rbfi); - } - } - } - } + // Final compressed continuation frame + fn copy_from_compressed_db_to_fb( + db: &mut Buffer, + fb: &mut FrameBuffer, + nc: &mut NC, + payload_start_idx: usize, + ) -> crate::Result + where + B: AsMut<[u8]> + AsMut> + AsRef<[u8]>, + { + db.push_bytes(DECOMPRESSION_SUFFIX); + let mut buffer_len = payload_start_idx + .checked_add(db.len()) + .map(|element| element.max(fb.buffer().as_ref().len())); + let payload_size = nc.decompress( + db.get(payload_start_idx..).unwrap_or_default(), + fb, + |local_fb| Self::begin_fb_bytes_mut(local_fb, payload_start_idx), + |local_fb, written| Self::expand_fb(&mut buffer_len, local_fb, payload_start_idx, written), + )?; + db.clear(); + Ok(payload_size) + } - async fn do_write_frame( - frame: &mut Frame, - is_stream_closed: &mut bool, - rng: &mut Rng, - stream: &mut S, - ) -> crate::Result<()> - where - B: AsMut<[u8]> + AsRef<[u8]>, - FB: BorrowMut>, - { - if IS_CLIENT { - let mut mask_opt = None; - if let [_, second_byte, .., a, b, c, d] = frame.fb_mut().borrow_mut().header_mut() { - if !has_masked_frame(*second_byte) { - *second_byte |= 0b1000_0000; - let mask = rng.random_u8_4(); - *a = mask[0]; - *b = mask[1]; - *c = mask[2]; - *d = mask[3]; - mask_opt = Some(mask); - } - } - if let Some(mask) = mask_opt { - unmask(frame.fb_mut().borrow_mut().payload_mut(), mask); - } - } - if frame.op_code() == OpCode::Close { - *is_stream_closed = true; + // Intermediate compressed continuation frame + fn copy_from_compressed_pb_to_db( + db: &mut Buffer, + payload_start_idx: usize, + pb: &mut PartitionedBuffer, + rfi: &ReadFrameInfo, + ) -> crate::Result { + Self::copy_from_pb(db, pb, rfi, |local_pb, local_db| { + let n = payload_start_idx.saturating_add(rfi.payload_len); + local_db.set_idx_through_expansion(n); + local_db + .get_mut(payload_start_idx..n) + .unwrap_or_default() + .copy_from_slice(local_pb.current().get(rfi.header_end_idx..).unwrap_or_default()); + Ok(()) + })?; + Ok(rfi.payload_len) + } + + // Final compressed single frame + fn copy_from_compressed_pb_to_fb( + fb: &mut FrameBuffer, + nc: &mut NC, + payload_start_idx: usize, + pb: &mut PartitionedBuffer, + rfi: &ReadFrameInfo, + ) -> crate::Result + where + B: AsMut<[u8]> + AsMut> + AsRef<[u8]>, + { + let mut buffer_len = payload_start_idx + .checked_add(rfi.payload_len) + .map(|element| element.max(fb.buffer().as_ref().len())); + let payload_len = Self::copy_from_pb(fb, pb, rfi, |local_pb, local_fb| { + local_pb.expand_buffer(local_pb._buffer().len().wrapping_add(4)); + let curr_end_idx = local_pb.current().len(); + let curr_end_idx_4p = curr_end_idx.wrapping_add(4); + let has_following = local_pb.has_following(); + let range = rfi.header_end_idx..curr_end_idx_4p; + let input = local_pb.current_trail_mut().get_mut(range).unwrap_or_default(); + let orig = if let [.., a, b, c, d] = input { + let array = [*a, *b, *c, *d]; + *a = 0; + *b = 0; + *c = 255; + *d = 255; + array + } else { + [0, 0, 0, 0] + }; + if has_following { + let payload_len = nc.decompress( + input, + local_fb, + |local_local_fb| Self::begin_fb_bytes_mut(local_local_fb, payload_start_idx), + |local_local_fb, written| { + Self::expand_fb(&mut buffer_len, local_local_fb, payload_start_idx, written) + }, + )?; + if let [.., a, b, c, d] = input { + *a = orig[0]; + *b = orig[1]; + *c = orig[2]; + *d = orig[3]; } - stream.write_all(frame.fb().borrow().frame()).await?; - Ok(()) + Ok(payload_len) + } else { + nc.decompress( + input, + local_fb, + |local_local_fb| Self::begin_fb_bytes_mut(local_local_fb, payload_start_idx), + |local_local_fb, written| { + Self::expand_fb(&mut buffer_len, local_local_fb, payload_start_idx, written) + }, + ) + } + })?; + Ok(payload_len) + } + + fn copy_from_pb( + output: &mut O, + pb: &mut PartitionedBuffer, + rfi: &ReadFrameInfo, + cb: impl FnOnce(&mut PartitionedBuffer, &mut O) -> crate::Result, + ) -> crate::Result { + debug!( + "{:<5} - {:<5} - {:<25}: {:?}, {:?}", + <&str>::from(Role::from_is_client(IS_CLIENT)), + "Read", + "Masked", + crate::misc::_truncated_slice(pb.current(), 0..32), + rfi.op_code + ); + + if !IS_CLIENT { + unmask( + pb.current_mut().get_mut(rfi.header_end_idx..).unwrap_or_default(), + rfi.mask.ok_or(WebSocketError::MissingFrameMask)?, + ); } - async fn fill_initial_rb_from_stream( - buffer: &mut [u8], - max_payload_len: usize, - read: &mut usize, - stream: &mut S, - ) -> crate::Result - where - S: Stream, - { - async fn read_until( - buffer: &mut [u8], - read: &mut usize, - start: usize, - stream: &mut S, - ) -> crate::Result<[u8; LEN]> - where - [u8; LEN]: Default, - S: Stream, - { - let until = start.wrapping_add(LEN); - while *read < until { - let actual_buffer = buffer.get_mut(*read..).unwrap_or_default(); - let local_read = stream.read(actual_buffer).await?; - if local_read == 0 { - return Err(crate::Error::UnexpectedEOF); - } - *read = read.wrapping_add(local_read); - } - Ok(buffer - .get(start..until) - .and_then(|el| el.try_into().ok()) - .unwrap_or_default()) - } + debug!( + "{:<5} - {:<5} - {:<25}: {:?}, {:?}", + <&str>::from(Role::from_is_client(IS_CLIENT)), + "Read", + "Unmasked", + crate::misc::_truncated_slice(pb.current(), 0..32), + rfi.op_code + ); - let first_two = read_until::<_, 2>(buffer, read, 0, stream).await?; + let rslt = cb(pb, output)?; + pb.borrow_mut().clear_if_following_is_empty(); - let fin = first_two[0] & 0b1000_0000 != 0; - let rsv1 = first_two[0] & 0b0100_0000 != 0; - let rsv2 = first_two[0] & 0b0010_0000 != 0; - let rsv3 = first_two[0] & 0b0001_0000 != 0; + Ok(rslt) + } - if rsv1 || rsv2 || rsv3 { - return Err(WebSocketError::ReservedBitsAreNotZero.into()); - } + // Final uncompressed single frame as well as intermediate uncompressed continuation frame + fn copy_from_uncompressed_pb_to_fb( + fb: &mut FrameBuffer, + payload_start_idx: usize, + pb: &mut PartitionedBuffer, + rfi: &ReadFrameInfo, + ) -> crate::Result + where + B: AsMut<[u8]> + AsMut>, + { + Self::copy_from_pb(fb, pb, rfi, |local_pb, local_fb| { + let n = payload_start_idx.saturating_add(rfi.payload_len); + local_fb.expand_buffer(n); + AsMut::<[u8]>::as_mut(local_fb.buffer_mut()) + .get_mut(payload_start_idx..n) + .unwrap_or_default() + .copy_from_slice(local_pb.current().get(rfi.header_end_idx..).unwrap_or_default()); + Ok(()) + })?; + Ok(rfi.payload_len) + } - let is_masked = has_masked_frame(first_two[1]); - let length_code = first_two[1] & 0b0111_1111; - let op_code = op_code(first_two[0])?; - - let (mut header_len, payload_len) = match length_code { - 126 => ( - 4, - u16::from_be_bytes(read_until::<_, 2>(buffer, read, 2, stream).await?).into(), - ), - 127 => { - let payload_len = read_until::<_, 8>(buffer, read, 2, stream).await?; - (10, u64::from_be_bytes(payload_len).try_into()?) - } - _ => (2, length_code.into()), - }; + fn curr_payload_bytes<'bytes, B>( + db: &'bytes Buffer, + fb: &'bytes FrameBuffer, + range: Range, + should_use_db: bool, + ) -> &'bytes [u8] + where + B: AsMut<[u8]> + AsMut> + AsRef<[u8]>, + { + if should_use_db { + db.get(range).unwrap_or_default() + } else { + fb.buffer().as_ref().get(range).unwrap_or_default() + } + } - let mut mask = None; - if is_masked { - mask = Some(read_until::<_, 4>(buffer, read, header_len, stream).await?); - header_len = header_len.wrapping_add(4); - } + async fn do_write_frame( + frame: &mut Frame, + is_stream_closed: &mut bool, + nc: &mut NC, + pb: &mut PartitionedBuffer, + rng: &mut RNG, + stream: &mut S, + ) -> crate::Result<()> + where + B: AsMut<[u8]> + AsRef<[u8]>, + FB: BorrowMut>, + { + let mut should_compress = false; + if frame.op_code() == OpCode::Close { + *is_stream_closed = true; + } + if !frame.op_code().is_control() { + if let Some(first) = frame.fb_mut().borrow_mut().header_mut().first_mut() { + should_compress = nc.rsv1() != 0; + *first |= nc.rsv1(); + } + } + if !should_compress || frame.op_code().is_control() { + debug!( + "{:<5} - {:<5} - {:<25}: {:?}, {:?}", + <&str>::from(Role::from_is_client(IS_CLIENT)), + "Write", + "Unmasked", + crate::misc::_truncated_slice(frame.fb().borrow().frame(), 0..32), + frame.op_code() + ); + Self::mask_frame(frame, rng); + debug!( + "{:<5} - {:<5} - {:<25}: {:?}, {:?}", + <&str>::from(Role::from_is_client(IS_CLIENT)), + "Write", + "Masked", + crate::misc::_truncated_slice(frame.fb().borrow().frame(), 0..32), + frame.op_code() + ); + stream.write_all(frame.fb().borrow().frame()).await?; + } else { + debug!( + "{:<5} - {:<5} - {:<25}: {:?}, {:?}", + <&str>::from(Role::from_is_client(IS_CLIENT)), + "Write", + "Uncompressed, Unmasked", + crate::misc::_truncated_slice(frame.fb().borrow().frame(), 0..32), + frame.op_code() + ); + let mut compressed_frame = Self::compress_frame(frame, nc, pb)?; + debug!( + "{:<5} - {:<5} - {:<25}: {:?}, {:?}", + <&str>::from(Role::from_is_client(IS_CLIENT)), + "Write", + "Compressed, Unmasked", + crate::misc::_truncated_slice(compressed_frame.fb().frame(), 0..32), + frame.op_code() + ); + Self::mask_frame(&mut compressed_frame, rng); + debug!( + "{:<5} - {:<5} - {:<25}: {:?}, {:?}", + <&str>::from(Role::from_is_client(IS_CLIENT)), + "Write", + "Compressed, Masked", + crate::misc::_truncated_slice(compressed_frame.fb().frame(), 0..32), + frame.op_code() + ); + stream.write_all(compressed_frame.fb().frame()).await?; + }; + Ok(()) + } - if op_code.is_control() && !fin { - return Err(WebSocketError::UnexpectedFragmentedControlFrame.into()); - } - if op_code == OpCode::Ping && payload_len > MAX_CONTROL_FRAME_PAYLOAD_LEN { - return Err(WebSocketError::VeryLargeControlFrame.into()); - } - if payload_len >= max_payload_len { - return Err(WebSocketError::VeryLargePayload.into()); - } + fn expand_fb<'fb, B>( + buffer_len: &mut Option, + fb: &'fb mut FrameBuffer, + payload_start_idx: usize, + written: usize, + ) -> &'fb mut [u8] + where + B: AsMut<[u8]> + AsMut>, + { + *buffer_len = buffer_len.and_then(|el| el.checked_mul(15)?.checked_div(10)); + fb.expand_buffer(buffer_len.unwrap_or(usize::MAX)); + let start = payload_start_idx.wrapping_add(written); + Self::begin_fb_bytes_mut(fb, start) + } - Ok(ReadBufferFrameInfo { - fin, - frame_len: header_len.wrapping_add(payload_len), - header_begin_idx: 0, - header_end_idx: header_len, - header_len: header_len.try_into().unwrap_or_default(), - mask, - op_code, - payload_len, - }) + async fn fetch_frame_from_stream(&mut self) -> crate::Result { + let mut read = self.pb.borrow_mut().following_len(); + let rfi = Self::fetch_header_from_stream( + self.max_payload_len, + &self.nc, + self.pb.borrow_mut(), + &mut read, + &mut self.stream, + ) + .await?; + if self.is_stream_closed && rfi.op_code != OpCode::Close { + return Err(WebSocketError::ConnectionClosed.into()); } + Self::fetch_payload_from_stream(self.pb.borrow_mut(), &mut read, &rfi, &mut self.stream) + .await?; + Ok(rfi) + } - async fn fill_rb_from_stream(&mut self) -> crate::Result { - let mut read = self.rb.borrow().following_len(); - self.rb.borrow_mut().merge_current_with_antecedent(); - self.rb.borrow_mut().expand_after_current(MAX_HDR_LEN_USIZE); - let rbfi = Self::fill_initial_rb_from_stream( - self.rb.borrow_mut().after_current_mut(), - self.max_payload_len, - &mut read, - &mut self.stream, - ) - .await?; - if self.is_stream_closed && rbfi.op_code != OpCode::Close { - return Err(WebSocketError::ConnectionClosed.into()); - } - loop { - if read >= rbfi.frame_len { - break; - } - self.rb.borrow_mut().expand_after_current(rbfi.frame_len); - let local_read = self - .stream - .read( - self.rb - .borrow_mut() - .after_current_mut() - .get_mut(read..) - .unwrap_or_default(), - ) - .await?; - read = read.wrapping_add(local_read); - } - let rb = self.rb.borrow_mut(); - rb.set_indices_through_expansion( - rb.antecedent_end_idx(), - rb.antecedent_end_idx().wrapping_add(rbfi.frame_len), - rb.antecedent_end_idx().wrapping_add(read), - ); - Ok(rbfi) + async fn fetch_header_from_stream( + max_payload_len: usize, + nc: &NC, + pb: &mut PartitionedBuffer, + read: &mut usize, + stream: &mut S, + ) -> crate::Result { + let buffer = pb.following_trail_mut(); + + let first_two = Self::read_until::<2>(buffer, read, 0, stream).await?; + + let rsv1 = first_two[0] & 0b0100_0000; + let rsv2 = first_two[0] & 0b0010_0000; + let rsv3 = first_two[0] & 0b0001_0000; + + if rsv2 != 0 || rsv3 != 0 { + return Err(WebSocketError::InvalidCompressionHeaderParameter.into()); } - async fn manage_read_msg_loop( - &mut self, - fb: &mut FrameBuffer, - first_frame_op_code: OpCode, - total_frame_len: &mut usize, - mut cb: impl FnMut(&[u8]) -> crate::Result<()>, - ) -> crate::Result<()> - where - B: AsMut<[u8]> + AsMut> + AsRef<[u8]>, - S: Stream, - { - loop { - let rbfi = self.do_read_frame::().await?; - Self::copy_from_rb_to_fb(CopyType::Msg(total_frame_len), fb, self.rb.borrow(), &rbfi); - match rbfi.op_code { - OpCode::Continuation => { - cb(self - .rb - .borrow() - .current() - .get(rbfi.header_end_idx..) - .unwrap_or_default())?; - if rbfi.fin { - let mut buffer = [0; MAX_HDR_LEN_USIZE]; - let header_len = copy_header_params_to_buffer::( - &mut buffer, - true, - first_frame_op_code, - fb.payload().len(), - )?; - let start_idx = - msg_header_placeholder::().wrapping_sub(header_len); - fb.header_mut() - .get_mut(start_idx.into()..) - .unwrap_or_default() - .copy_from_slice(buffer.get(..header_len.into()).unwrap_or_default()); - fb.set_params_through_expansion(start_idx, header_len, *total_frame_len); - self.rb.borrow_mut().clear_if_following_is_empty(); - break; - } - } - OpCode::Binary | OpCode::Close | OpCode::Ping | OpCode::Pong | OpCode::Text => { - return Err(WebSocketError::UnexpectedMessageFrame.into()); - } - } - } - Ok(()) + let should_decompress = if nc.rsv1() == 0 { + if rsv1 != 0 { + return Err(WebSocketError::InvalidCompressionHeaderParameter.into()); + } + false + } else { + rsv1 != 0 + }; + + let fin = first_two[0] & 0b1000_0000 != 0; + let length_code = first_two[1] & 0b0111_1111; + let op_code = op_code(first_two[0])?; + + let (mut header_len, payload_len) = match length_code { + 126 => (4, u16::from_be_bytes(Self::read_until::<2>(buffer, read, 2, stream).await?).into()), + 127 => { + let payload_len = Self::read_until::<8>(buffer, read, 2, stream).await?; + (10, u64::from_be_bytes(payload_len).try_into()?) + } + _ => (2, length_code.into()), + }; + + let mut mask = None; + if !IS_CLIENT { + mask = Some(Self::read_until::<4>(buffer, read, header_len, stream).await?); + header_len = header_len.wrapping_add(4); } - async fn write_control_frame( - frame: &mut FrameControlArray, - is_stream_closed: &mut bool, - rng: &mut Rng, - stream: &mut S, - ) -> crate::Result<()> { - Self::do_write_frame(frame, is_stream_closed, rng, stream).await?; - Ok(()) + if op_code.is_control() && !fin { + return Err(WebSocketError::UnexpectedFragmentedControlFrame.into()); + } + if op_code == OpCode::Ping && payload_len > MAX_CONTROL_FRAME_PAYLOAD_LEN { + return Err(WebSocketError::VeryLargeControlFrame.into()); + } + if payload_len >= max_payload_len { + return Err(WebSocketError::VeryLargePayload.into()); } -} -#[derive(Debug)] -enum CopyType<'read> { - Msg(&'read mut usize), - Normal, -} + Ok(ReadFrameInfo { + fin, + frame_len: header_len.wrapping_add(payload_len), + header_end_idx: header_len, + mask, + op_code, + payload_len, + should_decompress, + }) + } -#[derive(Debug)] -struct ReadBufferFrameInfo { - fin: bool, - frame_len: usize, - header_begin_idx: usize, - header_end_idx: usize, - header_len: u8, - mask: Option<[u8; 4]>, - op_code: OpCode, - payload_len: usize, -} + async fn fetch_payload_from_stream( + pb: &mut PartitionedBuffer, + read: &mut usize, + rfi: &ReadFrameInfo, + stream: &mut S, + ) -> crate::Result<()> { + let mut is_payload_filled = false; + pb.expand_following(rfi.frame_len); + for _ in 0..rfi.frame_len { + if *read >= rfi.frame_len { + is_payload_filled = true; + break; + } + *read = read.wrapping_add( + stream.read(pb.following_trail_mut().get_mut(*read..).unwrap_or_default()).await?, + ); + } + if !is_payload_filled { + return Err(crate::Error::UnexpectedBufferState); + } + pb.set_indices(pb.current_end_idx(), rfi.frame_len, read.wrapping_sub(rfi.frame_len))?; + Ok(()) + } -pub(crate) fn copy_header_params_to_buffer( - buffer: &mut [u8], - fin: bool, + // If this method returns `false`, then a `ping` frame was received and the caller should fetch + // more external data in order to get the desired frame. + async fn manage_auto_reply( + (auto_close, auto_pong, is_stream_closed): (bool, bool, &mut bool), + curr_payload: &[u8], + nc: &mut NC, op_code: OpCode, - payload_len: usize, -) -> crate::Result { - fn first_header_byte(fin: bool, op_code: OpCode) -> u8 { - u8::from(fin) << 7 | u8::from(op_code) + pb: &mut PB, + rng: &mut RNG, + stream: &mut S, + ) -> crate::Result { + match op_code { + OpCode::Close if auto_close && !*is_stream_closed => { + match curr_payload { + [] => {} + [_] => return Err(WebSocketError::InvalidCloseFrame.into()), + [a, b, rest @ ..] => { + if from_utf8_opt(rest).is_none() { + return Err(crate::Error::InvalidUTF8); + }; + let is_not_allowed = !CloseCode::from(u16::from_be_bytes([*a, *b])).is_allowed(); + if is_not_allowed || rest.len() > MAX_CONTROL_FRAME_PAYLOAD_LEN - 2 { + Self::write_control_frame( + &mut FrameControlArray::close_from_params( + CloseCode::Protocol, + <_>::default(), + rest, + )?, + is_stream_closed, + nc, + pb, + rng, + stream, + ) + .await?; + return Err(WebSocketError::InvalidCloseFrame.into()); + } + } + } + Self::write_control_frame( + &mut FrameControlArray::new_fin(<_>::default(), OpCode::Close, curr_payload)?, + is_stream_closed, + nc, + pb, + rng, + stream, + ) + .await?; + Ok(true) + } + OpCode::Ping if auto_pong => { + Self::write_control_frame( + &mut FrameControlArray::new_fin(<_>::default(), OpCode::Pong, curr_payload)?, + is_stream_closed, + nc, + pb, + rng, + stream, + ) + .await?; + Ok(false) + } + OpCode::Continuation + | OpCode::Binary + | OpCode::Close + | OpCode::Ping + | OpCode::Pong + | OpCode::Text => Ok(true), } + } - fn manage_mask( - rest: &mut [u8], - second_byte: &mut u8, - ) -> crate::Result { - Ok(if IS_CLIENT { - *second_byte &= 0b0111_1111; - let [a, b, c, d, ..] = rest else { - return Err(WebSocketError::InvalidFrameHeaderBounds.into()); - }; - *a = 0; - *b = 0; - *c = 0; - *d = 0; - N.wrapping_add(4) - } else { - N - }) + fn manage_continuation_text( + curr_payload: &[u8], + iuc: &mut Option, + ) -> crate::Result<()> { + let tail = if let Some(mut incomplete) = iuc.take() { + let (rslt, remaining) = incomplete.complete(curr_payload); + match rslt { + Err(CompleteErr::HasInvalidBytes) => { + return Err(crate::Error::InvalidUTF8); + } + Err(CompleteErr::InsufficientInput) => { + let _ = iuc.replace(incomplete); + &[] + } + Ok(_) => remaining, + } + } else { + curr_payload + }; + match from_utf8_ext_rslt(tail) { + Err(ExtUtf8Error::Incomplete { incomplete_ending_char, .. }) => { + *iuc = Some(incomplete_ending_char); + } + Err(ExtUtf8Error::Invalid { .. }) => { + return Err(crate::Error::InvalidUTF8); + } + Ok(_) => {} } - match payload_len { - 0..=125 => { - if let ([a, b, rest @ ..], Ok(u8_len)) = (buffer, u8::try_from(payload_len)) { - *a = first_header_byte(fin, op_code); - *b = u8_len; - return manage_mask::(rest, b); - } + Ok(()) + } + + fn mask_frame(frame: &mut Frame, rng: &mut RNG) + where + B: AsMut<[u8]> + AsRef<[u8]>, + FB: BorrowMut>, + { + if IS_CLIENT { + if let [_, second_byte, .., a, b, c, d] = frame.fb_mut().borrow_mut().header_mut() { + if !has_masked_frame(*second_byte) { + *second_byte |= 0b1000_0000; + let mask = rng.u8_4(); + *a = mask[0]; + *b = mask[1]; + *c = mask[2]; + *d = mask[3]; + unmask(frame.fb_mut().borrow_mut().payload_mut(), mask); } - 126..=0xFFFF => { - let rslt = u16::try_from(payload_len).map(u16::to_be_bytes); - if let ([a, b, c, d, rest @ ..], Ok([len_c, len_d])) = (buffer, rslt) { - *a = first_header_byte(fin, op_code); - *b = 126; - *c = len_c; - *d = len_d; - return manage_mask::(rest, b); - } + } + } + } + + async fn read_continuation_frames( + &mut self, + fb: &mut FrameBuffer, + first_rfi: &ReadFrameInfo, + payload_start_idx: usize, + total_frame_len: &mut usize, + (first_text_cb, continuation_cb, copy_cb): ReadContinuationFramesCbs, + ) -> crate::Result<()> + where + B: AsMut<[u8]> + AsMut> + AsRef<[u8]>, + { + let mut iuc = { + let (should_use_db, payload_len) = copy_cb( + &mut self.decompression_buffer, + fb, + self.pb.borrow_mut(), + first_rfi, + *total_frame_len, + )?; + *total_frame_len = total_frame_len.wrapping_add(payload_len); + match first_rfi.op_code { + OpCode::Binary => None, + OpCode::Text => first_text_cb(Self::curr_payload_bytes( + &self.decompression_buffer, + fb, + payload_start_idx..*total_frame_len, + should_use_db, + ))?, + OpCode::Close | OpCode::Continuation | OpCode::Ping | OpCode::Pong => { + return Err(WebSocketError::UnexpectedMessageFrame.into()); + } + } + }; + 'continuation_frames: loop { + let (curr_payload, fin, op_code) = 'auto_reply: loop { + let prev = *total_frame_len; + let mut rfi = self.fetch_frame_from_stream().await?; + rfi.should_decompress = first_rfi.should_decompress; + let (should_use_db, payload_len) = copy_cb( + &mut self.decompression_buffer, + fb, + self.pb.borrow_mut(), + &rfi, + *total_frame_len, + )?; + *total_frame_len = total_frame_len.wrapping_add(payload_len); + let curr_payload = Self::curr_payload_bytes( + &self.decompression_buffer, + fb, + prev..*total_frame_len, + should_use_db, + ); + if Self::manage_auto_reply( + (self.auto_close, self.auto_pong, &mut self.is_stream_closed), + curr_payload, + &mut self.nc, + rfi.op_code, + &mut self.pb, + &mut self.rng, + &mut self.stream, + ) + .await? + { + break 'auto_reply (curr_payload, rfi.fin, rfi.op_code); + } + *total_frame_len = prev; + }; + match op_code { + OpCode::Continuation => { + continuation_cb(curr_payload, &mut iuc)?; + if fin { + break 'continuation_frames; + } } - _ => { - if let ( - [a, b, c, d, e, f, g, h, i, j, rest @ ..], - Ok([len_c, len_d, len_e, len_f, len_g, len_h, len_i, len_j]), - ) = (buffer, u64::try_from(payload_len).map(u64::to_be_bytes)) - { - *a = first_header_byte(fin, op_code); - *b = 127; - *c = len_c; - *d = len_d; - *e = len_e; - *f = len_f; - *g = len_g; - *h = len_h; - *i = len_i; - *j = len_j; - return manage_mask::(rest, b); + OpCode::Binary | OpCode::Close | OpCode::Ping | OpCode::Pong | OpCode::Text => { + return Err(WebSocketError::UnexpectedMessageFrame.into()); + } + } + } + Ok(()) + } + + #[inline] + async fn read_first_frame<'fb, B>( + &mut self, + fb: &'fb mut FrameBuffer, + header_buffer_len: u8, + payload_start_idx: usize, + ) -> crate::Result> + where + B: AsMut<[u8]> + AsMut> + AsRef<[u8]>, + { + let first_rfi = 'auto_reply: loop { + let rfi = self.fetch_frame_from_stream().await?; + if !rfi.fin { + break 'auto_reply rfi; + } + let pb = self.pb.borrow_mut(); + let payload_len = if rfi.should_decompress { + Self::copy_from_compressed_pb_to_fb(fb, &mut self.nc, payload_start_idx, pb, &rfi)? + } else { + Self::copy_from_uncompressed_pb_to_fb(fb, payload_start_idx, pb, &rfi)? + }; + define_fb_from_header_params::<_, IS_CLIENT>( + fb, + rfi.fin, + Some(header_buffer_len), + rfi.op_code, + payload_len, + self.nc.rsv1(), + )?; + if Self::manage_auto_reply( + (self.auto_close, self.auto_pong, &mut self.is_stream_closed), + fb.payload(), + &mut self.nc, + rfi.op_code, + &mut self.pb, + &mut self.rng, + &mut self.stream, + ) + .await? + { + match rfi.op_code { + OpCode::Continuation => { + return Err(WebSocketError::UnexpectedMessageFrame.into()); + } + OpCode::Text => { + if from_utf8_opt(fb.payload()).is_none() { + return Err(crate::Error::InvalidUTF8); } + } + OpCode::Binary | OpCode::Close | OpCode::Ping | OpCode::Pong => {} } + return Ok(None); + } + }; + Ok(Some(first_rfi)) + } + + async fn read_until( + buffer: &mut [u8], + read: &mut usize, + start: usize, + stream: &mut S, + ) -> crate::Result<[u8; LEN]> + where + [u8; LEN]: Default, + { + let until = start.wrapping_add(LEN); + while *read < until { + let actual_buffer = buffer.get_mut(*read..).unwrap_or_default(); + let local_read = stream.read(actual_buffer).await?; + if local_read == 0 { + return Err(crate::Error::UnexpectedEOF); + } + *read = read.wrapping_add(local_read); } + Ok(buffer.get(start..until).and_then(|el| el.try_into().ok()).unwrap_or_default()) + } - Err(WebSocketError::InvalidFrameHeaderBounds.into()) + async fn write_control_frame( + frame: &mut FrameControlArray, + is_stream_closed: &mut bool, + nc: &mut NC, + pb: &mut PB, + rng: &mut RNG, + stream: &mut S, + ) -> crate::Result<()> { + Self::do_write_frame(frame, is_stream_closed, nc, pb.borrow_mut(), rng, stream).await?; + Ok(()) + } } -pub(crate) fn has_masked_frame(second_header_byte: u8) -> bool { - second_header_byte & 0b1000_0000 != 0 +#[cfg(feature = "web-socket-handshake")] +impl WebSocketClient { + /// Shortcut that has the same effect of [WebSocketConnect::connect]. + #[inline] + pub async fn connect(wsc: WSC) -> crate::Result<(WSC::Response, Self)> + where + WSC: handshake::WebSocketConnect, + { + wsc.connect().await + } } -pub(crate) fn op_code(first_header_byte: u8) -> crate::Result { - OpCode::try_from(first_header_byte & 0b0000_1111) +#[cfg(feature = "web-socket-handshake")] +impl WebSocketServer { + /// Shortcut that has the same effect of [WebSocketAccept::accept]. + #[inline] + pub async fn accept(wsc: WSA) -> crate::Result<(WSA::Response, Self)> + where + WSA: handshake::WebSocketAccept, + { + wsc.accept().await + } } -const fn msg_header_placeholder() -> u8 { - if IS_CLIENT { - MAX_HDR_LEN_U8 - } else { - MAX_HDR_LEN_U8 - 4 - } + +/// Parameters of the frame read from a stream +#[derive(Debug)] +struct ReadFrameInfo { + fin: bool, + frame_len: usize, + header_end_idx: usize, + mask: Option<[u8; 4]>, + op_code: OpCode, + payload_len: usize, + should_decompress: bool, } -fn remove_mask(header: &mut [u8]) -> u8 { - let Some(second_header_byte) = header.get_mut(1) else { - return 0; - }; - if !has_masked_frame(*second_header_byte) { - return 0; - } - *second_header_byte &= 0b0111_1111; - let prev_header_len = header.len(); - let until_mask = header - .get_mut(..prev_header_len.wrapping_sub(4)) - .unwrap_or_default(); - let mut buffer = [0u8; MAX_HDR_LEN_USIZE - 4]; - let swap_bytes = buffer.get_mut(..until_mask.len()).unwrap_or_default(); - swap_bytes.copy_from_slice(until_mask); - let new_header = header.get_mut(4..prev_header_len).unwrap_or_default(); - new_header.copy_from_slice(swap_bytes); - 4 +const fn has_masked_frame(second_header_byte: u8) -> bool { + second_header_byte & 0b1000_0000 != 0 } diff --git a/wtx/src/web_socket/close_code.rs b/wtx/src/web_socket/close_code.rs index 67eb8972..036ec2a8 100644 --- a/wtx/src/web_socket/close_code.rs +++ b/wtx/src/web_socket/close_code.rs @@ -1,101 +1,100 @@ /// Status code used to indicate why an endpoint is closing the WebSocket connection. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CloseCode { - /// Normal closure. - Normal, - /// An endpoint is not longer active. - Away, - /// Closing connection due to a protocol error. - Protocol, - /// An endpoint does not support a certain type of data. - Unsupported, - /// Closing frame without a status code. - Status, - /// Connection dropped without an error. - Abnormal, - /// Received data that differs from the frame type. - Invalid, - /// Generic error. - Policy, - /// Received a very large payload. - Size, - /// Client didn't receive extension from the server. - Extension, - /// An unexpected condition occurred. - Error, - /// Server is restarting. - Restart, - /// Server is busy and the client should reconnect. - Again, - #[doc(hidden)] - Tls, - #[doc(hidden)] - Reserved(u16), - #[doc(hidden)] - Iana(u16), - #[doc(hidden)] - Library(u16), - #[doc(hidden)] - Bad(u16), + /// Normal closure. + Normal, + /// An endpoint is not longer active. + Away, + /// Closing connection due to a protocol error. + Protocol, + /// An endpoint does not support a certain type of data. + Unsupported, + /// Closing frame without a status code. + Status, + /// Connection dropped without an error. + Abnormal, + /// Received data that differs from the frame type. + Invalid, + /// Generic error. + Policy, + /// Received a very large payload. + Size, + /// Client didn't receive extension from the server. + Extension, + /// An unexpected condition occurred. + Error, + /// Server is restarting. + Restart, + /// Server is busy and the client should reconnect. + Again, + #[doc(hidden)] + Tls, + #[doc(hidden)] + Reserved(u16), + #[doc(hidden)] + Iana(u16), + #[doc(hidden)] + Library(u16), + #[doc(hidden)] + Bad(u16), } impl CloseCode { - /// Checks if this instances is allowed. - pub fn is_allowed(self) -> bool { - !matches!( - self, - Self::Bad(_) | Self::Reserved(_) | Self::Status | Self::Abnormal | Self::Tls - ) - } + /// Checks if this instances is allowed. + #[inline] + pub fn is_allowed(self) -> bool { + !matches!(self, Self::Bad(_) | Self::Reserved(_) | Self::Status | Self::Abnormal | Self::Tls) + } } impl From for CloseCode { - fn from(code: u16) -> CloseCode { - match code { - 1000 => Self::Normal, - 1001 => Self::Away, - 1002 => Self::Protocol, - 1003 => Self::Unsupported, - 1005 => Self::Status, - 1006 => Self::Abnormal, - 1007 => Self::Invalid, - 1008 => Self::Policy, - 1009 => Self::Size, - 1010 => Self::Extension, - 1011 => Self::Error, - 1012 => Self::Restart, - 1013 => Self::Again, - 1015 => Self::Tls, - 1016..=2999 => Self::Reserved(code), - 3000..=3999 => Self::Iana(code), - 4000..=4999 => Self::Library(code), - _ => Self::Bad(code), - } + #[inline] + fn from(code: u16) -> CloseCode { + match code { + 1000 => Self::Normal, + 1001 => Self::Away, + 1002 => Self::Protocol, + 1003 => Self::Unsupported, + 1005 => Self::Status, + 1006 => Self::Abnormal, + 1007 => Self::Invalid, + 1008 => Self::Policy, + 1009 => Self::Size, + 1010 => Self::Extension, + 1011 => Self::Error, + 1012 => Self::Restart, + 1013 => Self::Again, + 1015 => Self::Tls, + 1016..=2999 => Self::Reserved(code), + 3000..=3999 => Self::Iana(code), + 4000..=4999 => Self::Library(code), + _ => Self::Bad(code), } + } } impl From for u16 { - #[inline] - fn from(from: CloseCode) -> u16 { - match from { - CloseCode::Normal => 1000, - CloseCode::Away => 1001, - CloseCode::Protocol => 1002, - CloseCode::Unsupported => 1003, - CloseCode::Status => 1005, - CloseCode::Abnormal => 1006, - CloseCode::Invalid => 1007, - CloseCode::Policy => 1008, - CloseCode::Size => 1009, - CloseCode::Extension => 1010, - CloseCode::Error => 1011, - CloseCode::Restart => 1012, - CloseCode::Again => 1013, - CloseCode::Tls => 1015, - CloseCode::Bad(code) - | CloseCode::Iana(code) - | CloseCode::Library(code) - | CloseCode::Reserved(code) => code, - } + #[inline] + fn from(from: CloseCode) -> u16 { + match from { + CloseCode::Normal => 1000, + CloseCode::Away => 1001, + CloseCode::Protocol => 1002, + CloseCode::Unsupported => 1003, + CloseCode::Status => 1005, + CloseCode::Abnormal => 1006, + CloseCode::Invalid => 1007, + CloseCode::Policy => 1008, + CloseCode::Size => 1009, + CloseCode::Extension => 1010, + CloseCode::Error => 1011, + CloseCode::Restart => 1012, + CloseCode::Again => 1013, + CloseCode::Tls => 1015, + CloseCode::Bad(code) + | CloseCode::Iana(code) + | CloseCode::Library(code) + | CloseCode::Reserved(code) => code, } + } } diff --git a/wtx/src/web_socket/compression.rs b/wtx/src/web_socket/compression.rs new file mode 100644 index 00000000..c5d52373 --- /dev/null +++ b/wtx/src/web_socket/compression.rs @@ -0,0 +1,202 @@ +//! https://datatracker.ietf.org/doc/html/rfc7692 + +mod compression_level; +mod deflate_config; +#[cfg(feature = "flate2")] +mod flate2; +mod window_bits; + +pub use compression_level::CompressionLevel; +pub use deflate_config::DeflateConfig; +#[cfg(feature = "flate2")] +pub use flate2::{Flate2, NegotiatedFlate2}; +pub use window_bits::WindowBits; + +/// Initial compression parameters defined before a handshake. +pub trait Compression { + /// See [NegotiatedCompression]. + type Negotiated: NegotiatedCompression; + + /// Manages the defined parameters with the received parameters to decide which + /// parameters will be settled. + #[cfg(feature = "web-socket-handshake")] + fn negotiate( + self, + headers: &[crate::http_structs::Header<'_>], + ) -> crate::Result; + + /// Writes headers bytes that will be sent to the server. + fn write_req_headers(&self, buffer: &mut B) + where + B: Extend; +} + +impl Compression for () { + type Negotiated = (); + + #[cfg(feature = "web-socket-handshake")] + #[inline] + fn negotiate(self, _: &[crate::http_structs::Header<'_>]) -> crate::Result { + Ok(()) + } + + #[inline] + fn write_req_headers(&self, _: &mut B) + where + B: Extend, + { + } +} + +/// Final compression parameters defined after a handshake. +pub trait NegotiatedCompression { + fn compress( + &mut self, + input: &[u8], + output: &mut O, + begin_cb: impl FnMut(&mut O) -> &mut [u8], + rem_cb: impl FnMut(&mut O, usize) -> &mut [u8], + ) -> crate::Result; + + fn decompress( + &mut self, + input: &[u8], + output: &mut O, + begin_cb: impl FnMut(&mut O) -> &mut [u8], + rem_cb: impl FnMut(&mut O, usize) -> &mut [u8], + ) -> crate::Result; + + fn rsv1(&self) -> u8; + + fn write_res_headers(&self, buffer: &mut B) + where + B: Extend; +} + +impl NegotiatedCompression for &mut T +where + T: NegotiatedCompression, +{ + #[inline] + fn compress( + &mut self, + input: &[u8], + output: &mut O, + begin_cb: impl FnMut(&mut O) -> &mut [u8], + rem_cb: impl FnMut(&mut O, usize) -> &mut [u8], + ) -> crate::Result { + (**self).compress(input, output, begin_cb, rem_cb) + } + + #[inline] + fn decompress( + &mut self, + input: &[u8], + output: &mut O, + begin_cb: impl FnMut(&mut O) -> &mut [u8], + rem_cb: impl FnMut(&mut O, usize) -> &mut [u8], + ) -> crate::Result { + (**self).decompress(input, output, begin_cb, rem_cb) + } + + #[inline] + fn rsv1(&self) -> u8 { + (**self).rsv1() + } + + #[inline] + fn write_res_headers(&self, buffer: &mut B) + where + B: Extend, + { + (**self).write_res_headers(buffer); + } +} + +impl NegotiatedCompression for () { + #[inline] + fn compress( + &mut self, + _: &[u8], + _: &mut O, + _: impl FnMut(&mut O) -> &mut [u8], + _: impl FnMut(&mut O, usize) -> &mut [u8], + ) -> crate::Result { + Ok(0) + } + + #[inline] + fn decompress( + &mut self, + _: &[u8], + _: &mut O, + _: impl FnMut(&mut O) -> &mut [u8], + _: impl FnMut(&mut O, usize) -> &mut [u8], + ) -> crate::Result { + Ok(0) + } + + #[inline] + fn rsv1(&self) -> u8 { + 0 + } + + #[inline] + fn write_res_headers(&self, _: &mut B) + where + B: Extend, + { + } +} + +impl NegotiatedCompression for Option +where + T: NegotiatedCompression, +{ + #[inline] + fn compress( + &mut self, + input: &[u8], + output: &mut O, + begin_cb: impl FnMut(&mut O) -> &mut [u8], + rem_cb: impl FnMut(&mut O, usize) -> &mut [u8], + ) -> crate::Result { + match self { + Some(el) => el.compress(input, output, begin_cb, rem_cb), + None => ().compress(input, output, begin_cb, rem_cb), + } + } + + #[inline] + fn decompress( + &mut self, + input: &[u8], + output: &mut O, + begin_cb: impl FnMut(&mut O) -> &mut [u8], + rem_cb: impl FnMut(&mut O, usize) -> &mut [u8], + ) -> crate::Result { + match self { + Some(el) => el.decompress(input, output, begin_cb, rem_cb), + None => ().decompress(input, output, begin_cb, rem_cb), + } + } + + #[inline] + fn rsv1(&self) -> u8 { + match self { + Some(el) => el.rsv1(), + None => ().rsv1(), + } + } + + #[inline] + fn write_res_headers(&self, buffer: &mut B) + where + B: Extend, + { + match self { + Some(el) => el.write_res_headers(buffer), + None => ().write_res_headers(buffer), + } + } +} diff --git a/wtx/src/web_socket/compression/compression_level.rs b/wtx/src/web_socket/compression/compression_level.rs new file mode 100644 index 00000000..981856c0 --- /dev/null +++ b/wtx/src/web_socket/compression/compression_level.rs @@ -0,0 +1,43 @@ +create_enum! { + /// A scale from 0 to 9 where 0 means "no compression" and 9 means "take as long as you'd like". + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + #[repr(u8)] + pub enum CompressionLevel { + /// Zero + Zero = 0, + /// One + One = 1, + /// Two + Two = 2, + /// Three + Three = 3, + /// Four + Four = 4, + /// Five + #[default] + Five = 5, + /// Six + Six = 6, + /// Seven + Seven = 7, + /// Eight + Eight = 8, + /// Nine + Nine = 9, + } +} + +impl CompressionLevel { + /// Instance that represents the minimum allowed value. + pub const MIN: Self = Self::Zero; + /// Instance that represents the maximum allowed value. + pub const MAX: Self = Self::Nine; +} + +#[cfg(feature = "flate2")] +impl From for flate2::Compression { + #[inline] + fn from(from: CompressionLevel) -> Self { + flate2::Compression::new(u8::from(from).into()) + } +} diff --git a/wtx/src/web_socket/compression/deflate_config.rs b/wtx/src/web_socket/compression/deflate_config.rs new file mode 100644 index 00000000..1da5cca5 --- /dev/null +++ b/wtx/src/web_socket/compression/deflate_config.rs @@ -0,0 +1,23 @@ +use crate::web_socket::compression::{CompressionLevel, WindowBits}; + +/// Configurations for the `permessage-deflate` extension from the IETF RFC 7692 +#[derive(Debug)] +pub struct DeflateConfig { + /// LZ77 sliding window size for the client. + pub client_max_window_bits: WindowBits, + /// Compression level. + pub compression_level: CompressionLevel, + /// LZ77 sliding window size for the server. + pub server_max_window_bits: WindowBits, +} + +impl Default for DeflateConfig { + #[inline] + fn default() -> Self { + DeflateConfig { + client_max_window_bits: WindowBits::Twelve, + compression_level: CompressionLevel::default(), + server_max_window_bits: WindowBits::Twelve, + } + } +} diff --git a/wtx/src/web_socket/compression/flate2.rs b/wtx/src/web_socket/compression/flate2.rs new file mode 100644 index 00000000..cbe35a67 --- /dev/null +++ b/wtx/src/web_socket/compression/flate2.rs @@ -0,0 +1,265 @@ +use crate::{ + misc::from_utf8_opt, + web_socket::{compression::NegotiatedCompression, Compression, DeflateConfig}, +}; +use core::str::FromStr; +use flate2::{Compress, Decompress, FlushCompress, FlushDecompress}; + +#[derive(Debug)] +pub struct Flate2 { + dc: DeflateConfig, +} + +impl Flate2 { + #[inline] + pub fn new(dc: DeflateConfig) -> Self { + Self { dc } + } +} + +impl Compression for Flate2 { + type Negotiated = Option; + + #[cfg(feature = "web-socket-handshake")] + #[inline] + fn negotiate( + self, + headers: &[crate::http_structs::Header<'_>], + ) -> crate::Result { + use crate::{misc::_trim, web_socket::WebSocketError}; + + let mut dc = DeflateConfig { + client_max_window_bits: self.dc.client_max_window_bits, + compression_level: self.dc.compression_level, + server_max_window_bits: self.dc.server_max_window_bits, + }; + + let mut has_extension = false; + + for sec_websocket_extensions in headers + .iter() + .filter(|local_header| local_header.name().eq_ignore_ascii_case("sec-websocket-extensions")) + { + for permessage_deflate_option in sec_websocket_extensions.value().split(|el| el == &b',') { + dc = DeflateConfig { + client_max_window_bits: self.dc.client_max_window_bits, + compression_level: self.dc.compression_level, + server_max_window_bits: self.dc.server_max_window_bits, + }; + let mut client_max_window_bits_flag = false; + let mut permessage_deflate_flag = false; + let mut server_max_window_bits_flag = false; + for param in permessage_deflate_option.split(|el| el == &b';').map(|elem| _trim(elem)) { + if param == b"client_no_context_takeover" || param == b"server_no_context_takeover" { + } else if param == b"permessage-deflate" { + _manage_header_uniqueness(&mut permessage_deflate_flag, || Ok(()))? + } else if let Some(after_cmwb) = param.strip_prefix(b"client_max_window_bits") { + _manage_header_uniqueness(&mut client_max_window_bits_flag, || { + if let Some(value) = _value_from_bytes::(after_cmwb) { + dc.client_max_window_bits = value.try_into()?; + } + Ok(()) + })?; + } else if let Some(after_smwb) = param.strip_prefix(b"server_max_window_bits") { + _manage_header_uniqueness(&mut server_max_window_bits_flag, || { + if let Some(value) = _value_from_bytes::(after_smwb) { + dc.server_max_window_bits = value.try_into()?; + } + Ok(()) + })?; + } else { + return Err(WebSocketError::InvalidCompressionHeaderParameter.into()); + } + } + if !permessage_deflate_flag { + return Err(WebSocketError::InvalidCompressionHeaderParameter.into()); + } + has_extension = true; + } + } + + if !has_extension { + return Ok(None); + } + + let decoder_wb = if IS_CLIENT { dc.server_max_window_bits } else { dc.client_max_window_bits }; + let encoder_wb = if IS_CLIENT { dc.client_max_window_bits } else { dc.server_max_window_bits }; + + Ok(Some(NegotiatedFlate2 { + decompress: Decompress::new_with_window_bits(false, decoder_wb.into()), + compress: Compress::new_with_window_bits( + dc.compression_level.into(), + false, + encoder_wb.into(), + ), + dc, + })) + } + + #[inline] + fn write_req_headers(&self, buffer: &mut B) + where + B: Extend, + { + write_headers(buffer, &self.dc) + } +} + +impl Default for Flate2 { + #[inline] + fn default() -> Self { + Self::new(<_>::default()) + } +} + +#[derive(Debug)] +pub struct NegotiatedFlate2 { + compress: Compress, + dc: DeflateConfig, + decompress: Decompress, +} + +impl NegotiatedCompression for NegotiatedFlate2 { + fn compress( + &mut self, + input: &[u8], + output: &mut O, + begin_cb: impl FnMut(&mut O) -> &mut [u8], + rem_cb: impl FnMut(&mut O, usize) -> &mut [u8], + ) -> crate::Result { + compress_or_decompress( + input, + self, + output, + true, + begin_cb, + |this, local_input, output_butes| { + let _ = this.compress.compress(local_input, output_butes, FlushCompress::Sync); + Ok(()) + }, + rem_cb, + |this| this.compress.reset(), + |this| this.compress.total_in(), + |this| this.compress.total_out(), + ) + } + + fn decompress( + &mut self, + input: &[u8], + output: &mut O, + begin_cb: impl FnMut(&mut O) -> &mut [u8], + rem_cb: impl FnMut(&mut O, usize) -> &mut [u8], + ) -> crate::Result { + compress_or_decompress( + input, + self, + output, + true, + begin_cb, + |this, local_input, output_butes| { + let _ = this.decompress.decompress(local_input, output_butes, FlushDecompress::Sync); + Ok(()) + }, + rem_cb, + |this| this.decompress.reset(false), + |this| this.decompress.total_in(), + |this| this.decompress.total_out(), + ) + } + + #[inline] + fn rsv1(&self) -> u8 { + 0b0100_0000 + } + + #[inline] + fn write_res_headers(&self, buffer: &mut B) + where + B: Extend, + { + write_headers(buffer, &self.dc) + } +} + +fn compress_or_decompress( + input: &[u8], + nc: &mut NC, + output: &mut O, + reset: bool, + mut begin_output_cb: impl FnMut(&mut O) -> &mut [u8], + mut call_cb: impl FnMut(&mut NC, &[u8], &mut [u8]) -> crate::Result<()>, + mut expand_output_cb: impl FnMut(&mut O, usize) -> &mut [u8], + mut reset_cb: impl FnMut(&mut NC), + mut total_in_cb: impl FnMut(&mut NC) -> u64, + mut total_out_cb: impl FnMut(&mut NC) -> u64, +) -> crate::Result { + call_cb(nc, input, begin_output_cb(output))?; + let mut total_in_sum = usize::try_from(total_in_cb(nc))?; + let mut total_out_sum = usize::try_from(total_out_cb(nc))?; + if total_in_sum == input.len() { + if reset { + reset_cb(nc); + } + return Ok(total_out_sum); + } + let mut prev_total_in_sum = total_in_sum; + loop { + let Some(slice) = input.get(total_in_sum..) else { + return Err(crate::Error::UnexpectedBufferState); + }; + call_cb(nc, slice, expand_output_cb(output, total_out_sum))?; + total_in_sum = usize::try_from(total_in_cb(nc))?; + if prev_total_in_sum == total_in_sum { + return Err(crate::Error::UnexpectedBufferState); + } + total_out_sum = usize::try_from(total_out_cb(nc))?; + if total_in_sum == input.len() { + if reset { + reset_cb(nc); + } + return Ok(total_out_sum); + } + prev_total_in_sum = total_in_sum; + } +} + +fn _manage_header_uniqueness( + flag: &mut bool, + mut cb: impl FnMut() -> crate::Result<()>, +) -> crate::Result<()> { + if *flag { + Err(crate::Error::DuplicatedHeader) + } else { + cb()?; + *flag = true; + Ok(()) + } +} + +fn _value_from_bytes(bytes: &[u8]) -> Option +where + T: FromStr, +{ + let after_equals = bytes.split(|byte| byte == &b'=').nth(1)?; + from_utf8_opt(after_equals)?.parse::().ok() +} + +#[inline] +fn write_headers(buffer: &mut B, dc: &DeflateConfig) +where + B: Extend, +{ + buffer.extend(*b"Sec-Websocket-Extensions: "); + + buffer.extend(*b"permessage-deflate; "); + + buffer.extend(*b"client_max_window_bits="); + buffer.extend(<&str>::from(dc.client_max_window_bits).as_bytes().iter().copied()); + buffer.extend(*b"; "); + + buffer.extend(*b"server_max_window_bits="); + buffer.extend(<&str>::from(dc.server_max_window_bits).as_bytes().iter().copied()); + + buffer.extend(*b"; client_no_context_takeover; server_no_context_takeover\r\n"); +} diff --git a/wtx/src/web_socket/compression/window_bits.rs b/wtx/src/web_socket/compression/window_bits.rs new file mode 100644 index 00000000..ec0fda86 --- /dev/null +++ b/wtx/src/web_socket/compression/window_bits.rs @@ -0,0 +1,47 @@ +create_enum! { + /// LZ77 sliding window size for the `permessage-deflate` extension from the IETF RFC 7692. + #[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] + #[repr(u8)] + pub enum WindowBits { + /// Eight + Eight = 8, + /// Nine + Nine = 9, + /// Ten + Ten = 10, + /// Eleven + #[default] + Eleven = 11, + /// Twelve + Twelve = 12, + /// Thirteen + Thirteen = 13, + /// Fourteen + Fourteen = 14, + /// Fifteen + Fifteen = 15, + } +} + +impl WindowBits { + /// Instance that represents the minimum allowed value. + pub const MIN: Self = Self::Eight; + /// Instance that represents the maximum allowed value. + pub const MAX: Self = Self::Fifteen; +} + +impl From for &'static str { + #[inline] + fn from(from: WindowBits) -> Self { + match from { + WindowBits::Eight => "8", + WindowBits::Nine => "9", + WindowBits::Ten => "10", + WindowBits::Eleven => "11", + WindowBits::Twelve => "12", + WindowBits::Thirteen => "13", + WindowBits::Fourteen => "14", + WindowBits::Fifteen => "15", + } + } +} diff --git a/wtx/src/web_socket/frame.rs b/wtx/src/web_socket/frame.rs index c7e9f00b..53380a23 100644 --- a/wtx/src/web_socket/frame.rs +++ b/wtx/src/web_socket/frame.rs @@ -1,24 +1,25 @@ use crate::{ - misc::{from_utf8_opt, Expand, SingleTypeStorage}, - web_socket::{ - copy_header_params_to_buffer, - frame_buffer::{ - FrameBufferControlArray, FrameBufferControlArrayMut, FrameBufferMut, FrameBufferVecMut, - }, - op_code, FrameBuffer, FrameBufferVec, OpCode, WebSocketError, - MAX_CONTROL_FRAME_PAYLOAD_LEN, MAX_HDR_LEN_USIZE, MIN_HEADER_LEN_USIZE, + misc::{from_utf8_opt, Expand, SingleTypeStorage}, + web_socket::{ + close_code::CloseCode, + frame_buffer::{ + FrameBufferControlArray, FrameBufferControlArrayMut, FrameBufferMut, FrameBufferVecMut, }, + misc::{define_fb_from_header_params, op_code}, + FrameBuffer, FrameBufferVec, OpCode, WebSocketError, MAX_CONTROL_FRAME_PAYLOAD_LEN, + MAX_HDR_LEN_USIZE, MIN_HEADER_LEN_USIZE, + }, }; use core::{ - borrow::{Borrow, BorrowMut}, - str, + borrow::{Borrow, BorrowMut}, + str, }; /// Composed by a [FrameBufferControlArray]. pub type FrameControlArray = Frame; /// Composed by a [FrameBufferControlArrayMut]. pub type FrameControlArrayMut<'bytes, const IS_CLIENT: bool> = - Frame, IS_CLIENT>; + Frame, IS_CLIENT>; /// Composed by a [FrameBufferMut]. pub type FrameMut<'bytes, const IS_CLIENT: bool> = Frame, IS_CLIENT>; /// Composed by a [FrameBufferVec]. @@ -28,167 +29,158 @@ pub type FrameVecMut<'bytes, const IS_CLIENT: bool> = Frame = - Frame<&'fb mut FrameBufferControlArray, IS_CLIENT>; + Frame<&'fb mut FrameBufferControlArray, IS_CLIENT>; /// Composed by an mutable [FrameBufferControlArrayMut] reference. pub type FrameMutControlArrayMut<'fb, const IS_CLIENT: bool> = - Frame<&'fb mut FrameBufferControlArray, IS_CLIENT>; + Frame<&'fb mut FrameBufferControlArray, IS_CLIENT>; /// Composed by an mutable [FrameBufferMut] reference. pub type FrameMutMut<'bytes, 'fb, const IS_CLIENT: bool> = - Frame<&'fb mut FrameBufferMut<'bytes>, IS_CLIENT>; + Frame<&'fb mut FrameBufferMut<'bytes>, IS_CLIENT>; /// Composed by an mutable [FrameBufferVec] reference. pub type FrameMutVec<'fb, const IS_CLIENT: bool> = Frame<&'fb mut FrameBufferVec, IS_CLIENT>; /// Composed by an mutable [FrameBufferVecMut] reference. pub type FrameMutVecMut<'bytes, 'fb, const IS_CLIENT: bool> = - Frame<&'fb mut FrameBufferVecMut<'bytes>, IS_CLIENT>; + Frame<&'fb mut FrameBufferVecMut<'bytes>, IS_CLIENT>; /// Represents a WebSocket frame #[derive(Debug)] pub struct Frame { - fin: bool, - op_code: OpCode, - fb: FB, + fb: FB, + fin: bool, + op_code: OpCode, } impl Frame { - /// Contains the raw bytes that compose this frame. - #[inline] - pub fn fb(&self) -> &FB { - &self.fb - } + /// Contains the raw bytes that compose this frame. + #[inline] + pub fn fb(&self) -> &FB { + &self.fb + } - pub(crate) fn fb_mut(&mut self) -> &mut FB { - &mut self.fb - } + pub(crate) fn fb_mut(&mut self) -> &mut FB { + &mut self.fb + } - /// Indicates if this is the final frame in a message. - #[inline] - pub fn fin(&self) -> bool { - self.fin - } + /// Indicates if this is the final frame in a message. + #[inline] + pub fn fin(&self) -> bool { + self.fin + } - /// See [OpCode]. - #[inline] - pub fn op_code(&self) -> OpCode { - self.op_code - } + /// See [OpCode]. + #[inline] + pub fn op_code(&self) -> OpCode { + self.op_code + } } impl Frame where - B: AsRef<[u8]>, - FB: Borrow> + SingleTypeStorage, + B: AsRef<[u8]>, + FB: Borrow> + SingleTypeStorage, { - /// Creates a new instance based on the contained bytes of `fb`. - #[inline] - pub fn from_fb(fb: FB) -> crate::Result { - let header = fb.borrow().header(); - let len = header.len(); - let has_valid_header = (MIN_HEADER_LEN_USIZE..=MAX_HDR_LEN_USIZE).contains(&len); - let (true, Some(first_header_byte)) = (has_valid_header, header.first().copied()) else { - return Err(WebSocketError::InvalidFrameHeaderBounds.into()); - }; - Ok(Self { - fb, - fin: first_header_byte & 0b1000_0000 != 0, - op_code: op_code(first_header_byte)?, - }) - } + /// Creates a new instance based on the contained bytes of `fb`. + #[inline] + pub fn from_fb(fb: FB) -> crate::Result { + let header = fb.borrow().header(); + let len = header.len(); + let has_valid_header = (MIN_HEADER_LEN_USIZE..=MAX_HDR_LEN_USIZE).contains(&len); + let (true, Some(first_header_byte)) = (has_valid_header, header.first().copied()) else { + return Err(WebSocketError::InvalidFrameHeaderBounds.into()); + }; + Ok(Self { fb, fin: first_header_byte & 0b1000_0000 != 0, op_code: op_code(first_header_byte)? }) + } - /// Checks if the frame payload is valid UTF-8, regardless of its type. - #[inline] - pub fn is_utf8(&self) -> bool { - self.op_code.is_text() || from_utf8_opt(self.fb.borrow().payload()).is_some() - } + /// Checks if the frame payload is valid UTF-8, regardless of its type. + #[inline] + pub fn is_utf8(&self) -> bool { + self.op_code.is_text() || from_utf8_opt(self.fb.borrow().payload()).is_some() + } - /// If the frame is of type [OpCode::Text], returns its payload interpreted as a string. - #[inline] - pub fn text_payload<'this>(&'this self) -> Option<&'this str> - where - B: 'this, - { - self.op_code.is_text().then(|| { - #[allow(unsafe_code)] - // SAFETY: All text frames have valid UTF-8 contents when read. - unsafe { - str::from_utf8_unchecked(self.fb.borrow().payload()) - } - }) - } + /// If the frame is of type [OpCode::Text], returns its payload interpreted as a string. + #[inline] + pub fn text_payload<'this>(&'this self) -> Option<&'this str> + where + B: 'this, + { + self.op_code.is_text().then(|| { + #[allow(unsafe_code)] + // SAFETY: All text frames have valid UTF-8 contents when read. + unsafe { + str::from_utf8_unchecked(self.fb.borrow().payload()) + } + }) + } } impl Frame where - B: AsMut<[u8]> + AsRef<[u8]> + Expand, - FB: BorrowMut> + SingleTypeStorage, + B: AsMut<[u8]> + AsRef<[u8]> + Expand, + FB: BorrowMut> + SingleTypeStorage, { - /// Creates based on the individual parameters that compose a close frame. - /// - /// `reason` is capped based on the maximum allowed size of a control frame minus 2. - #[inline] - pub fn close_from_params(code: u16, fb: FB, reason: &[u8]) -> crate::Result { - let reason_len = reason.len().min(MAX_CONTROL_FRAME_PAYLOAD_LEN - 2); - let payload_len = reason_len.wrapping_add(2); - Self::build_frame(fb, true, OpCode::Close, payload_len, |local_fb| { - let payload = local_fb.borrow_mut().payload_mut(); - payload - .get_mut(..2) - .unwrap_or_default() - .copy_from_slice(&code.to_be_bytes()); - payload - .get_mut(2..) - .unwrap_or_default() - .copy_from_slice(reason.get(..reason_len).unwrap_or_default()); - Ok(()) - }) - } + /// Creates based on the individual parameters that compose a close frame. + /// + /// `reason` is capped based on the maximum allowed size of a control frame minus 2. + #[inline] + pub fn close_from_params(code: CloseCode, fb: FB, reason: &[u8]) -> crate::Result { + let reason_len = reason.len().min(MAX_CONTROL_FRAME_PAYLOAD_LEN - 2); + let payload_len = reason_len.wrapping_add(2); + Self::build_frame(fb, true, OpCode::Close, payload_len, |local_fb| { + let payload = local_fb.borrow_mut().payload_mut(); + payload.get_mut(..2).unwrap_or_default().copy_from_slice(&u16::from(code).to_be_bytes()); + payload + .get_mut(2..) + .unwrap_or_default() + .copy_from_slice(reason.get(..reason_len).unwrap_or_default()); + Ok(()) + }) + } - /// Creates a new instance that is considered final. - #[inline] - pub fn new_fin(fb: FB, op_code: OpCode, payload: &[u8]) -> crate::Result { - Self::new(fb, true, op_code, payload) - } + /// Creates a new instance that is considered final. + #[inline] + pub fn new_fin(fb: FB, op_code: OpCode, payload: &[u8]) -> crate::Result { + Self::new(fb, true, op_code, payload) + } - /// Creates a new instance that is meant to be a continuation of previous frames. - #[inline] - pub fn new_unfin(fb: FB, op_code: OpCode, payload: &[u8]) -> crate::Result { - Self::new(fb, false, op_code, payload) - } + /// Creates a new instance that is meant to be a continuation of previous frames. + #[inline] + pub fn new_unfin(fb: FB, op_code: OpCode, payload: &[u8]) -> crate::Result { + Self::new(fb, false, op_code, payload) + } - fn build_frame( - mut fb: FB, - fin: bool, - op_code: OpCode, - payload_len: usize, - cb: impl FnOnce(&mut FB) -> crate::Result<()>, - ) -> crate::Result { - fb.borrow_mut().clear(); - fb.borrow_mut() - .buffer_mut() - .expand(MAX_HDR_LEN_USIZE.saturating_add(payload_len)); - let n = copy_header_params_to_buffer::( - fb.borrow_mut().buffer_mut().as_mut(), - fin, - op_code, - payload_len, - )?; - fb.borrow_mut().set_header_indcs(0, n)?; - fb.borrow_mut().set_payload_len(payload_len)?; - cb(&mut fb)?; - Ok(Self { fin, op_code, fb }) - } + fn build_frame( + mut fb: FB, + fin: bool, + op_code: OpCode, + payload_len: usize, + cb: impl FnOnce(&mut FB) -> crate::Result<()>, + ) -> crate::Result { + fb.borrow_mut().clear(); + fb.borrow_mut().buffer_mut().expand(MAX_HDR_LEN_USIZE.saturating_add(payload_len)); + define_fb_from_header_params::<_, IS_CLIENT>( + fb.borrow_mut(), + fin, + None, + op_code, + payload_len, + 0, + )?; + cb(&mut fb)?; + Ok(Self { fb, fin, op_code }) + } - fn new(fb: FB, fin: bool, op_code: OpCode, payload: &[u8]) -> crate::Result { - let payload_len = if op_code.is_control() { - payload.len().min(MAX_CONTROL_FRAME_PAYLOAD_LEN) - } else { - payload.len() - }; - Self::build_frame(fb, fin, op_code, payload_len, |local_fb| { - local_fb - .borrow_mut() - .payload_mut() - .copy_from_slice(payload.get(..payload_len).unwrap_or_default()); - Ok(()) - }) - } + fn new(fb: FB, fin: bool, op_code: OpCode, payload: &[u8]) -> crate::Result { + let payload_len = if op_code.is_control() { + payload.len().min(MAX_CONTROL_FRAME_PAYLOAD_LEN) + } else { + payload.len() + }; + Self::build_frame(fb, fin, op_code, payload_len, |local_fb| { + local_fb + .borrow_mut() + .payload_mut() + .copy_from_slice(payload.get(..payload_len).unwrap_or_default()); + Ok(()) + }) + } } diff --git a/wtx/src/web_socket/frame_buffer.rs b/wtx/src/web_socket/frame_buffer.rs index d38f64fe..2ab06f71 100644 --- a/wtx/src/web_socket/frame_buffer.rs +++ b/wtx/src/web_socket/frame_buffer.rs @@ -1,8 +1,11 @@ +#![allow( + // Indices point to valid memory + clippy::unreachable +)] + use crate::{ - misc::SingleTypeStorage, - web_socket::{ - WebSocketError, DFLT_FRAME_BUFFER_VEC_LEN, MAX_CONTROL_FRAME_LEN, MAX_HDR_LEN_U8, - }, + misc::SingleTypeStorage, + web_socket::{WebSocketError, DFLT_FRAME_BUFFER_VEC_LEN, MAX_CONTROL_FRAME_LEN, MAX_HDR_LEN_U8}, }; use alloc::{vec, vec::Vec}; use core::array; @@ -24,246 +27,263 @@ pub type FrameBufferVecMut<'bytes> = FrameBuffer<&'bytes mut Vec>; // [ prefix | header | payload | suffix ] // ``` #[derive(Debug)] -#[repr(C)] pub struct FrameBuffer { - header_begin_idx: u8, - header_end_idx: u8, - payload_end_idx: usize, - // Tail field to hopefully help transforms - buffer: B, + header_begin_idx: u8, + header_end_idx: u8, + payload_end_idx: usize, + // Tail field to hopefully help transforms + buffer: B, } impl FrameBuffer { - /// The underlying byte collection. - #[inline] - pub fn buffer(&self) -> &B { - &self.buffer - } + /// The underlying byte collection. + #[inline] + pub fn buffer(&self) -> &B { + &self.buffer + } - /// The indices that represent all frame parts contained in the underlying byte collection. - /// - /// ```rust - /// let fb = wtx::web_socket::FrameBufferVec::default(); - /// let (header_begin_idx, header_end_idx, payload_end_idx) = fb.indcs(); - /// assert_eq!( - /// fb.buffer().get(header_begin_idx.into()..header_end_idx.into()).unwrap_or_default(), - /// fb.header() - /// ); - /// assert_eq!( - /// fb.buffer().get(header_end_idx.into()..payload_end_idx).unwrap_or_default(), - /// fb.payload() - /// ); - /// ``` - #[inline] - pub fn indcs(&self) -> (u8, u8, usize) { - ( - self.header_begin_idx, - self.header_end_idx, - self.payload_end_idx, - ) - } + /// The indices that represent all frame parts contained in the underlying byte collection. + /// + /// ```rust + /// let fb = wtx::web_socket::FrameBufferVec::default(); + /// let (header_begin_idx, header_end_idx, payload_end_idx) = fb.indcs(); + /// assert_eq!( + /// fb.buffer().get(header_begin_idx.into()..header_end_idx.into()), + /// Some(fb.header()) + /// ); + /// assert_eq!(fb.buffer().get(header_end_idx.into()..payload_end_idx), Some(fb.payload())); + /// ``` + #[inline] + pub fn indcs(&self) -> (u8, u8, usize) { + (self.header_begin_idx, self.header_end_idx, self.payload_end_idx) + } - pub(crate) fn buffer_mut(&mut self) -> &mut B { - &mut self.buffer - } + pub(crate) fn buffer_mut(&mut self) -> &mut B { + &mut self.buffer + } - pub(crate) fn clear(&mut self) { - self.header_begin_idx = 0; - self.header_end_idx = 0; - self.payload_end_idx = 0; - } + pub(crate) fn clear(&mut self) { + self.header_begin_idx = 0; + self.header_end_idx = 0; + self.payload_end_idx = 0; + } - fn header_end_idx_from_parts(header_begin_idx: u8, header_len: u8) -> u8 { - header_begin_idx.saturating_add(header_len) - } + pub(crate) fn header_len(&self) -> u8 { + self.header_end_idx.saturating_sub(self.header_begin_idx) + } - fn payload_end_idx_from_parts(header_end: u8, payload_len: usize) -> usize { - usize::from(header_end).wrapping_add(payload_len) - } + fn header_end_idx_from_parts(header_begin_idx: u8, header_len: u8) -> u8 { + header_begin_idx.saturating_add(header_len) + } + + fn payload_end_idx_from_parts(header_end: u8, payload_len: usize) -> usize { + usize::from(header_end).wrapping_add(payload_len) + } } impl FrameBuffer where - B: AsRef<[u8]>, + B: AsRef<[u8]>, { - /// Creates a new instance from the given `buffer`. - #[inline] - pub fn new(buffer: B) -> Self { - Self { - header_begin_idx: 0, - header_end_idx: 0, - payload_end_idx: 0, - buffer, - } - } - - /// Sequence of bytes that composes the frame header. - #[inline] - pub fn header(&self) -> &[u8] { - self.buffer - .as_ref() - .get(self.header_begin_idx.into()..self.header_end_idx.into()) - .unwrap_or_default() - } + /// Creates a new instance from the given `buffer`. + #[inline] + pub fn new(buffer: B) -> Self { + Self { header_begin_idx: 0, header_end_idx: 0, payload_end_idx: 0, buffer } + } - /// Sequence of bytes that composes the frame payload. - #[inline] - pub fn payload(&self) -> &[u8] { - self.buffer - .as_ref() - .get(self.header_end_idx.into()..self.payload_end_idx) - .unwrap_or_default() + /// Sequence of bytes that composes the frame header. + #[inline] + pub fn header(&self) -> &[u8] { + if let Some(el) = + self.buffer.as_ref().get(self.header_begin_idx.into()..self.header_end_idx.into()) + { + el + } else { + unreachable!() } + } - pub(crate) fn frame(&self) -> &[u8] { - self.buffer - .as_ref() - .get(self.header_begin_idx.into()..self.payload_end_idx) - .unwrap_or_default() + /// Sequence of bytes that composes the frame payload. + #[inline] + pub fn payload(&self) -> &[u8] { + if let Some(el) = self.buffer.as_ref().get(self.header_end_idx.into()..self.payload_end_idx) { + el + } else { + unreachable!() } + } - pub(crate) fn set_header_indcs(&mut self, begin_idx: u8, len: u8) -> crate::Result<()> { - let header_end_idx = Self::header_end_idx_from_parts(begin_idx, len); - if len > MAX_HDR_LEN_U8 || usize::from(header_end_idx) > self.buffer.as_ref().len() { - return Err(WebSocketError::InvalidFrameHeaderBounds.into()); - } - self.header_begin_idx = begin_idx; - self.header_end_idx = header_end_idx; - self.payload_end_idx = usize::from(header_end_idx).max(self.payload_end_idx); - Ok(()) + pub(crate) fn frame(&self) -> &[u8] { + if let Some(el) = self.buffer.as_ref().get(self.header_begin_idx.into()..self.payload_end_idx) { + el + } else { + unreachable!() } + } - pub(crate) fn set_payload_len(&mut self, payload_len: usize) -> crate::Result<()> { - let payload_end_idx = Self::payload_end_idx_from_parts(self.header_end_idx, payload_len); - if payload_end_idx > self.buffer.as_ref().len() { - return Err(WebSocketError::InvalidPayloadBounds.into()); - } - self.payload_end_idx = payload_end_idx; - Ok(()) + pub(crate) fn set_indices( + &mut self, + header_begin_idx: u8, + header_len: u8, + payload_len: usize, + ) -> crate::Result<()> { + let header_end_idx = Self::header_end_idx_from_parts(header_begin_idx, header_len); + let payload_end_idx = Self::payload_end_idx_from_parts(header_end_idx, payload_len); + if header_len > MAX_HDR_LEN_U8 || payload_end_idx > self.buffer.as_ref().len() { + return Err(WebSocketError::InvalidPayloadBounds.into()); } + self.header_begin_idx = header_begin_idx; + self.header_end_idx = header_end_idx; + self.payload_end_idx = payload_end_idx; + Ok(()) + } } impl FrameBuffer where - B: AsMut<[u8]>, + B: AsMut<[u8]>, { - pub(crate) fn header_mut(&mut self) -> &mut [u8] { - self.buffer - .as_mut() - .get_mut(self.header_begin_idx.into()..self.header_end_idx.into()) - .unwrap_or_default() + pub(crate) fn header_mut(&mut self) -> &mut [u8] { + let range = self.header_begin_idx.into()..self.header_end_idx.into(); + if let Some(el) = self.buffer.as_mut().get_mut(range) { + el + } else { + unreachable!() } + } - pub(crate) fn payload_mut(&mut self) -> &mut [u8] { - self.buffer - .as_mut() - .get_mut(self.header_end_idx.into()..self.payload_end_idx) - .unwrap_or_default() + pub(crate) fn payload_mut(&mut self) -> &mut [u8] { + let range = self.header_end_idx.into()..self.payload_end_idx; + if let Some(el) = self.buffer.as_mut().get_mut(range) { + el + } else { + unreachable!() } + } } impl FrameBuffer where - B: AsMut>, + B: AsMut>, { - pub(crate) fn set_params_through_expansion( - &mut self, - header_begin_idx: u8, - header_len: u8, - mut payload_end_idx: usize, - ) { - let header_end_idx = Self::header_end_idx_from_parts(header_begin_idx, header_len); - payload_end_idx = payload_end_idx.max(header_len.into()); - if payload_end_idx > self.buffer.as_mut().len() { - self.buffer.as_mut().resize(payload_end_idx, 0); - } - self.header_begin_idx = header_begin_idx; - self.header_end_idx = header_end_idx; - self.payload_end_idx = payload_end_idx; + pub(crate) fn expand_buffer(&mut self, new_len: usize) { + if new_len > self.buffer.as_mut().len() { + self.buffer.as_mut().resize(new_len, 0); } + } + + pub(crate) fn _set_indices_through_expansion( + &mut self, + header_begin_idx: u8, + header_len: u8, + payload_len: usize, + ) { + let header_end_idx = Self::header_end_idx_from_parts(header_begin_idx, header_len); + let mut payload_end_idx = usize::from(header_end_idx).saturating_add(payload_len); + payload_end_idx = payload_end_idx.max(header_len.into()); + self.header_begin_idx = header_begin_idx; + self.header_end_idx = header_end_idx; + self.payload_end_idx = payload_end_idx; + self.expand_buffer(payload_end_idx); + } } impl FrameBufferVec { - /// Creates a new instance with pre-allocated bytes. - #[inline] - pub fn with_capacity(n: usize) -> Self { - Self { - header_begin_idx: 0, - header_end_idx: 0, - payload_end_idx: 0, - buffer: vec![0; n], - } - } + /// Creates a new instance with pre-allocated bytes. + #[inline] + pub fn with_capacity(n: usize) -> Self { + Self { header_begin_idx: 0, header_end_idx: 0, payload_end_idx: 0, buffer: vec![0; n] } + } } impl SingleTypeStorage for FrameBuffer { - type Item = B; + type Item = B; } impl Default for FrameBufferControlArray { - #[inline] - fn default() -> Self { - Self { - header_begin_idx: 0, - header_end_idx: 0, - payload_end_idx: 0, - buffer: array::from_fn(|_| 0), - } + #[inline] + fn default() -> Self { + Self { + header_begin_idx: 0, + header_end_idx: 0, + payload_end_idx: 0, + buffer: array::from_fn(|_| 0), } + } } impl Default for FrameBufferVec { - #[inline] - fn default() -> Self { - Self { - header_begin_idx: 0, - header_end_idx: 0, - payload_end_idx: 0, - buffer: vec![0; DFLT_FRAME_BUFFER_VEC_LEN], - } + #[inline] + fn default() -> Self { + Self { + header_begin_idx: 0, + header_end_idx: 0, + payload_end_idx: 0, + buffer: vec![0; DFLT_FRAME_BUFFER_VEC_LEN], } + } +} + +impl Extend for FrameBufferVec { + #[inline] + fn extend(&mut self, iter: T) + where + T: IntoIterator, + { + self.buffer.extend(iter); + } +} + +impl<'item> Extend<&'item u8> for FrameBufferVec { + #[inline] + fn extend(&mut self, iter: T) + where + T: IntoIterator, + { + self.buffer.extend(iter); + } } impl<'fb, B> From<&'fb mut FrameBuffer> for FrameBufferMut<'fb> where - B: AsMut<[u8]>, + B: AsMut<[u8]>, { - #[inline] - fn from(from: &'fb mut FrameBuffer) -> Self { - Self { - header_begin_idx: from.header_begin_idx, - header_end_idx: from.header_end_idx, - payload_end_idx: from.payload_end_idx, - buffer: from.buffer.as_mut(), - } + #[inline] + fn from(from: &'fb mut FrameBuffer) -> Self { + Self { + header_begin_idx: from.header_begin_idx, + header_end_idx: from.header_end_idx, + payload_end_idx: from.payload_end_idx, + buffer: from.buffer.as_mut(), } + } } impl<'bytes, 'fb> From<&'fb mut FrameBufferVec> for FrameBufferVecMut<'bytes> where - 'fb: 'bytes, + 'fb: 'bytes, { - #[inline] - fn from(from: &'fb mut FrameBufferVec) -> Self { - Self { - header_begin_idx: from.header_begin_idx, - header_end_idx: from.header_end_idx, - payload_end_idx: from.payload_end_idx, - buffer: &mut from.buffer, - } + #[inline] + fn from(from: &'fb mut FrameBufferVec) -> Self { + Self { + header_begin_idx: from.header_begin_idx, + header_end_idx: from.header_end_idx, + payload_end_idx: from.payload_end_idx, + buffer: &mut from.buffer, } + } } impl From> for FrameBufferVec { - #[inline] - fn from(from: Vec) -> Self { - Self::new(from) - } + #[inline] + fn from(from: Vec) -> Self { + Self::new(from) + } } impl<'bytes> From<&'bytes mut Vec> for FrameBufferVecMut<'bytes> { - #[inline] - fn from(from: &'bytes mut Vec) -> Self { - Self::new(from) - } + #[inline] + fn from(from: &'bytes mut Vec) -> Self { + Self::new(from) + } } diff --git a/wtx/src/web_socket/handshake.rs b/wtx/src/web_socket/handshake.rs index beb4168b..cb71374f 100644 --- a/wtx/src/web_socket/handshake.rs +++ b/wtx/src/web_socket/handshake.rs @@ -1,70 +1,66 @@ -//! Handshake +//! Initial data negotiation on both client and server sides to start exchanging frames. -#[cfg(feature = "web-socket-hyper")] -pub(super) mod hyper; mod misc; -pub(super) mod raw; +mod raw; #[cfg(test)] mod tests; -#[cfg(feature = "web-socket-hyper")] -pub use self::hyper::{UpgradeFutHyper, WebSocketHandshakeHyper, WebSocketUpgradeHyper}; -use crate::web_socket::{Stream, WebSocketClient, WebSocketServer}; -#[cfg(feature = "async-trait")] -use alloc::boxed::Box; +use crate::{ + http_structs::Header, + web_socket::{Stream, WebSocketClient, WebSocketServer}, +}; use core::future::Future; #[cfg(feature = "web-socket-handshake")] -pub use raw::{WebSocketAcceptRaw, WebSocketHandshakeRaw}; +pub use raw::{WebSocketAcceptRaw, WebSocketConnectRaw}; -/// Manages incoming data to establish WebSocket connections. -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -pub trait WebSocketAccept { - /// Specific implementation response. - type Response; - /// Specific implementation stream. - type Stream: Stream; +/// Reads external data to figure out if incoming requests can be accepted as WebSocket connections. +pub trait WebSocketAccept { + /// Specific implementation response. + type Response; + /// Specific implementation stream. + type Stream: Stream; - /// Try to upgrade a received request to a WebSocket connection. - async fn accept(self) -> crate::Result<(Self::Response, WebSocketServer)>; + /// Reads external data to figure out if incoming requests can be accepted as WebSocket connections. + async fn accept( + self, + ) -> crate::Result<(Self::Response, WebSocketServer)>; } -/// Initial negotiation sent by a client to start exchanging frames. -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -pub trait WebSocketHandshake { - /// Specific implementation response. - type Response; - /// Specific implementation stream. - type Stream: Stream; +/// Initial negotiation sent by a client to start a WebSocket connection. +pub trait WebSocketConnect { + /// Specific implementation response. + type Response; + /// Specific implementation stream. + type Stream: Stream; - /// Performs the client handshake. - async fn handshake(self) -> crate::Result<(Self::Response, WebSocketClient)>; + /// Initial negotiation sent by a client to start a WebSocket connection. + async fn connect( + self, + ) -> crate::Result<(Self::Response, WebSocketClient)>; } /// Manages the upgrade of already established requests into WebSocket connections. -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] pub trait WebSocketUpgrade { - /// Specific implementation response. - type Response; - /// Specific implementation stream. - type Stream: Stream; - /// Specific implementation future that resolves to [WebSocketServer]. - type Upgrade: Future>; + /// Specific implementation response. + type Response; + /// Specific implementation stream. + type Stream: Stream; + /// Specific implementation future that resolves to [WebSocketServer]. + type Upgrade: Future>; - /// Try to upgrade a received request to a WebSocket connection. - fn upgrade(self) -> crate::Result<(Self::Response, Self::Upgrade)>; + /// Manages the upgrade of already established requests into WebSocket connections. + fn upgrade(self) -> crate::Result<(Self::Response, Self::Upgrade)>; } /// Necessary to decode incoming bytes of responses or requests. #[derive(Debug)] pub struct HeadersBuffer<'buffer, const N: usize> { - pub(crate) headers: [httparse::Header<'buffer>; N], + pub(crate) headers: [Header<'buffer>; N], } impl Default for HeadersBuffer<'_, N> { - #[inline] - fn default() -> Self { - Self { - headers: core::array::from_fn(|_| httparse::EMPTY_HEADER), - } - } + #[inline] + fn default() -> Self { + Self { headers: core::array::from_fn(|_| Header::EMPTY) } + } } diff --git a/wtx/src/web_socket/handshake/hyper.rs b/wtx/src/web_socket/handshake/hyper.rs deleted file mode 100644 index 766c1960..00000000 --- a/wtx/src/web_socket/handshake/hyper.rs +++ /dev/null @@ -1,191 +0,0 @@ -use crate::{ - misc::AsyncBounds, - web_socket::{ - handshake::{ - misc::{derived_key, gen_key, trim}, - WebSocketHandshake, WebSocketUpgrade, - }, - WebSocketClient, WebSocketError, - }, - Error::MissingHeader, - ExpectedHeader, ReadBuffer, -}; -#[cfg(feature = "async-trait")] -use alloc::boxed::Box; -use core::{ - borrow::BorrowMut, - future::Future, - pin::{pin, Pin}, - task::{ready, Context, Poll}, -}; -use hyper::{ - client::conn::{self, Connection}, - header::{CONNECTION, HOST, UPGRADE}, - http::{HeaderMap, HeaderValue}, - rt::Executor, - upgrade::{self, OnUpgrade, Upgraded}, - Body, Request, Response, StatusCode, -}; -use tokio::io::{AsyncRead, AsyncWrite}; - -/// A future that resolves to a WebSocket stream when the associated HTTP upgrade completes. -#[derive(Debug)] -pub struct UpgradeFutHyper { - inner: OnUpgrade, -} - -impl Future for UpgradeFutHyper { - type Output = crate::Result; - - #[inline] - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let stream = ready!(pin!(&mut self.inner).poll(cx))?; - Poll::Ready(Ok(stream)) - } -} - -/// Marker used to implement [WebSocketHandshake]. -#[derive(Debug)] -pub struct WebSocketHandshakeHyper<'executor, E, RB, S> { - /// Executor - pub executor: &'executor E, - /// Read buffer - pub rb: RB, - /// Request - pub req: Request, - /// Stream - pub stream: S, -} - -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl<'executor, E, RB, S> WebSocketHandshake for WebSocketHandshakeHyper<'executor, E, RB, S> -where - E: AsyncBounds + Executor> + 'executor, - RB: AsyncBounds + BorrowMut, - S: AsyncRead + AsyncWrite + Send + Unpin + 'static, -{ - type Response = Response; - type Stream = Upgraded; - - #[inline] - async fn handshake( - mut self, - ) -> crate::Result<(Self::Response, WebSocketClient)> { - let fun = || { - let authority = self.req.uri().authority().map(|el| el.as_str())?; - let mut iter = authority.split('@'); - let before_at = iter.next()?; - Some(iter.next().unwrap_or(before_at)) - }; - let host = fun().ok_or(crate::Error::MissingHost)?.parse()?; - drop( - self.req - .headers_mut() - .insert(CONNECTION, HeaderValue::from_static("upgrade")), - ); - drop(self.req.headers_mut().insert(HOST, host)); - drop( - self.req - .headers_mut() - .insert("Sec-WebSocket-Key", gen_key(&mut <_>::default()).parse()?), - ); - drop( - self.req - .headers_mut() - .insert("Sec-WebSocket-Version", HeaderValue::from_static("13")), - ); - drop( - self.req - .headers_mut() - .insert(UPGRADE, HeaderValue::from_static("websocket")), - ); - let (mut sender, conn) = conn::handshake(self.stream).await?; - self.executor.execute(conn); - let mut res = sender.send_request(self.req).await?; - verify_res(&res)?; - match upgrade::on(&mut res).await { - Err(err) => Err(err.into()), - Ok(elem) => Ok((res, WebSocketClient::new(self.rb, elem))), - } - } -} - -/// Structured used to implement [WebSocketUpgrade]. -#[derive(Debug)] -pub struct WebSocketUpgradeHyper { - /// Request - pub req: Request, -} - -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl WebSocketUpgrade for WebSocketUpgradeHyper -where - T: AsyncBounds, -{ - type Response = Response; - type Stream = Upgraded; - type Upgrade = UpgradeFutHyper; - - #[inline] - fn upgrade(self) -> crate::Result<(Self::Response, Self::Upgrade)> { - verify_headers(self.req.headers())?; - let sws_opt = self.req.headers().get("Sec-WebSocket-Key"); - let swk = sws_opt.ok_or(MissingHeader { - expected: ExpectedHeader::SecWebSocketKey, - })?; - if self - .req - .headers() - .get("Sec-WebSocket-Version") - .map(HeaderValue::as_bytes) - != Some(b"13") - { - return Err(MissingHeader { - expected: ExpectedHeader::SecWebSocketVersion_13, - }); - } - let res = Response::builder() - .status(StatusCode::SWITCHING_PROTOCOLS) - .header(CONNECTION, "upgrade") - .header(UPGRADE, "websocket") - .header( - "Sec-WebSocket-Accept", - derived_key(&mut <_>::default(), swk.as_bytes()), - ) - .body(Body::from("switching to websocket protocol"))?; - let stream = UpgradeFutHyper { - inner: upgrade::on(self.req), - }; - Ok((res, stream)) - } -} - -fn verify_headers(hm: &HeaderMap) -> crate::Result<()> { - if !hm - .get("Upgrade") - .map(|h| h.as_bytes()) - .map_or(false, |h| trim(h).eq_ignore_ascii_case(b"websocket")) - { - return Err(MissingHeader { - expected: ExpectedHeader::Upgrade_WebSocket, - }); - } - if !hm - .get("Connection") - .map(|h| h.as_bytes()) - .map_or(false, |h| trim(h).eq_ignore_ascii_case(b"upgrade")) - { - return Err(MissingHeader { - expected: ExpectedHeader::Connection_Upgrade, - }); - } - Ok(()) -} - -fn verify_res(res: &Response) -> crate::Result<()> { - if res.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(WebSocketError::MissingSwitchingProtocols.into()); - } - verify_headers(res.headers())?; - Ok(()) -} diff --git a/wtx/src/web_socket/handshake/misc.rs b/wtx/src/web_socket/handshake/misc.rs index 31e53033..f6e45f79 100644 --- a/wtx/src/web_socket/handshake/misc.rs +++ b/wtx/src/web_socket/handshake/misc.rs @@ -1,20 +1,16 @@ -use crate::misc::{from_utf8_opt, Rng}; +use crate::{misc::from_utf8_opt, rng::Rng}; use base64::{engine::general_purpose::STANDARD, Engine}; use sha1::{Digest, Sha1}; pub(crate) fn derived_key<'buffer>(buffer: &'buffer mut [u8; 30], key: &[u8]) -> &'buffer str { - let mut sha1 = Sha1::new(); - sha1.update(key); - sha1.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); - base64_from_array(&sha1.finalize().into(), buffer) + let mut sha1 = Sha1::new(); + sha1.update(key); + sha1.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + base64_from_array(&sha1.finalize().into(), buffer) } -pub(crate) fn gen_key(buffer: &mut [u8; 26]) -> &str { - base64_from_array(&Rng::default()._random_u8_16(), buffer) -} - -pub(crate) fn trim(bytes: &[u8]) -> &[u8] { - trim_end(trim_begin(bytes)) +pub(crate) fn gen_key<'buffer>(buffer: &'buffer mut [u8; 26], rng: &mut impl Rng) -> &'buffer str { + base64_from_array(&rng.u8_16(), buffer) } #[allow( @@ -24,39 +20,17 @@ pub(crate) fn trim(bytes: &[u8]) -> &[u8] { clippy::unwrap_used )] fn base64_from_array<'output, const I: usize, const O: usize>( - input: &[u8; I], - output: &'output mut [u8; O], + input: &[u8; I], + output: &'output mut [u8; O], ) -> &'output str { - fn div_ceil(x: usize, y: usize) -> usize { - let fun = || { - let num = x.checked_add(y)?.checked_sub(1)?; - num.checked_div(y) - }; - fun().unwrap_or_default() - } - assert!(O >= div_ceil(I, 3).wrapping_mul(4)); - let len = STANDARD.encode_slice(input, output).unwrap(); - from_utf8_opt(output.get(..len).unwrap_or_default()).unwrap() -} - -fn trim_begin(mut bytes: &[u8]) -> &[u8] { - while let [first, rest @ ..] = bytes { - if first.is_ascii_whitespace() { - bytes = rest; - } else { - break; - } - } - bytes -} - -fn trim_end(mut bytes: &[u8]) -> &[u8] { - while let [rest @ .., last] = bytes { - if last.is_ascii_whitespace() { - bytes = rest; - } else { - break; - } - } - bytes + fn div_ceil(x: usize, y: usize) -> usize { + let fun = || { + let num = x.checked_add(y)?.checked_sub(1)?; + num.checked_div(y) + }; + fun().unwrap_or_default() + } + assert!(O >= div_ceil(I, 3).wrapping_mul(4)); + let len = STANDARD.encode_slice(input, output).unwrap(); + from_utf8_opt(output.get(..len).unwrap_or_default()).unwrap() } diff --git a/wtx/src/web_socket/handshake/raw.rs b/wtx/src/web_socket/handshake/raw.rs index 0ca3da13..62733800 100644 --- a/wtx/src/web_socket/handshake/raw.rs +++ b/wtx/src/web_socket/handshake/raw.rs @@ -1,259 +1,252 @@ use crate::{ - misc::AsyncBounds, - web_socket::{ - handshake::{ - misc::{derived_key, gen_key, trim}, - HeadersBuffer, WebSocketAccept, WebSocketHandshake, - }, - FrameBufferVec, WebSocketClient, WebSocketError, WebSocketServer, + http_structs::{Header, ParseStatus, Request, Response}, + misc::_trim, + rng::Rng, + web_socket::{ + compression::NegotiatedCompression, + handshake::{ + misc::{derived_key, gen_key}, + HeadersBuffer, WebSocketAccept, WebSocketConnect, }, - ExpectedHeader, ReadBuffer, Stream, UriParts, + Compression, FrameBuffer, WebSocketClient, WebSocketError, WebSocketServer, + }, + ExpectedHeader, PartitionedBuffer, Stream, UriParts, }; -#[cfg(feature = "async-trait")] -use alloc::boxed::Box; -use core::borrow::BorrowMut; -use httparse::{Header, Status}; +use core::{borrow::BorrowMut, str}; const MAX_READ_HEADER_LEN: usize = 64; const MAX_READ_LEN: usize = 2 * 1024; /// Marker used to implement [WebSocketAccept]. #[derive(Debug)] -pub struct WebSocketAcceptRaw<'any, RB, S> { - /// Frame buffer - pub fb: &'any mut FrameBufferVec, - /// Headers buffer - pub headers_buffer: &'any mut HeadersBuffer<'any, 3>, - /// Key buffer - pub key_buffer: &'any mut [u8; 30], - /// Read buffer - pub rb: RB, - /// Stream - pub stream: S, +pub struct WebSocketAcceptRaw<'any, C, PB, RNG, S> { + /// Compression + pub compression: C, + /// Headers buffer + pub headers_buffer: &'any mut HeadersBuffer<'any, 3>, + /// Key buffer + pub key_buffer: &'any mut [u8; 30], + /// Partitioned buffer + pub pb: PB, + /// Random Number Generator + pub rng: RNG, + /// Stream + pub stream: S, } -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl<'any, RB, S> WebSocketAccept for WebSocketAcceptRaw<'any, RB, S> +impl<'any, C, PB, RNG, S> WebSocketAccept + for WebSocketAcceptRaw<'any, C, PB, RNG, S> where - RB: AsyncBounds + BorrowMut, - S: AsyncBounds + Stream, + C: Compression, + PB: BorrowMut, + RNG: Rng, + S: Stream, { - type Response = crate::Response<'any, 'any>; - type Stream = S; + type Response = Response<'any, 'any>; + type Stream = S; - #[inline] - async fn accept( - mut self, - ) -> crate::Result<(Self::Response, WebSocketServer)> { - self.fb.set_params_through_expansion(0, 0, MAX_READ_LEN); - let mut read = 0; - let (key, version) = loop { - let read_buffer = self.fb.payload_mut().get_mut(read..).unwrap_or_default(); - let local_read = self.stream.read(read_buffer).await?; - read = read.wrapping_add(local_read); - if read > MAX_READ_LEN { - return Err(crate::Error::VeryLargeHttp); - } - if local_read == 0 { - return Err(crate::Error::UnexpectedEOF); - } - let working_buffer = self.fb.payload().get(..read).unwrap_or_default(); - let mut req_buffer = [httparse::EMPTY_HEADER; MAX_READ_HEADER_LEN]; - let mut req = httparse::Request::new(&mut req_buffer); - match req.parse(working_buffer)? { - Status::Complete(_) => { - if !req - .method - .map_or(false, |el| trim(el.as_bytes()).eq_ignore_ascii_case(b"get")) - { - return Err(crate::Error::UnexpectedHttpMethod); - } - verify_common_header(req.headers)?; - if !has_header_key_and_value(req.headers, "sec-websocket-version", b"13") { - return Err(crate::Error::MissingHeader { - expected: ExpectedHeader::SecWebSocketVersion_13, - }); - }; - let Some(key) = req.headers.iter().find_map(|el| { - (el.name.eq_ignore_ascii_case("sec-websocket-key")).then_some(el.value) - }) else { - return Err(crate::Error::MissingHeader { - expected: ExpectedHeader::SecWebSocketKey, - }); - }; - break (key, req.version); - } - Status::Partial => {} - } - }; - self.headers_buffer.headers[0] = Header { - name: "Connection", - value: b"Upgrade", - }; - self.headers_buffer.headers[1] = Header { - name: "Sec-WebSocket-Accept", - value: derived_key(self.key_buffer, key).as_bytes(), - }; - self.headers_buffer.headers[2] = Header { - name: "Upgrade", - value: b"websocket", - }; - let mut httparse_res = httparse::Response::new(&mut self.headers_buffer.headers); - httparse_res.code = Some(101); - httparse_res.version = version; - let res = crate::Response::new(&[], httparse_res); - let res_bytes = build_101_res(self.fb, res.headers()); - self.stream.write_all(res_bytes).await?; - Ok((res, WebSocketServer::new(self.rb, self.stream))) + #[inline] + async fn accept( + mut self, + ) -> crate::Result<(Self::Response, WebSocketServer)> { + let pb = self.pb.borrow_mut(); + pb._set_indices_through_expansion(0, 0, MAX_READ_LEN); + let mut read = 0; + loop { + let read_buffer = pb._following_mut().get_mut(read..).unwrap_or_default(); + let local_read = self.stream.read(read_buffer).await?; + if local_read == 0 { + return Err(crate::Error::UnexpectedEOF); + } + read = read.wrapping_add(local_read); + let mut req_buffer = [Header::EMPTY; MAX_READ_HEADER_LEN]; + let mut req = Request::new(&mut req_buffer); + match req.parse(pb._following())? { + ParseStatus::Complete(_) => { + if !req.method().map_or(false, |el| _trim(el.as_bytes()).eq_ignore_ascii_case(b"get")) { + return Err(crate::Error::UnexpectedHttpMethod); + } + verify_common_header(req.headers())?; + if !has_header_key_and_value(req.headers(), "sec-websocket-version", b"13") { + return Err(crate::Error::MissingHeader { + expected: ExpectedHeader::SecWebSocketVersion_13, + }); + }; + let Some(key) = req.headers().iter().find_map(|el| { + (el.name().eq_ignore_ascii_case("sec-websocket-key")).then_some(el.value()) + }) else { + return Err(crate::Error::MissingHeader { expected: ExpectedHeader::SecWebSocketKey }); + }; + let compression = self.compression.negotiate(req.headers())?; + let swa = derived_key(self.key_buffer, key); + self.headers_buffer.headers[0] = Header::new("Connection", b"Upgrade"); + self.headers_buffer.headers[1] = Header::new("Sec-WebSocket-Accept", swa.as_bytes()); + self.headers_buffer.headers[2] = Header::new("Upgrade", b"websocket"); + let mut res = Response::new(&mut self.headers_buffer.headers); + *res.code_mut() = Some(101); + *res.version_mut() = req.version(); + let res_bytes = build_res(&compression, res.headers(), pb); + self.stream.write_all(res_bytes).await?; + pb.clear(); + return Ok((res, WebSocketServer::new(compression, self.pb, self.rng, self.stream))); + } + ParseStatus::Partial => {} + } } + } } -/// Marker used to implement [WebSocketHandshake]. +/// Marker used to implement [WebSocketConnect]. #[derive(Debug)] -pub struct WebSocketHandshakeRaw<'any, RB, S> { - /// Frame buffer - pub fb: &'any mut FrameBufferVec, - /// Headers buffer - pub headers_buffer: &'any mut HeadersBuffer<'any, MAX_READ_HEADER_LEN>, - /// Read buffer - pub rb: RB, - /// Stream - pub stream: S, - /// Uri - pub uri: &'any str, +pub struct WebSocketConnectRaw<'any, B, C, PB, RNG, S> { + /// Initial compression + pub compression: C, + /// Frame buffer + pub fb: &'any mut FrameBuffer, + /// Headers buffer + pub headers_buffer: &'any mut HeadersBuffer<'any, MAX_READ_HEADER_LEN>, + /// Partitioned Buffer + pub pb: PB, + /// Random Number Generator + pub rng: RNG, + /// Stream + pub stream: S, + /// Uri + pub uri: &'any str, } -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl<'any, RB, S> WebSocketHandshake for WebSocketHandshakeRaw<'any, RB, S> +impl<'any, B, C, PB, RNG, S> WebSocketConnect + for WebSocketConnectRaw<'any, B, C, PB, RNG, S> where - RB: AsyncBounds + BorrowMut, - S: AsyncBounds + Stream, + B: AsMut<[u8]> + AsMut> + AsRef<[u8]>, + C: Compression, + PB: BorrowMut, + RNG: Rng, + S: Stream, { - type Response = crate::Response<'any, 'any>; - type Stream = S; + type Response = Response<'any, 'any>; + type Stream = S; - #[inline] - async fn handshake( - mut self, - ) -> crate::Result<(Self::Response, WebSocketClient)> { - self.fb.set_params_through_expansion(0, 0, MAX_READ_LEN); - let key_buffer = &mut <_>::default(); - let (key, req) = build_upgrade_req(self.fb, key_buffer, self.uri); - self.stream.write_all(req).await?; - let mut read = 0; - let res_len = loop { - let read_buffer = self.fb.payload_mut().get_mut(read..).unwrap_or_default(); - let local_read = self.stream.read(read_buffer).await?; - read = read.wrapping_add(local_read); - if read > MAX_READ_LEN { - return Err(crate::Error::VeryLargeHttp); - } - if local_read == 0 { - return Err(crate::Error::UnexpectedEOF); - } - let mut headers = [httparse::EMPTY_HEADER; MAX_READ_HEADER_LEN]; - let mut httparse_res = httparse::Response::new(&mut headers); - match httparse_res.parse(self.fb.payload().get(..read).unwrap_or_default())? { - Status::Complete(el) => break el, - Status::Partial => {} - } - }; - let mut httparse_res = httparse::Response::new(&mut self.headers_buffer.headers); - let _rslt = httparse_res.parse(self.fb.payload().get(..res_len).unwrap_or_default())?; - let res = crate::Response::new(&[], httparse_res); - if res.code() != Some(101) { - return Err(WebSocketError::MissingSwitchingProtocols.into()); - } - verify_common_header(res.headers())?; - if !has_header_key_and_value( - res.headers(), - "sec-websocket-accept", - derived_key(&mut <_>::default(), key.as_bytes()).as_bytes(), - ) { - return Err(crate::Error::MissingHeader { - expected: crate::ExpectedHeader::SecWebSocketKey, - }); - } - let idx = read.wrapping_sub(res_len); - self.rb - .borrow_mut() - .set_indices_through_expansion(0, 0, idx); - self.rb - .borrow_mut() - .after_current_mut() - .get_mut(..idx) - .unwrap_or_default() - .copy_from_slice(self.fb.payload().get(res_len..read).unwrap_or_default()); - Ok((res, WebSocketClient::new(self.rb, self.stream))) + #[inline] + async fn connect( + mut self, + ) -> crate::Result<(Self::Response, WebSocketClient)> { + let key_buffer = &mut <_>::default(); + let pb = self.pb.borrow_mut(); + pb.clear(); + let (key, req) = build_req(&self.compression, key_buffer, pb, &mut self.rng, self.uri); + self.stream.write_all(req).await?; + let mut read = 0; + self.fb._set_indices_through_expansion(0, 0, MAX_READ_LEN); + let len = loop { + let mut local_header = [Header::EMPTY; MAX_READ_HEADER_LEN]; + let read_buffer = self.fb.payload_mut().get_mut(read..).unwrap_or_default(); + let local_read = self.stream.read(read_buffer).await?; + if local_read == 0 { + return Err(crate::Error::UnexpectedEOF); + } + read = read.wrapping_add(local_read); + match Response::new(&mut local_header).parse(self.fb.payload())? { + ParseStatus::Complete(len) => break len, + ParseStatus::Partial => {} + } + }; + let mut res = Response::new(&mut self.headers_buffer.headers); + let _status = res.parse(self.fb.payload())?; + if res.code() != Some(101) { + return Err(WebSocketError::MissingSwitchingProtocols.into()); + } + verify_common_header(res.headers())?; + if !has_header_key_and_value( + res.headers(), + "sec-websocket-accept", + derived_key(&mut <_>::default(), key.as_bytes()).as_bytes(), + ) { + return Err(crate::Error::MissingHeader { expected: crate::ExpectedHeader::SecWebSocketKey }); } + let compression = self.compression.negotiate(res.headers())?; + pb.borrow_mut()._set_indices_through_expansion(0, 0, read.wrapping_sub(len)); + pb._following_mut().copy_from_slice(self.fb.payload().get(len..read).unwrap_or_default()); + Ok((res, WebSocketClient::new(compression, self.pb, self.rng, self.stream))) + } } -fn build_upgrade_req<'fb, 'kb>( - fb: &'fb mut FrameBufferVec, - key_buffer: &'kb mut [u8; 26], - uri: &str, -) -> (&'kb str, &'fb [u8]) { - let uri_parts = UriParts::from(uri); - let key = gen_key(key_buffer); +/// Client request +fn build_req<'pb, 'kb, C>( + compression: &C, + key_buffer: &'kb mut [u8; 26], + pb: &'pb mut PartitionedBuffer, + rng: &mut impl Rng, + uri: &str, +) -> (&'kb str, &'pb [u8]) +where + C: Compression, +{ + let uri_parts = UriParts::from(uri); + let key = gen_key(key_buffer, rng); - let idx = fb.buffer().len(); - fb.buffer_mut().extend(b"GET "); - fb.buffer_mut().extend(uri_parts.href.as_bytes()); - fb.buffer_mut().extend(b" HTTP/1.1\r\n"); + let idx = pb._buffer().len(); + pb.extend(b"GET "); + pb.extend(uri_parts.href.as_bytes()); + pb.extend(b" HTTP/1.1\r\n"); - fb.buffer_mut().extend(b"Connection: Upgrade\r\n"); - fb.buffer_mut().extend(b"Host: "); - fb.buffer_mut().extend(uri_parts.host.as_bytes()); - fb.buffer_mut().extend(b"\r\n"); - fb.buffer_mut().extend(b"Sec-WebSocket-Key: "); - fb.buffer_mut().extend(key.as_bytes()); - fb.buffer_mut().extend(b"\r\n"); - fb.buffer_mut().extend(b"Sec-WebSocket-Version: 13\r\n"); - fb.buffer_mut().extend(b"Upgrade: websocket\r\n"); + pb.extend(b"Connection: Upgrade\r\n"); + pb.extend(b"Host: "); + pb.extend(uri_parts.host.as_bytes()); + pb.extend(b"\r\n"); + pb.extend(b"Sec-WebSocket-Key: "); + pb.extend(key.as_bytes()); + pb.extend(b"\r\n"); + pb.extend(b"Sec-WebSocket-Version: 13\r\n"); + pb.extend(b"Upgrade: websocket\r\n"); - fb.buffer_mut().extend(b"\r\n"); + compression.write_req_headers(pb); - (key, fb.buffer().get(idx..).unwrap_or_default()) + pb.extend(b"\r\n"); + + (key, pb._buffer().get(idx..).unwrap_or_default()) } -fn build_101_res<'fb>(fb: &'fb mut FrameBufferVec, headers: &[Header<'_>]) -> &'fb [u8] { - let idx = fb.buffer().len(); - fb.buffer_mut() - .extend(b"HTTP/1.1 101 Switching Protocols\r\n"); - for header in headers { - fb.buffer_mut().extend(header.name.as_bytes()); - fb.buffer_mut().extend(b": "); - fb.buffer_mut().extend(header.value); - fb.buffer_mut().extend(b"\r\n"); - } - fb.buffer_mut().extend(b"\r\n"); - fb.buffer().get(idx..).unwrap_or_default() +/// Server response +fn build_res<'pb, C>( + compression: &C, + headers: &[Header<'_>], + pb: &'pb mut PartitionedBuffer, +) -> &'pb [u8] +where + C: NegotiatedCompression, +{ + let idx = pb._buffer().len(); + pb.extend(b"HTTP/1.1 101 Switching Protocols\r\n"); + for header in headers { + pb.extend(header.name().as_bytes()); + pb.extend(b": "); + pb.extend(header.value()); + pb.extend(b"\r\n"); + } + compression.write_res_headers(pb); + pb.extend(b"\r\n"); + pb._buffer().get(idx..).unwrap_or_default() } -fn has_header_key_and_value(buffer: &[Header<'_>], key: &str, value: &[u8]) -> bool { - buffer - .iter() - .find_map(|h| { - let has_key = trim(h.name.as_bytes()).eq_ignore_ascii_case(key.as_bytes()); - let has_value = h - .value - .split(|el| el == &b',') - .any(|el| trim(el).eq_ignore_ascii_case(value)); - (has_key && has_value).then_some(true) - }) - .unwrap_or(false) +fn has_header_key_and_value(headers: &[Header<'_>], key: &str, value: &[u8]) -> bool { + headers + .iter() + .find_map(|h| { + let has_key = _trim(h.name().as_bytes()).eq_ignore_ascii_case(key.as_bytes()); + let has_value = + h.value().split(|el| el == &b',').any(|el| _trim(el).eq_ignore_ascii_case(value)); + (has_key && has_value).then_some(true) + }) + .unwrap_or(false) } fn verify_common_header(buffer: &[Header<'_>]) -> crate::Result<()> { - if !has_header_key_and_value(buffer, "connection", b"upgrade") { - return Err(crate::Error::MissingHeader { - expected: ExpectedHeader::Connection_Upgrade, - }); - } - if !has_header_key_and_value(buffer, "upgrade", b"websocket") { - return Err(crate::Error::MissingHeader { - expected: ExpectedHeader::Upgrade_WebSocket, - }); - } - Ok(()) + if !has_header_key_and_value(buffer, "connection", b"upgrade") { + return Err(crate::Error::MissingHeader { expected: ExpectedHeader::Connection_Upgrade }); + } + if !has_header_key_and_value(buffer, "upgrade", b"websocket") { + return Err(crate::Error::MissingHeader { expected: ExpectedHeader::Upgrade_WebSocket }); + } + Ok(()) } diff --git a/wtx/src/web_socket/handshake/tests.rs b/wtx/src/web_socket/handshake/tests.rs index 32eb38ee..ecda28ef 100644 --- a/wtx/src/web_socket/handshake/tests.rs +++ b/wtx/src/web_socket/handshake/tests.rs @@ -1,5 +1,5 @@ macro_rules! call_tests { - (($ty:ident, $fb:expr, $ws:expr), $($struct:ident),+ $(,)?) => { + (($ty:ident, $fb:expr, $ws:expr), $($struct:ident),* $(,)?) => { $( println!("***** {} - {}", stringify!($ty), stringify!($struct)); $struct::$ty($fb, $ws).await; @@ -8,329 +8,269 @@ macro_rules! call_tests { }; } -use crate::web_socket::{ +use crate::{ + misc::_tracing, + rng::StdRng, + web_socket::{ + compression::{Flate2, NegotiatedCompression}, frame::FrameMutVec, - handshake::{WebSocketAccept, WebSocketAcceptRaw, WebSocketHandshake, WebSocketHandshakeRaw}, - FrameBufferVec, OpCode, WebSocketClientOwned, WebSocketServerOwned, + handshake::{WebSocketAcceptRaw, WebSocketConnectRaw}, + Compression, FrameBufferVec, OpCode, WebSocket, WebSocketClientOwned, WebSocketServerOwned, + }, + PartitionedBuffer, }; use core::{ - sync::atomic::{AtomicBool, Ordering}, - time::Duration, + sync::atomic::{AtomicBool, Ordering}, + time::Duration, }; use tokio::net::{TcpListener, TcpStream}; +use tracing_subscriber::util::SubscriberInitExt; static HAS_SERVER_FINISHED: AtomicBool = AtomicBool::new(false); #[tokio::test] async fn client_and_server_frames() { - let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap(); + let _rslt = _tracing().unwrap().try_init(); + do_test_client_and_server_frames((), ()).await; + tokio::time::sleep(Duration::from_millis(200)).await; + do_test_client_and_server_frames((), Flate2::default()).await; + tokio::time::sleep(Duration::from_millis(200)).await; + do_test_client_and_server_frames(Flate2::default(), ()).await; + tokio::time::sleep(Duration::from_millis(200)).await; + do_test_client_and_server_frames(Flate2::default(), Flate2::default()).await; +} - let _server_jh = tokio::spawn(async move { - let (stream, _) = listener.accept().await.unwrap(); - let mut fb = <_>::default(); - let (_, mut ws) = WebSocketAcceptRaw { - fb: &mut fb, - headers_buffer: &mut <_>::default(), - rb: <_>::default(), - key_buffer: &mut <_>::default(), - stream, - } - .accept() - .await - .unwrap(); - call_tests!( - (server, &mut fb, &mut ws), - FragmentedMessage, - LargeFragmentedMessage, - PingAndText, - SeveralBytes, - TwoPings, - // Last - HelloAndGoodbye, - ); - HAS_SERVER_FINISHED.store(true, Ordering::Relaxed); - }); +async fn do_test_client_and_server_frames(client_compression: CC, server_compression: SC) +where + CC: Compression, + SC: Compression + Send + 'static, + SC::Negotiated: Send + Sync, +{ + let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap(); - let mut fb = <_>::default(); - let (_res, mut ws) = WebSocketHandshakeRaw { - fb: &mut fb, - headers_buffer: &mut <_>::default(), - rb: <_>::default(), - stream: TcpStream::connect("127.0.0.1:8080").await.unwrap(), - uri: "http://127.0.0.1:8080", - } - .handshake() + let _server_jh = tokio::spawn(async move { + let (stream, _) = listener.accept().await.unwrap(); + let mut fb = FrameBufferVec::with_capacity(0); + let (_, mut ws) = WebSocketServerOwned::accept(WebSocketAcceptRaw { + compression: server_compression, + headers_buffer: &mut <_>::default(), + key_buffer: &mut <_>::default(), + pb: PartitionedBuffer::with_capacity(0), + rng: StdRng::default(), + stream, + }) .await .unwrap(); call_tests!( - (client, &mut fb, &mut ws), - FragmentedMessage, - LargeFragmentedMessage, - PingAndText, - SeveralBytes, - TwoPings, - // Last - HelloAndGoodbye, + (server, &mut fb, &mut ws), + //FragmentedText, + //LargeFragmentedText, + //PingAndText, + //PingBetweenFragmentedText, + SeveralBytes, + TwoPings, + // Last, + HelloAndGoodbye, ); + HAS_SERVER_FINISHED.store(true, Ordering::Relaxed); + }); - let mut has_server_finished = false; - for _ in 0..15 { - let local_has_server_finished = HAS_SERVER_FINISHED.load(Ordering::Relaxed); - if local_has_server_finished { - has_server_finished = local_has_server_finished; - break; - } - tokio::time::sleep(Duration::from_millis(200)).await; - } - if !has_server_finished { - panic!("Server didn't finish"); + let mut fb = FrameBufferVec::with_capacity(0); + let (_, mut ws) = WebSocketClientOwned::connect(WebSocketConnectRaw { + compression: client_compression, + fb: &mut fb, + headers_buffer: &mut <_>::default(), + pb: PartitionedBuffer::with_capacity(0), + rng: StdRng::default(), + stream: TcpStream::connect("127.0.0.1:8080").await.unwrap(), + uri: "http://127.0.0.1:8080", + }) + .await + .unwrap(); + call_tests!( + (client, &mut fb, &mut ws), + //FragmentedText, + //LargeFragmentedText, + //PingAndText, + //PingBetweenFragmentedText, + SeveralBytes, + TwoPings, + // Last, + HelloAndGoodbye, + ); + + let mut has_server_finished = false; + for _ in 0..15 { + let local_has_server_finished = HAS_SERVER_FINISHED.load(Ordering::Relaxed); + if local_has_server_finished { + has_server_finished = local_has_server_finished; + break; } + tokio::time::sleep(Duration::from_millis(200)).await; + } + if !has_server_finished { + panic!("Server didn't finish"); + } } -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -trait Test { - async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned); +trait Test { + async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned); - async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned); + async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned); } -struct FragmentedMessage; -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl Test for FragmentedMessage { - async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { - ws.write_frame(&mut FrameMutVec::new_unfin(fb, OpCode::Text, b"1").unwrap()) - .await - .unwrap(); - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Continuation, b"23").unwrap()) - .await - .unwrap(); - } +struct FragmentedText; +impl Test for FragmentedText +where + NC: NegotiatedCompression, +{ + async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { + write(FrameMutVec::new_unfin(fb, OpCode::Text, b"1").unwrap(), ws).await; + write(FrameMutVec::new_fin(fb, OpCode::Continuation, b"23").unwrap(), ws).await; + } - async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { - let text = ws.read_msg(fb).await.unwrap(); - assert_eq!(OpCode::Text, text.op_code()); - assert_eq!(b"123", text.fb().payload()); - } + async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { + let text = ws.read_frame(fb).await.unwrap(); + assert_eq!(OpCode::Text, text.op_code()); + assert_eq!(b"123", text.fb().payload()); + } } struct HelloAndGoodbye; -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl Test for HelloAndGoodbye { - async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { - let hello = ws.read_frame(fb).await.unwrap(); - assert_eq!(OpCode::Text, hello.op_code()); - assert_eq!(b"Hello!", hello.fb().payload()); - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Text, b"Goodbye!").unwrap()) - .await - .unwrap(); - assert_eq!(OpCode::Close, ws.read_frame(fb).await.unwrap().op_code()); - } +impl Test for HelloAndGoodbye +where + NC: NegotiatedCompression, +{ + async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { + let hello = ws.read_frame(fb).await.unwrap(); + assert_eq!(OpCode::Text, hello.op_code()); + assert_eq!(b"Hello!", hello.fb().payload()); + write(FrameMutVec::new_fin(fb, OpCode::Text, b"Goodbye!").unwrap(), ws).await; + assert_eq!(OpCode::Close, ws.read_frame(fb).await.unwrap().op_code()); + } - async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Text, b"Hello!").unwrap()) - .await - .unwrap(); - assert_eq!( - ws.read_frame(&mut *fb).await.unwrap().fb().payload(), - b"Goodbye!" - ); - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Close, &[]).unwrap()) - .await - .unwrap(); - } + async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { + write(FrameMutVec::new_fin(fb, OpCode::Text, b"Hello!").unwrap(), ws).await; + assert_eq!(ws.read_frame(&mut *fb).await.unwrap().fb().payload(), b"Goodbye!"); + write(FrameMutVec::new_fin(fb, OpCode::Close, &[]).unwrap(), ws).await; + } } -struct LargeFragmentedMessage; -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl Test for LargeFragmentedMessage { - async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { - async fn write( - frame: &mut FrameMutVec<'_, true>, - ws: &mut WebSocketClientOwned, - ) { - ws.write_frame(frame).await.unwrap(); - } - let bytes = vec![51; 256 * 1024]; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Text, &bytes).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_fin(fb, OpCode::Continuation, &bytes).unwrap(), - ws, - ) - .await; - } +struct LargeFragmentedText; +impl Test for LargeFragmentedText +where + NC: NegotiatedCompression, +{ + async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { + let bytes = vec![51; 256 * 1024]; + write(FrameMutVec::new_unfin(fb, OpCode::Text, &bytes).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &bytes).unwrap(), ws).await; + write(FrameMutVec::new_fin(fb, OpCode::Continuation, &bytes).unwrap(), ws).await; + } - async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { - let text = ws.read_msg(fb).await.unwrap(); - assert_eq!(OpCode::Text, text.op_code()); - assert_eq!(&vec![51; 10 * 256 * 1024], text.fb().payload()); - } + async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { + let text = ws.read_frame(fb).await.unwrap(); + assert_eq!(OpCode::Text, text.op_code()); + assert_eq!(&vec![51; 10 * 256 * 1024], text.fb().payload()); + } } struct PingAndText; -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl Test for PingAndText { - async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Ping, b"").unwrap()) - .await - .unwrap(); - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Text, b"ipat").unwrap()) - .await - .unwrap(); - assert_eq!(OpCode::Pong, ws.read_frame(fb).await.unwrap().op_code()); - } +impl Test for PingAndText +where + NC: NegotiatedCompression, +{ + async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { + write(FrameMutVec::new_fin(fb, OpCode::Ping, b"123").unwrap(), ws).await; + write(FrameMutVec::new_fin(fb, OpCode::Text, b"ipat").unwrap(), ws).await; + assert_eq!(OpCode::Pong, ws.read_frame(fb).await.unwrap().op_code()); + } - async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { - assert_eq!(b"ipat", ws.read_frame(fb).await.unwrap().fb().payload()); - } + async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { + assert_eq!(b"ipat", ws.read_frame(fb).await.unwrap().fb().payload()); + } +} + +struct PingBetweenFragmentedText; +impl Test for PingBetweenFragmentedText +where + NC: NegotiatedCompression, +{ + async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { + write(FrameMutVec::new_unfin(fb, OpCode::Text, b"1").unwrap(), ws).await; + write(FrameMutVec::new_fin(fb, OpCode::Ping, b"9").unwrap(), ws).await; + write(FrameMutVec::new_fin(fb, OpCode::Continuation, b"23").unwrap(), ws).await; + assert_eq!(OpCode::Pong, ws.read_frame(fb).await.unwrap().op_code()); + } + + async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { + assert_eq!(OpCode::Text, ws.read_frame(fb).await.unwrap().op_code()); + } } struct SeveralBytes; -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl Test for SeveralBytes { - async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { - async fn write( - frame: &mut FrameMutVec<'_, true>, - ws: &mut WebSocketClientOwned, - ) { - ws.write_frame(frame).await.unwrap(); - } - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Text, &[206]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &[186]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &[225]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &[189]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &[185]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &[207]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &[131]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &[206]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &[188]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &[206]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_unfin(fb, OpCode::Continuation, &[181]).unwrap(), - ws, - ) - .await; - write( - &mut FrameMutVec::new_fin(fb, OpCode::Continuation, &[]).unwrap(), - ws, - ) - .await; - } +impl Test for SeveralBytes +where + NC: NegotiatedCompression, +{ + async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { + write(FrameMutVec::new_unfin(fb, OpCode::Text, &[206]).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &[186]).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &[225]).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &[189]).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &[185]).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &[207]).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &[131]).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &[206]).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &[188]).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &[206]).unwrap(), ws).await; + write(FrameMutVec::new_unfin(fb, OpCode::Continuation, &[181]).unwrap(), ws).await; + write(FrameMutVec::new_fin(fb, OpCode::Continuation, &[]).unwrap(), ws).await; + } - async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { - let text = ws.read_msg(fb).await.unwrap(); - assert_eq!(OpCode::Text, text.op_code()); - assert_eq!("κόσμε".as_bytes(), text.fb().payload()); - } + async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { + let text = ws.read_frame(fb).await.unwrap(); + assert_eq!(OpCode::Text, text.op_code()); + assert_eq!("κόσμε".as_bytes(), text.fb().payload()); + } } struct TwoPings; -#[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl Test for TwoPings { - async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Ping, b"0").unwrap()) - .await - .unwrap(); - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Ping, b"1").unwrap()) - .await - .unwrap(); - let _0 = ws.read_frame(fb).await.unwrap(); - assert_eq!(OpCode::Pong, _0.op_code()); - assert_eq!(b"0", _0.fb().payload()); - let _1 = ws.read_frame(fb).await.unwrap(); - assert_eq!(OpCode::Pong, _1.op_code()); - assert_eq!(b"1", _1.fb().payload()); - ws.write_frame(&mut FrameMutVec::new_fin(fb, OpCode::Text, b"").unwrap()) - .await - .unwrap(); - } +impl Test for TwoPings +where + NC: NegotiatedCompression, +{ + async fn client(fb: &mut FrameBufferVec, ws: &mut WebSocketClientOwned) { + write(FrameMutVec::new_fin(fb, OpCode::Ping, b"0").unwrap(), ws).await; + write(FrameMutVec::new_fin(fb, OpCode::Ping, b"1").unwrap(), ws).await; + let _0 = ws.read_frame(fb).await.unwrap(); + assert_eq!(OpCode::Pong, _0.op_code()); + assert_eq!(b"0", _0.fb().payload()); + let _1 = ws.read_frame(fb).await.unwrap(); + assert_eq!(OpCode::Pong, _1.op_code()); + assert_eq!(b"1", _1.fb().payload()); + write(FrameMutVec::new_fin(fb, OpCode::Text, b"").unwrap(), ws).await; + } - async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { - let _0 = ws.read_frame(fb).await.unwrap(); - assert_eq!(OpCode::Text, _0.op_code()); - assert_eq!(b"", _0.fb().payload()); - } + async fn server(fb: &mut FrameBufferVec, ws: &mut WebSocketServerOwned) { + let _0 = ws.read_frame(fb).await.unwrap(); + assert_eq!(OpCode::Text, _0.op_code()); + assert_eq!(b"", _0.fb().payload()); + } +} + +async fn write( + mut frame: FrameMutVec<'_, IS_CLIENT>, + ws: &mut WebSocket, +) where + NC: NegotiatedCompression, +{ + ws.write_frame(&mut frame).await.unwrap(); } diff --git a/wtx/src/web_socket/mask.rs b/wtx/src/web_socket/mask.rs index f6d18743..ee210d77 100644 --- a/wtx/src/web_socket/mask.rs +++ b/wtx/src/web_socket/mask.rs @@ -1,22 +1,22 @@ /// Unmasks a sequence of bytes using the given 4-byte `mask`. #[inline] pub fn unmask(bytes: &mut [u8], mask: [u8; 4]) { - let mut mask_u32 = u32::from_ne_bytes(mask); - #[allow(unsafe_code)] - // SAFETY: Changing a sequence of `u8` to `u32` should be fine - let (prefix, words, suffix) = unsafe { bytes.align_to_mut::() }; - unmask_u8_slice(prefix, mask); - let mut shift = u32::try_from(prefix.len() & 3).unwrap_or_default(); - if shift > 0 { - shift = shift.wrapping_mul(8); - if cfg!(target_endian = "big") { - mask_u32 = mask_u32.rotate_left(shift); - } else { - mask_u32 = mask_u32.rotate_right(shift); - } + let mut mask_u32 = u32::from_ne_bytes(mask); + #[allow(unsafe_code)] + // SAFETY: Changing a sequence of `u8` to `u32` should be fine + let (prefix, words, suffix) = unsafe { bytes.align_to_mut::() }; + unmask_u8_slice(prefix, mask); + let mut shift = u32::try_from(prefix.len() & 3).unwrap_or_default(); + if shift > 0 { + shift = shift.wrapping_mul(8); + if cfg!(target_endian = "big") { + mask_u32 = mask_u32.rotate_left(shift); + } else { + mask_u32 = mask_u32.rotate_right(shift); } - unmask_u32_slice(words, mask_u32); - unmask_u8_slice(suffix, mask_u32.to_ne_bytes()); + } + unmask_u32_slice(words, mask_u32); + unmask_u8_slice(suffix, mask_u32.to_ne_bytes()); } #[allow( @@ -24,77 +24,54 @@ pub fn unmask(bytes: &mut [u8], mask: [u8; 4]) { clippy::indexing_slicing )] fn unmask_u8_slice(bytes: &mut [u8], mask: [u8; 4]) { - for (idx, elem) in bytes.iter_mut().enumerate() { - *elem ^= mask[idx & 3]; - } + for (idx, elem) in bytes.iter_mut().enumerate() { + *elem ^= mask[idx & 3]; + } } fn unmask_u32_slice(bytes: &mut [u32], mask: u32) { - macro_rules! loop_chunks { + macro_rules! loop_chunks { ($bytes:expr, $mask:expr, $($elem:ident),* $(,)?) => {{ - let mut iter; - #[cfg(feature = "async-trait")] - { - iter = $bytes.chunks_exact_mut(0 $( + { let $elem = 1; $elem })*); - while let Some([$($elem,)*]) = iter.next() { - $( *$elem ^= $mask; )* - } - } - #[cfg(not(feature = "async-trait"))] - { - iter = $bytes.array_chunks_mut::<{ 0 $( + { let $elem = 1; $elem })* }>(); - for [$($elem,)*] in iter.by_ref() { - $( *$elem ^= $mask; )* - } + let mut iter = $bytes.array_chunks_mut::<{ 0 $( + { let $elem = 1; $elem })* }>(); + for [$($elem,)*] in iter.by_ref() { + $( *$elem ^= $mask; )* } iter }}; } - loop_chunks!(bytes, mask, _1, _2, _3, _4) - .into_remainder() - .iter_mut() - .for_each(|elem| *elem ^= mask); + loop_chunks!(bytes, mask, _1, _2, _3, _4) + .into_remainder() + .iter_mut() + .for_each(|elem| *elem ^= mask); } #[cfg(test)] mod tests { - use crate::{misc::Rng, web_socket::mask::unmask}; - use alloc::{vec, vec::Vec}; - - #[test] - fn test_unmask() { - let mut payload = [0u8; 33]; - let mask = [1, 2, 3, 4]; - unmask(&mut payload, mask); - assert_eq!( - &payload, - &[ - 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, - 1, 2, 3, 4, 1 - ] - ); - } - - #[test] - fn length_variation_unmask() { - for len in &[0, 2, 3, 8, 16, 18, 31, 32, 40] { - let mut payload = vec![0u8; *len]; - let mask = [1, 2, 3, 4]; - unmask(&mut payload, mask); + use crate::web_socket::mask::unmask; + use alloc::{vec, vec::Vec}; - let expected = (0..*len).map(|i| (i & 3) as u8 + 1).collect::>(); - assert_eq!(payload, expected); - } - } + #[test] + fn test_unmask() { + let mut payload = [0u8; 33]; + let mask = [1, 2, 3, 4]; + unmask(&mut payload, mask); + assert_eq!( + &payload, + &[ + 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, + 3, 4, 1 + ] + ); + } - #[test] - fn length_variation_unmask_2() { - for len in &[0, 2, 3, 8, 16, 18, 31, 32, 40] { - let mut payload = vec![0u8; *len]; - let mask = Rng::default().random_u8_4(); - unmask(&mut payload, mask); - let expected = (0..*len).map(|i| mask[i & 3]).collect::>(); - assert_eq!(payload, expected); - } + #[test] + fn length_variation_unmask() { + for len in &[0, 2, 3, 8, 16, 18, 31, 32, 40] { + let mut payload = vec![0u8; *len]; + let mask = [1, 2, 3, 4]; + unmask(&mut payload, mask); + let expected = (0..*len).map(|i| (i & 3) as u8 + 1).collect::>(); + assert_eq!(payload, expected); } + } } diff --git a/wtx/src/web_socket/misc.rs b/wtx/src/web_socket/misc.rs new file mode 100644 index 00000000..8da58dd6 --- /dev/null +++ b/wtx/src/web_socket/misc.rs @@ -0,0 +1,119 @@ +use crate::web_socket::{FrameBuffer, OpCode, WebSocketError, MAX_HDR_LEN_U8}; + +pub(crate) fn define_fb_from_header_params( + fb: &mut FrameBuffer, + fin: bool, + header_buffer_len: Option, + op_code: OpCode, + payload_len: usize, + rsv1: u8, +) -> crate::Result<()> +where + B: AsMut<[u8]> + AsRef<[u8]>, +{ + let new_header_len = header_len_from_payload_len::(payload_len); + let (buffer, header_begin_idx) = if let Some(el) = header_buffer_len { + let header_begin_idx = el.saturating_sub(new_header_len); + let buffer = fb.buffer_mut().as_mut().get_mut(header_begin_idx.into()..).unwrap_or_default(); + (buffer, header_begin_idx) + } else { + (fb.buffer_mut().as_mut(), 0) + }; + copy_header_params_to_buffer::(buffer, fin, op_code, payload_len, rsv1)?; + fb.set_indices(header_begin_idx, new_header_len, payload_len)?; + Ok(()) +} + +pub(crate) const fn header_placeholder() -> u8 { + if IS_CLIENT { + MAX_HDR_LEN_U8 + } else { + MAX_HDR_LEN_U8 - 4 + } +} + +pub(crate) fn op_code(first_header_byte: u8) -> crate::Result { + OpCode::try_from(first_header_byte & 0b0000_1111) +} + +fn copy_header_params_to_buffer( + buffer: &mut [u8], + fin: bool, + op_code: OpCode, + payload_len: usize, + rsv1: u8, +) -> crate::Result { + fn first_header_byte(fin: bool, op_code: OpCode, rsv1: u8) -> u8 { + u8::from(fin) << 7 | rsv1 | u8::from(op_code) + } + + fn manage_mask( + rest: &mut [u8], + second_byte: &mut u8, + ) -> crate::Result { + Ok(if IS_CLIENT { + *second_byte &= 0b0111_1111; + let [a, b, c, d, ..] = rest else { + return Err(WebSocketError::InvalidFrameHeaderBounds.into()); + }; + *a = 0; + *b = 0; + *c = 0; + *d = 0; + N.wrapping_add(4) + } else { + N + }) + } + + match payload_len { + 0..=125 => { + if let ([a, b, rest @ ..], Ok(u8_len)) = (buffer, u8::try_from(payload_len)) { + *a = first_header_byte(fin, op_code, rsv1); + *b = u8_len; + return manage_mask::(rest, b); + } + } + 126..=0xFFFF => { + let rslt = u16::try_from(payload_len).map(u16::to_be_bytes); + if let ([a, b, c, d, rest @ ..], Ok([len_c, len_d])) = (buffer, rslt) { + *a = first_header_byte(fin, op_code, rsv1); + *b = 126; + *c = len_c; + *d = len_d; + return manage_mask::(rest, b); + } + } + _ => { + if let ( + [a, b, c, d, e, f, g, h, i, j, rest @ ..], + Ok([len_c, len_d, len_e, len_f, len_g, len_h, len_i, len_j]), + ) = (buffer, u64::try_from(payload_len).map(u64::to_be_bytes)) + { + *a = first_header_byte(fin, op_code, rsv1); + *b = 127; + *c = len_c; + *d = len_d; + *e = len_e; + *f = len_f; + *g = len_g; + *h = len_h; + *i = len_i; + *j = len_j; + return manage_mask::(rest, b); + } + } + } + + Err(WebSocketError::InvalidFrameHeaderBounds.into()) +} + +fn header_len_from_payload_len(payload_len: usize) -> u8 { + let mask_len = if IS_CLIENT { 4 } else { 0 }; + let n: u8 = match payload_len { + 0..=125 => 2, + 126..=0xFFFF => 4, + _ => 10, + }; + n.wrapping_add(mask_len) +} diff --git a/wtx/src/web_socket/op_code.rs b/wtx/src/web_socket/op_code.rs index 0bca667e..cb01de1f 100644 --- a/wtx/src/web_socket/op_code.rs +++ b/wtx/src/web_socket/op_code.rs @@ -1,37 +1,6 @@ -macro_rules! create_enum { - ($(#[$meta:meta])* $vis:vis enum $name:ident { - $($(#[$variant_meta:meta])* $variant_ident:ident = $variant_value:expr,)* - }) => { - $(#[$meta])* - $vis enum $name { - $($(#[$variant_meta])* $variant_ident = $variant_value,)* - } - - impl From<$name> for u8 { - #[inline] - fn from(from: $name) -> Self { - match from { - $($name::$variant_ident => $variant_value,)* - } - } - } - - impl TryFrom for $name { - type Error = crate::Error; - - #[inline] - fn try_from(from: u8) -> Result { - match from { - $(x if x == u8::from($name::$variant_ident) => Ok($name::$variant_ident),)* - _ => Err(crate::web_socket::WebSocketError::InvalidOpCodeByte { provided: from }.into()), - } - } - } - } -} - create_enum! { /// Defines how to interpret the payload data. + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum OpCode { @@ -51,18 +20,13 @@ create_enum! { } impl OpCode { - #[inline] - pub(crate) fn is_continuation(self) -> bool { - matches!(self, OpCode::Continuation) - } - - #[inline] - pub(crate) fn is_control(self) -> bool { - matches!(self, OpCode::Close | OpCode::Ping | OpCode::Pong) - } + #[inline] + pub(crate) fn is_control(self) -> bool { + matches!(self, OpCode::Close | OpCode::Ping | OpCode::Pong) + } - #[inline] - pub(crate) fn is_text(self) -> bool { - matches!(self, OpCode::Text) - } + #[inline] + pub(crate) fn is_text(self) -> bool { + matches!(self, OpCode::Text) + } } diff --git a/wtx/src/web_socket/web_socket_error.rs b/wtx/src/web_socket/web_socket_error.rs index 8feba292..2607949b 100644 --- a/wtx/src/web_socket/web_socket_error.rs +++ b/wtx/src/web_socket/web_socket_error.rs @@ -1,35 +1,41 @@ /// Errors related to the WebSocket module #[derive(Debug)] pub enum WebSocketError { - /// Received close frame has invalid parameters. - InvalidCloseFrame, - /// Header indices are out-of-bounds or the number of bytes are too small. - InvalidFrameHeaderBounds, - /// No op code can be represented with the provided byte. - InvalidOpCodeByte { - /// Provided byte - provided: u8, - }, - /// Payload indices are out-of-bounds or the number of bytes are too small. - InvalidPayloadBounds, + /// Received close frame has invalid parameters. + InvalidCloseFrame, + /// Received an invalid header compression parameter. + InvalidCompressionHeaderParameter, + /// Header indices are out-of-bounds or the number of bytes are too small. + InvalidFrameHeaderBounds, + /// No element can be represented with the provided byte. + InvalidFromByte { + /// Provided byte + provided: u8, + }, + /// Payload indices are out-of-bounds or the number of bytes are too small. + InvalidPayloadBounds, - /// Server received a frame without a mask. - MissingFrameMask, - /// Status code is expected to be - MissingSwitchingProtocols, + /// Server received a frame without a mask. + MissingFrameMask, + /// Client sent "permessage-deflate" but didn't receive back from the server + MissingPermessageDeflate, + /// Status code is expected to be + MissingSwitchingProtocols, - /// Received control frame wasn't supposed to be fragmented. - UnexpectedFragmentedControlFrame, - /// The first frame of a message is a continuation or the following frames are not a - /// continuation. - UnexpectedMessageFrame, + /// Received control frame wasn't supposed to be fragmented. + UnexpectedFragmentedControlFrame, + /// The first frame of a message is a continuation or the following frames are not a + /// continuation. + UnexpectedMessageFrame, - /// It it not possible to read a frame of a connection that was previously closed. - ConnectionClosed, - /// Reserved bits are not zero. - ReservedBitsAreNotZero, - /// Control frames have a maximum allowed size. - VeryLargeControlFrame, - /// Frame payload exceeds the defined threshold. - VeryLargePayload, + /// It it not possible to read a frame of a connection that was previously closed. + ConnectionClosed, + /// Server responded without a compression context but the client does not allow such behavior. + NoCompressionContext, + /// Reserved bits are not zero. + ReservedBitsAreNotZero, + /// Control frames have a maximum allowed size. + VeryLargeControlFrame, + /// Frame payload exceeds the defined threshold. + VeryLargePayload, }