From 6d65cb2e4fcadd17fea065a46be5bfe8b320be91 Mon Sep 17 00:00:00 2001 From: Caio Date: Sat, 10 Feb 2024 20:52:24 -0300 Subject: [PATCH] Improvements --- .cargo/config.toml | 2 + .github/workflows/bencher.yaml | 24 + .github/workflows/deploy-docs.yml | 36 ++ .github/workflows/{ci.yaml => tests.yaml} | 2 +- .gitignore | 4 +- .scripts/autobahn-fuzzingclient.sh | 4 +- .scripts/autobahn-fuzzingserver.sh | 4 +- .scripts/internal-tests.sh | 3 +- .scripts/wtx-bench-postgres.sh | 2 +- .scripts/wtx-bench-web-socket.sh | 43 +- Cargo.lock | 328 +++++++++----- Cargo.toml | 10 +- README.md | 48 +- clippy.toml | 11 +- wtx-bench/Cargo.toml | 2 +- wtx-bench/README.md | 2 +- wtx-bench/src/web_socket.rs | 2 +- wtx-docs/book.toml | 6 + wtx-docs/src/README.md | 1 + wtx-docs/src/SUMMARY.md | 10 + wtx-docs/src/client-api-framework/README.md | 13 + wtx-docs/src/database/README.md | 3 + wtx-docs/src/database/client-connection.md | 26 ++ .../object\342\200\223relational-mapping.md" | 64 +++ wtx-docs/src/database/schema-management.md | 192 ++++++++ wtx-docs/src/pool_manager/README.md | 43 ++ wtx-docs/src/web-socket/README.md | 51 +++ wtx-ui/src/web_socket.rs | 2 +- wtx/Cargo.toml | 10 +- .../web-socket-client-cli-raw-tokio-rustls.rs | 2 +- .../web-socket-server-echo-raw-tokio.rs | 2 +- wtx/src/bin/autobahn-client.rs | 6 +- wtx/src/client_api_framework/misc.rs | 2 +- .../network/http/status_code.rs | 1 - .../client/postgres/authentication.rs | 8 +- wtx/src/database/client/postgres/config.rs | 12 +- wtx/src/database/client/postgres/db_error.rs | 109 +++-- .../postgres/executor/authentication.rs | 4 +- .../client/postgres/executor/prepare.rs | 9 +- wtx/src/database/client/postgres/field.rs | 13 +- wtx/src/database/client/postgres/message.rs | 9 +- wtx/src/database/client/postgres/record.rs | 27 +- .../database/client/postgres/statements.rs | 38 +- wtx/src/database/client/postgres/tys.rs | 12 +- wtx/src/database/orm/misc.rs | 3 - wtx/src/database/orm/table_params.rs | 10 +- wtx/src/database/record.rs | 7 +- .../schema_manager/migration/db_migration.rs | 4 +- .../schema_manager/migration_parser.rs | 15 +- wtx/src/database/schema_manager/misc.rs | 4 +- .../database/schema_manager/toml_parser.rs | 5 +- wtx/src/error.rs | 44 +- wtx/src/http.rs | 10 +- wtx/src/http/abstract_headers.rs | 419 ++++++++++++++++++ wtx/src/http/{header.rs => generic_header.rs} | 10 +- wtx/src/http/header_name.rs | 221 +++++++++ wtx/src/http/headers.rs | 91 ++++ wtx/src/http/status_code.rs | 1 - wtx/src/http/version.rs | 1 - wtx/src/http1.rs | 28 +- wtx/src/macros.rs | 37 ++ wtx/src/misc.rs | 121 +++-- wtx/src/misc/array_chunks.rs | 12 +- wtx/src/misc/basic_utf8_error.rs | 10 - wtx/src/misc/either.rs | 56 +++ wtx/src/misc/filled_buffer_writer.rs | 25 +- wtx/src/{database/orm => }/misc/fx_hasher.rs | 12 +- .../misc/incomplete_utf8_char.rs | 56 ++- wtx/src/misc/mem_transfer.rs | 203 +++++++++ wtx/src/misc/optimization.rs | 159 +++++++ wtx/src/misc/partitioned_filled_buffer.rs | 25 +- wtx/src/misc/poll_once.rs | 2 +- wtx/src/misc/stream.rs | 27 +- wtx/src/misc/tokio_rustls.rs | 5 +- wtx/src/misc/uri.rs | 32 +- wtx/src/misc/usize.rs | 75 ++++ wtx/src/misc/utf8_errors.rs | 33 ++ wtx/src/pool_manager/static_pool.rs | 24 +- wtx/src/web_socket.rs | 14 +- wtx/src/web_socket/compression.rs | 6 +- .../compression/compression_level.rs | 1 - wtx/src/web_socket/compression/flate2.rs | 25 +- wtx/src/web_socket/compression/window_bits.rs | 1 - wtx/src/web_socket/frame_buffer.rs | 17 +- wtx/src/web_socket/handshake.rs | 3 +- wtx/src/web_socket/handshake/raw.rs | 18 +- wtx/src/web_socket/handshake/tests.rs | 2 +- wtx/src/web_socket/misc.rs | 34 -- wtx/src/web_socket/misc/filled_buffer.rs | 10 +- wtx/src/web_socket/misc/utf8_errors.rs | 13 - wtx/src/web_socket/op_code.rs | 1 - wtx/src/web_socket/unmask.rs | 48 +- 92 files changed, 2580 insertions(+), 602 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 .github/workflows/bencher.yaml create mode 100644 .github/workflows/deploy-docs.yml rename .github/workflows/{ci.yaml => tests.yaml} (99%) create mode 100644 wtx-docs/book.toml create mode 120000 wtx-docs/src/README.md create mode 100644 wtx-docs/src/SUMMARY.md create mode 100644 wtx-docs/src/client-api-framework/README.md create mode 100644 wtx-docs/src/database/README.md create mode 100644 wtx-docs/src/database/client-connection.md create mode 100644 "wtx-docs/src/database/object\342\200\223relational-mapping.md" create mode 100644 wtx-docs/src/database/schema-management.md create mode 100644 wtx-docs/src/pool_manager/README.md create mode 100644 wtx-docs/src/web-socket/README.md create mode 100644 wtx/src/http/abstract_headers.rs rename wtx/src/http/{header.rs => generic_header.rs} (77%) create mode 100644 wtx/src/http/header_name.rs create mode 100644 wtx/src/http/headers.rs delete mode 100644 wtx/src/misc/basic_utf8_error.rs rename wtx/src/{database/orm => }/misc/fx_hasher.rs (50%) rename wtx/src/{web_socket => }/misc/incomplete_utf8_char.rs (61%) create mode 100644 wtx/src/misc/mem_transfer.rs create mode 100644 wtx/src/misc/optimization.rs create mode 100644 wtx/src/misc/usize.rs create mode 100644 wtx/src/misc/utf8_errors.rs delete mode 100644 wtx/src/web_socket/misc/utf8_errors.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..f599ec91 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.x86_64-unknown-linux-gnu] +rustflags = ["-C", "linker=clang", "-C", "link-arg=-fuse-ld=lld"] \ No newline at end of file diff --git a/.github/workflows/bencher.yaml b/.github/workflows/bencher.yaml new file mode 100644 index 00000000..fe9d952e --- /dev/null +++ b/.github/workflows/bencher.yaml @@ -0,0 +1,24 @@ +name: Bencher +on: + push: + branches: + - main + +jobs: + benchmark_with_bencher: + name: Continuous Benchmarking with Bencher + runs-on: ubuntu-latest + env: + BENCHER_ADAPTER: rust_bench + BENCHER_PROJECT: wtx + BENCHER_TESTBED: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: bencherdev/bencher@main + - name: Track Benchmarks with Bencher + run: | + bencher run \ + --branch "$GITHUB_REF_NAME" \ + --err \ + --token "${{ secrets.BENCHER_API_TOKEN }}" \ + "cargo bench --all-features" \ No newline at end of file diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 00000000..1bbd86a2 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,36 @@ +name: Deploy docs +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: write + pages: write + id-token: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install latest mdbook + run: | + tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name') + url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz" + mkdir mdbook + curl -sSL $url | tar -xz --directory=./mdbook + echo `pwd`/mdbook >> $GITHUB_PATH + - name: Test and Build Book + run: | + cd wtx-docs && mdbook test && mdbook build + - name: Setup Pages + uses: actions/configure-pages@v2 + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: 'wtx-docs/book' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/tests.yaml similarity index 99% rename from .github/workflows/ci.yaml rename to .github/workflows/tests.yaml index ae421cbf..3c16f39c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/tests.yaml @@ -1,4 +1,4 @@ -name: CI +name: Tests on: pull_request: push: diff --git a/.gitignore b/.gitignore index 2aef59e4..5ca8e2d5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ **/artifacts **/corpus **/target -**/target \ No newline at end of file +**/target +profile.json +wtx-docs/book \ No newline at end of file diff --git a/.scripts/autobahn-fuzzingclient.sh b/.scripts/autobahn-fuzzingclient.sh index 5ec4c898..91913ec8 100755 --- a/.scripts/autobahn-fuzzingclient.sh +++ b/.scripts/autobahn-fuzzingclient.sh @@ -7,8 +7,8 @@ fi; ## fuzzingclient -cargo build --bin autobahn-server --features atoi,flate2,simdutf8,tokio,web-socket-handshake --release -cargo run --bin autobahn-server --features atoi,flate2,simdutf8,tokio,web-socket-handshake --release & cargo_pid=$! +cargo build --bin autobahn-server --features atoi,flate2,simdutf8,tokio,web-socket-handshake --profile bench +cargo run --bin autobahn-server --features atoi,flate2,simdutf8,tokio,web-socket-handshake --profile bench & cargo_pid=$! mkdir -p .scripts/autobahn/reports/fuzzingclient podman run \ -p 9070:9070 \ diff --git a/.scripts/autobahn-fuzzingserver.sh b/.scripts/autobahn-fuzzingserver.sh index ee331eb3..8c376941 100755 --- a/.scripts/autobahn-fuzzingserver.sh +++ b/.scripts/autobahn-fuzzingserver.sh @@ -5,7 +5,7 @@ if [ "$ARG" != "ci" ]; then trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT fi; -cargo build --bin autobahn-client --features atoi,flate2,simdutf8,tokio,web-socket-handshake --release +cargo build --bin autobahn-client --features atoi,flate2,simdutf8,tokio,web-socket-handshake --profile bench mkdir -p .scripts/autobahn/reports/fuzzingserver podman run \ -d \ @@ -16,7 +16,7 @@ podman run \ --net=host \ docker.io/crossbario/autobahn-testsuite:0.8.2 wstest -m fuzzingserver -s fuzzingserver.json sleep 5 -cargo run --bin autobahn-client --features atoi,flate2,simdutf8,tokio,web-socket-handshake --release +cargo run --bin autobahn-client --features atoi,flate2,simdutf8,tokio,web-socket-handshake --profile bench podman rm --force --ignore fuzzingserver if [ $(grep -ci "failed" .scripts/autobahn/reports/fuzzingserver/index.json) -gt 0 ] diff --git a/.scripts/internal-tests.sh b/.scripts/internal-tests.sh index abbdb292..61e2b491 100755 --- a/.scripts/internal-tests.sh +++ b/.scripts/internal-tests.sh @@ -75,6 +75,7 @@ $rt test-with-features wtx tracing $rt test-with-features wtx web-socket $rt test-with-features wtx web-socket-handshake $rt test-with-features wtx webpki-roots +$rt test-with-features wtx x509-certificate $rt check-with-features wtx _bench $rt check-with-features wtx _hack @@ -101,7 +102,7 @@ cargo check --bin autobahn-server --features "flate2,web-socket-handshake" cargo check --example database-client-postgres-tokio-rustls --features "_tokio-rustls-client,postgres" cargo check --example web-socket-client-cli-raw-tokio-rustls --features "_tokio-rustls-client,web-socket-handshake" -cargo check --example web-socket-server-echo-raw-async-std --features "async-std/attributes,web-socket-handshake" +cargo check --example web-socket-server-echo-raw-async-std --features "async-std,web-socket-handshake" cargo check --example web-socket-server-echo-raw-glommio --features "glommio,web-socket-handshake" cargo check --example web-socket-server-echo-raw-smol --features "smol,web-socket-handshake" cargo check --example web-socket-server-echo-raw-tokio --features "tokio,web-socket-handshake" diff --git a/.scripts/wtx-bench-postgres.sh b/.scripts/wtx-bench-postgres.sh index db982116..e5ddaf09 100755 --- a/.scripts/wtx-bench-postgres.sh +++ b/.scripts/wtx-bench-postgres.sh @@ -2,4 +2,4 @@ set -euxo pipefail -RUSTFLAGS="-Ctarget-cpu=native" cargo run --bin wtx-bench --release -- postgres postgres://wtx_md5:wtx@localhost:5432/wtx \ No newline at end of file +RUSTFLAGS="-Ctarget-cpu=native" cargo run --bin wtx-bench --profile bench -- postgres postgres://wtx_md5:wtx@localhost:5432/wtx \ No newline at end of file diff --git a/.scripts/wtx-bench-web-socket.sh b/.scripts/wtx-bench-web-socket.sh index e3152a4f..7dbc3c9b 100755 --- a/.scripts/wtx-bench-web-socket.sh +++ b/.scripts/wtx-bench-web-socket.sh @@ -12,39 +12,30 @@ pushd /tmp git clone https://github.com/c410-f3r/tokio-tungstenite || true cd tokio-tungstenite git checkout -t origin/bench || true -RUSTFLAGS="$FLAGS" cargo build --example echo-server --release -RUSTFLAGS="$FLAGS" cargo run --example echo-server --release 127.0.0.1:8081 & - -cd /tmp -git clone --recursive https://github.com/c410-f3r/uWebSockets.git || true -cd uWebSockets -git checkout -t origin/bench || true -if [ ! -e ./EchoServer ] -then - make examples -fi -./EchoServer 8082 & +RUSTFLAGS="$FLAGS" cargo build --example echo-server --profile bench +RUSTFLAGS="$FLAGS" cargo run --example echo-server --profile bench 127.0.0.1:8081 & popd -RUSTFLAGS="$FLAGS" cargo build --example web-socket-server-echo-raw-async-std --features atoi,async-std/attributes,simdutf8,web-socket-handshake --release -RUSTFLAGS="$FLAGS" cargo run --example web-socket-server-echo-raw-async-std --features atoi,async-std/attributes,simdutf8,web-socket-handshake --release 127.0.0.1:8083 & +FEATURES="atoi,memchr,simdutf8,web-socket-handshake" + +RUSTFLAGS="$FLAGS" cargo build --example web-socket-server-echo-raw-async-std --features "async-std,$FEATURES" --profile bench +RUSTFLAGS="$FLAGS" cargo run --example web-socket-server-echo-raw-async-std --features "async-std,$FEATURES" --profile bench 127.0.0.1:8082 & -RUSTFLAGS="$FLAGS" cargo build --example web-socket-server-echo-raw-glommio --features atoi,glommio,simdutf8,web-socket-handshake --release -RUSTFLAGS="$FLAGS" cargo run --example web-socket-server-echo-raw-glommio --features atoi,glommio,simdutf8,web-socket-handshake --release 127.0.0.1:8084 & +RUSTFLAGS="$FLAGS" cargo build --example web-socket-server-echo-raw-glommio --features "glommio,$FEATURES" --profile bench +RUSTFLAGS="$FLAGS" cargo run --example web-socket-server-echo-raw-glommio --features "glommio,$FEATURES" --profile bench 127.0.0.1:8083 & -RUSTFLAGS="$FLAGS" cargo build --example web-socket-server-echo-raw-smol --features atoi,simdutf8,smol,web-socket-handshake --release -RUSTFLAGS="$FLAGS" cargo run --example web-socket-server-echo-raw-smol --features atoi,simdutf8,smol,web-socket-handshake --release 127.0.0.1:8085 & +RUSTFLAGS="$FLAGS" cargo build --example web-socket-server-echo-raw-smol --features "smol,$FEATURES" --profile bench +RUSTFLAGS="$FLAGS" cargo run --example web-socket-server-echo-raw-smol --features "smol,$FEATURES" --profile bench 127.0.0.1:8084 & -RUSTFLAGS="$FLAGS" cargo build --example web-socket-server-echo-raw-tokio --features atoi,simdutf8,tokio,web-socket-handshake --release -RUSTFLAGS="$FLAGS" cargo run --example web-socket-server-echo-raw-tokio --features atoi,simdutf8,tokio,web-socket-handshake --release 127.0.0.1:8086 & +RUSTFLAGS="$FLAGS" cargo build --example web-socket-server-echo-raw-tokio --features "tokio,$FEATURES" --profile bench +RUSTFLAGS="$FLAGS" cargo run --example web-socket-server-echo-raw-tokio --features "tokio,$FEATURES" --profile bench 127.0.0.1:8085 & sleep 1 -RUSTFLAGS="$FLAGS" cargo run --bin wtx-bench --release -- \ +RUSTFLAGS="$FLAGS" cargo run --bin wtx-bench --profile bench -- \ web-socket \ http://127.0.0.1:8081/tokio-tungstenite \ - http://127.0.0.1:8082/uWebSockets \ - http://127.0.0.1:8083/wtx-raw-async-std \ - http://127.0.0.1:8084/wtx-raw-glommio \ - http://127.0.0.1:8085/wtx-raw-smol \ - http://127.0.0.1:8086/wtx-raw-tokio + http://127.0.0.1:8082/wtx-raw-async-std \ + http://127.0.0.1:8083/wtx-raw-glommio \ + http://127.0.0.1:8084/wtx-raw-smol \ + http://127.0.0.1:8085/wtx-raw-tokio diff --git a/Cargo.lock b/Cargo.lock index c8366782..6172ceff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "arbitrary" @@ -164,13 +164,13 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" +checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue 2.4.0", - "event-listener 4.0.3", - "event-listener-strategy", + "event-listener 5.0.0", + "event-listener-strategy 0.5.0", "futures-core", "pin-project-lite", ] @@ -191,9 +191,9 @@ dependencies = [ [[package]] name = "async-fs" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1f344136bad34df1f83a47f3fd7f2ab85d75cb8a940af4ccf6d482a84ea01b" +checksum = "bc19683171f287921f2405677dd2ed2549c3b3bda697a563ebc3a121ace2aba1" dependencies = [ "async-lock 3.3.0", "blocking", @@ -206,9 +206,9 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 2.1.1", + "async-channel 2.2.0", "async-executor", - "async-io 2.2.2", + "async-io 2.3.1", "async-lock 3.3.0", "blocking", "futures-lite 2.2.0", @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7" +checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" dependencies = [ "async-lock 3.3.0", "cfg-if", @@ -247,8 +247,8 @@ dependencies = [ "futures-io", "futures-lite 2.2.0", "parking", - "polling 3.3.1", - "rustix 0.38.28", + "polling 3.4.0", + "rustix 0.38.31", "slab", "tracing", "windows-sys 0.52.0", @@ -270,7 +270,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ "event-listener 4.0.3", - "event-listener-strategy", + "event-listener-strategy 0.4.0", "pin-project-lite", ] @@ -280,7 +280,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "async-io 2.2.2", + "async-io 2.3.1", "blocking", "futures-lite 2.2.0", ] @@ -291,15 +291,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15c1cd5d253ecac3d3cf15e390fd96bd92a13b1d14497d81abf077304794fb04" dependencies = [ - "async-channel 2.1.1", - "async-io 2.2.2", + "async-channel 2.2.0", + "async-io 2.3.1", "async-lock 3.3.0", "async-signal", "blocking", "cfg-if", "event-listener 4.0.3", "futures-lite 2.2.0", - "rustix 0.38.28", + "rustix 0.38.31", "windows-sys 0.52.0", ] @@ -309,13 +309,13 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" dependencies = [ - "async-io 2.2.2", + "async-io 2.3.1", "async-lock 2.8.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.28", + "rustix 0.38.31", "signal-hook-registry", "slab", "windows-sys 0.48.0", @@ -443,6 +443,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "basic-toml" version = "0.1.8" @@ -452,6 +458,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bcder" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627747a6774aab38beb35990d88309481378558875a41da1a4b2e373c906ef0" +dependencies = [ + "bytes", + "smallvec", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -460,15 +476,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bitmaps" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" [[package]] name = "bitvec" @@ -497,7 +513,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ - "async-channel 2.1.1", + "async-channel 2.2.0", "async-lock 3.3.0", "async-task", "fastrand 2.0.1", @@ -545,9 +561,9 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytecheck" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -555,9 +571,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", @@ -876,7 +892,7 @@ version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62c6fcf842f17f8c78ecf7c81d75c5ce84436b41ee07e03f490fbb5f5a8731d8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "byteorder", "diesel_derives", "itoa", @@ -951,9 +967,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" dependencies = [ "serde", ] @@ -1193,6 +1209,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72557800024fabbaa2449dd4bf24e37b93702d457a4d4f2b0dd1f0f039f20c1" +dependencies = [ + "concurrent-queue 2.4.0", + "parking", + "pin-project-lite", +] + [[package]] name = "event-listener-strategy" version = "0.4.0" @@ -1203,6 +1230,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +dependencies = [ + "event-listener 5.0.0", + "pin-project-lite", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1533,9 +1570,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -1670,9 +1707,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" [[package]] name = "hex" @@ -1767,9 +1804,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1800,9 +1837,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" +checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1854,9 +1891,9 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itertools" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -1869,18 +1906,18 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -1966,9 +2003,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libfuzzer-sys" @@ -1989,9 +2026,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libz-ng-sys" -version = "1.1.14" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81157dde2fd4ad2b45ea3a4bb47b8193b52a6346b678840d91d80d3c2cd166c5" +checksum = "c6409efc61b12687963e602df8ecf70e8ddacf95bc6576bcf16e3ac6328083c5" dependencies = [ "cmake", "libc", @@ -2005,9 +2042,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "litrs" @@ -2128,9 +2165,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -2189,7 +2226,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "libc", ] @@ -2221,9 +2258,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -2311,6 +2348,16 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pem" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +dependencies = [ + "base64", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2337,18 +2384,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", @@ -2424,14 +2471,14 @@ dependencies = [ [[package]] name = "polling" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e" +checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14" dependencies = [ "cfg-if", "concurrent-queue 2.4.0", "pin-project-lite", - "rustix 0.38.28", + "rustix 0.38.31", "tracing", "windows-sys 0.52.0", ] @@ -2494,9 +2541,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b2685dd208a3771337d8d386a89840f0f43cd68be8dae90a5f8c2384effc9cd" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ "toml_edit", ] @@ -2539,7 +2586,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "lazy_static", "num-traits", "rand", @@ -2654,13 +2701,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.5", "regex-syntax 0.8.2", ] @@ -2675,9 +2722,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -2698,9 +2745,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rend" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] @@ -2793,9 +2840,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.34.2" +version = "1.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755392e1a2f77afd95580d3f0d0e94ac83eeeb7167552c9b5bca549e61a94d83" +checksum = "b39449a79f45e8da28c57c341891b69a183044b29518bb8f86dbac9df60bb7df" dependencies = [ "arrayvec", "num-traits", @@ -2833,14 +2880,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", - "linux-raw-sys 0.4.12", + "linux-raw-sys 0.4.13", "windows-sys 0.52.0", ] @@ -2875,9 +2922,9 @@ checksum = "0a716eb65e3158e90e17cd93d855216e27bde02745ab842f2cab4a39dba1bacf" [[package]] name = "rustls-webpki" -version = "0.102.1" +version = "0.102.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ "ring", "rustls-pki-types", @@ -3055,6 +3102,15 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + [[package]] name = "simd-json" version = "0.11.1" @@ -3109,10 +3165,10 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e635339259e51ef85ac7aa29a1cd991b957047507288697a690e80ab97d07cad" dependencies = [ - "async-channel 2.1.1", + "async-channel 2.2.0", "async-executor", "async-fs", - "async-io 2.2.2", + "async-io 2.3.1", "async-lock 3.3.0", "async-net", "async-process", @@ -3162,6 +3218,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sqlformat" version = "0.2.3" @@ -3270,7 +3336,7 @@ checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" dependencies = [ "atoi", "base64", - "bitflags 2.4.1", + "bitflags 2.4.2", "byteorder", "crc", "dotenvy", @@ -3422,14 +3488,13 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand 2.0.1", - "redox_syscall", - "rustix 0.38.28", + "rustix 0.38.31", "windows-sys 0.52.0", ] @@ -3597,9 +3662,9 @@ checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", @@ -3706,9 +3771,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -3727,9 +3792,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode_categories" @@ -3778,9 +3843,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cdbaf5e132e593e9fc1de6a15bbec912395b11fb9719e061cf64f804524c503" +checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b" [[package]] name = "value-trait" @@ -3829,9 +3894,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3839,9 +3904,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", @@ -3854,9 +3919,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", @@ -3866,9 +3931,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3876,9 +3941,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", @@ -3889,15 +3954,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -4096,9 +4161,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.34" +version = "0.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29" dependencies = [ "memchr", ] @@ -4141,6 +4206,7 @@ dependencies = [ "hmac", "httparse", "md-5", + "memchr", "miniserde", "proptest", "protobuf", @@ -4168,6 +4234,7 @@ dependencies = [ "tracing-subscriber", "tracing-tree", "webpki-roots", + "x509-certificate", ] [[package]] @@ -4223,6 +4290,25 @@ dependencies = [ "tap", ] +[[package]] +name = "x509-certificate" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66534846dec7a11d7c50a74b7cdb208b9a581cad890b7866430d438455847c85" +dependencies = [ + "bcder", + "bytes", + "chrono", + "der", + "hex", + "pem", + "ring", + "signature", + "spki", + "thiserror", + "zeroize", +] + [[package]] name = "xml-rs" version = "0.8.19" @@ -4254,3 +4340,17 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] diff --git a/Cargo.toml b/Cargo.toml index 5f3ea5f5..62ba2c16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,4 @@ -[profile.profiling] -inherits = "release" -debug = true - -[profile.release] +[profile.bench] codegen-units = 1 debug = false debug-assertions = false @@ -14,6 +10,10 @@ panic = 'abort' rpath = false strip = "symbols" +[profile.profiling] +inherits = "release" +debug = true + [workspace] members = ["wtx", "wtx-bench", "wtx-fuzz", "wtx-macros", "wtx-ui"] resolver = "2" diff --git a/README.md b/README.md index 01f05d2e..be1cdc95 100644 --- a/README.md +++ b/README.md @@ -3,31 +3,37 @@ [![CI](https://github.com/c410-f3r/wtx/workflows/CI/badge.svg)](https://github.com/c410-f3r/wtx/actions?query=workflow%3ACI) [![crates.io](https://img.shields.io/crates/v/wtx.svg)](https://crates.io/crates/wtx) [![Documentation](https://docs.rs/wtx/badge.svg)](https://docs.rs/wtx) -[![License](https://img.shields.io/badge/license-APACHE2-blue.svg)](./LICENSE) -[![Rustc](https://img.shields.io/badge/rustc-1.75-lightgray")](https://blog.rust-lang.org/2020/03/12/Rust-1.75.html) +[![License](https://img.shields.io/badge/license-APACHE2-blue.svg)](https://github.com/c410-f3r/wtx/blob/main/LICENSE) +[![Rustc](https://img.shields.io/badge/rustc-1.75-lightgray")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html) -A collection of different transport implementations and related tools focused primarily on web technologies. +A collection of different transport implementations and related tools focused primarily on web technologies. Contains the implementations of 5 IETF RFCs ([RFC6455](https://datatracker.ietf.org/doc/html/rfc6455), [RFC7541](https://datatracker.ietf.org/doc/html/rfc7541), [RFC7692](https://datatracker.ietf.org/doc/html/rfc7692), [RFC8441](https://datatracker.ietf.org/doc/html/rfc8441), [RFC9113](https://datatracker.ietf.org/doc/html/rfc9113)), 2 formal specifications ([PostgreSQL](https://www.postgresql.org/docs/16/protocol.html), [gRPC](https://github.com/grpc/grpc/blob/8ce97741877cc205f7ced333f617ea9cc79f65d2/doc/PROTOCOL-HTTP2.md)) and several other invented ideas. -1. [Client API Framework](https://github.com/c410-f3r/wtx/tree/main/wtx/src/client_api_framework) -2. [Database Client](https://github.com/c410-f3r/wtx/tree/main/wtx/src/database/client) -3. [Database Object–Relational Mapping](https://github.com/c410-f3r/wtx/tree/main/wtx/src/database/orm) -4. [Database Schema Manager](https://github.com/c410-f3r/wtx/tree/main/wtx/src/database/schema_manager) -5. [HTTP2 Client/Server](https://github.com/c410-f3r/wtx/tree/main/wtx/src/http2) -6. [Pool Manager](https://github.com/c410-f3r/wtx/tree/main/wtx/src/pool_manager) -7. [WebSocket Client/Server](https://github.com/c410-f3r/wtx/tree/main/wtx/src/web_socket) +1. [Client API Framework](https://c410-f3r.github.io/wtx-site/client-api-framework/index.html) +2. [Database Client](https://c410-f3r.github.io/wtx-site/database/client-connection.html) +3. [Database Object–Relational Mapping](https://c410-f3r.github.io/wtx-site/database/object%E2%80%93relational-mapping.html) +4. [Database Schema Manager](https://c410-f3r.github.io/wtx-site/database/schema-management.html) +5. [Generic HTTP Client/Server](https://c410-f3r.github.io/wtx-site/http/index.html) +6. [WebTransport Client/Server](https://c410-f3r.github.io/wtx-site/web-socket/index.html) +7. [Pool Manager](https://c410-f3r.github.io/wtx-site/pool_manager/index.html) -Embedded devices that have a heap allocator can use this `no_std` crate. +Embedded devices with a working heap allocator can use this `no_std` crate. -A more resourceful documentation is available at . +## Performance -## Benchmarks +Many things that generally improve performance are used in the project, to name a few: + +1. **Manual vectorization**: When an algorithm is known for processing large amounts of data, several experiments are performed to analyze the best way to split loops in order to allow the compiler to take advantage of SIMD instructions in x86 processors. +2. **Memory allocation**: Whenever possible, all heap allocations are called only once at the start of an instance creation and additionally, stack memory usage is preferably prioritized over heap memory. +3. **Fewer dependencies**: No third-party is injected by default. In other words, additional dependencies are up to the user through the selection of Cargo features, which decreases compilation times by a large margin. For example, you can see the mere 18 dependencies required by the PostgreSQL client using `cargo tree -e normal --features postgres`. + +Since memory are usually held at the instance level instead of being created and dropped on the fly, it is worth noting that its usage can growth significantly depending on the use-case. If appropriated, try using a shared pool of resources or try limiting how much data can be exchanged between parties. + +## High-level benchmarks If you disagree with any of the mentioned charts, feel free to checkout [wtx-bench](https://github.com/c410-f3r/wtx/tree/main/wtx-bench) to point any misunderstandings or misconfigurations. 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 instance for each handshake, pre-allocating a high number of bytes for short-lived or low-transfer connections can have a negative impact. -It is also possible to use libraries that manage pools of resources to avoid having to reconstruct expensive elements all the time. - ### PostgreSQL client ![PostgreSQL Benchmark](https://i.imgur.com/vf2tYxY.jpg) @@ -35,3 +41,15 @@ It is also possible to use libraries that manage pools of resources to avoid hav ### WebSocket ![WebSocket Benchmark](https://i.imgur.com/Iv2WzJV.jpg) + +## Low-level benchmarks + +Anything marked with `#[bench]` in the repository is considered a low-level benchmark in the sense that they measure very specific operations that generally serve as the basis for other parts. + +Take a look at to see all low-level benchmarks over different periods of time. + +## Limitations + +Does not support systems with 16bit memory addresses and expects the infallible addition of the sizes of 4 allocated chunks of memories, otherwise the program will overflow in certain arithmetic operations potentially resulting in unexpected operations. + +For example, in a 32bit system you can allocate a maximum of 2^30 bytes of memory for at most 4 elements. Such a scenario should be viable with little swap memory due to the likely triggering of the OOM killer or specific limiters like `ulimit`. \ No newline at end of file diff --git a/clippy.toml b/clippy.toml index 4dc4c312..9671f8cf 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1,10 @@ -disallowed-types = ["std::collections::HashMap", "std::collections::HashSet"] \ No newline at end of file +disallowed-methods = [ + "<[u8]>::rsplit", + "<[u8]>::split", + "core::str::from_utf8", + "str::rsplit", +] +disallowed-types = [ + "std::collections::HashMap", + "std::collections::HashSet" +] \ No newline at end of file diff --git a/wtx-bench/Cargo.toml b/wtx-bench/Cargo.toml index f2e9cf78..addacefb 100644 --- a/wtx-bench/Cargo.toml +++ b/wtx-bench/Cargo.toml @@ -6,7 +6,7 @@ plotters = { default-features = false, features = ["histogram", "svg_backend"], sqlx = { default-features = false, features = ["postgres", "runtime-tokio"], version = "0.7" } tokio = { default-features = false, features = ["macros", "rt-multi-thread"], version = "1.36" } tokio-postgres = { default-features = false, features = ["runtime"], version = "0.7" } -wtx = { default-features = false, features = ["atoi", "postgres", "simdutf8", "tokio", "web-socket-handshake"], path = "../wtx" } +wtx = { default-features = false, features = ["atoi", "memchr", "postgres", "simdutf8", "tokio", "web-socket-handshake"], path = "../wtx" } [package] description = "Benchmarks" diff --git a/wtx-bench/README.md b/wtx-bench/README.md index 66c16b00..c1c0249e 100644 --- a/wtx-bench/README.md +++ b/wtx-bench/README.md @@ -3,5 +3,5 @@ Call the `wtx-bench` binary passing the necessary parameters according to the desired target. ``` -cargo run --bin wtx-bench --release -- web-socket http://127.0.0.1:8080/some_server_name http://127.0.0.1:8081/another_server_name +cargo run --bin wtx-bench --profile bench -- web-socket http://127.0.0.1:8080/some_server_name http://127.0.0.1:8081/another_server_name ``` diff --git a/wtx-bench/src/web_socket.rs b/wtx-bench/src/web_socket.rs index dc6cbaf3..d3b3e203 100644 --- a/wtx-bench/src/web_socket.rs +++ b/wtx-bench/src/web_socket.rs @@ -52,7 +52,7 @@ pub(crate) async fn bench(agent: &mut Agent, uri: &UriRef<'_>) { uri: &local_uri.to_ref(), wsb: WebSocketBuffer::default(), } - .connect() + .connect([]) .await .unwrap(); for _ in 0..NUM_MESSAGES { diff --git a/wtx-docs/book.toml b/wtx-docs/book.toml new file mode 100644 index 00000000..f012fd71 --- /dev/null +++ b/wtx-docs/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Caio"] +language = "en" +multilingual = false +src = "src" +title = "WTX" diff --git a/wtx-docs/src/README.md b/wtx-docs/src/README.md new file mode 120000 index 00000000..fe840054 --- /dev/null +++ b/wtx-docs/src/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/wtx-docs/src/SUMMARY.md b/wtx-docs/src/SUMMARY.md new file mode 100644 index 00000000..994241b6 --- /dev/null +++ b/wtx-docs/src/SUMMARY.md @@ -0,0 +1,10 @@ +# Summary + +- [Introduction](README.md) +- [Client API Framework](client-api-framework/README.md) +- [Database](database/README.md) + - [Client Connection](database/client-connection.md) + - [Object–Relational Mapping](database/object–relational-mapping.md) + - [Schema management](database/schema-management.md) +- [Pool Manager](pool_manager/README.md) +- [WebSocket](web-socket/README.md) diff --git a/wtx-docs/src/client-api-framework/README.md b/wtx-docs/src/client-api-framework/README.md new file mode 100644 index 00000000..8e851198 --- /dev/null +++ b/wtx-docs/src/client-api-framework/README.md @@ -0,0 +1,13 @@ +# Client API Framework + +A flexible client API framework for writing asynchronous, fast, organizable, scalable and maintainable applications. Supports several data formats, transports and custom parameters. + +Activation feature is called `client-api-framework`. Checkout the `wtx-apis` project to see a collection of APIs based on `wtx`. + +## Objective + +It is possible to directly decode responses using built-in methods provided by some transport implementations like `reqwest` or `surf` but as complexity grows, the cost of maintaining large sets of endpoints with ad-hoc solutions usually becomes unsustainable. Based on this scenario, `wtx` comes into play to organize and centralize data flow in a well-defined manner to increase productivity and maintainability. + +For API consumers, the calling convention of `wtx` endpoints is based on fluent interfaces which makes the usage more pleasant and intuitive. + +Moreover, the project may in the future create automatic bindings for other languages in order to avoid having duplicated API repositories. \ No newline at end of file diff --git a/wtx-docs/src/database/README.md b/wtx-docs/src/database/README.md new file mode 100644 index 00000000..8094ba71 --- /dev/null +++ b/wtx-docs/src/database/README.md @@ -0,0 +1,3 @@ +# Database + +A set of tools for interacting with databases. \ No newline at end of file diff --git a/wtx-docs/src/database/client-connection.md b/wtx-docs/src/database/client-connection.md new file mode 100644 index 00000000..94203ae0 --- /dev/null +++ b/wtx-docs/src/database/client-connection.md @@ -0,0 +1,26 @@ + +# Client Connection + +PostgreSQL is currently the only supported database and more SQL or NoSQL variants shouldn't be too difficult to implement architecture-wise. + +Activation feature is called `postgres`. + +![PostgreSQL Benchmark](https://i.imgur.com/vf2tYxY.jpg) + +```ignore,rust,edition2021 +use core::borrow::BorrowMut; +use wtx::{ + database::{client::postgres::{Executor, ExecutorBuffer}, Executor as _, Record, Records}, + misc::Stream, +}; + +async fn query_foo( + executor: &mut Executor, impl Stream>, +) -> wtx::Result<(u32, String)> { + let record = executor.fetch_with_stmt::( + "SELECT bar,baz FROM foo WHERE bar = $1 AND baz = $2", + (1u32, "2") + ).await?; + Ok((record.decode("bar")?, record.decode("baz")?)) +} +``` \ No newline at end of file diff --git "a/wtx-docs/src/database/object\342\200\223relational-mapping.md" "b/wtx-docs/src/database/object\342\200\223relational-mapping.md" new file mode 100644 index 00000000..818bffc9 --- /dev/null +++ "b/wtx-docs/src/database/object\342\200\223relational-mapping.md" @@ -0,0 +1,64 @@ +# Object–Relational Mapping + +A very rudimentary ORM that currently supports very few operations that are not well tested. You probably should look for other similar projects. + +Activation feature is called `orm`. + +```ignore,rust,edition2021 +use wtx::database::{ + orm::{Crud, FromSuffixRslt, NoTableAssociation, Table, TableField, TableParams}, + Database, FromRecords, Record, TableSuffix, +}; + +struct User<'conn> { + id: u32, + name: &'conn str, + password: &'conn str, +} + +impl<'conn> FromRecords for User<'conn> { + type Database = (); + type Error = wtx::Error; + + fn from_records( + _: &mut String, + curr_record: &::Record<'_>, + _: &::Records<'_>, + _: TableSuffix, + ) -> Result<(usize, Self), Self::Error> { + let id = curr_record.decode(0)?; + let name = curr_record.decode(1)?; + let password = curr_record.decode(2)?; + Ok((1, Self { id, name, password })) + } +} + +impl<'conn, 'entity> Table<'entity> for User<'conn> { + const PRIMARY_KEY_NAME: &'static str = "id"; + const TABLE_NAME: &'static str = "user"; + + type Associations = NoTableAssociation; + type Error = wtx::Error; + type Fields = (TableField<&'conn str>, TableField<&'conn str>); + type PrimaryKeyValue = &'entity u32; + + fn type_instances(_: TableSuffix) -> FromSuffixRslt<'entity, Self> { + (NoTableAssociation::new(), (TableField::new("name"), TableField::new("password"))) + } + + fn update_all_table_fields(entity: &'entity Self, table: &mut TableParams<'entity, Self>) { + *table.id_field_mut().value_mut() = Some((&entity.id).into()); + *table.fields_mut().0.value_mut() = Some((entity.name).into()); + *table.fields_mut().1.value_mut() = Some((entity.password).into()); + } +} + +async fn all_users<'conn>( + crud: &'conn mut impl Crud, +) -> wtx::Result>> { + let mut buffer = String::new(); + let mut results = Vec::new(); + crud.read_all::>(&mut buffer, &mut results, &TableParams::default()).await?; + Ok(results) +} +``` \ No newline at end of file diff --git a/wtx-docs/src/database/schema-management.md b/wtx-docs/src/database/schema-management.md new file mode 100644 index 00000000..2dcef54f --- /dev/null +++ b/wtx-docs/src/database/schema-management.md @@ -0,0 +1,192 @@ +# Schema Management + +Embedded and CLI workflows using raw SQL commands. + +Activation feature is called `sm`. + +## CLI + +```bash +# Example + +cargo install --git https://github.com/c410-f3r/wtx --features sm-dev wtx-ui +echo DATABASE_URI="postgres://USER:PASSWORD@localhost:5432/DATABASE" > .env +RUST_LOG=debug wtx-cli migrate +``` + +The CLI application expects a configuration file that contains a set of paths where each path is a directory with multiple migrations. + +```toml +# wtx.toml + +migration_groups = [ + "migrations/1__initial", + "migrations/2__fancy_stuff" +] +``` + +Each provided migration and group must contain an unique version and a name summarized by the following structure: + +```txt +// Execution order of migrations is dictated by their numeric declaration order. + +migrations ++-- 1__initial (Group) + +-- 1__create_author.sql (Migration) + +-- 2__create_post.sql (Migration) ++-- 2__fancy_stuff (Group) + +-- 1__something_fancy.sql (Migration) +wtx.toml +``` + +The SQL file itself is composed by two parts, one for migrations (`-- wtx IN` section) and another for rollbacks (`-- wtx OUT` section). + +```sql +-- wtx IN + +CREATE TABLE author ( + id INT NOT NULL PRIMARY KEY, + added TIMESTAMP NOT NULL, + birthdate DATE NOT NULL, + email VARCHAR(100) NOT NULL, + first_name VARCHAR(50) NOT NULL, + last_name VARCHAR(50) NOT NULL +); + +-- wtx OUT + +DROP TABLE author; +``` + +One cool thing about the expected file configuration is that it can also be divided into smaller pieces, for example, the above migration could be transformed into `1__author_up.sql` and `1__author_down.sql`. + +```sql +-- 1__author_up.sql + +CREATE TABLE author ( + id INT NOT NULL PRIMARY KEY, + added TIMESTAMP NOT NULL, + birthdate DATE NOT NULL, + email VARCHAR(100) NOT NULL, + first_name VARCHAR(50) NOT NULL, + last_name VARCHAR(50) NOT NULL +); +``` + +```sql +-- 1__author_down.sql + +DROP TABLE author; +``` + +```txt +migrations ++-- 1__some_group (Group) + +-- 1__author (Migration directory) + +-- 1__author_down.sql (Down migration) + +-- 1__author_up.sql (Up migration) + +-- 1__author.toml (Optional configuration) +wtx.toml +``` + +## Library + +The library gives freedom to arrange groups and uses some external crates, bringing ~10 additional dependencies into your application. If this overhead is not acceptable, then you probably should discard the library and use the CLI binary instead as part of a custom deployment strategy. + +```ignore,rust,edition2021 +use wtx::{ + database::{sm::Commands, DEFAULT_URI_VAR}, + misc::UriParts, + rng::StaticRng, +}; +use std::path::Path; + +#[tokio::main] +async fn main() { + let mut commands = Commands::with_executor(()); + commands + .migrate_from_dir( + (&mut <_>::default(), &mut <_>::default()), + Path::new("my_custom_migration_group_path"), + ) + .await + .unwrap(); +} +``` + +## Embedded migrations + +To make deployment easier, the final binary of your application can embed all necessary migrations through the binary that is available in the `wtx-ui` crate. + +```ignore,rust,edition2021 +// This is an example! The actual contents are filled by the `wtx-ui embed-migrations` binary call. +mod embedded_migrations { + pub(crate) const GROUPS: wtx::database::sm::EmbeddedMigrationsTy = &[]; +} + +use wtx::database::sm::Commands; + +async fn migrate() -> wtx::Result<()> { + Commands::with_executor(()) + .migrate_from_groups((&mut String::new(), &mut Vec::new()), embedded_migrations::GROUPS) + .await +} +``` + +## Conditional migrations + +If one particular migration needs to be executed in a specific set of databases, then it is possible to use the `-- wtx dbs` parameter in a file. + +```sql +-- wtx dbs mssql,postgres + +-- wtx IN + +CREATE SCHEMA foo; + +-- wtx OUT + +DROP SCHEMA foo; +``` + +## Repeatable migrations + +Repeatability can be specified with `-- wtx repeatability SOME_VALUE` where `SOME_VALUE` can be either `always` (regardless of the checksum) or `on-checksum-change` (runs only when the checksums changes). + +```sql +-- wtx dbs postgres +-- wtx repeatability always + +-- wtx IN + +CREATE OR REPLACE PROCEDURE something() LANGUAGE SQL AS $$ $$ + +-- wtx OUT + +DROP PROCEDURE something(); +``` + +Keep in mind that repeatable migrations might break subsequent operations, therefore, you must known what you are doing. If desirable, they can be separated into dedicated groups. + +```ini +migrations/1__initial_repeatable_migrations +migrations/2__normal_migrations +migrations/3__final_repeatable_migrations +``` + +## Namespaces/Schemas + +For supported databases, there is no direct user parameter that inserts migrations inside a single database schema but it is possible to specify the schema inside the SQL file and arrange the migration groups structure in a way that most suits you. + +```sql +-- wtx IN + +CREATE TABLE cool_department_schema.author ( + id INT NOT NULL PRIMARY KEY, + full_name VARCHAR(50) NOT NULL +); + +-- wtx OUT + +DROP TABLE cool_department_schema.author; +``` \ No newline at end of file diff --git a/wtx-docs/src/pool_manager/README.md b/wtx-docs/src/pool_manager/README.md new file mode 100644 index 00000000..4dd49583 --- /dev/null +++ b/wtx-docs/src/pool_manager/README.md @@ -0,0 +1,43 @@ +# Pool Manager + +An asynchronous pool of arbitrary objects where each element is dynamically created or re-created when invalid. + +Can also be used for database connections, which is quite handy because it enhances the performance of executing commands and alleviates the use of hardware resources. + +Activation feature is called `pool-manager`. + +```ignore,rust,edition2021 +use wtx::pool_manager::{ResourceManager, StaticPool}; +use core::cell::RefCell; + +struct TestManager; + +impl ResourceManager for TestManager { + type Persistent = (); + type Error = crate::Error; + type Resource = i32; + + #[inline] + async fn create(&self) -> Result { + Ok(0) + } + + #[inline] + fn check_integrity(&self, _: &mut Self::Resource) -> Option { + None + } + + #[inline] + async fn recycle( + &self, + _: Self::Persistent, + _: &mut Self::Resource, + ) -> Result<(), Self::Error> { + Ok(()) + } +} + +fn pool() -> StaticPool>, TestManager, 2> { + StaticPool::new(TestManager).unwrap() +} +``` diff --git a/wtx-docs/src/web-socket/README.md b/wtx-docs/src/web-socket/README.md new file mode 100644 index 00000000..84bede0d --- /dev/null +++ b/wtx-docs/src/web-socket/README.md @@ -0,0 +1,51 @@ +# 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. + +Activation feature is called `web-socket`. + +![WebSocket Benchmark](https://i.imgur.com/Iv2WzJV.jpg) + +```ignore,rust,edition2021 +use wtx::{ + misc::Stream, + rng::Rng, + web_socket::{ + FrameBufferVec, FrameMutVec, FrameVecMut, compression::NegotiatedCompression, OpCode, + WebSocketClientOwned + } +}; + +pub async fn handle_client_frames( + fb: &mut FrameBufferVec, + ws: &mut WebSocketClientOwned +) -> wtx::Result<()> { + loop { + let 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(), frame.text_payload()) { + (_, Some(elem)) => println!("{elem}"), + (OpCode::Close, _) => break, + _ => {} + } + } + Ok(()) +} +``` + +See the `examples` directory for more suggestions. + +## Autobahn Reports + +1. fuzzingclient +2. fuzzingserver + +## 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. diff --git a/wtx-ui/src/web_socket.rs b/wtx-ui/src/web_socket.rs index 9a99f4ae..f66f2b0f 100644 --- a/wtx-ui/src/web_socket.rs +++ b/wtx-ui/src/web_socket.rs @@ -24,7 +24,7 @@ pub(crate) async fn _connect(uri: &str, cb: impl Fn(&str)) -> wtx::Result<()> { uri: &uri, compression: (), } - .connect() + .connect([]) .await?; let mut buffer = String::new(); let mut reader = BufReader::new(tokio::io::stdin()); diff --git a/wtx/Cargo.toml b/wtx/Cargo.toml index d0a6ea6c..ddbf5398 100644 --- a/wtx/Cargo.toml +++ b/wtx/Cargo.toml @@ -21,7 +21,7 @@ required-features = ["_tokio-rustls-client", "tokio/io-std", "web-socket-handsha [[example]] name = "web-socket-server-echo-raw-async-std" path = "examples/web-socket-server-echo-raw-async-std.rs" -required-features = ["async-std/attributes", "web-socket-handshake"] +required-features = ["async-std", "web-socket-handshake"] [[example]] name = "web-socket-server-echo-raw-glommio" @@ -73,6 +73,7 @@ hashbrown = { default-features = false, features = ["ahash", "allocator-api2", " hmac = { default-features = false, optional = true, version = "0.12" } httparse = { default-features = false, optional = true, version = "1.0" } md-5 = { default-features = false, optional = true, version = "0.10" } +memchr = { default-features = false, optional = true, version = "2.0" } miniserde = { default-features = false, optional = true, version = "0.1" } proptest = { default-features = false, features = ["alloc"], optional = true, version = "1.0" } protobuf = { default-features = false, optional = true, version = "3.0" } @@ -100,6 +101,7 @@ tracing = { default-features = false, features = ["attributes"], optional = true tracing-subscriber = { default-features = false, features = ["env-filter", "fmt"], optional = true, version = "0.3" } tracing-tree = { default-features = false, optional = true, version = "0.3" } webpki-roots = { default-features = false, optional = true, version = "0.26" } +x509-certificate = { default-features = false, optional = true, version = "0.23" } [dev-dependencies] chrono = { default-features = false, features = ["clock"], version = "0.4" } @@ -122,15 +124,15 @@ orm = ["database", "dep:smallvec"] pool-manager = [] postgres = ["ahash", "base64", "crypto-common", "database", "digest", "hashbrown", "md-5", "hmac", "sha2"] protobuf = ["dep:protobuf", "std"] +schema-manager = ["database", "chrono"] +schema-manager-dev = ["schema-manager"] serde = ["arrayvec?/serde", "cl-aux?/serde", "dep:serde"] serde_json = ["serde", "dep:serde_json", "std"] serde_yaml = ["serde", "dep:serde_yaml", "std"] serde-xml-rs = ["serde", "dep:serde-xml-rs", "std"] simd-json = ["serde", "dep:simd-json", "std"] -schema-manager = ["database", "chrono"] -schema-manager-dev = ["schema-manager"] smol = ["dep:smol", "std"] -std = ["arrayvec?/std", "cl-aux?/std", "miniserde?/std", "serde?/std", "serde_json?/std"] +std = ["ahash?/std", "arrayvec?/std", "atoi?/std", "cl-aux?/std", "memchr?/std", "miniserde?/std", "serde?/std", "serde_json?/std", "simdutf8?/std"] tokio = ["std", "dep:tokio"] tokio-rustls = ["ring", "rustls-pki-types", "tokio", "dep:tokio-rustls"] web-socket = [] diff --git a/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs b/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs index 4b5d1344..ab99c85b 100644 --- a/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs +++ b/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs @@ -32,7 +32,7 @@ async fn main() { uri: &uri, wsb: WebSocketBuffer::default(), } - .connect() + .connect([]) .await .unwrap(); let mut buffer = String::new(); diff --git a/wtx/examples/web-socket-server-echo-raw-tokio.rs b/wtx/examples/web-socket-server-echo-raw-tokio.rs index 1007e7ac..ab912251 100644 --- a/wtx/examples/web-socket-server-echo-raw-tokio.rs +++ b/wtx/examples/web-socket-server-echo-raw-tokio.rs @@ -5,7 +5,7 @@ mod common; use tokio::net::TcpListener; -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() { let listener = TcpListener::bind(common::_host_from_args()).await.unwrap(); loop { diff --git a/wtx/src/bin/autobahn-client.rs b/wtx/src/bin/autobahn-client.rs index f22436cc..1bfdf700 100644 --- a/wtx/src/bin/autobahn-client.rs +++ b/wtx/src/bin/autobahn-client.rs @@ -26,7 +26,7 @@ async fn main() { uri: &UriRef::new(&format!("http://{host}/runCase?case={case}&agent=wtx")), wsb: &mut wsb, } - .connect() + .connect([]) .await .unwrap(); loop { @@ -54,7 +54,7 @@ async fn main() { uri: &UriRef::new(&format!("http://{host}/updateReports?agent=wtx")), wsb, } - .connect() + .connect([]) .await .unwrap() .1 @@ -73,7 +73,7 @@ async fn get_case_count(fb: &mut FrameBufferVec, host: &str, wsb: &mut WebSocket uri: &UriRef::new(&format!("http://{host}/getCaseCount")), wsb, } - .connect() + .connect([]) .await .unwrap(); let rslt = ws.read_frame(fb).await.unwrap().text_payload().unwrap_or_default().parse().unwrap(); diff --git a/wtx/src/client_api_framework/misc.rs b/wtx/src/client_api_framework/misc.rs index 451f513f..71ec0b7f 100644 --- a/wtx/src/client_api_framework/misc.rs +++ b/wtx/src/client_api_framework/misc.rs @@ -51,7 +51,7 @@ pub(crate) fn log_req( /// Not used in [crate::network::transport::Transport::send_retrieve_and_decode_batch] because /// [crate::Requests::decode_responses] takes precedence. pub(crate) fn log_res(_res: &[u8]) { - _debug!("Response: {:?}", core::str::from_utf8(_res)); + _debug!("Response: {:?}", crate::misc::from_utf8_basic_rslt(_res)); } pub(crate) async fn manage_after_sending_related( diff --git a/wtx/src/client_api_framework/network/http/status_code.rs b/wtx/src/client_api_framework/network/http/status_code.rs index bfcc826f..3046b02f 100644 --- a/wtx/src/client_api_framework/network/http/status_code.rs +++ b/wtx/src/client_api_framework/network/http/status_code.rs @@ -3,7 +3,6 @@ /// As defined by [rfc7231 section 6](https://tools.ietf.org/html/rfc7231#section-6). /// /// Copied from . -#[repr(u16)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum StatusCode { /// 100 Continue diff --git a/wtx/src/database/client/postgres/authentication.rs b/wtx/src/database/client/postgres/authentication.rs index 4a4d6980..f2296a10 100644 --- a/wtx/src/database/client/postgres/authentication.rs +++ b/wtx/src/database/client/postgres/authentication.rs @@ -1,4 +1,4 @@ -use crate::misc::_atoi; +use crate::misc::{atoi, bytes_split1}; use core::any::type_name; #[derive(Debug)] @@ -23,14 +23,14 @@ impl<'bytes> TryFrom<&'bytes [u8]> for Authentication<'bytes> { 5 => Self::Md5Password(rest.try_into()?), 10 => Self::Sasl(rest), 11 => { - let mut iter = rest.split(|b| *b == b','); + let mut iter = bytes_split1(rest, b','); let mut iterations = None; let mut nonce = None; let mut salt = None; while let Some([key, _, local_rest @ ..]) = iter.next() { match key { b'i' => { - iterations = Some(_atoi(local_rest)?); + iterations = Some(atoi(local_rest)?); } b'r' => { nonce = Some(local_rest); @@ -49,7 +49,7 @@ impl<'bytes> TryFrom<&'bytes [u8]> for Authentication<'bytes> { } } 12 => { - let mut iter = rest.split(|b| *b == b','); + let mut iter = bytes_split1(rest, b','); let mut verifier = None; while let Some([b'v', _, local_rest @ ..]) = iter.next() { verifier = Some(local_rest); diff --git a/wtx/src/database/client/postgres/config.rs b/wtx/src/database/client/postgres/config.rs index 5e32c987..be4fc526 100644 --- a/wtx/src/database/client/postgres/config.rs +++ b/wtx/src/database/client/postgres/config.rs @@ -1,4 +1,4 @@ -use crate::misc::UriRef; +use crate::misc::{atoi, str_split1, UriRef}; use core::time::Duration; /// Configuration @@ -29,13 +29,13 @@ impl<'data> Config<'data> { keepalives: true, load_balance_hosts: LoadBalanceHosts::Disable, password: uri.password(), - port: uri.port().parse()?, + port: atoi(uri.port().as_bytes())?, target_session_attrs: TargetSessionAttrs::Any, tcp_user_timeout: Duration::ZERO, user: uri.user(), }; - for key_value in uri.query().split('&') { - let mut iter = key_value.split(':'); + for key_value in str_split1(uri.query(), b'&') { + let mut iter = str_split1(key_value, b':'); if let [Some(key), Some(value)] = [iter.next(), iter.next()] { this.set_param(key, value)?; } @@ -49,7 +49,7 @@ impl<'data> Config<'data> { self.app_name = value; } "connect_timeout" => { - if let Ok(timeout) = value.parse::() { + if let Ok(timeout) = atoi(value.as_bytes()) { self.connect_timeout = Duration::from_secs(timeout); } } @@ -68,7 +68,7 @@ impl<'data> Config<'data> { }; } "tcp_user_timeout" => { - if let Ok(timeout) = value.parse::() { + if let Ok(timeout) = atoi(value.as_bytes()) { self.tcp_user_timeout = Duration::from_secs(timeout); } } diff --git a/wtx/src/database/client/postgres/db_error.rs b/wtx/src/database/client/postgres/db_error.rs index b3306ef0..04b48222 100644 --- a/wtx/src/database/client/postgres/db_error.rs +++ b/wtx/src/database/client/postgres/db_error.rs @@ -1,4 +1,7 @@ -use crate::database::client::postgres::SqlState; +use crate::{ + database::client::postgres::SqlState, + misc::{str_split1, Usize, _usize_range_from_u32_range, atoi}, +}; use alloc::boxed::Box; use core::{ fmt::{Debug, Formatter}, @@ -13,7 +16,7 @@ pub enum ErrorPosition { /// Byte position. position: u32, /// Query generated by the server. - query: Range, + query: Range, }, /// Position in the original query. Original(u32), @@ -47,21 +50,21 @@ create_enum! { pub struct DbError { buffer: Box, code: SqlState, - column: Option>, - constraint: Option>, - datatype: Option>, - detail: Option>, - file: Option>, - hint: Option>, + column: Option>, + constraint: Option>, + datatype: Option>, + detail: Option>, + file: Option>, + hint: Option>, line: Option, - message: Range, + message: Range, position: Option, - routine: Option>, - schema: Option>, - severity_localized: Range, + routine: Option>, + schema: Option>, + severity_localized: Range, severity_nonlocalized: Option, - table: Option>, - r#where: Option>, + table: Option>, + r#where: Option>, } impl DbError { @@ -74,38 +77,50 @@ impl DbError { /// If the error was associated with a specific table column, the name of the column. #[inline] pub fn column(&self) -> Option<&str> { - self.column.as_ref().and_then(|range| self.buffer.get(range.clone())) + self + .column + .as_ref() + .and_then(|range| self.buffer.get(_usize_range_from_u32_range(range.clone()))) } /// If the error was associated with a specific constraint, the name of the constraint. #[inline] pub fn constraint(&self) -> Option<&str> { - self.constraint.as_ref().and_then(|range| self.buffer.get(range.clone())) + self + .constraint + .as_ref() + .and_then(|range| self.buffer.get(_usize_range_from_u32_range(range.clone()))) } /// If the error was associated with a specific data type, the name of the data type. #[inline] pub fn datatype(&self) -> Option<&str> { - self.datatype.as_ref().and_then(|range| self.buffer.get(range.clone())) + self + .datatype + .as_ref() + .and_then(|range| self.buffer.get(_usize_range_from_u32_range(range.clone()))) } /// An optional secondary error message carrying more detail about the problem. Might run to /// multiple lines. #[inline] pub fn detail(&self) -> Option<&str> { - self.detail.as_ref().and_then(|range| self.buffer.get(range.clone())) + self + .detail + .as_ref() + .and_then(|range| self.buffer.get(_usize_range_from_u32_range(range.clone()))) } /// The file name of the source-code location where the error was reported. #[inline] pub fn file(&self) -> Option<&str> { - self.file.as_ref().and_then(|range| self.buffer.get(range.clone())) + self.file.as_ref().and_then(|range| self.buffer.get(_usize_range_from_u32_range(range.clone()))) } /// An optional suggestion what to do about the problem. #[inline] pub fn hint(&self) -> Option<&str> { - self.hint.as_ref().and_then(|range| self.buffer.get(range.clone())) + self.hint.as_ref().and_then(|range| self.buffer.get(_usize_range_from_u32_range(range.clone()))) } /// The line number of the source-code location where the error was reported. @@ -117,7 +132,7 @@ impl DbError { /// The primary human-readable error message. #[inline] pub fn message(&self) -> &str { - self.buffer.get(self.message.clone()).unwrap_or_default() + self.buffer.get(_usize_range_from_u32_range(self.message.clone())).unwrap_or_default() } /// The field value is a decimal ASCII integer, indicating an error cursor position as an index @@ -130,20 +145,29 @@ impl DbError { /// The name of the source-code routine reporting the error. #[inline] pub fn routine(&self) -> Option<&str> { - self.routine.as_ref().and_then(|range| self.buffer.get(range.clone())) + self + .routine + .as_ref() + .and_then(|range| self.buffer.get(_usize_range_from_u32_range(range.clone()))) } /// If the error was associated with a specific database object, the name of the schema /// containing that object, if any. #[inline] pub fn schema(&self) -> Option<&str> { - self.schema.as_ref().and_then(|range| self.buffer.get(range.clone())) + self + .schema + .as_ref() + .and_then(|range| self.buffer.get(_usize_range_from_u32_range(range.clone()))) } /// Localized severity. #[inline] pub fn severity_localized(&self) -> &str { - self.buffer.get(self.severity_localized.clone()).unwrap_or_default() + self + .buffer + .get(_usize_range_from_u32_range(self.severity_localized.clone())) + .unwrap_or_default() } /// Nonlocalized `severity`. @@ -155,14 +179,20 @@ impl DbError { /// If the error was associated with a specific table, the name of the table. #[inline] pub fn table(&self) -> Option<&str> { - self.table.as_ref().and_then(|range| self.buffer.get(range.clone())) + self + .table + .as_ref() + .and_then(|range| self.buffer.get(_usize_range_from_u32_range(range.clone()))) } /// An indication of the context in which the error occurred. Presently this includes a call /// stack traceback of active procedural language functions and internally-generated queries. #[inline] pub fn r#where(&self) -> Option<&str> { - self.r#where.as_ref().and_then(|range| self.buffer.get(range.clone())) + self + .r#where + .as_ref() + .and_then(|range| self.buffer.get(_usize_range_from_u32_range(range.clone()))) } } @@ -214,9 +244,9 @@ impl TryFrom<&str> for DbError { let mut table = None; let mut r#where = None; - let mut idx: usize = 0; + let mut idx: u32 = 0; loop { - let Some(curr) = from.get(idx..) else { + let Some(curr) = from.get(*Usize::from(idx)..) else { break; }; if curr.is_empty() { @@ -230,20 +260,27 @@ impl TryFrom<&str> for DbError { } return Err(crate::Error::UnexpectedString { length: rest.len() }); } - let Some((data, _)) = rest.split_once('\0') else { + let Some(data) = str_split1(rest, b'\0').next() else { return Err(crate::Error::UnexpectedEOF); }; let begin = idx; - let end = idx.wrapping_add(data.len()); + let (end, new_idx) = u32::try_from(data.len()) + .ok() + .and_then(|data_len_u32| { + let end = idx.checked_add(data_len_u32)?; + let new_idx = end.checked_add(1)?; + Some((end, new_idx)) + }) + .unwrap_or((u32::MAX, u32::MAX)); let range = begin..end; - idx = end.wrapping_add(1); + idx = new_idx; match ty { "C" => code = Some(SqlState::try_from(data)?), "D" => detail = Some(range), "H" => hint = Some(range), - "L" => line = Some(data.parse::()?), + "L" => line = Some(atoi(data.as_bytes())?), "M" => message = Some(range), - "P" => normal_position = Some(data.parse::()?), + "P" => normal_position = Some(atoi(data.as_bytes())?), "R" => routine = Some(range), "S" => severity_localized = Some(range), "V" => severity_nonlocalized = Some(Severity::try_from(data)?), @@ -252,18 +289,18 @@ impl TryFrom<&str> for DbError { "d" => datatype = Some(range), "F" => file = Some(range), "n" => constraint = Some(range), - "p" => internal_position = Some(data.parse::()?), + "p" => internal_position = Some(atoi(data.as_bytes())?), "q" => internal_query = Some(range), "s" => schema = Some(range), "t" => table = Some(range), _ => { - return Err(crate::Error::UnexpectedUint { received: ty.parse()? }); + return Err(crate::Error::UnexpectedUint { received: atoi(ty.as_bytes())? }); } } } Ok(Self { - buffer: from.get(..idx).unwrap_or_default().into(), + buffer: from.get(..*Usize::from(idx)).unwrap_or_default().into(), code: code.ok_or(crate::Error::NoInnerValue("No code"))?, column, constraint, diff --git a/wtx/src/database/client/postgres/executor/authentication.rs b/wtx/src/database/client/postgres/executor/authentication.rs index 0ecb84bf..cc9478dc 100644 --- a/wtx/src/database/client/postgres/executor/authentication.rs +++ b/wtx/src/database/client/postgres/executor/authentication.rs @@ -6,7 +6,7 @@ use crate::{ }, Identifier, }, - misc::{from_utf8_basic_rslt, FilledBufferWriter, PartitionedFilledBuffer, Stream}, + misc::{bytes_split1, from_utf8_basic_rslt, FilledBufferWriter, PartitionedFilledBuffer, Stream}, rng::Rng, }; use alloc::vec::Vec; @@ -64,7 +64,7 @@ where } MessageTy::Authentication(Authentication::Sasl(data)) => { let mut has_sasl_plus = false; - for elem in data.split(|byte| *byte == b'\0') { + for elem in bytes_split1(data, b'\0') { if elem == b"SCRAM-SHA-256-PLUS" { has_sasl_plus = true; } diff --git a/wtx/src/database/client/postgres/executor/prepare.rs b/wtx/src/database/client/postgres/executor/prepare.rs index ae026bb8..bbb4fcb2 100644 --- a/wtx/src/database/client/postgres/executor/prepare.rs +++ b/wtx/src/database/client/postgres/executor/prepare.rs @@ -1,8 +1,3 @@ -#![allow( - // Borrow checker limitation - clippy::unreachable -)] - use crate::{ database::{ client::postgres::{ @@ -17,7 +12,7 @@ use crate::{ }, RecordValues, StmtCmd, }, - misc::{FilledBufferWriter, PartitionedFilledBuffer, Stream}, + misc::{FilledBufferWriter, PartitionedFilledBuffer, Stream, _unreachable}, }; use arrayvec::ArrayString; use core::{borrow::BorrowMut, ops::Range}; @@ -111,7 +106,7 @@ where if let Some(stmt) = builder.finish().get_by_stmt_hash(stmt_hash) { Ok((stmt_hash, stmt_id_str, stmt)) } else { - unreachable!() + _unreachable() } } diff --git a/wtx/src/database/client/postgres/field.rs b/wtx/src/database/client/postgres/field.rs index e82cd0ab..bbc58f7a 100644 --- a/wtx/src/database/client/postgres/field.rs +++ b/wtx/src/database/client/postgres/field.rs @@ -1,4 +1,7 @@ -use crate::{database::client::postgres::Oid, misc::from_utf8_basic_rslt}; +use crate::{ + database::client::postgres::Oid, + misc::{bytes_pos1, from_utf8_basic_rslt}, +}; #[derive(Debug)] pub(crate) struct MsgField<'bytes> { @@ -8,12 +11,8 @@ pub(crate) struct MsgField<'bytes> { impl<'bytes> MsgField<'bytes> { pub(crate) fn parse(value: &'bytes [u8]) -> crate::Result<(usize, Self)> { - let (name_bytes, rest_bytes) = value.split_at( - value - .iter() - .position(|el| *el == b'\0') - .ok_or(crate::Error::UnexpectedDatabaseMessageBytes)?, - ); + let (name_bytes, rest_bytes) = + value.split_at(bytes_pos1(value, b'\0').ok_or(crate::Error::UnexpectedDatabaseMessageBytes)?); let &[_, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, ..] = rest_bytes else { return Err(crate::Error::UnexpectedDatabaseMessageBytes); }; diff --git a/wtx/src/database/client/postgres/message.rs b/wtx/src/database/client/postgres/message.rs index f22096bd..62e1c800 100644 --- a/wtx/src/database/client/postgres/message.rs +++ b/wtx/src/database/client/postgres/message.rs @@ -1,6 +1,6 @@ use crate::{ database::client::postgres::{Authentication, DbError}, - misc::{_atoi, from_utf8_basic_rslt}, + misc::{atoi, bytes_rsplit1, bytes_split1, from_utf8_basic_rslt}, }; use core::any::type_name; @@ -66,13 +66,12 @@ impl<'bytes> TryFrom<(&mut bool, &'bytes [u8])> for MessageTy<'bytes> { [b'3', ..] => Self::CloseComplete, [b'A', ..] => Self::NotificationResponse, [b'C', _, _, _, _, rest @ ..] => { - let rows = rest - .rsplit(|&el| el == b' ') + let rows = bytes_rsplit1(rest, b' ') .next() .and_then( |el| { if let [all_but_last @ .., _] = el { - _atoi(all_but_last).ok() + atoi(all_but_last).ok() } else { None } @@ -94,7 +93,7 @@ impl<'bytes> TryFrom<(&mut bool, &'bytes [u8])> for MessageTy<'bytes> { [b'R', _, _, _, _, rest @ ..] => Self::Authentication(rest.try_into()?), [b'S', _, _, _, _, rest @ ..] => { let rslt = || { - let mut iter = rest.split(|elem| elem == &0); + let mut iter = bytes_split1(rest, b'\0'); let name = iter.next()?; let value = iter.next()?; let _ = iter.next()?; diff --git a/wtx/src/database/client/postgres/record.rs b/wtx/src/database/client/postgres/record.rs index d6e88334..23110d56 100644 --- a/wtx/src/database/client/postgres/record.rs +++ b/wtx/src/database/client/postgres/record.rs @@ -1,6 +1,9 @@ -use crate::database::{ - client::postgres::{statements::Statement, DecodeValue, Postgres}, - Database, ValueIdent, +use crate::{ + database::{ + client::postgres::{statements::Statement, DecodeValue, Postgres}, + Database, ValueIdent, + }, + misc::{_unlikely_dflt, _unlikely_elem}, }; use alloc::vec::Vec; use core::{marker::PhantomData, ops::Range}; @@ -72,7 +75,7 @@ impl<'exec, E> Record<'exec, E> { stmt, values_bytes_offsets: values_bytes_offsets .get(values_bytes_offsets_start..) - .unwrap_or_default(), + .unwrap_or_else(_unlikely_dflt), }) } } @@ -97,14 +100,24 @@ where CI: ValueIdent>, { let idx = ci.idx(self)?; - let (is_null, range) = self.values_bytes_offsets.get(idx)?; + let (is_null, range) = match self.values_bytes_offsets.get(idx) { + None => return _unlikely_elem(None), + Some(elem) => elem, + }; if *is_null { None } else { let begin = range.start.wrapping_sub(self.initial_value_offset); - let column = self.stmt.columns.get(idx)?; + let column = match self.stmt.columns.get(idx) { + None => return _unlikely_elem(None), + Some(elem) => elem, + }; let end = range.end.wrapping_sub(self.initial_value_offset); - Some(DecodeValue::new(self.bytes.get(begin..end)?, &column.ty)) + let bytes = match self.bytes.get(begin..end) { + None => return _unlikely_elem(None), + Some(elem) => elem, + }; + Some(DecodeValue::new(bytes, &column.ty)) } } } diff --git a/wtx/src/database/client/postgres/statements.rs b/wtx/src/database/client/postgres/statements.rs index e3571bf5..6a5ec7d9 100644 --- a/wtx/src/database/client/postgres/statements.rs +++ b/wtx/src/database/client/postgres/statements.rs @@ -1,10 +1,6 @@ -#![allow( - // Indices point to valid data - clippy::unreachable -)] - use crate::{ database::{client::postgres::ty::Ty, Identifier}, + misc::{_random_state, _unreachable}, rng::Rng, }; use ahash::RandomState; @@ -20,42 +16,34 @@ const NUM_OF_ELEMENTS_TO_REMOVE_WHEN_FULL: u8 = 8; /// Statements #[derive(Debug)] pub struct Statements { - columns: VecDeque, columns_start: usize, - hasher: RandomState, - info_by_cmd_hash: HashMap, + columns: VecDeque, info_by_cmd_hash_start: usize, + info_by_cmd_hash: HashMap, info: VecDeque, max_stmts: usize, num_of_elements_to_remove_when_full: u8, - params: VecDeque, params_start: usize, + params: VecDeque, + rs: RandomState, } impl Statements { - pub(crate) fn new(max_stmts: usize, rng: &mut RNG) -> Self + pub(crate) fn new(max_stmts: usize, rng: RNG) -> Self where RNG: Rng, { - let (seed0, seed1) = { - let [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = rng.u8_16(); - (u64::from_ne_bytes([a, b, c, d, e, f, g, h]), u64::from_ne_bytes([i, j, k, l, m, n, o, p])) - }; - let (seed2, seed3) = { - let [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = rng.u8_16(); - (u64::from_ne_bytes([a, b, c, d, e, f, g, h]), u64::from_ne_bytes([i, j, k, l, m, n, o, p])) - }; Self { columns: VecDeque::with_capacity(INITIAL_ELEMENTS_CAP.saturating_mul(AVG_STMT_COLUMNS_LEN)), columns_start: 0, - info: VecDeque::with_capacity(INITIAL_ELEMENTS_CAP), info_by_cmd_hash: HashMap::with_capacity(INITIAL_ELEMENTS_CAP), info_by_cmd_hash_start: 0, - hasher: RandomState::with_seeds(seed0, seed1, seed2, seed3), + info: VecDeque::with_capacity(INITIAL_ELEMENTS_CAP), max_stmts, num_of_elements_to_remove_when_full: NUM_OF_ELEMENTS_TO_REMOVE_WHEN_FULL, params: VecDeque::with_capacity(INITIAL_ELEMENTS_CAP.saturating_mul(AVG_STMT_PARAMS_LEN)), params_start: 0, + rs: _random_state(rng), } } @@ -66,7 +54,7 @@ impl Statements { info: VecDeque::new(), info_by_cmd_hash: HashMap::new(), info_by_cmd_hash_start: 0, - hasher: RandomState::with_seeds(0, 0, 0, 0), + rs: RandomState::with_seeds(0, 0, 0, 0), max_stmts: 0, num_of_elements_to_remove_when_full: 0, params: VecDeque::new(), @@ -85,7 +73,7 @@ impl Statements { let Self { columns, columns_start, - hasher: _, + rs: _, info_by_cmd_hash, info_by_cmd_hash_start, info, @@ -108,7 +96,7 @@ impl Statements { info_idx = info_idx.wrapping_sub(self.info_by_cmd_hash_start); let info_slice_opt = self.info.as_slices().0.get(..=info_idx); let (columns_range, params_range) = match info_slice_opt { - None | Some([]) => unreachable!(), + None | Some([]) => _unreachable(), Some([a]) => ( 0..a.columns_offset.wrapping_sub(self.columns_start), 0..a.params_offset.wrapping_sub(self.params_start), @@ -131,12 +119,12 @@ impl Statements { if let (Some(a), Some(b)) = (columns.get(columns_range), params.get(params_range)) { Some(Statement::new(a, b)) } else { - unreachable!(); + _unreachable(); } } pub(crate) fn hasher_mut(&mut self) -> &mut RandomState { - &mut self.hasher + &mut self.rs } pub(crate) fn push(&mut self, stmt_hash: u64) -> PushRslt<'_> { diff --git a/wtx/src/database/client/postgres/tys.rs b/wtx/src/database/client/postgres/tys.rs index 53f8d6e7..7a61e28f 100644 --- a/wtx/src/database/client/postgres/tys.rs +++ b/wtx/src/database/client/postgres/tys.rs @@ -214,7 +214,7 @@ mod pg_numeric { client::postgres::{DecodeValue, Postgres, Ty}, Decode, Encode, }, - misc::FilledBufferWriter, + misc::{FilledBufferWriter, Usize}, }; use arrayvec::ArrayVec; @@ -238,7 +238,7 @@ mod pg_numeric { return Err( crate::Error::UnexpectedBufferSize { expected: 8, - received: input.bytes().len().try_into().map_err(Into::into)?, + received: Usize::from(input.bytes().len()).into(), } .into(), ); @@ -338,7 +338,7 @@ mod primitives { client::postgres::{DecodeValue, Postgres, Ty}, Decode, Encode, }, - misc::FilledBufferWriter, + misc::{FilledBufferWriter, Usize}, }; use core::mem; @@ -352,7 +352,7 @@ mod primitives { return Err( crate::Error::UnexpectedBufferSize { expected: 1, - received: input.bytes().len().try_into().map_err(Into::into)?, + received: Usize::from(input.bytes().len()).into(), } .into(), ); @@ -423,8 +423,8 @@ mod primitives { return Ok(<$ty>::from_be_bytes([$($elem),+])); } Err(crate::Error::UnexpectedBufferSize { - expected: mem::size_of::<$ty>().try_into().map_err(Into::into)?, - received: input.bytes().len().try_into().map_err(Into::into)?, + expected: Usize::from(mem::size_of::<$ty>()).into(), + received: Usize::from(input.bytes().len()).into() }.into()) } } diff --git a/wtx/src/database/orm/misc.rs b/wtx/src/database/orm/misc.rs index f241b348..78e1eefc 100644 --- a/wtx/src/database/orm/misc.rs +++ b/wtx/src/database/orm/misc.rs @@ -1,12 +1,9 @@ -mod fx_hasher; - use crate::database::{ orm::{AuxNodes, FullTableAssociation, Table, TableParams}, Database, Decode, FromRecords, Record, Records, TableSuffix, ValueIdent, }; use alloc::string::String; use core::fmt::Write; -pub(crate) use fx_hasher::FxHasher; /// Seeks all rows that equals `T`'s primary key and suffix. Can be `T` itself or any other /// associated/related entity. diff --git a/wtx/src/database/orm/table_params.rs b/wtx/src/database/orm/table_params.rs index bbd91060..10b12e58 100644 --- a/wtx/src/database/orm/table_params.rs +++ b/wtx/src/database/orm/table_params.rs @@ -1,6 +1,9 @@ -use crate::database::{ - orm::{FxHasher, Table, TableField}, - TableSuffix, +use crate::{ + database::{ + orm::{Table, TableField}, + TableSuffix, + }, + misc::FxHasher, }; use core::{ hash::{Hash, Hasher}, @@ -85,7 +88,6 @@ where T::update_all_table_fields(entity, self) } - #[inline] pub(crate) fn instance_hash(&self) -> u64 { let mut fx_hasher = FxHasher::default(); T::PRIMARY_KEY_NAME.hash(&mut fx_hasher); diff --git a/wtx/src/database/record.rs b/wtx/src/database/record.rs index d167a6cb..997d80b9 100644 --- a/wtx/src/database/record.rs +++ b/wtx/src/database/record.rs @@ -1,4 +1,7 @@ -use crate::database::{Database, Decode, ValueIdent}; +use crate::{ + database::{Database, Decode, ValueIdent}, + misc::_unlikely_elem, +}; /// A collection of values. pub trait Record { @@ -12,7 +15,7 @@ pub trait Record { CI: ValueIdent<::Record<'this>>, D: Decode<'this, Self::Database>, { - D::decode(&self.value(ci).ok_or(crate::Error::AbsentFieldDataInDecoding)?) + D::decode(&self.value(ci).ok_or(_unlikely_elem(crate::Error::AbsentFieldDataInDecoding))?) } /// Tries to retrieve and decode an optional value. diff --git a/wtx/src/database/schema_manager/migration/db_migration.rs b/wtx/src/database/schema_manager/migration/db_migration.rs index b5023655..7635260c 100644 --- a/wtx/src/database/schema_manager/migration/db_migration.rs +++ b/wtx/src/database/schema_manager/migration/db_migration.rs @@ -3,7 +3,7 @@ use crate::{ schema_manager::{MigrationCommon, MigrationGroup, Repeatability}, DatabaseTy, Identifier, }, - misc::_atoi, + misc::atoi, }; use chrono::{DateTime, NaiveDateTime, Utc}; use core::fmt; @@ -88,7 +88,7 @@ impl fmt::Display for DbMigration { } fn _checksum_from_str(bytes: &[u8]) -> crate::Result { - _atoi(bytes).map_err(|_err| crate::Error::ChecksumMustBeANumber) + atoi(bytes).map_err(|_err| crate::Error::ChecksumMustBeANumber) } fn _fixed_from_naive_utc(naive: NaiveDateTime) -> DateTime { diff --git a/wtx/src/database/schema_manager/migration_parser.rs b/wtx/src/database/schema_manager/migration_parser.rs index eefeda0c..783db66f 100644 --- a/wtx/src/database/schema_manager/migration_parser.rs +++ b/wtx/src/database/schema_manager/migration_parser.rs @@ -1,11 +1,14 @@ //! Migration file parser -use crate::database::{ - schema_manager::{ - toml_parser::{toml, Expr}, - Repeatability, +use crate::{ + database::{ + schema_manager::{ + toml_parser::{toml, Expr}, + Repeatability, + }, + DatabaseTy, }, - DatabaseTy, + misc::str_split1, }; use arrayvec::ArrayVec; use std::io::{BufRead, BufReader, Read}; @@ -43,7 +46,7 @@ where iterations(&mut overall_buffer, &mut br, |_| false)?; if let Some(rslt) = overall_buffer.split("-- wtx dbs").nth(1) { - for db_str in rslt.split(',') { + for db_str in str_split1(rslt, b',') { if let Ok(db) = db_str.trim().try_into() { let is_not_already_inserted = !parsed_migration.cfg.dbs.contains(&db); if is_not_already_inserted { diff --git a/wtx/src/database/schema_manager/misc.rs b/wtx/src/database/schema_manager/misc.rs index e5e1a05c..5b5eabd6 100644 --- a/wtx/src/database/schema_manager/misc.rs +++ b/wtx/src/database/schema_manager/misc.rs @@ -249,7 +249,7 @@ fn dir_name_parts(s: &str) -> crate::Result<(String, i32)> { return None; } let mut split = s.split("__"); - let version = split.next()?.parse::().ok()?; + let version = crate::misc::atoi(split.next()?.as_bytes()).ok()?; let name = split.next()?.into(); Some((name, version)) }; @@ -264,7 +264,7 @@ fn migration_file_name_parts(s: &str) -> crate::Result<(String, i32)> { return None; } let mut split = s.split("__"); - let version = split.next()?.parse::().ok()?; + let version = crate::misc::atoi(split.next()?.as_bytes()).ok()?; let name = split.next()?.strip_suffix(".sql")?.into(); Some((name, version)) }; diff --git a/wtx/src/database/schema_manager/toml_parser.rs b/wtx/src/database/schema_manager/toml_parser.rs index e8f8cb22..c579ffd0 100644 --- a/wtx/src/database/schema_manager/toml_parser.rs +++ b/wtx/src/database/schema_manager/toml_parser.rs @@ -1,5 +1,6 @@ //! Migration TOML parser +use crate::misc::str_split1; use arrayvec::{ArrayString, ArrayVec}; use std::io::{BufRead, BufReader, Read}; @@ -92,7 +93,7 @@ fn try_parse_expr_array(s: &str) -> crate::Result { if s.is_empty() { return Ok(array); } - for elem in s.split(',') { + for elem in str_split1(s, b',') { let expr_string = try_parse_expr_string(elem.trim())?; array.try_push(expr_string).map_err(|_err| crate::Error::TomlValueIsTooLarge)?; } @@ -101,7 +102,7 @@ fn try_parse_expr_array(s: &str) -> crate::Result { #[inline] fn try_parse_expr_string(s: &str) -> crate::Result { - let mut iter = s.split('"'); + let mut iter = str_split1(s, b'"'); let _ = iter.next().ok_or(crate::Error::TomlParserOnlySupportsStringsAndArraysOfStrings)?; let value = iter.next().ok_or(crate::Error::TomlParserOnlySupportsStringsAndArraysOfStrings)?; let _ = iter.next().ok_or(crate::Error::TomlParserOnlySupportsStringsAndArraysOfStrings)?; diff --git a/wtx/src/error.rs b/wtx/src/error.rs index 1676f601..ace26b5b 100644 --- a/wtx/src/error.rs +++ b/wtx/src/error.rs @@ -1,5 +1,8 @@ use crate::http::ExpectedHeader; -use core::fmt::{Debug, Display, Formatter}; +use core::{ + fmt::{Debug, Display, Formatter}, + ops::RangeInclusive, +}; #[allow(unused_imports)] use {alloc::boxed::Box, alloc::string::String}; @@ -77,6 +80,8 @@ pub enum Error { TokioRustLsError(Box), #[cfg(feature = "_tracing-subscriber")] TryInitError(tracing_subscriber::util::TryInitError), + #[cfg(feature = "x509-certificate")] + X509CertificateError(Box), // External - Std // @@ -144,8 +149,8 @@ pub enum Error { StatementHashCollision, /// Received size differs from expected size. UnexpectedBufferSize { - expected: u32, - received: u32, + expected: u64, + received: u64, }, /// Received an unexpected message type. UnexpectedDatabaseMessage { @@ -220,6 +225,11 @@ pub enum Error { UnexpectedUint { received: u32, }, + /// Unexpected Unsigned integer + UnboundedNumber { + expected: RangeInclusive, + received: u32, + }, /// Missing Header MissingHeader { @@ -249,7 +259,27 @@ pub enum Error { /// Stream does not support TLS channels. StreamDoesNotSupportTlsChannels, + // ***** Internal - HTTP ***** + // + /// Unknown header name. + UnknownHeaderName, + + // ***** Internal - HTTP/2 ***** + // + /// Unknown header name. + UnexpectedPreFixedHeaderName, + /// Decoding logic encountered an unexpected ending string signal. + UnexpectedEndingHuffman, + /// A container does not contain an element referred by the given idx + InvalidHpackIdx(usize), + /// Header integers must be equal or lesser than `u16::MAX` + VeryLargeHeaderInteger, + /// Size updates of dynamic table can't be placed after the first header + InvalidDynTableSizeUpdate, + // ***** Internal - PM ***** + // + /// Zero fixed pools are unsupported StaticPoolMustHaveCapacityForAtLeastOneElement, // ***** Internal - WebSocket ***** @@ -573,6 +603,14 @@ impl From for Error { } } +#[cfg(feature = "x509-certificate")] +impl From for Error { + #[inline] + fn from(from: x509_certificate::X509CertificateError) -> Self { + Self::X509CertificateError(from.into()) + } +} + /// The error type for operations interacting with environment variables. #[cfg(feature = "std")] #[derive(Clone, Copy, Debug)] diff --git a/wtx/src/http.rs b/wtx/src/http.rs index 89b3255e..6cbf6c79 100644 --- a/wtx/src/http.rs +++ b/wtx/src/http.rs @@ -1,7 +1,10 @@ //! Generic HTTP elements +mod abstract_headers; mod expected_header; -mod header; +mod generic_header; +mod header_name; +mod headers; mod method; mod mime; mod request; @@ -9,8 +12,11 @@ mod response; mod status_code; mod version; +pub(crate) use abstract_headers::AbstractHeaders; pub use expected_header::ExpectedHeader; -pub use header::Header; +pub use generic_header::GenericHeader; +pub use header_name::*; +pub use headers::Headers; pub use method::Method; pub use mime::Mime; pub use request::Request; diff --git a/wtx/src/http/abstract_headers.rs b/wtx/src/http/abstract_headers.rs new file mode 100644 index 00000000..ea9a5893 --- /dev/null +++ b/wtx/src/http/abstract_headers.rs @@ -0,0 +1,419 @@ +use crate::misc::{Usize, _unlikely_cb, _unlikely_elem}; +use alloc::collections::VecDeque; +use core::ops::Range; + +const DFLT_MAX_BYTES: u32 = 4 * 1024; + +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct AbstractHeader<'ah, M> { + pub(crate) misc: &'ah M, + pub(crate) name_bytes: &'ah [u8], + pub(crate) name_range: Range, + pub(crate) value_bytes: &'ah [u8], +} + +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct AbstractHeaders { + buffer: VecDeque, + elements_len: u32, + first_idx: u32, + max_bytes: u32, + metadata: VecDeque>, +} + +impl AbstractHeaders { + pub(crate) fn with_capacity(len: u32) -> Self { + Self { + buffer: VecDeque::with_capacity(*Usize::from(len)), + elements_len: 0, + first_idx: 0, + max_bytes: DFLT_MAX_BYTES, + metadata: VecDeque::with_capacity(*Usize::from(len)), + } + } + + // Insertions are limited to u32 + #[allow(clippy::cast_possible_truncation, clippy::as_conversions)] + pub(crate) fn bytes_len(&self) -> u32 { + self.buffer.len() as u32 + } + + pub(crate) fn clear(&mut self) { + let Self { buffer, elements_len, first_idx, max_bytes: _, metadata } = self; + buffer.clear(); + *elements_len = 0; + *first_idx = 0; + metadata.clear(); + } + + pub(crate) fn elements_len(&self) -> u32 { + self.elements_len + } + + pub(crate) fn get_by_idx(&self, idx: usize) -> Option> { + let Some(Metadata { is_activated, name_begin_idx, misc, sep_idx, value_end_idx }) = + self.metadata.get(idx) + else { + return _unlikely_elem(None); + }; + if !is_activated { + return _unlikely_elem(None); + } + let Some(name_bytes) = self.buffer.as_slices().0.get( + *Usize::from(name_begin_idx.wrapping_sub(self.first_idx)) + ..*Usize::from(sep_idx.wrapping_sub(self.first_idx)), + ) else { + return _unlikely_elem(None); + }; + let Some(value_bytes) = self.buffer.as_slices().0.get( + *Usize::from(sep_idx.wrapping_sub(self.first_idx)) + ..*Usize::from(value_end_idx.wrapping_sub(self.first_idx)), + ) else { + return _unlikely_elem(None); + }; + Some(AbstractHeader { misc, name_bytes, name_range: *name_begin_idx..*sep_idx, value_bytes }) + } + + pub(crate) fn get_by_name(&self, name: &[u8]) -> Option> { + self.iter().find(|elem| (name == elem.name_bytes)) + } + + pub(crate) fn iter(&self) -> impl Iterator> { + self.metadata.iter().filter_map( + |Metadata { is_activated, name_begin_idx, misc, sep_idx, value_end_idx }| { + if !is_activated { + return None; + } + let Some(name_bytes) = self.buffer.as_slices().0.get( + *Usize::from(name_begin_idx.wrapping_sub(self.first_idx)) + ..*Usize::from(sep_idx.wrapping_sub(self.first_idx)), + ) else { + return _unlikely_elem(None); + }; + let Some(value_bytes) = self.buffer.as_slices().0.get( + *Usize::from(sep_idx.wrapping_sub(self.first_idx)) + ..*Usize::from(value_end_idx.wrapping_sub(self.first_idx)), + ) else { + return _unlikely_elem(None); + }; + Some(AbstractHeader { + misc, + name_bytes, + name_range: *name_begin_idx..*sep_idx, + value_bytes, + }) + }, + ) + } + + pub(crate) fn max_bytes(&self) -> u32 { + self.max_bytes + } + + pub(crate) fn normalize_indcs(&mut self) { + let mut iter = self.metadata.as_mut_slices().0.iter_mut(); + let first = if let Some(elem) = iter.next() { + let first = elem.name_begin_idx; + elem.name_begin_idx = elem.name_begin_idx.wrapping_sub(first); + elem.sep_idx = elem.sep_idx.wrapping_sub(first); + elem.value_end_idx = elem.value_end_idx.wrapping_sub(first); + first + } else { + return; + }; + for elem in iter { + elem.name_begin_idx = elem.name_begin_idx.wrapping_sub(first); + elem.sep_idx = elem.sep_idx.wrapping_sub(first); + elem.value_end_idx = elem.value_end_idx.wrapping_sub(first); + } + } + + pub(crate) fn pop_back(&mut self) { + let Some(Metadata { name_begin_idx, .. }) = self.metadata.pop_back() else { + return; + }; + self.buffer.truncate(*Usize::from(name_begin_idx)); + self.elements_len = self.elements_len.wrapping_sub(1); + } + + pub(crate) fn pop_front(&mut self) { + let Some(Metadata { value_end_idx, .. }) = self.metadata.pop_front() else { + return; + }; + for _ in 0..value_end_idx.wrapping_sub(self.first_idx) { + let _ = self.buffer.pop_front(); + } + self.elements_len = self.elements_len.wrapping_sub(1); + self.first_idx = value_end_idx; + } + + pub(crate) fn push(&mut self, misc: M, name: &[u8], value: &[u8]) { + let local_len = name.len().wrapping_add(value.len()); + if local_len > *Usize::from(self.max_bytes) { + self.clear(); + return; + } + while Usize::from(self.bytes_len()).wrapping_add(local_len) > *Usize::from(self.max_bytes) { + self.pop_front(); + } + if Usize::from(self.first_idx).overflowing_add(local_len).1 { + _unlikely_cb(|| self.normalize_indcs()); + } + let name_begin_idx = self.bytes_len(); + self.buffer.extend(name); + let sep_idx = self.bytes_len(); + self.buffer.extend(value); + let value_begin_idx = self.bytes_len(); + self.push_metadata(misc, name_begin_idx, sep_idx, value_begin_idx); + } + + pub(crate) fn push_metadata( + &mut self, + misc: M, + name_begin_idx: u32, + sep_idx: u32, + value_end_idx: u32, + ) { + self.elements_len = self.elements_len.wrapping_add(1); + self.metadata.push_back(Metadata { + is_activated: true, + misc, + name_begin_idx, + sep_idx, + value_end_idx, + }); + } + + pub(crate) fn remove(&mut self, names: &[&[u8]]) { + if names.is_empty() { + return; + } + let mut names_start = 0; + for metadata in &mut self.metadata { + let Metadata { is_activated, name_begin_idx, misc: _, sep_idx, value_end_idx: _ } = metadata; + if !*is_activated { + continue; + } + let tuple = ( + self.buffer.as_slices().0.get(*Usize::from(*name_begin_idx)..*Usize::from(*sep_idx)), + names.get(names_start..), + ); + let (Some(name_bytes), Some(slice)) = tuple else { + break; + }; + if slice.contains(&name_bytes) { + *is_activated = false; + names_start = names_start.wrapping_add(1); + self.elements_len = self.elements_len.wrapping_sub(1); + } + } + } + + pub(crate) fn set_max_bytes(&mut self, max_bytes: u32) { + self.max_bytes = max_bytes; + while self.bytes_len() > self.max_bytes { + self.pop_front(); + } + } +} + +impl Default for AbstractHeaders { + #[inline] + fn default() -> Self { + Self { + buffer: VecDeque::new(), + elements_len: 0, + first_idx: 0, + max_bytes: DFLT_MAX_BYTES, + metadata: VecDeque::new(), + } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct Metadata { + is_activated: bool, + misc: M, + name_begin_idx: u32, + sep_idx: u32, + value_end_idx: u32, +} + +#[cfg(test)] +mod tests { + use crate::http::{abstract_headers::AbstractHeader, AbstractHeaders}; + + #[test] + fn elements_are_added_and_cleared() { + let mut header = AbstractHeaders::default(); + header.push(0, b"abc", b"def"); + assert_eq!( + header.get_by_name(b"abc"), + Some(AbstractHeader { + misc: &0, + name_range: 0..3, + name_bytes: "abc".as_bytes(), + value_bytes: "def".as_bytes() + }) + ); + assert_eq!( + header.iter().next(), + Some(AbstractHeader { + misc: &0, + name_range: 0..3, + name_bytes: "abc".as_bytes(), + value_bytes: "def".as_bytes() + }) + ); + assert_eq!(header.elements_len(), 1); + assert_eq!(header.bytes_len(), 6); + header.clear(); + assert_eq!(header.get_by_name(b"abc"), None); + assert_eq!(header.iter().next(), None); + assert_eq!(header.elements_len(), 0); + assert_eq!(header.bytes_len(), 0); + } + + #[test] + fn elements_are_added_and_removed() { + let mut header = AbstractHeaders::default(); + + header.push(0, b"abc", b"def"); + assert_eq!( + header.get_by_name(b"abc"), + Some(AbstractHeader { + misc: &0, + name_range: 0..3, + name_bytes: "abc".as_bytes(), + value_bytes: "def".as_bytes() + }) + ); + assert_eq!(header.get_by_name(b"ghi"), None); + assert_eq!( + header.iter().nth(0), + Some(AbstractHeader { + misc: &0, + name_range: 0..3, + name_bytes: "abc".as_bytes(), + value_bytes: "def".as_bytes() + }) + ); + assert_eq!(header.iter().nth(1), None); + assert_eq!(header.iter().nth(2), None); + assert_eq!(header.elements_len(), 1); + assert_eq!(header.bytes_len(), 6); + header.push(1, b"ghi", b"jkl"); + assert_eq!( + header.get_by_name(b"abc"), + Some(AbstractHeader { + misc: &0, + name_range: 0..3, + name_bytes: "abc".as_bytes(), + value_bytes: "def".as_bytes() + }) + ); + assert_eq!( + header.get_by_name(b"ghi"), + Some(AbstractHeader { + misc: &1, + name_range: 6..9, + name_bytes: "ghi".as_bytes(), + value_bytes: "jkl".as_bytes() + }) + ); + assert_eq!( + header.iter().nth(0), + Some(AbstractHeader { + misc: &0, + name_range: 0..3, + name_bytes: "abc".as_bytes(), + value_bytes: "def".as_bytes() + }) + ); + assert_eq!( + header.iter().nth(1), + Some(AbstractHeader { + misc: &1, + name_range: 6..9, + name_bytes: "ghi".as_bytes(), + value_bytes: "jkl".as_bytes() + }) + ); + assert_eq!(header.iter().nth(2), None); + assert_eq!(header.elements_len(), 2); + assert_eq!(header.bytes_len(), 12); + + header.remove(&[b"123"]); + assert_eq!( + header.get_by_name(b"abc"), + Some(AbstractHeader { + misc: &0, + name_range: 0..3, + name_bytes: "abc".as_bytes(), + value_bytes: "def".as_bytes() + }) + ); + assert_eq!( + header.get_by_name(b"ghi"), + Some(AbstractHeader { + misc: &1, + name_range: 6..9, + name_bytes: "ghi".as_bytes(), + value_bytes: "jkl".as_bytes() + }) + ); + assert_eq!( + header.iter().nth(0), + Some(AbstractHeader { + misc: &0, + name_range: 0..3, + name_bytes: "abc".as_bytes(), + value_bytes: "def".as_bytes() + }) + ); + assert_eq!( + header.iter().nth(1), + Some(AbstractHeader { + misc: &1, + name_range: 6..9, + name_bytes: "ghi".as_bytes(), + value_bytes: "jkl".as_bytes() + }) + ); + assert_eq!(header.iter().nth(2), None); + assert_eq!(header.elements_len(), 2); + assert_eq!(header.bytes_len(), 12); + header.remove(&[b"abc"]); + assert_eq!(header.get_by_name(b"abc"), None); + assert_eq!( + header.get_by_name(b"ghi"), + Some(AbstractHeader { + misc: &1, + name_range: 6..9, + name_bytes: "ghi".as_bytes(), + value_bytes: "jkl".as_bytes() + }) + ); + assert_eq!( + header.iter().nth(0), + Some(AbstractHeader { + misc: &1, + name_range: 6..9, + name_bytes: "ghi".as_bytes(), + value_bytes: "jkl".as_bytes() + }) + ); + assert_eq!(header.iter().nth(1), None); + assert_eq!(header.iter().nth(2), None); + assert_eq!(header.elements_len(), 1); + assert_eq!(header.bytes_len(), 12); + header.remove(&[b"ghi"]); + assert_eq!(header.get_by_name(b"abc"), None); + assert_eq!(header.get_by_name(b"ghi"), None); + assert_eq!(header.iter().nth(0), None); + assert_eq!(header.iter().nth(1), None); + assert_eq!(header.iter().nth(2), None); + assert_eq!(header.elements_len(), 0); + assert_eq!(header.bytes_len(), 12); + } +} diff --git a/wtx/src/http/header.rs b/wtx/src/http/generic_header.rs similarity index 77% rename from wtx/src/http/header.rs rename to wtx/src/http/generic_header.rs index 863955d5..7991f864 100644 --- a/wtx/src/http/header.rs +++ b/wtx/src/http/generic_header.rs @@ -1,5 +1,5 @@ /// HTTP header. -pub trait Header { +pub trait GenericHeader { /// Name. fn name(&self) -> &[u8]; @@ -7,7 +7,7 @@ pub trait Header { fn value(&self) -> &[u8]; } -impl Header for () { +impl GenericHeader for () { #[inline] fn name(&self) -> &[u8] { &[] @@ -19,9 +19,9 @@ impl Header for () { } } -impl Header for &T +impl GenericHeader for &T where - T: Header, + T: GenericHeader, { #[inline] fn name(&self) -> &[u8] { @@ -34,7 +34,7 @@ where } } -impl Header for [&[u8]; 2] { +impl GenericHeader for [&[u8]; 2] { #[inline] fn name(&self) -> &[u8] { self[0] diff --git a/wtx/src/http/header_name.rs b/wtx/src/http/header_name.rs new file mode 100644 index 00000000..88d339f4 --- /dev/null +++ b/wtx/src/http/header_name.rs @@ -0,0 +1,221 @@ +macro_rules! create_statics { + ( + $( + $(#[$mac:meta])* + $name:ident = $value:literal; + )* + ) => { + $( + $(#[$mac])* + pub const $name: HeaderNameStaticStr = HeaderNameStaticStr::new($value); + )* + + impl<'hn> TryFrom<&'hn [u8]> for HeaderName<&'hn [u8]> { + type Error = crate::Error; + + #[inline] + fn try_from(from: &'hn [u8]) -> crate::Result { + Ok(match from { + $( + elem if elem == $value.as_bytes() => $name.into(), + )* + _ => Self(from) + }) + } + } + + impl<'hn> TryFrom<&'hn str> for HeaderName<&'hn str> { + type Error = crate::Error; + + #[inline] + fn try_from(from: &'hn str) -> crate::Result { + Ok(match from { + $( + $value => $name, + )* + _ => Self(from) + }) + } + } + + impl<'hn> From> for HeaderName<&'hn [u8]> { + #[inline] + fn from(from: HeaderName<&'hn str>) -> Self { + Self::new(from.0.as_bytes()) + } + } + }; +} + +create_statics! { + /// accept + ACCEPT = "accept"; + /// accept-charset + ACCEPT_CHARSET = "accept-charset"; + /// accept-encoding + ACCEPT_ENCODING = "accept-encoding"; + /// accept-language + ACCEPT_LANGUAGE = "accept-language"; + /// accept-ranges + ACCEPT_RANGES = "accept-ranges"; + /// access-control-allow-credentials + ACCESS_CONTROL_ALLOW_CREDENTIALS = "access-control-allow-credentials"; + /// access-control-allow-headers + ACCESS_CONTROL_ALLOW_HEADERS = "access-control-allow-headers"; + /// access-control-allow-methods + ACCESS_CONTROL_ALLOW_METHODS = "access-control-allow-methods"; + /// access-control-allow-origin + ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin"; + /// access-control-expose-headers + ACCESS_CONTROL_EXPOSE_HEADERS = "access-control-expose-headers"; + /// access-control-max-age + ACCESS_CONTROL_MAX_AGE = "access-control-max-age"; + /// access-control-request-headers + ACCESS_CONTROL_REQUEST_HEADERS = "access-control-request-headers"; + /// access-control-request-method + ACCESS_CONTROL_REQUEST_METHOD = "access-control-request-method"; + /// age + AGE = "age"; + /// allow + ALLOW = "allow"; + /// authorization + AUTHORIZATION = "authorization"; + /// cache-control + CACHE_CONTROL = "cache-control"; + /// clear-site-data + CLEAR_SITE_DATA = "clear-site-data"; + /// Connection + CONNECTION = "connection"; + /// content-disposition + CONTENT_DISPOSITION = "content-disposition"; + /// content-encoding + CONTENT_ENCODING = "content-encoding"; + /// content-language + CONTENT_LANGUAGE = "content-language"; + /// content-length + CONTENT_LENGTH = "content-length"; + /// content-location + CONTENT_LOCATION = "content-location"; + /// content-md5 + CONTENT_MD5 = "content-md5"; + /// content-range + CONTENT_RANGE = "content-range"; + /// content-type + CONTENT_TYPE = "content-type"; + /// cookie + COOKIE = "cookie"; + /// date + DATE = "date"; + /// eTag + ETAG = "etag"; + /// expect + EXPECT = "expect"; + /// expires + EXPIRES = "expires"; + /// forwarded + FORWARDED = "forwarded"; + /// from + FROM = "from"; + /// host + HOST = "host"; + /// if-match + IF_MATCH = "if-match"; + /// if-modified-since + IF_MODIFIED_SINCE = "if-modified-since"; + /// if-none-match + IF_NONE_MATCH = "if-none-match"; + /// if-range + IF_RANGE = "if-range"; + /// if-unmodified-since + IF_UNMODIFIED_SINCE = "if-unmodified-since"; + /// last-modified + LAST_MODIFIED = "last-modified"; + /// link + LINK = "link"; + /// location + LOCATION = "location"; + /// max-forwards + MAX_FORWARDS = "max-forwards"; + /// origin + ORIGIN = "origin"; + /// pragma + PRAGMA = "pragma"; + /// proxy-authenticate + PROXY_AUTHENTICATE = "proxy-authenticate"; + /// proxy-authorization + PROXY_AUTHORIZATION = "proxy-authorization"; + /// proxy-connection + PROXY_CONNECTION = "proxy-connection"; + /// range + RANGE = "range"; + /// referer + REFERER = "referer"; + /// refresh + REFRESH = "refresh"; + /// retry-after + RETRY_AFTER = "retry-after"; + /// server + SERVER_TIMING = "server-timing"; + /// server + SERVER = "server"; + /// set-cookie + SET_COOKIE = "set-cookie"; + /// sourcemap + SOURCE_MAP = "sourcemap"; + /// strict-transport-security + STRICT_TRANSPORT_SECURITY = "strict-transport-security"; + /// te + TE = "te"; + /// timing-allow-origin + TIMING_ALLOW_ORIGIN = "timing-allow-origin"; + /// traceparent + TRACEPARENT = "traceparent"; + /// trailer + TRAILER = "trailer"; + /// transfer-encoding + TRANSFER_ENCODING = "transfer-encoding"; + /// upgrade + UPGRADE = "upgrade"; + /// user-Agent + USER_AGENT = "user-agent"; + /// vary + VARY = "vary"; + /// via + VIA = "via"; + /// warning + WARNING = "warning"; + /// www-authenticate + WWW_AUTHENTICATE = "www-authenticate"; +} + +/// [HeaderName] composed by static bytes. +pub type HeaderNameStaticBytes = HeaderName<&'static [u8]>; +/// [HeaderName] composed by a static string. +pub type HeaderNameStaticStr = HeaderName<&'static str>; + +/// HTTP header name +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct HeaderName(S); + +impl HeaderName +where + S: AsRef<[u8]>, +{ + /// Instance from a generic type content. + #[inline] + pub const fn new(content: S) -> Self { + Self(content) + } + + /// Generic type content in bytes form + #[inline] + pub fn bytes(&self) -> &[u8] { + self.0.as_ref() + } + + /// Generic type content. + #[inline] + pub const fn content(&self) -> &S { + &self.0 + } +} diff --git a/wtx/src/http/headers.rs b/wtx/src/http/headers.rs new file mode 100644 index 00000000..b9d30bb6 --- /dev/null +++ b/wtx/src/http/headers.rs @@ -0,0 +1,91 @@ +use crate::http::AbstractHeaders; + +/// List of pairs sent and received on every request. +#[derive(Debug, Default)] +pub struct Headers { + ab: AbstractHeaders<()>, +} + +impl Headers { + /// Pre-allocates bytes according to the number of passed elements. + #[inline] + pub fn with_capacity(len: u32) -> Self { + Self { ab: AbstractHeaders::with_capacity(len) } + } + + /// The amount of bytes used by all of the headers + #[inline] + pub fn bytes_len(&self) -> u32 { + self.ab.bytes_len() + } + + /// Clears the internal buffer "erasing" all previously inserted elements. + #[inline] + pub fn clear(&mut self) { + self.ab.clear(); + } + + /// The number of headers + #[inline] + pub fn elements_len(&self) -> u32 { + self.ab.elements_len() + } + + /// Returns the header's pair referenced by its index, if any. + #[inline] + pub fn get_by_idx(&self, idx: usize) -> Option<(&[u8], &[u8])> { + self.ab.get_by_idx(idx).map(|el| (el.name_bytes, el.value_bytes)) + } + + /// Returns the header value of the **first** corresponding header `name` key, if any. + #[inline] + pub fn get_by_name(&self, name: &[u8]) -> Option<&[u8]> { + self.ab.get_by_name(name).map(|el| el.value_bytes) + } + + /// Retrieves all stored pairs. + #[inline] + pub fn iter(&self) -> impl Iterator { + self.ab.iter().map(|el| (el.name_bytes, el.value_bytes)) + } + + /// The maximum allowed number of bytes. + #[inline] + pub fn max_bytes(&self) -> u32 { + self.ab.max_bytes() + } + + /// Removes the last element. + #[inline] + pub fn pop_back(&mut self) { + self.ab.pop_back(); + } + + /// Removes the first element. + #[inline] + pub fn pop_front(&mut self) { + self.ab.pop_front(); + } + + /// Pushes a new pair of `name` and `value` at the end of the internal buffer. + /// + /// If the sum of `name` and `value` is greater than the maximum number of bytes, then the first + /// inserted entries will be deleted accordantly. + #[inline] + pub fn push(&mut self, name: &[u8], value: &[u8]) { + self.ab.push((), name, value); + } + + /// Removes all pairs referenced by the `names` parameter. + #[inline] + pub fn remove(&mut self, names: &[&[u8]]) { + self.ab.remove(names); + } + + /// If `max_bytes` is lesser than the current number of bytes, then the first inserted entries + /// will be deleted accordantly. + #[inline] + pub fn set_max_bytes(&mut self, max_bytes: u32) { + self.ab.set_max_bytes(max_bytes); + } +} diff --git a/wtx/src/http/status_code.rs b/wtx/src/http/status_code.rs index ce747fff..8fd5f8eb 100644 --- a/wtx/src/http/status_code.rs +++ b/wtx/src/http/status_code.rs @@ -1,7 +1,6 @@ create_enum! { /// HTTP status codes. #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u16)] pub enum StatusCode { /// 100 Continue Continue = (100), diff --git a/wtx/src/http/version.rs b/wtx/src/http/version.rs index 64e43a46..e55fc64a 100644 --- a/wtx/src/http/version.rs +++ b/wtx/src/http/version.rs @@ -1,6 +1,5 @@ create_enum! { #[derive(Clone, Copy, Debug, Default, PartialEq)] - #[repr(u8)] /// HTTP version pub enum Version { /// HTTP/1 diff --git a/wtx/src/http1.rs b/wtx/src/http1.rs index 3523e653..41d237f7 100644 --- a/wtx/src/http1.rs +++ b/wtx/src/http1.rs @@ -1,11 +1,9 @@ -#![allow( - // All methods are internally called only after parsing - clippy::unreachable - )] +use crate::{ + http::{GenericHeader, Request, Response, Version}, + misc::_unreachable, +}; -use crate::http::{Header, Request, Response, Version}; - -impl Header for httparse::Header<'_> { +impl GenericHeader for httparse::Header<'_> { #[inline] fn name(&self) -> &[u8] { self.name.as_bytes() @@ -23,7 +21,7 @@ impl Request for httparse::Request<'_, '_> { if let Some(el) = self.method { el.as_bytes() } else { - unreachable!() + _unreachable() } } @@ -32,7 +30,7 @@ impl Request for httparse::Request<'_, '_> { if let Some(el) = self.path { el.as_bytes() } else { - unreachable!() + _unreachable() } } @@ -41,9 +39,7 @@ impl Request for httparse::Request<'_, '_> { match self.version { Some(0) => Version::Http1, Some(1) => Version::Http1_1, - _ => { - unreachable!() - } + _ => _unreachable(), } } } @@ -54,7 +50,7 @@ impl Response for httparse::Response<'_, '_> { if let Some(el) = self.code { el } else { - unreachable!() + _unreachable() } } @@ -62,10 +58,8 @@ impl Response for httparse::Response<'_, '_> { fn version(&self) -> Version { match self.version { Some(0) => Version::Http1, - Some(1) => Version::Http2, - _ => { - unreachable!() - } + Some(1) => Version::Http1_1, + _ => _unreachable(), } } } diff --git a/wtx/src/macros.rs b/wtx/src/macros.rs index 6fa00af8..990f3a9c 100644 --- a/wtx/src/macros.rs +++ b/wtx/src/macros.rs @@ -94,6 +94,23 @@ macro_rules! create_enum { Ok(rslt) } } + + impl TryFrom<&[u8]> for $enum_ident { + type Error = crate::Error; + + #[inline] + fn try_from(from: &[u8]) -> crate::Result { + $( + if from == stringify!($variant_ident_fixed).as_bytes() + || from == stringify!($variant_n_fixed).as_bytes() + $(|| from == $variant_str_fixed.as_bytes())? + { + return Ok(Self::$variant_ident_fixed); + } + )* + Err(crate::Error::UnexpectedString { length: from.len() }) + } + } } } @@ -116,6 +133,26 @@ macro_rules! _internal_doc { }; } +macro_rules! _iter4 { + ($slice:expr, |$elem:ident| $block:block) => {{ + let mut iter = crate::misc::ArrayChunks::_new($slice); + for [a, b, c, d] in iter.by_ref() { + let $elem = a; + $block; + let $elem = b; + $block; + let $elem = c; + $block; + let $elem = d; + $block; + } + for elem in iter._into_remainder() { + let $elem = elem; + $block; + } + }}; +} + macro_rules! _iter4_mut { ($slice:expr, |$elem:ident| $block:block) => {{ let mut iter = crate::misc::ArrayChunksMut::_new($slice); diff --git a/wtx/src/misc.rs b/wtx/src/misc.rs index cfdc97f8..1cac8718 100644 --- a/wtx/src/misc.rs +++ b/wtx/src/misc.rs @@ -1,18 +1,16 @@ //! Miscellaneous -#![allow( - // Used by other features - unused_imports -)] - mod array_chunks; mod async_bounds; -mod basic_utf8_error; mod either; mod enum_var_strings; mod filled_buffer_writer; mod fn_mut_fut; +mod fx_hasher; mod generic_time; +mod incomplete_utf8_char; +mod mem_transfer; +mod optimization; mod partitioned_filled_buffer; mod poll_once; mod query_writer; @@ -21,35 +19,37 @@ mod stream; mod tokio_rustls; mod traits; mod uri; +mod usize; +mod utf8_errors; #[cfg(feature = "tokio-rustls")] pub use self::tokio_rustls::{TokioRustlsAcceptor, TokioRustlsConnector}; -#[cfg(test)] -use alloc::string::String; -pub(crate) use array_chunks::ArrayChunksMut; pub use async_bounds::AsyncBounds; -pub use basic_utf8_error::BasicUtf8Error; -use core::{any::type_name, time::Duration}; +use core::{any::type_name, ops::Range, time::Duration}; pub use either::Either; pub use enum_var_strings::EnumVarStrings; pub use filled_buffer_writer::FilledBufferWriter; pub use fn_mut_fut::FnMutFut; +pub use fx_hasher::FxHasher; pub use generic_time::GenericTime; -pub(crate) use partitioned_filled_buffer::PartitionedFilledBuffer; +pub use incomplete_utf8_char::{CompletionErr, IncompleteUtf8Char}; +pub use optimization::*; pub use poll_once::PollOnce; pub use query_writer::QueryWriter; pub use stream::{BytesStream, Stream, TlsStream}; pub use traits::SingleTypeStorage; pub use uri::{Uri, UriRef, UriString}; - -/// Internally uses `simdutf8` if the feature was activated. -#[inline] -pub fn from_utf8_basic_rslt(bytes: &[u8]) -> Result<&str, BasicUtf8Error> { - #[cfg(feature = "simdutf8")] - return simdutf8::basic::from_utf8(bytes).ok().ok_or(BasicUtf8Error {}); - #[cfg(not(feature = "simdutf8"))] - return core::str::from_utf8(bytes).ok().ok_or(BasicUtf8Error {}); -} +pub use usize::Usize; +pub use utf8_errors::{BasicUtf8Error, ExtUtf8Error, StdUtf8Error}; +#[allow( + // Used by other features + unused_imports +)] +pub(crate) use { + array_chunks::{ArrayChunks, ArrayChunksMut}, + mem_transfer::_shift_bytes, + partitioned_filled_buffer::PartitionedFilledBuffer, +}; /// Useful when a request returns an optional field but the actual usage is within a /// [core::result::Result] context. @@ -98,7 +98,6 @@ pub fn tracing_subscriber_init() -> Result<(), tracing_subscriber::util::TryInit prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, EnvFilter, }; let env_filter = EnvFilter::from_default_env(); - let mut tracing_tree = tracing_tree::HierarchicalLayer::default(); #[cfg(feature = "std")] { @@ -115,28 +114,17 @@ pub fn tracing_subscriber_init() -> Result<(), tracing_subscriber::util::TryInit tracing_subscriber::Registry::default().with(env_filter).with(tracing_tree).try_init() } -#[cfg(not(feature = "atoi"))] -pub(crate) fn _atoi(bytes: &[u8]) -> crate::Result -where - T: core::str::FromStr, - T::Err: Into, -{ - Ok(from_utf8_basic_rslt(bytes)?.parse().map_err(Into::into)?) -} -#[cfg(feature = "atoi")] -pub(crate) fn _atoi(bytes: &[u8]) -> crate::Result -where - T: atoi::FromRadix10SignedChecked, -{ - atoi::atoi(bytes).ok_or(crate::Error::AtoiInvalidBytes) -} - -#[cfg(test)] -pub(crate) fn _uri() -> UriString { - use core::sync::atomic::{AtomicU32, Ordering}; - static PORT: AtomicU32 = AtomicU32::new(7000); - let uri = alloc::format!("http://127.0.0.1:{}", PORT.fetch_add(1, Ordering::Relaxed)); - UriString::new(uri) +#[cfg(feature = "ahash")] +pub(crate) fn _random_state(mut rng: impl crate::rng::Rng) -> ahash::RandomState { + let (seed0, seed1) = { + let [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = rng.u8_16(); + (u64::from_ne_bytes([a, b, c, d, e, f, g, h]), u64::from_ne_bytes([i, j, k, l, m, n, o, p])) + }; + let (seed2, seed3) = { + let [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = rng.u8_16(); + (u64::from_ne_bytes([a, b, c, d, e, f, g, h]), u64::from_ne_bytes([i, j, k, l, m, n, o, p])) + }; + ahash::RandomState::with_seeds(seed0, seed1, seed2, seed3) } pub(crate) async fn _read_until( @@ -162,5 +150,48 @@ where } *read = read.wrapping_add(local_read); } - Ok(buffer.get(start..until).and_then(|el| el.try_into().ok()).unwrap_or_default()) + Ok(buffer.get(start..until).and_then(|el| el.try_into().ok()).unwrap_or_else(_unlikely_dflt)) +} + +#[cfg(test)] +pub(crate) fn _uri() -> UriString { + use core::sync::atomic::{AtomicU32, Ordering}; + static PORT: AtomicU32 = AtomicU32::new(7000); + let uri = alloc::format!("http://127.0.0.1:{}", PORT.fetch_add(1, Ordering::Relaxed)); + UriString::new(uri) +} + +#[cold] +#[inline(never)] +#[track_caller] +pub(crate) fn _unlikely_cb(cb: impl FnOnce() -> T) -> T { + cb() +} + +#[cold] +#[inline(never)] +#[track_caller] +pub(crate) fn _unlikely_dflt() -> T +where + T: Default, +{ + T::default() +} + +#[cold] +#[inline(never)] +#[track_caller] +pub(crate) fn _unlikely_elem(elem: T) -> T { + elem +} + +#[cold] +#[inline(never)] +#[track_caller] +pub(crate) const fn _unreachable() -> ! { + panic!("Entered in a branch that should be impossible. This is a bug!"); +} + +pub(crate) fn _usize_range_from_u32_range(range: Range) -> Range { + *Usize::from(range.start)..*Usize::from(range.end) } diff --git a/wtx/src/misc/array_chunks.rs b/wtx/src/misc/array_chunks.rs index bf4cadd1..1845e6c4 100644 --- a/wtx/src/misc/array_chunks.rs +++ b/wtx/src/misc/array_chunks.rs @@ -5,7 +5,7 @@ use core::{ iter::FusedIterator, - slice::{self, IterMut}, + slice::{self, Iter, IterMut}, }; macro_rules! create_and_impl { @@ -87,6 +87,16 @@ macro_rules! create_and_impl { }; } +create_and_impl!( + &'slice [T; N], + from_raw_parts, + iter, + Iter, + ArrayChunks, + as_ptr, + split_at, + &'slice [T] +); create_and_impl!( &'slice mut [T; N], from_raw_parts_mut, diff --git a/wtx/src/misc/basic_utf8_error.rs b/wtx/src/misc/basic_utf8_error.rs deleted file mode 100644 index 2c58e29f..00000000 --- a/wtx/src/misc/basic_utf8_error.rs +++ /dev/null @@ -1,10 +0,0 @@ -/// Basic string error that doesn't contain any information. -#[derive(Debug)] -pub struct BasicUtf8Error; - -impl From for crate::Error { - #[inline] - fn from(_: BasicUtf8Error) -> Self { - Self::InvalidUTF8 - } -} diff --git a/wtx/src/misc/either.rs b/wtx/src/misc/either.rs index 7546939e..a36c4306 100644 --- a/wtx/src/misc/either.rs +++ b/wtx/src/misc/either.rs @@ -6,3 +6,59 @@ pub enum Either { /// Right Right(R), } + +impl AsRef<[u8]> for Either +where + L: AsRef<[u8]>, + R: AsRef<[u8]>, +{ + #[inline] + fn as_ref(&self) -> &[u8] { + match self { + Either::Left(elem) => elem.as_ref(), + Either::Right(elem) => elem.as_ref(), + } + } +} + +impl<'any, L, R> AsRef<&'any [u8]> for Either +where + L: AsRef<&'any [u8]>, + R: AsRef<&'any [u8]>, +{ + #[inline] + fn as_ref(&self) -> &&'any [u8] { + match self { + Either::Left(elem) => elem.as_ref(), + Either::Right(elem) => elem.as_ref(), + } + } +} + +impl AsRef for Either +where + L: AsRef, + R: AsRef, +{ + #[inline] + fn as_ref(&self) -> &str { + match self { + Either::Left(elem) => elem.as_ref(), + Either::Right(elem) => elem.as_ref(), + } + } +} + +impl<'any, L, R> AsRef<&'any str> for Either +where + L: AsRef<&'any str>, + R: AsRef<&'any str>, +{ + #[inline] + fn as_ref(&self) -> &&'any str { + match self { + Either::Left(elem) => elem.as_ref(), + Either::Right(elem) => elem.as_ref(), + } + } +} diff --git a/wtx/src/misc/filled_buffer_writer.rs b/wtx/src/misc/filled_buffer_writer.rs index 55a0b134..d8f37578 100644 --- a/wtx/src/misc/filled_buffer_writer.rs +++ b/wtx/src/misc/filled_buffer_writer.rs @@ -1,8 +1,4 @@ -#![allow( - // Indices point to valid memory - clippy::unreachable -)] - +use crate::misc::_unreachable; use alloc::vec::Vec; /// Helper that manages the copy of initialized bytes. @@ -22,7 +18,7 @@ impl<'vec> FilledBufferWriter<'vec> { if let Some(elem) = self._vec.get_mut(self._curr_idx..) { elem } else { - unreachable!() + _unreachable() } } @@ -30,7 +26,7 @@ impl<'vec> FilledBufferWriter<'vec> { if let Some(elem) = self._vec.get(self._initial_idx..self._curr_idx) { elem } else { - unreachable!() + _unreachable() } } @@ -38,7 +34,7 @@ impl<'vec> FilledBufferWriter<'vec> { if let Some(elem) = self._vec.get_mut(self._initial_idx..self._curr_idx) { elem } else { - unreachable!() + _unreachable() } } @@ -50,7 +46,7 @@ impl<'vec> FilledBufferWriter<'vec> { let new_start = self._curr_idx.wrapping_add(1); self._expand_buffer(new_start); let Some(vec_byte) = self._vec.get_mut(self._curr_idx) else { - unreachable!(); + _unreachable(); }; *vec_byte = byte; self._curr_idx = new_start; @@ -112,28 +108,27 @@ impl<'vec> FilledBufferWriter<'vec> { if let Some(vec_slice) = self._vec.get_mut(self._curr_idx..until_slice) { vec_slice.copy_from_slice(slice); } else { - unreachable!(); + _unreachable(); } if let Some(vec_slice) = self._vec.get_mut(until_slice..until_suffix) { vec_slice.copy_from_slice(&suffix); } else { - unreachable!(); + _unreachable(); } self._curr_idx = until_suffix; } } +#[cfg(feature = "_bench")] #[cfg(test)] -mod tests { - #[cfg(feature = "_bench")] +mod bench { #[bench] fn extend_from_slice(b: &mut test::Bencher) { - use alloc::{vec, vec::Vec}; let array: [u8; 64] = core::array::from_fn(|idx| { let n = idx % 255; n.try_into().unwrap_or(u8::MAX) }); - let mut vec = vec![0; 128]; + let mut vec = alloc::vec![0; 128]; let mut fbw = crate::misc::FilledBufferWriter::new(32, &mut vec); b.iter(|| { fbw._extend_from_slice(&array); diff --git a/wtx/src/database/orm/misc/fx_hasher.rs b/wtx/src/misc/fx_hasher.rs similarity index 50% rename from wtx/src/database/orm/misc/fx_hasher.rs rename to wtx/src/misc/fx_hasher.rs index 170c57b8..c8b576c3 100644 --- a/wtx/src/database/orm/misc/fx_hasher.rs +++ b/wtx/src/misc/fx_hasher.rs @@ -1,11 +1,13 @@ -// https://nnethercote.github.io/2021/12/08/a-brutally-effective-hash-function-in-rust.html - use core::{hash::Hasher, ops::BitXor}; -const K: u64 = 0x517cc1b727220a95; +const K: u64 = 0x517c_c1b7_2722_0a95; -#[derive(Default)] -pub(crate) struct FxHasher(u64); +/// https://nnethercote.github.io/2021/12/08/a-brutally-effective-hash-function-in-rust.html +/// +/// Has a fixed output standard, as such, it can be used in algorithms where a hash needs to be +/// sent over the network, or persisted. +#[derive(Debug, Default)] +pub struct FxHasher(u64); impl Hasher for FxHasher { #[inline] diff --git a/wtx/src/web_socket/misc/incomplete_utf8_char.rs b/wtx/src/misc/incomplete_utf8_char.rs similarity index 61% rename from wtx/src/web_socket/misc/incomplete_utf8_char.rs rename to wtx/src/misc/incomplete_utf8_char.rs index d385d482..feb3c70d 100644 --- a/wtx/src/web_socket/misc/incomplete_utf8_char.rs +++ b/wtx/src/misc/incomplete_utf8_char.rs @@ -1,24 +1,55 @@ -use crate::web_socket::misc::from_utf8_std_rslt; +use crate::misc::from_utf8_std_rslt; +/// Completion error #[derive(Debug)] -pub(crate) struct IncompleteUtf8Char { +pub enum CompletionErr { + /// It is impossible to verify the string + HasInvalidBytes, + /// More bytes are needed to verify the string. + InsufficientInput, +} + +/// Bytes that can or can not represent an incomplete UTF-8 character. +#[derive(Debug)] +pub struct IncompleteUtf8Char { 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); + match bytes { + [] => {} + [a] => { + buffer[0] = *a; + } + [a, b] => { + buffer[0] = *a; + buffer[1] = *b; + } + [a, b, c] => { + buffer[0] = *a; + buffer[1] = *b; + buffer[2] = *c; + } + [a, b, c, d] => { + buffer[0] = *a; + buffer[1] = *b; + buffer[2] = *c; + buffer[3] = *d; + } + _ => return None, + } Some(Self { buffer, len: bytes.len() }) } - pub(crate) fn complete<'bytes>( + /// Tries to join the current set of bytes with the provided `bytes` to form a valid UTF-8 character. + #[inline] + pub fn complete<'bytes>( &mut self, bytes: &'bytes [u8], - ) -> (Result<(), CompleteErr>, &'bytes [u8]) { + ) -> (Result<(), CompletionErr>, &'bytes [u8]) { let (consumed, complete_err) = self.push_to_build_valid_char(bytes); let remaining = bytes.get(consumed..).unwrap_or_default(); match complete_err { @@ -27,7 +58,7 @@ impl IncompleteUtf8Char { } } - fn push_to_build_valid_char(&mut self, bytes: &[u8]) -> (usize, Option) { + 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(); @@ -50,11 +81,11 @@ impl IncompleteUtf8Char { match err.error_len { None => { self.len = new_bytes.len(); - (to_write_len, Some(CompleteErr::InsufficientInput)) + (to_write_len, Some(CompletionErr::InsufficientInput)) } Some(invalid_seq_len) => { self.len = invalid_seq_len; - (invalid_seq_len.wrapping_sub(initial_len), Some(CompleteErr::HasInvalidBytes)) + (invalid_seq_len.wrapping_sub(initial_len), Some(CompletionErr::HasInvalidBytes)) } } } @@ -64,8 +95,3 @@ impl IncompleteUtf8Char { } } } - -pub(crate) enum CompleteErr { - HasInvalidBytes, - InsufficientInput, -} diff --git a/wtx/src/misc/mem_transfer.rs b/wtx/src/misc/mem_transfer.rs new file mode 100644 index 00000000..24645deb --- /dev/null +++ b/wtx/src/misc/mem_transfer.rs @@ -0,0 +1,203 @@ +#![allow( + // See safety comments + unsafe_code +)] + +//! On a fragmented vector of bytes, performs several experiments to see which is faster: +//! +//! 1. Transfer interval fragments to another vector (memcpy). +//! +//! ```ignore +//! // A vector fragmented in 3 pieces where the last two digits of each piece is copied +//! // to another vector. +//! +//! | | | | +//! A: |00|01|02|03|04|05|06|07|08|09|10|11| +//! | |_____| |_____| |_____| +//! | | | | | | | +//! | \|/ | \|/ | \|/ | +//! +//! B: |02|03|06|07|10|11| +//! ``` +//! +//! 2. In-place shifts of interval fragments (memmove). +//! +//! ```ignore +//! // A vector fragmented in 3 pieces where the last two digits of each piece are +//! // shifted to the left +//! +//! | | | | +//! A: |00|01|02|03|04|05|06|07|08|09|10|11| +//! | << <<| | | +//! +//! | | | | +//! A: |02|03|02|03|04|05|06|07|08|09|10|11| +//! |^^ ^^ | << <<| | +//! +//! | | | | +//! A: |02|03|06|07|04|05|06|07|08|09|10|11| +//! |^^ ^^ ^^ ^^| | << <<| +//! +//! | | | | +//! A: |02|03|06|07|10|11|06|07|08|09|10|11| +//! |^^ ^^ ^^ ^^|^^ ^^ | | +//! +//! A: |02|03|06|07|10|11| +//! ``` + +use crate::misc::{_unlikely_cb, _unlikely_elem}; +use core::ptr; + +#[cfg(test)] +pub(crate) fn _copy_bytes<'to>( + from: &[u8], + to: &'to mut [u8], + start: usize, + iter: impl IntoIterator, +) -> &'to mut [u8] { + if start >= from.len() || start >= to.len() { + return _unlikely_elem(to); + } + let mut new_len = start; + unsafe { + ptr::copy_nonoverlapping(from.as_ptr(), to.as_mut_ptr(), start); + } + for (begin, end) in iter { + let len = end.wrapping_sub(begin); + let to_end = new_len.wrapping_add(len); + let from_is_not_valid = from.get(begin..end).is_none(); + let to_is_not_valid = to.get(new_len..to_end).is_none(); + if from_is_not_valid || to_is_not_valid { + return _unlikely_cb(|| unsafe { to.get_unchecked_mut(..new_len) }); + } + unsafe { + ptr::copy_nonoverlapping(from.as_ptr().add(begin), to.as_mut_ptr().add(new_len), len); + } + new_len = to_end; + } + unsafe { to.get_unchecked_mut(..new_len) } +} + +pub(crate) fn _shift_bytes( + slice: &mut [u8], + start: usize, + iter: impl IntoIterator, +) -> &mut [u8] { + if start >= slice.len() { + return _unlikely_elem(slice); + } + let mut new_len = start; + for (begin, end) in iter { + if slice.get(begin..end).is_none() { + // SAFETY: Top-level check enforces bounds + return _unlikely_cb(|| unsafe { slice.get_unchecked_mut(..new_len) }); + } + let len = end.wrapping_sub(begin); + // SAFETY: Loop-level check enforces bounds + unsafe { + ptr::copy(slice.as_ptr().add(begin), slice.as_mut_ptr().add(new_len), len); + } + new_len = new_len.wrapping_add(len); + } + // SAFETY: Top-level check enforces bounds + unsafe { slice.get_unchecked_mut(..new_len) } +} + +#[cfg(feature = "_bench")] +#[cfg(test)] +mod bench { + use crate::misc::mem_transfer::{_copy_bytes, _shift_bytes}; + use alloc::{vec, vec::Vec}; + use core::hint::black_box; + + const SHIFT_LEN: usize = 1000; + const SLICES_LEN: usize = 10000000; + const SLICES_NUM: usize = 1000; + + #[bench] + fn copy_bytes(b: &mut test::Bencher) { + let from: Vec = crate::bench::_data(SLICES_LEN * SLICES_NUM); + let mut to = vec![0; SLICES_LEN * SLICES_NUM]; + b.iter(|| { + black_box({ + let iter = (0..SLICES_NUM).skip(1).scan(SLICES_LEN, |begin, _| { + let end = begin.wrapping_add(SHIFT_LEN); + let rslt = (*begin, end); + *begin = begin.wrapping_add(SLICES_LEN); + Some(rslt) + }); + assert_eq!(_copy_bytes(&from, &mut to, 0, iter).len(), SHIFT_LEN * (SLICES_NUM - 1)); + }) + }); + } + + #[bench] + fn shift_bytes(b: &mut test::Bencher) { + let mut vector = crate::bench::_data(SLICES_LEN * SLICES_NUM); + b.iter(|| { + black_box({ + let iter = (0..SLICES_NUM).skip(1).scan(SLICES_LEN, |begin, _| { + let end = begin.wrapping_add(SHIFT_LEN); + let rslt = (*begin, end); + *begin = begin.wrapping_add(SLICES_LEN); + Some(rslt) + }); + assert_eq!(_shift_bytes(&mut vector, 0, iter).len(), SHIFT_LEN * (SLICES_NUM - 1)); + }) + }); + } +} + +#[cfg(feature = "_proptest")] +#[cfg(test)] +mod proptest { + use crate::misc::{_shift_bytes, mem_transfer::_copy_bytes}; + use core::ops::Range; + + #[test_strategy::proptest] + fn copy_bytes(from: Vec, range: Range) { + let mut begin: usize = range.start.into(); + let mut end: usize = range.end.into(); + let mut from_clone = from.clone(); + let mut to = vec![0; from.len()]; + begin = begin.min(from.len()); + end = end.min(from.len()); + let rslt = _copy_bytes(&from, &mut to, 0, [(begin, end)]); + from_clone.rotate_left(begin); + from_clone.truncate(rslt.len()); + assert_eq!(rslt, &from_clone); + } + + #[test_strategy::proptest] + fn shift_bytes(mut data: Vec, range: Range) { + let mut begin: usize = range.start.into(); + let mut end: usize = range.end.into(); + let mut data_clone = data.clone(); + begin = begin.min(data.len()); + end = end.min(data.len()); + let rslt = _shift_bytes(&mut data, 0, [(begin, end)]); + data_clone.rotate_left(begin); + data_clone.truncate(rslt.len()); + assert_eq!(rslt, &data_clone); + } +} + +#[cfg(test)] +mod test { + use crate::misc::mem_transfer::{_copy_bytes, _shift_bytes}; + + #[test] + fn _copy_bytes_has_correct_outputs() { + let from = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let to = &mut [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + assert_eq!(_copy_bytes(from, to, 2, [(4, 6), (8, 10)]), &mut [0, 1, 4, 5, 8, 9]); + assert_eq!(to, &mut [0, 1, 4, 5, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + } + + #[test] + fn _shift_bytes_has_correct_outputs() { + let bytes = &mut [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + assert_eq!(_shift_bytes(bytes, 2, [(4, 6), (8, 10)]), &mut [0, 1, 4, 5, 8, 9]); + assert_eq!(bytes, &mut [0, 1, 4, 5, 8, 9, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + } +} diff --git a/wtx/src/misc/optimization.rs b/wtx/src/misc/optimization.rs new file mode 100644 index 00000000..df086166 --- /dev/null +++ b/wtx/src/misc/optimization.rs @@ -0,0 +1,159 @@ +use crate::misc::{BasicUtf8Error, ExtUtf8Error, IncompleteUtf8Char, StdUtf8Error}; + +/// Internally uses `atoi` if the feature is active. +#[cfg(not(feature = "atoi"))] +#[inline] +pub fn atoi(bytes: &[u8]) -> crate::Result +where + T: core::str::FromStr, + T::Err: Into, +{ + Ok(from_utf8_basic_rslt(bytes)?.parse().map_err(Into::into)?) +} +/// Internally uses `atoi` if the feature is active. +#[cfg(feature = "atoi")] +#[inline] +pub fn atoi(bytes: &[u8]) -> crate::Result +where + T: atoi::FromRadix10SignedChecked, +{ + atoi::atoi(bytes).ok_or(crate::Error::AtoiInvalidBytes) +} + +/// Internally uses `memchr` if the feature is active. +#[inline] +pub fn bytes_pos1(bytes: B, elem: u8) -> Option +where + B: AsRef<[u8]>, +{ + #[cfg(feature = "memchr")] + return memchr::memchr(elem, bytes.as_ref()); + #[cfg(not(feature = "memchr"))] + return bytes.as_ref().iter().position(|byte| *byte == elem); +} + +/// Internally uses `memchr` if the feature is active. +#[inline] +pub fn bytes_rsplit1(bytes: &[u8], elem: u8) -> impl Iterator { + #[cfg(feature = "memchr")] + return memchr::memrchr_iter(elem, bytes) + .map(|el| el.wrapping_add(1)) + .chain(core::iter::once(0)) + .scan(bytes.len(), move |end, begin| { + let rslt = bytes.get(begin..*end); + *end = begin.wrapping_sub(1); + rslt + }); + #[cfg(not(feature = "memchr"))] + return bytes.rsplit(move |byte| *byte == elem); +} + +/// Internally uses `memchr` if the feature is active. +#[inline] +pub fn bytes_split1(bytes: &[u8], elem: u8) -> impl Iterator { + #[cfg(feature = "memchr")] + return memchr::memchr_iter(elem, bytes).chain(core::iter::once(bytes.len())).scan( + 0, + move |begin, end| { + let rslt = bytes.get(*begin..end); + *begin = end.wrapping_add(1); + rslt + }, + ); + #[cfg(not(feature = "memchr"))] + return bytes.split(move |byte| *byte == elem); +} + +/// Internally uses `simdutf8` if the feature is active. +#[inline] +pub fn from_utf8_basic_rslt(bytes: &[u8]) -> Result<&str, BasicUtf8Error> { + #[cfg(feature = "simdutf8")] + return simdutf8::basic::from_utf8(bytes).ok().ok_or(BasicUtf8Error {}); + #[cfg(not(feature = "simdutf8"))] + return core::str::from_utf8(bytes).ok().ok_or(BasicUtf8Error {}); +} + +/// Internally uses `simdutf8` if the feature is active. +#[inline] +pub 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: { + IncompleteUtf8Char::new(after_valid).ok_or(ExtUtf8Error::Invalid)? + }, + }), + Some(_) => Err(ExtUtf8Error::Invalid), + } +} + +/// Internally uses `simdutf8` if the feature is active. +#[inline] +pub 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(), + }); +} + +/// Internally uses `memchr` if the feature is active. +#[inline] +pub fn str_pos1(str: &str, elem: u8) -> Option { + #[cfg(feature = "memchr")] + return memchr::memchr(elem, str.as_bytes()); + #[cfg(not(feature = "memchr"))] + return str.as_bytes().iter().position(|byte| *byte == elem); +} + +/// Internally uses `memchr` if the feature is active. +#[inline] +pub fn str_rpos1(str: &str, elem: u8) -> Option { + #[cfg(feature = "memchr")] + return memchr::memrchr(elem, str.as_bytes()); + #[cfg(not(feature = "memchr"))] + return str.as_bytes().iter().rev().position(|byte| *byte == elem); +} + +/// Internally uses `memchr` if the feature is active. +#[inline] +pub fn str_rsplit_once1(str: &str, elem: u8) -> Option<(&str, &str)> { + #[cfg(feature = "memchr")] + return str_pos1(str, elem).map(|idx| str.split_at(idx)); + #[cfg(not(feature = "memchr"))] + return str.rsplit_once(char::from(elem)); +} + +/// Internally uses `memchr` if the feature is active. +#[inline] +pub fn str_split1(str: &str, elem: u8) -> impl Iterator { + #[cfg(feature = "memchr")] + return memchr::memchr_iter(elem, str.as_bytes()).chain(core::iter::once(str.len())).scan( + 0, + move |begin, end| { + let rslt = str.get(*begin..end); + *begin = end.wrapping_add(1); + rslt + }, + ); + #[cfg(not(feature = "memchr"))] + return str.split(char::from(elem)); +} + +/// Internally uses `memchr` if the feature is active. +#[inline] +pub fn str_split_once1(str: &str, elem: u8) -> Option<(&str, &str)> { + #[cfg(feature = "memchr")] + return str_pos1(str, elem).map(|idx| str.split_at(idx)); + #[cfg(not(feature = "memchr"))] + return str.split_once(char::from(elem)); +} diff --git a/wtx/src/misc/partitioned_filled_buffer.rs b/wtx/src/misc/partitioned_filled_buffer.rs index 5c0d678f..df195f38 100644 --- a/wtx/src/misc/partitioned_filled_buffer.rs +++ b/wtx/src/misc/partitioned_filled_buffer.rs @@ -1,12 +1,9 @@ -#![allow( - // Indices point to valid memory - clippy::unreachable -)] - -use core::ops::Range; - -use crate::{misc::FilledBufferWriter, DFLT_PARTITIONED_BUFFER_LEN}; +use crate::{ + misc::{FilledBufferWriter, _unreachable}, + DFLT_PARTITIONED_BUFFER_LEN, +}; use alloc::{vec, vec::Vec}; +use core::ops::Range; // ``` // [ Antecedent | Current | Following | Trailing ] @@ -62,7 +59,7 @@ impl PartitionedFilledBuffer { if let Some(el) = self._buffer.get_mut(self._antecedent_end_idx..) { el } else { - unreachable!() + _unreachable() } } @@ -70,7 +67,7 @@ impl PartitionedFilledBuffer { if let Some(el) = self._buffer.get(self._current_range()) { el } else { - unreachable!() + _unreachable() } } @@ -83,7 +80,7 @@ impl PartitionedFilledBuffer { if let Some(el) = self._buffer.get_mut(range) { el } else { - unreachable!() + _unreachable() } } @@ -106,7 +103,7 @@ impl PartitionedFilledBuffer { if let Some(el) = self._buffer.get(self._current_end_idx..self._following_end_idx) { el } else { - unreachable!() + _unreachable() } } @@ -114,7 +111,7 @@ impl PartitionedFilledBuffer { if let Some(el) = self._buffer.get_mut(self._current_end_idx..self._following_end_idx) { el } else { - unreachable!() + _unreachable() } } @@ -127,7 +124,7 @@ impl PartitionedFilledBuffer { if let Some(el) = self._buffer.get_mut(self._current_end_idx..) { el } else { - unreachable!() + _unreachable() } } diff --git a/wtx/src/misc/poll_once.rs b/wtx/src/misc/poll_once.rs index f9fd50e2..04197fad 100644 --- a/wtx/src/misc/poll_once.rs +++ b/wtx/src/misc/poll_once.rs @@ -1,7 +1,7 @@ use core::{ fmt::{Debug, Formatter}, future::Future, - pin::{pin, Pin}, + pin::Pin, task::{Context, Poll}, }; diff --git a/wtx/src/misc/stream.rs b/wtx/src/misc/stream.rs index 5a0c65f3..b1a9dff6 100644 --- a/wtx/src/misc/stream.rs +++ b/wtx/src/misc/stream.rs @@ -371,7 +371,32 @@ mod tokio_rustls { fn tls_server_end_point(&self) -> crate::Result> { let (_, conn) = self.get_ref(); Ok(match conn.peer_certificates() { - Some([cert, ..]) => Some(digest::digest(&digest::SHA256, cert)), + Some([cert, ..]) => { + #[cfg(feature = "x509-certificate")] + let algorithm = { + use x509_certificate::{DigestAlgorithm, SignatureAlgorithm}; + let x509_cer = x509_certificate::X509Certificate::from_der(cert)?; + let Some(sa) = x509_cer.signature_algorithm() else { + return Ok(None); + }; + match sa { + SignatureAlgorithm::EcdsaSha256 + | SignatureAlgorithm::RsaSha1 + | SignatureAlgorithm::RsaSha256 => &digest::SHA256, + SignatureAlgorithm::EcdsaSha384 | SignatureAlgorithm::RsaSha384 => &digest::SHA384, + SignatureAlgorithm::Ed25519 => &digest::SHA512, + SignatureAlgorithm::NoSignature(da) => match da { + DigestAlgorithm::Sha1 | DigestAlgorithm::Sha256 => &digest::SHA256, + DigestAlgorithm::Sha384 => &digest::SHA384, + DigestAlgorithm::Sha512 => &digest::SHA512, + }, + SignatureAlgorithm::RsaSha512 => &digest::SHA512, + } + }; + #[cfg(not(feature = "x509-certificate"))] + let algorithm = &digest::SHA256; + Some(digest::digest(algorithm, cert.as_ref())) + } _ => None, }) } diff --git a/wtx/src/misc/tokio_rustls.rs b/wtx/src/misc/tokio_rustls.rs index 8bd183bb..fa531bab 100644 --- a/wtx/src/misc/tokio_rustls.rs +++ b/wtx/src/misc/tokio_rustls.rs @@ -6,10 +6,7 @@ use tokio::{ }; use tokio_rustls::{ client::TlsStream, - rustls::{ - server::WantsServerCert, ClientConfig, ConfigBuilder, RootCertStore, ServerConfig, - WantsVerifier, - }, + rustls::{server::WantsServerCert, ClientConfig, ConfigBuilder, RootCertStore, ServerConfig}, TlsConnector, }; diff --git a/wtx/src/misc/uri.rs b/wtx/src/misc/uri.rs index 377a0d69..39ca3ad4 100644 --- a/wtx/src/misc/uri.rs +++ b/wtx/src/misc/uri.rs @@ -1,4 +1,4 @@ -use crate::misc::QueryWriter; +use crate::misc::{QueryWriter, _unlikely_dflt, str_rsplit_once1, str_split_once1}; use alloc::string::String; use core::fmt::{Arguments, Debug, Formatter, Write}; @@ -28,12 +28,12 @@ where #[inline] pub fn new(uri: S) -> Self { let initial_len = uri.as_ref().len().try_into().unwrap_or(u16::MAX); - let valid_uri = uri.as_ref().get(..initial_len.into()).unwrap_or_default(); + let valid_uri = uri.as_ref().get(..initial_len.into()).unwrap_or_else(_unlikely_dflt); let authority_start_idx: u16 = valid_uri .match_indices("://") .next() .and_then(|(element, _)| element.wrapping_add(3).try_into().ok()) - .unwrap_or_default(); + .unwrap_or_else(_unlikely_dflt); let href_start_idx = valid_uri .as_bytes() .iter() @@ -55,7 +55,7 @@ where .uri .as_ref() .get(self.authority_start_idx.into()..self.href_start_idx.into()) - .unwrap_or_default() + .unwrap_or_else(_unlikely_dflt) } /// ```rust @@ -65,8 +65,8 @@ where #[inline] pub fn fragment(&self) -> &str { let href = self.href(); - let maybe_rslt = href.rsplit_once('?').map_or(href, |el| el.1); - if let Some((_, rslt)) = maybe_rslt.rsplit_once('#') { + let maybe_rslt = str_rsplit_once1(href, b'?').map_or(href, |el| el.1); + if let Some((_, rslt)) = str_rsplit_once1(maybe_rslt, b'#') { rslt } else { maybe_rslt @@ -80,7 +80,7 @@ where #[inline] pub fn host(&self) -> &str { let authority = self.authority(); - if let Some(elem) = authority.split_once('@') { + if let Some(elem) = str_split_once1(authority, b'@') { elem.1 } else { authority @@ -94,7 +94,7 @@ where #[inline] pub fn hostname(&self) -> &str { let host = self.host(); - host.split_once(':').map_or(host, |el| el.0) + str_split_once1(host, b':').map_or(host, |el| el.0) } /// ```rust @@ -117,7 +117,7 @@ where /// ``` #[inline] pub fn password(&self) -> &str { - if let Some(elem) = self.userinfo().split_once(':') { + if let Some(elem) = str_split_once1(self.userinfo(), b':') { elem.1 } else { "" @@ -131,7 +131,7 @@ where #[inline] pub fn path(&self) -> &str { let href = self.href(); - href.rsplit_once('?').map_or(href, |el| el.0) + str_rsplit_once1(href, b'?').map_or(href, |el| el.0) } /// ```rust @@ -141,7 +141,7 @@ where #[inline] pub fn port(&self) -> &str { let host = self.host(); - host.split_once(':').map_or(host, |el| el.1) + str_split_once1(host, b':').map_or(host, |el| el.1) } /// ```rust @@ -151,8 +151,8 @@ where #[inline] pub fn query(&self) -> &str { let href = self.href(); - let before_hash = if let Some((elem, _)) = href.rsplit_once('#') { elem } else { href }; - if let Some((_, elem)) = before_hash.rsplit_once('?') { + let before_hash = if let Some((elem, _)) = str_rsplit_once1(href, b'#') { elem } else { href }; + if let Some((_, elem)) = str_rsplit_once1(before_hash, b'?') { elem } else { "" @@ -168,7 +168,7 @@ where let mut iter = self.uri.as_ref().split("://"); let first_opt = iter.next(); if iter.next().is_some() { - first_opt.unwrap_or_default() + first_opt.unwrap_or_else(_unlikely_dflt) } else { "" } @@ -208,7 +208,7 @@ where /// ``` #[inline] pub fn user(&self) -> &str { - if let Some(elem) = self.userinfo().split_once(':') { + if let Some(elem) = str_split_once1(self.userinfo(), b':') { elem.0 } else { "" @@ -221,7 +221,7 @@ where /// ``` #[inline] pub fn userinfo(&self) -> &str { - if let Some(elem) = self.authority().split_once('@') { + if let Some(elem) = str_split_once1(self.authority(), b'@') { elem.0 } else { "" diff --git a/wtx/src/misc/usize.rs b/wtx/src/misc/usize.rs new file mode 100644 index 00000000..3708dde8 --- /dev/null +++ b/wtx/src/misc/usize.rs @@ -0,0 +1,75 @@ +#![allow( + // Some platforms were removed to allow infallible casts + clippy::as_conversions +)] + +use core::ops::{Deref, DerefMut}; + +/// An `usize` that can be infallible converted from an `u32`. In other words, this effectively kills +/// the support for 16bit hardware. +/// +/// Additionally, 128bit support is also dropped. +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Usize(usize); + +impl Deref for Usize { + type Target = usize; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Usize { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for Usize { + #[inline] + fn from(from: u8) -> Self { + Self(from.into()) + } +} + +impl From for Usize { + #[inline] + fn from(from: u16) -> Self { + Self(from.into()) + } +} + +impl From for Usize { + #[inline] + fn from(from: u32) -> Self { + #[cfg(target_pointer_width = "16")] + compile_error!("WTX does not support 16bit hardware"); + Self(from as usize) + } +} + +impl From for Usize { + #[inline] + fn from(from: usize) -> Self { + Self(from) + } +} + +impl From for u64 { + #[inline] + fn from(from: Usize) -> Self { + #[cfg(target_pointer_width = "128")] + compile_error!("WTX does not support 128bit hardware"); + from.0 as u64 + } +} + +impl From for u128 { + #[inline] + fn from(from: Usize) -> Self { + from.0 as u128 + } +} diff --git a/wtx/src/misc/utf8_errors.rs b/wtx/src/misc/utf8_errors.rs new file mode 100644 index 00000000..7740e45b --- /dev/null +++ b/wtx/src/misc/utf8_errors.rs @@ -0,0 +1,33 @@ +use crate::misc::IncompleteUtf8Char; + +/// Extended error built upon [StdUtf8Error]. +#[derive(Debug)] +pub enum ExtUtf8Error { + /// More bytes are needed to validate the string. + Incomplete { + /// See [IncompleteUtf8Char]. + incomplete_ending_char: IncompleteUtf8Char, + }, + /// It is impossible to validate the string + Invalid, +} + +/// Basic string error that doesn't contain any information. +#[derive(Debug)] +pub struct BasicUtf8Error; + +impl From for crate::Error { + #[inline] + fn from(_: BasicUtf8Error) -> Self { + Self::InvalidUTF8 + } +} + +/// Standard error that is similar to the error type of the standard library. +#[derive(Debug)] +pub struct StdUtf8Error { + /// Error length + pub error_len: Option, + /// Starting index of mal-formatted bytes + pub valid_up_to: usize, +} diff --git a/wtx/src/pool_manager/static_pool.rs b/wtx/src/pool_manager/static_pool.rs index 75bda8d9..3b4b1c0e 100644 --- a/wtx/src/pool_manager/static_pool.rs +++ b/wtx/src/pool_manager/static_pool.rs @@ -1,5 +1,5 @@ use crate::{ - misc::PollOnce, + misc::{PollOnce, _unreachable}, pool_manager::{Lock, LockGuard, Pool, ResourceManager}, }; use alloc::collections::VecDeque; @@ -64,10 +64,6 @@ where Ok(Self { idx: AtomicUsize::new(0), locks: array::from_fn(|_| RL::new(None)), rm }) } - #[allow( - // Inner code does not trigger `panic!` - clippy::missing_panics_doc - )] #[inline] async fn get<'this>(&'this self) -> Result, RM::Error> where @@ -79,11 +75,10 @@ where clippy::arithmetic_side_effects, )] let local_idx = self.idx.fetch_add(1, Ordering::Release) % N; - #[allow( - // `locks` is an array that will always have a valid `self.idx % N` element - clippy::unwrap_used - )] - let lock = self.locks.get(local_idx).unwrap(); + let lock = match self.locks.get(local_idx) { + Some(elem) => elem, + None => _unreachable(), + }; if let Some(mut guard) = PollOnce(pin!(lock.lock())).await { match &mut *guard { None => { @@ -94,11 +89,10 @@ where } _ => {} } - #[allow( - // The above match took care of nullable guards - clippy::unwrap_used - )] - return Ok(LockGuard::map(guard, |el| el.as_mut().unwrap())); + return Ok(LockGuard::map(guard, |el| match el.as_mut() { + Some(elem) => elem, + None => _unreachable(), + })); } } } diff --git a/wtx/src/web_socket.rs b/wtx/src/web_socket.rs index aea0f057..4879da73 100644 --- a/wtx/src/web_socket.rs +++ b/wtx/src/web_socket.rs @@ -21,14 +21,14 @@ mod web_socket_buffer; #[cfg(feature = "tracing")] use crate::web_socket::misc::Role; use crate::{ - misc::{from_utf8_basic_rslt, PartitionedFilledBuffer, Stream, _read_until}, + misc::{ + from_utf8_basic_rslt, from_utf8_ext_rslt, CompletionErr, ExtUtf8Error, IncompleteUtf8Char, + PartitionedFilledBuffer, Stream, _read_until, + }, rng::Rng, web_socket::{ compression::NegotiatedCompression, - misc::{ - define_fb_from_header_params, from_utf8_ext_rslt, op_code, CompleteErr, ExtUtf8Error, - FilledBuffer, IncompleteUtf8Char, - }, + misc::{define_fb_from_header_params, op_code, FilledBuffer}, }, _MAX_PAYLOAD_LEN, }; @@ -751,10 +751,10 @@ where let tail = if let Some(mut incomplete) = iuc.take() { let (rslt, remaining) = incomplete.complete(curr_payload); match rslt { - Err(CompleteErr::HasInvalidBytes) => { + Err(CompletionErr::HasInvalidBytes) => { return Err(crate::Error::InvalidUTF8); } - Err(CompleteErr::InsufficientInput) => { + Err(CompletionErr::InsufficientInput) => { let _ = iuc.replace(incomplete); &[] } diff --git a/wtx/src/web_socket/compression.rs b/wtx/src/web_socket/compression.rs index 13b52dc4..9c8b4cc9 100644 --- a/wtx/src/web_socket/compression.rs +++ b/wtx/src/web_socket/compression.rs @@ -8,7 +8,7 @@ mod window_bits; #[cfg(feature = "flate2")] pub use self::flate2::{Flate2, NegotiatedFlate2}; -use crate::{http::Header, misc::FilledBufferWriter}; +use crate::{http::GenericHeader, misc::FilledBufferWriter}; pub use compression_level::CompressionLevel; pub use deflate_config::DeflateConfig; pub use window_bits::WindowBits; @@ -22,7 +22,7 @@ pub trait Compression { /// parameters will be settled. fn negotiate( self, - headers: impl Iterator, + headers: impl Iterator, ) -> crate::Result; /// Writes headers bytes that will be sent to the server. @@ -35,7 +35,7 @@ impl Compression for () { #[inline] fn negotiate( self, - _: impl Iterator, + _: impl Iterator, ) -> crate::Result { Ok(()) } diff --git a/wtx/src/web_socket/compression/compression_level.rs b/wtx/src/web_socket/compression/compression_level.rs index a7012b4f..629be5b4 100644 --- a/wtx/src/web_socket/compression/compression_level.rs +++ b/wtx/src/web_socket/compression/compression_level.rs @@ -1,7 +1,6 @@ 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), diff --git a/wtx/src/web_socket/compression/flate2.rs b/wtx/src/web_socket/compression/flate2.rs index da680bd3..80560cc1 100644 --- a/wtx/src/web_socket/compression/flate2.rs +++ b/wtx/src/web_socket/compression/flate2.rs @@ -1,9 +1,8 @@ use crate::{ - http::Header, - misc::{from_utf8_basic_rslt, FilledBufferWriter}, + http::GenericHeader, + misc::{atoi, bytes_split1, FilledBufferWriter}, web_socket::{compression::NegotiatedCompression, misc::_trim_bytes, Compression, DeflateConfig}, }; -use core::str::FromStr; use flate2::{Compress, Decompress, FlushCompress, FlushDecompress}; /// Initial Flate2 compression @@ -25,7 +24,7 @@ impl Compression for Flate2 { #[inline] fn negotiate( self, - headers: impl Iterator, + headers: impl Iterator, ) -> crate::Result { let mut dc = DeflateConfig { client_max_window_bits: self.dc.client_max_window_bits, @@ -36,7 +35,7 @@ impl Compression for Flate2 { let mut has_extension = false; for swe in headers.filter(|el| el.name().eq_ignore_ascii_case(b"sec-websocket-extensions")) { - for permessage_deflate_option in swe.value().split(|el| el == &b',') { + for permessage_deflate_option in bytes_split1(swe.value(), b',') { dc = DeflateConfig { client_max_window_bits: self.dc.client_max_window_bits, compression_level: self.dc.compression_level, @@ -45,21 +44,20 @@ impl Compression for Flate2 { 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_bytes(elem)) - { + for param in bytes_split1(permessage_deflate_option, b';').map(|elem| _trim_bytes(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) { + if let Some(value) = _byte_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) { + if let Some(value) = _byte_from_bytes(after_smwb) { dc.server_max_window_bits = value.try_into()?; } Ok(()) @@ -229,12 +227,9 @@ fn _manage_header_uniqueness( } } -fn _value_from_bytes(bytes: &[u8]) -> Option -where - T: FromStr, -{ - let after_equals = bytes.split(|byte| byte == &b'=').nth(1)?; - from_utf8_basic_rslt(after_equals).ok()?.parse::().ok() +fn _byte_from_bytes(bytes: &[u8]) -> Option { + let after_equals = bytes_split1(bytes, b'=').nth(1)?; + atoi(after_equals).ok() } #[inline] diff --git a/wtx/src/web_socket/compression/window_bits.rs b/wtx/src/web_socket/compression/window_bits.rs index 47af9015..ea2e45e0 100644 --- a/wtx/src/web_socket/compression/window_bits.rs +++ b/wtx/src/web_socket/compression/window_bits.rs @@ -1,7 +1,6 @@ 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), diff --git a/wtx/src/web_socket/frame_buffer.rs b/wtx/src/web_socket/frame_buffer.rs index ccca96b9..659c9b6d 100644 --- a/wtx/src/web_socket/frame_buffer.rs +++ b/wtx/src/web_socket/frame_buffer.rs @@ -1,10 +1,5 @@ -#![allow( - // Indices point to valid memory - clippy::unreachable -)] - use crate::{ - misc::SingleTypeStorage, + misc::{SingleTypeStorage, _unreachable}, web_socket::{DFLT_FRAME_BUFFER_VEC_LEN, MAX_CONTROL_FRAME_LEN, MAX_HDR_LEN_U8}, }; use alloc::{vec, vec::Vec}; @@ -99,7 +94,7 @@ where { el } else { - unreachable!() + _unreachable() } } @@ -109,7 +104,7 @@ where if let Some(el) = self.buffer.as_ref().get(self.header_end_idx.into()..self.payload_end_idx) { el } else { - unreachable!() + _unreachable() } } @@ -117,7 +112,7 @@ where if let Some(el) = self.buffer.as_ref().get(self.header_begin_idx.into()..self.payload_end_idx) { el } else { - unreachable!() + _unreachable() } } @@ -148,7 +143,7 @@ where if let Some(el) = self.buffer.as_mut().get_mut(range) { el } else { - unreachable!() + _unreachable() } } @@ -157,7 +152,7 @@ where if let Some(el) = self.buffer.as_mut().get_mut(range) { el } else { - unreachable!() + _unreachable() } } } diff --git a/wtx/src/web_socket/handshake.rs b/wtx/src/web_socket/handshake.rs index 7aaac063..d46c35f5 100644 --- a/wtx/src/web_socket/handshake.rs +++ b/wtx/src/web_socket/handshake.rs @@ -28,8 +28,9 @@ pub trait WebSocketConnect { type Response; /// Initial negotiation sent by a client to start a WebSocket connection. - fn connect( + fn connect<'bytes>( self, + headers: impl IntoIterator, ) -> impl Future)>>; } diff --git a/wtx/src/web_socket/handshake/raw.rs b/wtx/src/web_socket/handshake/raw.rs index 125c7a38..03d62d56 100644 --- a/wtx/src/web_socket/handshake/raw.rs +++ b/wtx/src/web_socket/handshake/raw.rs @@ -40,8 +40,8 @@ pub struct WebSocketConnectRaw<'fb, 'hb, 'uri, B, C, H, RNG, S, WSB> { #[cfg(feature = "web-socket-handshake")] mod httparse_impls { use crate::{ - http::{ExpectedHeader, Header as _, Request as _}, - misc::{FilledBufferWriter, Stream, UriRef}, + http::{ExpectedHeader, GenericHeader as _, Request as _}, + misc::{bytes_split1, FilledBufferWriter, Stream, UriRef}, rng::Rng, web_socket::{ compression::NegotiatedCompression, @@ -141,15 +141,17 @@ mod httparse_impls { type Response = Response<'hb, 'fb>; #[inline] - async fn connect( + async fn connect<'bytes>( mut self, + headers: impl IntoIterator, ) -> crate::Result<(Self::Response, WebSocketClient)> { let key_buffer = &mut <_>::default(); let nb = &mut self.wsb.borrow_mut().nb; nb._clear(); let mut fbw = nb.into(); - let key = build_req(&self.compression, &mut fbw, key_buffer, &mut self.rng, self.uri); + let key = + build_req(&self.compression, &mut fbw, headers, key_buffer, &mut self.rng, self.uri); self.stream.write_all(fbw._curr_bytes()).await?; let mut read = 0; self.fb._set_indices_through_expansion(0, 0, MAX_READ_LEN); @@ -189,9 +191,10 @@ mod httparse_impls { } /// Client request - fn build_req<'kb, C>( + fn build_req<'bytes, 'kb, C>( compression: &C, fbw: &mut FilledBufferWriter<'_>, + headers: impl IntoIterator, key_buffer: &'kb mut [u8; 26], rng: &mut impl Rng, uri: &UriRef<'_>, @@ -201,6 +204,9 @@ mod httparse_impls { { let key = gen_key(key_buffer, rng); fbw._extend_from_slices_group_rn(&[b"GET ", uri.href().as_bytes(), b" HTTP/1.1"]); + for (name, value) in headers { + fbw._extend_from_slices_group_rn(&[name, b": ", value]); + } fbw._extend_from_slice_rn(b"Connection: Upgrade"); match (uri.schema(), uri.port()) { ("http" | "ws", "80") | ("https" | "wss", "443") => { @@ -240,7 +246,7 @@ mod httparse_impls { .find_map(|h| { let has_key = _trim_bytes(h.name()).eq_ignore_ascii_case(key); let has_value = - h.value().split(|el| el == &b',').any(|el| _trim_bytes(el).eq_ignore_ascii_case(value)); + bytes_split1(h.value(), b',').any(|el| _trim_bytes(el).eq_ignore_ascii_case(value)); (has_key && has_value).then_some(true) }) .unwrap_or(false) diff --git a/wtx/src/web_socket/handshake/tests.rs b/wtx/src/web_socket/handshake/tests.rs index 59d5d34e..cb123e13 100644 --- a/wtx/src/web_socket/handshake/tests.rs +++ b/wtx/src/web_socket/handshake/tests.rs @@ -94,7 +94,7 @@ where uri: &uri.to_ref(), wsb: WebSocketBuffer::with_capacity(0, 0), } - .connect() + .connect([]) .await .unwrap(); call_tests!( diff --git a/wtx/src/web_socket/misc.rs b/wtx/src/web_socket/misc.rs index 740b570c..f1e0e8e0 100644 --- a/wtx/src/web_socket/misc.rs +++ b/wtx/src/web_socket/misc.rs @@ -1,17 +1,13 @@ mod filled_buffer; -mod incomplete_utf8_char; #[cfg(feature = "tracing")] mod role; mod traits; -mod utf8_errors; use crate::web_socket::{FrameBuffer, OpCode}; pub(crate) use filled_buffer::FilledBuffer; -pub(crate) use incomplete_utf8_char::{CompleteErr, IncompleteUtf8Char}; #[cfg(feature = "tracing")] pub(crate) use role::Role; pub(crate) use traits::Expand; -pub(crate) use utf8_errors::{ExtUtf8Error, StdUtf8Error}; pub(crate) fn define_fb_from_header_params( fb: &mut FrameBuffer, @@ -37,36 +33,6 @@ where 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), - } -} - -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(), - }); -} - pub(crate) fn op_code(first_header_byte: u8) -> crate::Result { OpCode::try_from(first_header_byte & 0b0000_1111) } diff --git a/wtx/src/web_socket/misc/filled_buffer.rs b/wtx/src/web_socket/misc/filled_buffer.rs index 6abe4cb0..5c218be5 100644 --- a/wtx/src/web_socket/misc/filled_buffer.rs +++ b/wtx/src/web_socket/misc/filled_buffer.rs @@ -1,8 +1,4 @@ -#![allow( - // Indices point to valid memory - clippy::unreachable - )] - +use crate::misc::_unreachable; use alloc::{vec, vec::Vec}; use core::ops::{Deref, DerefMut}; @@ -55,7 +51,7 @@ impl Deref for FilledBuffer { if let Some(el) = self.buffer.get(..self.len) { el } else { - unreachable!() + _unreachable() } } } @@ -66,7 +62,7 @@ impl DerefMut for FilledBuffer { if let Some(el) = self.buffer.get_mut(..self.len) { el } else { - unreachable!() + _unreachable() } } } diff --git a/wtx/src/web_socket/misc/utf8_errors.rs b/wtx/src/web_socket/misc/utf8_errors.rs deleted file mode 100644 index 1938566b..00000000 --- a/wtx/src/web_socket/misc/utf8_errors.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::web_socket::misc::IncompleteUtf8Char; - -/// Extended error built upon [StdUtf8Error]. -pub(crate) enum ExtUtf8Error { - Incomplete { incomplete_ending_char: IncompleteUtf8Char }, - Invalid, -} - -/// Standard error that is similar to the error type of the std. -pub(crate) struct StdUtf8Error { - pub(crate) error_len: Option, - pub(crate) valid_up_to: usize, -} diff --git a/wtx/src/web_socket/op_code.rs b/wtx/src/web_socket/op_code.rs index d6332103..f4f826ff 100644 --- a/wtx/src/web_socket/op_code.rs +++ b/wtx/src/web_socket/op_code.rs @@ -2,7 +2,6 @@ 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 { /// Continuation of a previous frame. Continuation = (0b0000_0000), diff --git a/wtx/src/web_socket/unmask.rs b/wtx/src/web_socket/unmask.rs index bd046f52..61b2c8dc 100644 --- a/wtx/src/web_socket/unmask.rs +++ b/wtx/src/web_socket/unmask.rs @@ -35,26 +35,45 @@ fn unmask_u32_slice(bytes: &mut [u32], mask: u32) { }); } +#[cfg(feature = "_bench")] #[cfg(test)] -mod tests { - use crate::web_socket::unmask::unmask; - use alloc::{vec, vec::Vec}; +mod bench { + use crate::web_socket::unmask; - #[cfg(feature = "_bench")] #[bench] - fn unmask_bench(b: &mut test::Bencher) { + fn bench_unmask(b: &mut test::Bencher) { let mut data = crate::bench::_data(64 << 20); b.iter(|| unmask(&mut data, [3, 5, 7, 11])); } +} - #[cfg(feature = "_proptest")] +#[cfg(test)] +#[cfg(feature = "_proptest")] +mod proptest { #[test_strategy::proptest] - fn my_test(mut data: Vec, mask: [u8; 4]) { - unmask(&mut data, mask); + fn unmask(mut data: Vec, mask: [u8; 4]) { + crate::web_socket::unmask(&mut data, mask); } +} + +#[cfg(test)] +mod tests { + use crate::web_socket::unmask::unmask; + use alloc::{vec, vec::Vec}; #[test] - fn test_unmask() { + 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); + } + } + + #[test] + fn unmask_has_correct_output() { let mut payload = [0u8; 33]; let mask = [1, 2, 3, 4]; unmask(&mut payload, mask); @@ -66,15 +85,4 @@ mod tests { ] ); } - - #[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); - } - } }