diff options
author | Jeff Vander Stoep <jeffv@google.com> | 2024-02-08 09:24:11 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2024-02-08 09:24:11 +0000 |
commit | a9fa421db36446a10ed8729cdd7e72d1384abb41 (patch) | |
tree | 7bde71e0a7cbedeeef4e1b24c8431c7d601e1766 | |
parent | cd29fbc386ad5c9ca54db48146b3d2524a4a4428 (diff) | |
parent | f3862b033b5d27d17114ad9653d9e39fc9304113 (diff) | |
download | tungstenite-main.tar.gz |
Upgrade tungstenite to 0.21.0 am: f3862b033bHEADmastermainemu-34-2-dev
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/tungstenite/+/2952912
Change-Id: Ief07511a22b6203971dc88a6cdf449745be43a0f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | .cargo_vcs_info.json | 6 | ||||
-rw-r--r-- | Android.bp | 2 | ||||
-rw-r--r-- | CHANGELOG.md | 27 | ||||
-rw-r--r-- | Cargo.lock | 836 | ||||
-rw-r--r-- | Cargo.toml | 60 | ||||
-rw-r--r-- | Cargo.toml.orig | 55 | ||||
-rw-r--r-- | METADATA | 26 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | benches/buffer.rs | 3 | ||||
-rw-r--r-- | benches/write.rs | 75 | ||||
-rw-r--r-- | examples/autobahn-client.rs | 53 | ||||
-rw-r--r-- | examples/autobahn-server.rs | 47 | ||||
-rw-r--r-- | examples/callback-error.rs | 23 | ||||
-rw-r--r-- | examples/client.rs | 23 | ||||
-rw-r--r-- | examples/server.rs | 38 | ||||
-rw-r--r-- | examples/srv_accept_unmasked_frames.rs | 48 | ||||
-rw-r--r-- | src/client.rs | 1 | ||||
-rw-r--r-- | src/error.rs | 13 | ||||
-rw-r--r-- | src/handshake/client.rs | 6 | ||||
-rw-r--r-- | src/handshake/machine.rs | 85 | ||||
-rw-r--r-- | src/lib.rs | 4 | ||||
-rw-r--r-- | src/protocol/frame/frame.rs | 21 | ||||
-rw-r--r-- | src/protocol/frame/mask.rs | 2 | ||||
-rw-r--r-- | src/protocol/frame/mod.rs | 122 | ||||
-rw-r--r-- | src/protocol/message.rs | 2 | ||||
-rw-r--r-- | src/protocol/mod.rs | 464 | ||||
-rw-r--r-- | src/tls.rs | 29 |
27 files changed, 1247 insertions, 832 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..c0c5983 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "85463b264e3f672ef2004294d82fd3f4ee6a8ca3" + }, + "path_in_vcs": "" +}
\ No newline at end of file @@ -6,7 +6,7 @@ rust_library { host_supported: true, crate_name: "tungstenite", cargo_env_compat: true, - cargo_pkg_version: "0.19.0", + cargo_pkg_version: "0.21.0", srcs: ["src/lib.rs"], edition: "2018", rustlibs: [ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aa0a41..5c0348e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +# Unreleased +- Fix read-predominant auto pong responses not flushing when hitting WouldBlock errors. +- Improve `FrameHeader::format` write correctness. +- Up minimum _rustls_ to `0.21.6`. +- Update _webpki-roots_ to `0.26`. + +# 0.20.1 +- Fixes [CVE-2023-43669](https://github.com/snapview/tungstenite-rs/pull/379). + +# 0.20.0 +- Remove many implicit flushing behaviours. In general reading and writing messages will no + longer flush until calling `flush`. An exception is automatic responses (e.g. pongs) + which will continue to be written and flushed when reading and writing. + This allows writing a batch of messages and flushing once, improving performance. +- Add `WebSocket::read`, `write`, `send`, `flush`. Deprecate `read_message`, `write_message`, `write_pending`. +- Add `FrameSocket::read`, `write`, `send`, `flush`. Remove `read_frame`, `write_frame`, `write_pending`. + Note: Previous use of `write_frame` may be replaced with `send`. +- Add `WebSocketContext::read`, `write`, `flush`. Remove `read_message`, `write_message`, `write_pending`. + Note: Previous use of `write_message` may be replaced with `write` + `flush`. +- Remove `send_queue`, replaced with using the frame write buffer to achieve similar results. + * Add `WebSocketConfig::max_write_buffer_size`. Deprecate `max_send_queue`. + * Add `Error::WriteBufferFull`. Remove `Error::SendQueueFull`. + Note: `WriteBufferFull` returns the message that could not be written as a `Message::Frame`. +- Add ability to buffer multiple writes before writing to the underlying stream, controlled by + `WebSocketConfig::write_buffer_size` (default 128 KiB). Improves batch message write performance. +- Panic on receiving invalid `WebSocketConfig`. + # 0.19.0 - Update TLS dependencies. @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.18" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -18,15 +18,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] -name = "atty" -version = "0.2.14" +name = "anstyle" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "autocfg" @@ -36,9 +31,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bitflags" @@ -47,31 +42,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cast" @@ -81,15 +82,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -99,9 +97,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ciborium" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", @@ -110,15 +108,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half", @@ -126,30 +124,34 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.21" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ed5341b2301a26ab80be5cbdced622e80ed808483c52e45e3310a877d3b37d7" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ - "bitflags", - "clap_lex", - "indexmap", - "textwrap", + "clap_builder", ] [[package]] -name = "clap_lex" -version = "0.2.4" +name = "clap_builder" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" dependencies = [ - "os_str_bytes", + "anstyle", + "clap_lex", ] [[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -157,34 +159,34 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "criterion" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", - "atty", "cast", "ciborium", "clap", "criterion-plot", + "is-terminal", "itertools", - "lazy_static", "num-traits", + "once_cell", "oorandom", "plotters", "rayon", @@ -207,55 +209,43 @@ dependencies = [ ] [[package]] -name = "crossbeam-channel" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", -] - -[[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.9" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ - "cfg-if 1.0.0", - "once_cell", + "cfg-if", ] [[package]] name = "crypto-common" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ccfd8c0ee4cce11e45b3fd6f9d5e69e0cc62912aa6a0cb1bf4617b0eba5a12f" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -263,15 +253,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.3.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "digest" -version = "0.10.3" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -279,15 +269,15 @@ dependencies = [ [[package]] name = "either" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -298,33 +288,19 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.45.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] name = "fastrand" -version = "1.7.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fnv" @@ -349,19 +325,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -369,11 +344,11 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "wasi", ] @@ -385,31 +360,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "http" -version = "0.2.8" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" dependencies = [ "bytes", "fnv", @@ -418,9 +378,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "humantime" @@ -430,26 +390,15 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "idna" -version = "0.2.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] [[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] name = "input_buffer" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -459,57 +408,36 @@ dependencies = [ ] [[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", -] - -[[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", + "hermit-abi", "rustix", "windows-sys 0.48.0", ] [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -522,51 +450,42 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.141" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" -version = "0.3.1" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "matches" -version = "0.1.9" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -581,40 +500,19 @@ dependencies = [ ] [[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi", -] - -[[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi 0.1.19", - "libc", -] - -[[package]] name = "once_cell" -version = "1.13.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" @@ -624,12 +522,12 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "openssl" -version = "0.10.41" +version = "0.10.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" dependencies = [ - "bitflags", - "cfg-if 1.0.0", + "bitflags 2.4.1", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -639,9 +537,9 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", @@ -656,20 +554,19 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.22.0+1.1.1q" +version = "300.1.6+3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853" +checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.75" +version = "0.9.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" dependencies = [ - "autocfg", "cc", "libc", "openssl-src", @@ -678,28 +575,22 @@ dependencies = [ ] [[package]] -name = "os_str_bytes" -version = "6.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" - -[[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plotters" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", @@ -710,39 +601,39 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -770,149 +661,157 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rayon" -version = "1.5.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.6.0" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", + "regex-automata", "regex-syntax", ] [[package]] -name = "regex-syntax" -version = "0.6.27" +name = "regex-automata" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "ring" -version = "0.16.20" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", + "getrandom", "libc", - "once_cell", "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "rustix" -version = "0.37.9" +version = "0.38.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3eb76a3b09109e78c52d45979fea3cd8ddaadb223531d0846bedb60e72c3e99" +checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" dependencies = [ - "bitflags", + "bitflags 2.4.1", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07180898a28ed6a7f7ba2311594308f595e3dd2e3c3812fa0a80a47b45f17e5d" +checksum = "5bc238b76c51bbc449c55ffbc39d03772a057cc8cf783c49d4af4c2537b74a8b" dependencies = [ "log", "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", "rustls-pemfile", + "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" dependencies = [ "base64", + "rustls-pki-types", ] [[package]] +name = "rustls-pki-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7673e0aa20ee4937c6aacfc12bb8341cfbf054cdd21df6bec5fd0629fe9339b" + +[[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +checksum = "de2635c8bc2b88d367767c5de8ea1d8db9af3f6219eba28442242d9ab81d1b89" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -925,37 +824,26 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys 0.48.0", ] [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.6.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -964,9 +852,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -974,18 +862,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.139" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.139" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -994,9 +882,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1005,26 +893,42 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest", ] [[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "subtle" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "1.0.98" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -1033,47 +937,40 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys 0.48.0", ] [[package]] name = "termcolor" -version = "1.1.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] [[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" - -[[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -1101,13 +998,13 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tungstenite" -version = "0.19.0" +version = "0.21.0" dependencies = [ "byteorder", "bytes", @@ -1119,60 +1016,59 @@ dependencies = [ "input_buffer", "log", "native-tls", - "net2", "rand", "rustls", "rustls-native-certs", + "rustls-pki-types", "sha1", + "socket2", "thiserror", "url", "utf-8", - "webpki", "webpki-roots", ] [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.2.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -1196,12 +1092,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -1213,23 +1108,23 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -1238,9 +1133,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1248,9 +1143,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -1261,37 +1156,27 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] name = "webpki-roots" -version = "0.23.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125" +checksum = "0de2cfda980f21be5a7ed2eadb3e6fe074d56022bea2cdeb1a62eb220fc04188" dependencies = [ - "rustls-webpki", + "rustls-pki-types", ] [[package]] @@ -1312,9 +1197,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1327,175 +1212,138 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.48.5", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.52.0", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" +name = "zeroize" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" @@ -13,7 +13,7 @@ edition = "2018" rust-version = "1.51" name = "tungstenite" -version = "0.19.0" +version = "0.21.0" authors = [ "Alexey Galakhov", "Daniel Abramov", @@ -21,13 +21,14 @@ authors = [ include = [ "benches/**/*", "src/**/*", + "examples/**/*", "LICENSE-*", "README.md", "CHANGELOG.md", ] description = "Lightweight stream-based WebSocket implementation" homepage = "https://github.com/snapview/tungstenite-rs" -documentation = "https://docs.rs/tungstenite/0.19.0" +documentation = "https://docs.rs/tungstenite/0.21.0" readme = "README.md" keywords = [ "websocket", @@ -44,10 +45,38 @@ repository = "https://github.com/snapview/tungstenite-rs" [package.metadata.docs.rs] all-features = true +[[example]] +name = "client" +required-features = ["handshake"] + +[[example]] +name = "server" +required-features = ["handshake"] + +[[example]] +name = "autobahn-client" +required-features = ["handshake"] + +[[example]] +name = "autobahn-server" +required-features = ["handshake"] + +[[example]] +name = "callback-error" +required-features = ["handshake"] + +[[example]] +name = "srv_accept_unmasked_frames" +required-features = ["handshake"] + [[bench]] name = "buffer" harness = false +[[bench]] +name = "write" +harness = false + [dependencies.byteorder] version = "1.3.2" @@ -59,7 +88,7 @@ version = "2" optional = true [dependencies.http] -version = "0.2" +version = "1.0" optional = true [dependencies.httparse] @@ -78,11 +107,15 @@ package = "native-tls" version = "0.8.0" [dependencies.rustls] -version = "0.21.0" +version = "0.22.0" optional = true [dependencies.rustls-native-certs] -version = "0.6.0" +version = "0.7.0" +optional = true + +[dependencies.rustls-pki-types] +version = "1.0" optional = true [dependencies.sha1] @@ -99,17 +132,12 @@ optional = true [dependencies.utf-8] version = "0.7.5" -[dependencies.webpki] -version = "0.22" -features = ["std"] -optional = true - [dependencies.webpki-roots] -version = "0.23" +version = "0.26" optional = true [dev-dependencies.criterion] -version = "0.4.0" +version = "0.5.0" [dev-dependencies.env_logger] version = "0.10.0" @@ -117,16 +145,16 @@ version = "0.10.0" [dev-dependencies.input_buffer] version = "0.5.0" -[dev-dependencies.net2] -version = "0.2.37" - [dev-dependencies.rand] version = "0.8.4" +[dev-dependencies.socket2] +version = "0.5.5" + [features] __rustls-tls = [ "rustls", - "webpki", + "rustls-pki-types", ] default = ["handshake"] handshake = [ diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 6640559..2e67f01 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -7,12 +7,12 @@ authors = ["Alexey Galakhov", "Daniel Abramov"] license = "MIT OR Apache-2.0" readme = "README.md" homepage = "https://github.com/snapview/tungstenite-rs" -documentation = "https://docs.rs/tungstenite/0.19.0" +documentation = "https://docs.rs/tungstenite/0.21.0" repository = "https://github.com/snapview/tungstenite-rs" -version = "0.19.0" +version = "0.21.0" edition = "2018" rust-version = "1.51" -include = ["benches/**/*", "src/**/*", "LICENSE-*", "README.md", "CHANGELOG.md"] +include = ["benches/**/*", "src/**/*", "examples/**/*", "LICENSE-*", "README.md", "CHANGELOG.md"] [package.metadata.docs.rs] all-features = true @@ -24,13 +24,13 @@ native-tls = ["native-tls-crate"] native-tls-vendored = ["native-tls", "native-tls-crate/vendored"] rustls-tls-native-roots = ["__rustls-tls", "rustls-native-certs"] rustls-tls-webpki-roots = ["__rustls-tls", "webpki-roots"] -__rustls-tls = ["rustls", "webpki"] +__rustls-tls = ["rustls", "rustls-pki-types"] [dependencies] data-encoding = { version = "2", optional = true } byteorder = "1.3.2" bytes = "1.0" -http = { version = "0.2", optional = true } +http = { version = "1.0", optional = true } httparse = { version = "1.3.4", optional = true } log = "0.4.8" rand = "0.8.0" @@ -46,28 +46,55 @@ version = "0.2.3" [dependencies.rustls] optional = true -version = "0.21.0" +version = "0.22.0" -[dependencies.rustls-native-certs] +[dependencies.rustls-pki-types] optional = true -version = "0.6.0" +version = "1.0" -[dependencies.webpki] +[dependencies.rustls-native-certs] optional = true -version = "0.22" -features = ["std"] +version = "0.7.0" [dependencies.webpki-roots] optional = true -version = "0.23" +version = "0.26" [dev-dependencies] -criterion = "0.4.0" +criterion = "0.5.0" env_logger = "0.10.0" input_buffer = "0.5.0" -net2 = "0.2.37" rand = "0.8.4" +socket2 = "0.5.5" [[bench]] name = "buffer" harness = false + +[[bench]] +name = "write" +harness = false + +[[example]] +name = "client" +required-features = ["handshake"] + +[[example]] +name = "server" +required-features = ["handshake"] + +[[example]] +name = "autobahn-client" +required-features = ["handshake"] + +[[example]] +name = "autobahn-server" +required-features = ["handshake"] + +[[example]] +name = "callback-error" +required-features = ["handshake"] + +[[example]] +name = "srv_accept_unmasked_frames" +required-features = ["handshake"] @@ -1,20 +1,20 @@ +# This project was upgraded with external_updater. +# Usage: tools/external_updater/updater.sh update external/rust/crates/tungstenite +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md + name: "tungstenite" description: "Lightweight stream-based WebSocket implementation" third_party { - url { - type: HOMEPAGE - value: "https://crates.io/crates/tungstenite" - } - url { - type: ARCHIVE - value: "https://static.crates.io/crates/tungstenite/tungstenite-0.19.0.crate" - } - version: "0.19.0" - # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. license_type: NOTICE last_upgrade_date { - year: 2023 - month: 6 - day: 2 + year: 2024 + month: 2 + day: 8 + } + homepage: "https://crates.io/crates/tungstenite" + identifier { + type: "Archive" + value: "https://static.crates.io/crates/tungstenite/tungstenite-0.21.0.crate" + version: "0.21.0" } } @@ -14,11 +14,11 @@ fn main () { spawn (move || { let mut websocket = accept(stream.unwrap()).unwrap(); loop { - let msg = websocket.read_message().unwrap(); + let msg = websocket.read().unwrap(); // We do not want to send back ping/pong messages. if msg.is_binary() || msg.is_text() { - websocket.write_message(msg).unwrap(); + websocket.send(msg).unwrap(); } } }); @@ -36,7 +36,7 @@ take a look at [`tokio-tungstenite`](https://github.com/snapview/tokio-tungsteni [![MIT licensed](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE-MIT) [![Apache-2.0 licensed](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) [![Crates.io](https://img.shields.io/crates/v/tungstenite.svg?maxAge=2592000)](https://crates.io/crates/tungstenite) -[![Build Status](https://travis-ci.org/snapview/tungstenite-rs.svg?branch=master)](https://travis-ci.org/snapview/tungstenite-rs) +[![Build Status](https://github.com/snapview/tungstenite-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/snapview/tungstenite-rs/actions) [Documentation](https://docs.rs/tungstenite) @@ -77,7 +77,7 @@ There is no support for permessage-deflate at the moment, but the PRs are welcom Testing ------- -Tungstenite is thoroughly tested and passes the [Autobahn Test Suite](https://crossbar.io/autobahn/) for +Tungstenite is thoroughly tested and passes the [Autobahn Test Suite](https://github.com/crossbario/autobahn-testsuite) for WebSockets. It is also covered by internal unit tests as well as possible. Contributing diff --git a/benches/buffer.rs b/benches/buffer.rs index 4f50649..9f15d13 100644 --- a/benches/buffer.rs +++ b/benches/buffer.rs @@ -1,5 +1,4 @@ -use std::io::Result as IoResult; -use std::io::{Cursor, Read}; +use std::io::{Cursor, Read, Result as IoResult}; use bytes::Buf; use criterion::*; diff --git a/benches/write.rs b/benches/write.rs new file mode 100644 index 0000000..7908818 --- /dev/null +++ b/benches/write.rs @@ -0,0 +1,75 @@ +//! Benchmarks for write performance. +use criterion::{BatchSize, Criterion}; +use std::{ + hint, + io::{self, Read, Write}, + time::{Duration, Instant}, +}; +use tungstenite::{Message, WebSocket}; + +const MOCK_WRITE_LEN: usize = 8 * 1024 * 1024; + +/// `Write` impl that simulates slowish writes and slow flushes. +/// +/// Each `write` can buffer up to 8 MiB before flushing but takes an additional **~80ns** +/// to simulate stuff going on in the underlying stream. +/// Each `flush` takes **~8µs** to simulate flush io. +struct MockWrite(Vec<u8>); + +impl Read for MockWrite { + fn read(&mut self, _: &mut [u8]) -> io::Result<usize> { + Err(io::Error::new(io::ErrorKind::WouldBlock, "reads not supported")) + } +} +impl Write for MockWrite { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + if self.0.len() + buf.len() > MOCK_WRITE_LEN { + self.flush()?; + } + // simulate io + spin(Duration::from_nanos(80)); + self.0.extend(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + if !self.0.is_empty() { + // simulate io + spin(Duration::from_micros(8)); + self.0.clear(); + } + Ok(()) + } +} + +fn spin(duration: Duration) { + let a = Instant::now(); + while a.elapsed() < duration { + hint::spin_loop(); + } +} + +fn benchmark(c: &mut Criterion) { + // Writes 100k small json text messages then flushes + c.bench_function("write 100k small texts then flush", |b| { + let mut ws = WebSocket::from_raw_socket( + MockWrite(Vec::with_capacity(MOCK_WRITE_LEN)), + tungstenite::protocol::Role::Server, + None, + ); + + b.iter_batched( + || (0..100_000).map(|i| Message::Text(format!("{{\"id\":{i}}}"))), + |batch| { + for msg in batch { + ws.write(msg).unwrap(); + } + ws.flush().unwrap(); + }, + BatchSize::SmallInput, + ) + }); +} + +criterion::criterion_group!(write_benches, benchmark); +criterion::criterion_main!(write_benches); diff --git a/examples/autobahn-client.rs b/examples/autobahn-client.rs new file mode 100644 index 0000000..ac7a7d1 --- /dev/null +++ b/examples/autobahn-client.rs @@ -0,0 +1,53 @@ +use log::*; +use url::Url; + +use tungstenite::{connect, Error, Message, Result}; + +const AGENT: &str = "Tungstenite"; + +fn get_case_count() -> Result<u32> { + let (mut socket, _) = connect(Url::parse("ws://localhost:9001/getCaseCount").unwrap())?; + let msg = socket.read()?; + socket.close(None)?; + Ok(msg.into_text()?.parse::<u32>().unwrap()) +} + +fn update_reports() -> Result<()> { + let (mut socket, _) = connect( + Url::parse(&format!("ws://localhost:9001/updateReports?agent={}", AGENT)).unwrap(), + )?; + socket.close(None)?; + Ok(()) +} + +fn run_test(case: u32) -> Result<()> { + info!("Running test case {}", case); + let case_url = + Url::parse(&format!("ws://localhost:9001/runCase?case={}&agent={}", case, AGENT)).unwrap(); + let (mut socket, _) = connect(case_url)?; + loop { + match socket.read()? { + msg @ Message::Text(_) | msg @ Message::Binary(_) => { + socket.send(msg)?; + } + Message::Ping(_) | Message::Pong(_) | Message::Close(_) | Message::Frame(_) => {} + } + } +} + +fn main() { + env_logger::init(); + + let total = get_case_count().unwrap(); + + for case in 1..=total { + if let Err(e) = run_test(case) { + match e { + Error::ConnectionClosed | Error::Protocol(_) | Error::Utf8 => (), + err => error!("test: {}", err), + } + } + } + + update_reports().unwrap(); +} diff --git a/examples/autobahn-server.rs b/examples/autobahn-server.rs new file mode 100644 index 0000000..dafe37b --- /dev/null +++ b/examples/autobahn-server.rs @@ -0,0 +1,47 @@ +use std::{ + net::{TcpListener, TcpStream}, + thread::spawn, +}; + +use log::*; +use tungstenite::{accept, handshake::HandshakeRole, Error, HandshakeError, Message, Result}; + +fn must_not_block<Role: HandshakeRole>(err: HandshakeError<Role>) -> Error { + match err { + HandshakeError::Interrupted(_) => panic!("Bug: blocking socket would block"), + HandshakeError::Failure(f) => f, + } +} + +fn handle_client(stream: TcpStream) -> Result<()> { + let mut socket = accept(stream).map_err(must_not_block)?; + info!("Running test"); + loop { + match socket.read()? { + msg @ Message::Text(_) | msg @ Message::Binary(_) => { + socket.send(msg)?; + } + Message::Ping(_) | Message::Pong(_) | Message::Close(_) | Message::Frame(_) => {} + } + } +} + +fn main() { + env_logger::init(); + + let server = TcpListener::bind("127.0.0.1:9002").unwrap(); + + for stream in server.incoming() { + spawn(move || match stream { + Ok(stream) => { + if let Err(err) = handle_client(stream) { + match err { + Error::ConnectionClosed | Error::Protocol(_) | Error::Utf8 => (), + e => error!("test: {}", e), + } + } + } + Err(e) => error!("Error accepting stream: {}", e), + }); + } +} diff --git a/examples/callback-error.rs b/examples/callback-error.rs new file mode 100644 index 0000000..cf78a2e --- /dev/null +++ b/examples/callback-error.rs @@ -0,0 +1,23 @@ +use std::{net::TcpListener, thread::spawn}; + +use tungstenite::{ + accept_hdr, + handshake::server::{Request, Response}, + http::StatusCode, +}; + +fn main() { + let server = TcpListener::bind("127.0.0.1:3012").unwrap(); + for stream in server.incoming() { + spawn(move || { + let callback = |_req: &Request, _resp| { + let resp = Response::builder() + .status(StatusCode::FORBIDDEN) + .body(Some("Access denied".into())) + .unwrap(); + Err(resp) + }; + accept_hdr(stream.unwrap(), callback).unwrap_err(); + }); + } +} diff --git a/examples/client.rs b/examples/client.rs new file mode 100644 index 0000000..a24f316 --- /dev/null +++ b/examples/client.rs @@ -0,0 +1,23 @@ +use tungstenite::{connect, Message}; +use url::Url; + +fn main() { + env_logger::init(); + + let (mut socket, response) = + connect(Url::parse("ws://localhost:3012/socket").unwrap()).expect("Can't connect"); + + println!("Connected to the server"); + println!("Response HTTP code: {}", response.status()); + println!("Response contains the following headers:"); + for (ref header, _value) in response.headers() { + println!("* {}", header); + } + + socket.send(Message::Text("Hello WebSocket".into())).unwrap(); + loop { + let msg = socket.read().expect("Error reading message"); + println!("Received: {}", msg); + } + // socket.close(None); +} diff --git a/examples/server.rs b/examples/server.rs new file mode 100644 index 0000000..2183b96 --- /dev/null +++ b/examples/server.rs @@ -0,0 +1,38 @@ +use std::{net::TcpListener, thread::spawn}; + +use tungstenite::{ + accept_hdr, + handshake::server::{Request, Response}, +}; + +fn main() { + env_logger::init(); + let server = TcpListener::bind("127.0.0.1:3012").unwrap(); + for stream in server.incoming() { + spawn(move || { + let callback = |req: &Request, mut response: Response| { + println!("Received a new ws handshake"); + println!("The request's path is: {}", req.uri().path()); + println!("The request's headers are:"); + for (ref header, _value) in req.headers() { + println!("* {}", header); + } + + // Let's add an additional header to our response to the client. + let headers = response.headers_mut(); + headers.append("MyCustomHeader", ":)".parse().unwrap()); + headers.append("SOME_TUNGSTENITE_HEADER", "header_value".parse().unwrap()); + + Ok(response) + }; + let mut websocket = accept_hdr(stream.unwrap(), callback).unwrap(); + + loop { + let msg = websocket.read().unwrap(); + if msg.is_binary() || msg.is_text() { + websocket.send(msg).unwrap(); + } + } + }); + } +} diff --git a/examples/srv_accept_unmasked_frames.rs b/examples/srv_accept_unmasked_frames.rs new file mode 100644 index 0000000..b65e4f7 --- /dev/null +++ b/examples/srv_accept_unmasked_frames.rs @@ -0,0 +1,48 @@ +use std::{net::TcpListener, thread::spawn}; +use tungstenite::{ + accept_hdr_with_config, + handshake::server::{Request, Response}, + protocol::WebSocketConfig, +}; + +fn main() { + env_logger::init(); + let server = TcpListener::bind("127.0.0.1:3012").unwrap(); + for stream in server.incoming() { + spawn(move || { + let callback = |req: &Request, mut response: Response| { + println!("Received a new ws handshake"); + println!("The request's path is: {}", req.uri().path()); + println!("The request's headers are:"); + for (ref header, _value) in req.headers() { + println!("* {}", header); + } + + // Let's add an additional header to our response to the client. + let headers = response.headers_mut(); + headers.append("MyCustomHeader", ":)".parse().unwrap()); + headers.append("SOME_TUNGSTENITE_HEADER", "header_value".parse().unwrap()); + + Ok(response) + }; + + let config = Some(WebSocketConfig { + // This setting allows to accept client frames which are not masked + // This is not in compliance with RFC 6455 but might be handy in some + // rare cases where it is necessary to integrate with existing/legacy + // clients which are sending unmasked frames + accept_unmasked_frames: true, + ..<_>::default() + }); + + let mut websocket = accept_hdr_with_config(stream.unwrap(), callback, config).unwrap(); + + loop { + let msg = websocket.read().unwrap(); + if msg.is_binary() || msg.is_text() { + println!("received message {}", msg); + } + } + }); + } +} diff --git a/src/client.rs b/src/client.rs index 9301939..9b30037 100644 --- a/src/client.rs +++ b/src/client.rs @@ -54,6 +54,7 @@ pub fn connect_with_config<Req: IntoClientRequest>( let uri = request.uri(); let mode = uri_mode(uri)?; let host = request.uri().host().ok_or(Error::Url(UrlError::NoHostName))?; + let host = if host.starts_with('[') { &host[1..host.len() - 1] } else { host }; let port = uri.port_u16().unwrap_or(match mode { Mode::Plain => 80, Mode::Tls => 443, diff --git a/src/error.rs b/src/error.rs index c830024..faea80b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -53,12 +53,15 @@ pub enum Error { /// Protocol violation. #[error("WebSocket protocol error: {0}")] Protocol(#[from] ProtocolError), - /// Message send queue full. - #[error("Send queue is full")] - SendQueueFull(Message), + /// Message write buffer is full. + #[error("Write buffer is full")] + WriteBufferFull(Message), /// UTF coding error. #[error("UTF-8 encoding error")] Utf8, + /// Attack attempt detected. + #[error("Attack attempt detected")] + AttackAttempt, /// Invalid URL. #[error("URL error: {0}")] Url(#[from] UrlError), @@ -271,10 +274,6 @@ pub enum TlsError { #[cfg(feature = "__rustls-tls")] #[error("rustls error: {0}")] Rustls(#[from] rustls::Error), - /// Webpki error. - #[cfg(feature = "__rustls-tls")] - #[error("webpki error: {0}")] - Webpki(#[from] webpki::Error), /// DNS name resolution error. #[cfg(feature = "__rustls-tls")] #[error("Invalid DNS name")] diff --git a/src/handshake/client.rs b/src/handshake/client.rs index a6d9f1c..08cc7b2 100644 --- a/src/handshake/client.rs +++ b/src/handshake/client.rs @@ -87,8 +87,8 @@ impl<S: Read + Write> HandshakeRole for ClientHandshake<S> { Ok(r) => r, Err(Error::Http(mut e)) => { *e.body_mut() = Some(tail); - return Err(Error::Http(e)) - }, + return Err(Error::Http(e)); + } Err(e) => return Err(e), }; @@ -258,7 +258,7 @@ impl TryParse for Response { impl<'h, 'b: 'h> FromHttparse<httparse::Response<'h, 'b>> for Response { fn from_httparse(raw: httparse::Response<'h, 'b>) -> Result<Self> { if raw.version.expect("Bug: no HTTP version") < /*1.*/1 { - return Err(Error::Protocol(ProtocolError::WrongHttpMethod)); + return Err(Error::Protocol(ProtocolError::WrongHttpVersion)); } let headers = HeaderMap::from_httparse(raw.headers)?; diff --git a/src/handshake/machine.rs b/src/handshake/machine.rs index eacb4bf..2e3f2cb 100644 --- a/src/handshake/machine.rs +++ b/src/handshake/machine.rs @@ -20,7 +20,7 @@ pub struct HandshakeMachine<Stream> { impl<Stream> HandshakeMachine<Stream> { /// Start reading data from the peer. pub fn start_read(stream: Stream) -> Self { - HandshakeMachine { stream, state: HandshakeState::Reading(ReadBuffer::new()) } + Self { stream, state: HandshakeState::Reading(ReadBuffer::new(), AttackCheck::new()) } } /// Start writing data to the peer. pub fn start_write<D: Into<Vec<u8>>>(stream: Stream, data: D) -> Self { @@ -41,25 +41,31 @@ impl<Stream: Read + Write> HandshakeMachine<Stream> { pub fn single_round<Obj: TryParse>(mut self) -> Result<RoundResult<Obj, Stream>> { trace!("Doing handshake round."); match self.state { - HandshakeState::Reading(mut buf) => { + HandshakeState::Reading(mut buf, mut attack_check) => { let read = buf.read_from(&mut self.stream).no_block()?; match read { Some(0) => Err(Error::Protocol(ProtocolError::HandshakeIncomplete)), - Some(_) => Ok(if let Some((size, obj)) = Obj::try_parse(Buf::chunk(&buf))? { - buf.advance(size); - RoundResult::StageFinished(StageResult::DoneReading { - result: obj, - stream: self.stream, - tail: buf.into_vec(), + Some(count) => { + attack_check.check_incoming_packet_size(count)?; + // TODO: this is slow for big headers with too many small packets. + // The parser has to be reworked in order to work on streams instead + // of buffers. + Ok(if let Some((size, obj)) = Obj::try_parse(Buf::chunk(&buf))? { + buf.advance(size); + RoundResult::StageFinished(StageResult::DoneReading { + result: obj, + stream: self.stream, + tail: buf.into_vec(), + }) + } else { + RoundResult::Incomplete(HandshakeMachine { + state: HandshakeState::Reading(buf, attack_check), + ..self + }) }) - } else { - RoundResult::Incomplete(HandshakeMachine { - state: HandshakeState::Reading(buf), - ..self - }) - }), + } None => Ok(RoundResult::WouldBlock(HandshakeMachine { - state: HandshakeState::Reading(buf), + state: HandshakeState::Reading(buf, attack_check), ..self })), } @@ -119,7 +125,54 @@ pub trait TryParse: Sized { #[derive(Debug)] enum HandshakeState { /// Reading data from the peer. - Reading(ReadBuffer), + Reading(ReadBuffer, AttackCheck), /// Sending data to the peer. Writing(Cursor<Vec<u8>>), } + +/// Attack mitigation. Contains counters needed to prevent DoS attacks +/// and reject valid but useless headers. +#[derive(Debug)] +pub(crate) struct AttackCheck { + /// Number of HTTP header successful reads (TCP packets). + number_of_packets: usize, + /// Total number of bytes in HTTP header. + number_of_bytes: usize, +} + +impl AttackCheck { + /// Initialize attack checking for incoming buffer. + fn new() -> Self { + Self { number_of_packets: 0, number_of_bytes: 0 } + } + + /// Check the size of an incoming packet. To be called immediately after `read()` + /// passing its returned bytes count as `size`. + fn check_incoming_packet_size(&mut self, size: usize) -> Result<()> { + self.number_of_packets += 1; + self.number_of_bytes += size; + + // TODO: these values are hardcoded. Instead of making them configurable, + // rework the way HTTP header is parsed to remove this check at all. + const MAX_BYTES: usize = 65536; + const MAX_PACKETS: usize = 512; + const MIN_PACKET_SIZE: usize = 128; + const MIN_PACKET_CHECK_THRESHOLD: usize = 64; + + if self.number_of_bytes > MAX_BYTES { + return Err(Error::AttackAttempt); + } + + if self.number_of_packets > MAX_PACKETS { + return Err(Error::AttackAttempt); + } + + if self.number_of_packets > MIN_PACKET_CHECK_THRESHOLD + && self.number_of_packets * MIN_PACKET_SIZE > self.number_of_bytes + { + return Err(Error::AttackAttempt); + } + + Ok(()) + } +} @@ -25,7 +25,7 @@ pub mod protocol; #[cfg(feature = "handshake")] mod server; pub mod stream; -#[cfg(any(feature = "native-tls", feature = "__rustls-tls"))] +#[cfg(all(any(feature = "native-tls", feature = "__rustls-tls"), feature = "handshake"))] mod tls; pub mod util; @@ -44,5 +44,5 @@ pub use crate::{ server::{accept, accept_hdr, accept_hdr_with_config, accept_with_config}, }; -#[cfg(any(feature = "native-tls", feature = "__rustls-tls"))] +#[cfg(all(any(feature = "native-tls", feature = "__rustls-tls"), feature = "handshake"))] pub use tls::{client_tls, client_tls_with_config, Connector}; diff --git a/src/protocol/frame/frame.rs b/src/protocol/frame/frame.rs index 08def34..6b797a9 100644 --- a/src/protocol/frame/frame.rs +++ b/src/protocol/frame/frame.rs @@ -1,4 +1,4 @@ -use byteorder::{ByteOrder, NetworkEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{NetworkEndian, ReadBytesExt}; use log::*; use std::{ borrow::Cow, @@ -108,8 +108,12 @@ impl FrameHeader { output.write_all(&[one, two])?; match lenfmt { LengthFormat::U8(_) => (), - LengthFormat::U16 => output.write_u16::<NetworkEndian>(length as u16)?, - LengthFormat::U64 => output.write_u64::<NetworkEndian>(length)?, + LengthFormat::U16 => { + output.write_all(&(length as u16).to_be_bytes())?; + } + LengthFormat::U64 => { + output.write_all(&length.to_be_bytes())?; + } } if let Some(ref mask) = self.mask { @@ -295,7 +299,7 @@ impl Frame { 1 => Err(Error::Protocol(ProtocolError::InvalidCloseSequence)), _ => { let mut data = self.payload; - let code = NetworkEndian::read_u16(&data[0..2]).into(); + let code = u16::from_be_bytes([data[0], data[1]]).into(); data.drain(0..2); let text = String::from_utf8(data)?; Ok(Some(CloseFrame { code, reason: text.into() })) @@ -340,7 +344,7 @@ impl Frame { pub fn close(msg: Option<CloseFrame>) -> Frame { let payload = if let Some(CloseFrame { code, reason }) = msg { let mut p = Vec::with_capacity(reason.as_bytes().len() + 2); - p.write_u16::<NetworkEndian>(code.into()).unwrap(); // can't fail + p.extend(u16::from(code).to_be_bytes()); p.extend_from_slice(reason.as_bytes()); p } else { @@ -366,6 +370,8 @@ impl Frame { impl fmt::Display for Frame { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use std::fmt::Write; + write!( f, " @@ -385,7 +391,10 @@ payload: 0x{} // self.mask.map(|mask| format!("{:?}", mask)).unwrap_or("NONE".into()), self.len(), self.payload.len(), - self.payload.iter().map(|byte| format!("{:02x}", byte)).collect::<String>() + self.payload.iter().fold(String::new(), |mut output, byte| { + _ = write!(output, "{byte:02x}"); + output + }) ) } } diff --git a/src/protocol/frame/mask.rs b/src/protocol/frame/mask.rs index 31ff264..ff6eb75 100644 --- a/src/protocol/frame/mask.rs +++ b/src/protocol/frame/mask.rs @@ -48,7 +48,7 @@ mod tests { #[test] fn test_apply_mask() { let mask = [0x6d, 0xb6, 0xb2, 0x80]; - let unmasked = vec![ + let unmasked = [ 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9, 0x12, 0x03, ]; diff --git a/src/protocol/frame/mod.rs b/src/protocol/frame/mod.rs index 39066be..7d2ee41 100644 --- a/src/protocol/frame/mod.rs +++ b/src/protocol/frame/mod.rs @@ -6,15 +6,14 @@ pub mod coding; mod frame; mod mask; -use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read, Write}; - -use log::*; - -pub use self::frame::{CloseFrame, Frame, FrameHeader}; use crate::{ error::{CapacityError, Error, Result}, - ReadBuffer, + Message, ReadBuffer, }; +use log::*; +use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read, Write}; + +pub use self::frame::{CloseFrame, Frame, FrameHeader}; /// A reader and writer for WebSocket frames. #[derive(Debug)] @@ -57,7 +56,7 @@ where Stream: Read, { /// Read a frame from stream. - pub fn read_frame(&mut self, max_size: Option<usize>) -> Result<Option<Frame>> { + pub fn read(&mut self, max_size: Option<usize>) -> Result<Option<Frame>> { self.codec.read_frame(&mut self.stream, max_size) } } @@ -66,18 +65,28 @@ impl<Stream> FrameSocket<Stream> where Stream: Write, { + /// Writes and immediately flushes a frame. + /// Equivalent to calling [`write`](Self::write) then [`flush`](Self::flush). + pub fn send(&mut self, frame: Frame) -> Result<()> { + self.write(frame)?; + self.flush() + } + /// Write a frame to stream. /// - /// This function guarantees that the frame is queued regardless of any errors. - /// There is no need to resend the frame. In order to handle WouldBlock or Incomplete, - /// call write_pending() afterwards. - pub fn write_frame(&mut self, frame: Frame) -> Result<()> { - self.codec.write_frame(&mut self.stream, frame) + /// A subsequent call should be made to [`flush`](Self::flush) to flush writes. + /// + /// This function guarantees that the frame is queued unless [`Error::WriteBufferFull`] + /// is returned. + /// In order to handle WouldBlock or Incomplete, call [`flush`](Self::flush) afterwards. + pub fn write(&mut self, frame: Frame) -> Result<()> { + self.codec.buffer_frame(&mut self.stream, frame) } - /// Complete pending write, if any. - pub fn write_pending(&mut self) -> Result<()> { - self.codec.write_pending(&mut self.stream) + /// Flush writes. + pub fn flush(&mut self) -> Result<()> { + self.codec.write_out_buffer(&mut self.stream)?; + Ok(self.stream.flush()?) } } @@ -88,6 +97,14 @@ pub(super) struct FrameCodec { in_buffer: ReadBuffer, /// Buffer to send packets to the network. out_buffer: Vec<u8>, + /// Capacity limit for `out_buffer`. + max_out_buffer_len: usize, + /// Buffer target length to reach before writing to the stream + /// on calls to `buffer_frame`. + /// + /// Setting this to non-zero will buffer small writes from hitting + /// the stream. + out_buffer_write_len: usize, /// Header and remaining size of the incoming packet being processed. header: Option<(FrameHeader, u64)>, } @@ -95,7 +112,13 @@ pub(super) struct FrameCodec { impl FrameCodec { /// Create a new frame codec. pub(super) fn new() -> Self { - Self { in_buffer: ReadBuffer::new(), out_buffer: Vec::new(), header: None } + Self { + in_buffer: ReadBuffer::new(), + out_buffer: Vec::new(), + max_out_buffer_len: usize::MAX, + out_buffer_write_len: 0, + header: None, + } } /// Create a new frame codec from partially read data. @@ -103,10 +126,23 @@ impl FrameCodec { Self { in_buffer: ReadBuffer::from_partially_read(part), out_buffer: Vec::new(), + max_out_buffer_len: usize::MAX, + out_buffer_write_len: 0, header: None, } } + /// Sets a maximum size for the out buffer. + pub(super) fn set_max_out_buffer_len(&mut self, max: usize) { + self.max_out_buffer_len = max; + } + + /// Sets [`Self::buffer_frame`] buffer target length to reach before + /// writing to the stream. + pub(super) fn set_out_buffer_write_len(&mut self, len: usize) { + self.out_buffer_write_len = len; + } + /// Read a frame from the provided stream. pub(super) fn read_frame<Stream>( &mut self, @@ -165,19 +201,37 @@ impl FrameCodec { Ok(Some(frame)) } - /// Write a frame to the provided stream. - pub(super) fn write_frame<Stream>(&mut self, stream: &mut Stream, frame: Frame) -> Result<()> + /// Writes a frame into the `out_buffer`. + /// If the out buffer size is over the `out_buffer_write_len` will also write + /// the out buffer into the provided `stream`. + /// + /// To ensure buffered frames are written call [`Self::write_out_buffer`]. + /// + /// May write to the stream, will **not** flush. + pub(super) fn buffer_frame<Stream>(&mut self, stream: &mut Stream, frame: Frame) -> Result<()> where Stream: Write, { + if frame.len() + self.out_buffer.len() > self.max_out_buffer_len { + return Err(Error::WriteBufferFull(Message::Frame(frame))); + } + trace!("writing frame {}", frame); + self.out_buffer.reserve(frame.len()); frame.format(&mut self.out_buffer).expect("Bug: can't write to vector"); - self.write_pending(stream) + + if self.out_buffer.len() > self.out_buffer_write_len { + self.write_out_buffer(stream) + } else { + Ok(()) + } } - /// Complete pending write, if any. - pub(super) fn write_pending<Stream>(&mut self, stream: &mut Stream) -> Result<()> + /// Writes the out_buffer to the provided stream. + /// + /// Does **not** flush. + pub(super) fn write_out_buffer<Stream>(&mut self, stream: &mut Stream) -> Result<()> where Stream: Write, { @@ -193,16 +247,8 @@ impl FrameCodec { } self.out_buffer.drain(0..len); } - stream.flush()?; - Ok(()) - } -} -#[cfg(test)] -impl FrameCodec { - /// Returns the size of the output buffer. - pub(super) fn output_buffer_len(&self) -> usize { - self.out_buffer.len() + Ok(()) } } @@ -224,11 +270,11 @@ mod tests { let mut sock = FrameSocket::new(raw); assert_eq!( - sock.read_frame(None).unwrap().unwrap().into_data(), + sock.read(None).unwrap().unwrap().into_data(), vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07] ); - assert_eq!(sock.read_frame(None).unwrap().unwrap().into_data(), vec![0x03, 0x02, 0x01]); - assert!(sock.read_frame(None).unwrap().is_none()); + assert_eq!(sock.read(None).unwrap().unwrap().into_data(), vec![0x03, 0x02, 0x01]); + assert!(sock.read(None).unwrap().is_none()); let (_, rest) = sock.into_inner(); assert_eq!(rest, vec![0x99]); @@ -239,7 +285,7 @@ mod tests { let raw = Cursor::new(vec![0x02, 0x03, 0x04, 0x05, 0x06, 0x07]); let mut sock = FrameSocket::from_partially_read(raw, vec![0x82, 0x07, 0x01]); assert_eq!( - sock.read_frame(None).unwrap().unwrap().into_data(), + sock.read(None).unwrap().unwrap().into_data(), vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07] ); } @@ -249,10 +295,10 @@ mod tests { let mut sock = FrameSocket::new(Vec::new()); let frame = Frame::ping(vec![0x04, 0x05]); - sock.write_frame(frame).unwrap(); + sock.send(frame).unwrap(); let frame = Frame::pong(vec![0x01]); - sock.write_frame(frame).unwrap(); + sock.send(frame).unwrap(); let (buf, _) = sock.into_inner(); assert_eq!(buf, vec![0x89, 0x02, 0x04, 0x05, 0x8a, 0x01, 0x01]); @@ -264,7 +310,7 @@ mod tests { 0x83, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, ]); let mut sock = FrameSocket::new(raw); - let _ = sock.read_frame(None); // should not crash + let _ = sock.read(None); // should not crash } #[test] @@ -272,7 +318,7 @@ mod tests { let raw = Cursor::new(vec![0x82, 0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]); let mut sock = FrameSocket::new(raw); assert!(matches!( - sock.read_frame(Some(5)), + sock.read(Some(5)), Err(Error::Capacity(CapacityError::MessageTooLong { size: 7, max_size: 5 })) )); } diff --git a/src/protocol/message.rs b/src/protocol/message.rs index cdebabc..2b2ed0b 100644 --- a/src/protocol/message.rs +++ b/src/protocol/message.rs @@ -185,7 +185,7 @@ impl Message { Message::Text(string.into()) } - /// Create a new binary WebSocket message by converting to Vec<u8>. + /// Create a new binary WebSocket message by converting to `Vec<u8>`. pub fn binary<B>(bin: B) -> Message where B: Into<Vec<u8>>, diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 94397e9..21c996a 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -6,13 +6,6 @@ mod message; pub use self::{frame::CloseFrame, message::Message}; -use log::*; -use std::{ - collections::VecDeque, - io::{ErrorKind as IoErrorKind, Read, Write}, - mem::replace, -}; - use self::{ frame::{ coding::{CloseCode, Control as OpCtl, Data as OpData, OpCode}, @@ -20,9 +13,11 @@ use self::{ }, message::{IncompleteMessage, IncompleteMessageType}, }; -use crate::{ - error::{Error, ProtocolError, Result}, - util::NonBlockingResult, +use crate::error::{Error, ProtocolError, Result}; +use log::*; +use std::{ + io::{self, Read, Write}, + mem::replace, }; /// Indicates a Client or Server role of the websocket @@ -37,15 +32,34 @@ pub enum Role { /// The configuration for WebSocket connection. #[derive(Debug, Clone, Copy)] pub struct WebSocketConfig { - /// The size of the send queue. You can use it to turn on/off the backpressure features. `None` - /// means here that the size of the queue is unlimited. The default value is the unlimited - /// queue. + /// Does nothing, instead use `max_write_buffer_size`. + #[deprecated] pub max_send_queue: Option<usize>, - /// The maximum size of a message. `None` means no size limit. The default value is 64 MiB + /// The target minimum size of the write buffer to reach before writing the data + /// to the underlying stream. + /// The default value is 128 KiB. + /// + /// If set to `0` each message will be eagerly written to the underlying stream. + /// It is often more optimal to allow them to buffer a little, hence the default value. + /// + /// Note: [`flush`](WebSocket::flush) will always fully write the buffer regardless. + pub write_buffer_size: usize, + /// The max size of the write buffer in bytes. Setting this can provide backpressure + /// in the case the write buffer is filling up due to write errors. + /// The default value is unlimited. + /// + /// Note: The write buffer only builds up past [`write_buffer_size`](Self::write_buffer_size) + /// when writes to the underlying stream are failing. So the **write buffer can not + /// fill up if you are not observing write errors even if not flushing**. + /// + /// Note: Should always be at least [`write_buffer_size + 1 message`](Self::write_buffer_size) + /// and probably a little more depending on error handling strategy. + pub max_write_buffer_size: usize, + /// The maximum size of an incoming message. `None` means no size limit. The default value is 64 MiB /// which should be reasonably big for all normal use-cases but small enough to prevent /// memory eating by a malicious user. pub max_message_size: Option<usize>, - /// The maximum size of a single message frame. `None` means no size limit. The limit is for + /// The maximum size of a single incoming message frame. `None` means no size limit. The limit is for /// frame payload NOT including the frame header. The default value is 16 MiB which should /// be reasonably big for all normal use-cases but small enough to prevent memory eating /// by a malicious user. @@ -60,8 +74,11 @@ pub struct WebSocketConfig { impl Default for WebSocketConfig { fn default() -> Self { + #[allow(deprecated)] WebSocketConfig { max_send_queue: None, + write_buffer_size: 128 * 1024, + max_write_buffer_size: usize::MAX, max_message_size: Some(64 << 20), max_frame_size: Some(16 << 20), accept_unmasked_frames: false, @@ -69,10 +86,23 @@ impl Default for WebSocketConfig { } } +impl WebSocketConfig { + /// Panic if values are invalid. + pub(crate) fn assert_valid(&self) { + assert!( + self.max_write_buffer_size > self.write_buffer_size, + "WebSocketConfig::max_write_buffer_size must be greater than write_buffer_size, \ + see WebSocketConfig docs`" + ); + } +} + /// WebSocket input-output stream. /// /// This is THE structure you want to create to be able to speak the WebSocket protocol. /// It may be created by calling `connect`, `accept` or `client` functions. +/// +/// Use [`WebSocket::read`], [`WebSocket::send`] to received and send messages. #[derive(Debug)] pub struct WebSocket<Stream> { /// The underlying socket. @@ -87,6 +117,9 @@ impl<Stream> WebSocket<Stream> { /// Call this function if you're using Tungstenite as a part of a web framework /// or together with an existing one. If you need an initial handshake, use /// `connect()` or `accept()` functions of the crate to construct a websocket. + /// + /// # Panics + /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`. pub fn from_raw_socket(stream: Stream, role: Role, config: Option<WebSocketConfig>) -> Self { WebSocket { socket: stream, context: WebSocketContext::new(role, config) } } @@ -96,6 +129,9 @@ impl<Stream> WebSocket<Stream> { /// Call this function if you're using Tungstenite as a part of a web framework /// or together with an existing one. If you need an initial handshake, use /// `connect()` or `accept()` functions of the crate to construct a websocket. + /// + /// # Panics + /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`. pub fn from_partially_read( stream: Stream, part: Vec<u8>, @@ -118,6 +154,9 @@ impl<Stream> WebSocket<Stream> { } /// Change the configuration. + /// + /// # Panics + /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`. pub fn set_config(&mut self, set_func: impl FnOnce(&mut WebSocketConfig)) { self.context.set_config(set_func) } @@ -146,82 +185,116 @@ impl<Stream> WebSocket<Stream> { impl<Stream: Read + Write> WebSocket<Stream> { /// Read a message from stream, if possible. /// - /// This will queue responses to ping and close messages to be sent. It will call - /// `write_pending` before trying to read in order to make sure that those responses - /// make progress even if you never call `write_pending`. That does mean that they - /// get sent out earliest on the next call to `read_message`, `write_message` or `write_pending`. + /// This will also queue responses to ping and close messages. These responses + /// will be written and flushed on the next call to [`read`](Self::read), + /// [`write`](Self::write) or [`flush`](Self::flush). /// - /// ## Closing the connection + /// # Closing the connection /// When the remote endpoint decides to close the connection this will return /// the close message with an optional close frame. /// - /// You should continue calling `read_message`, `write_message` or `write_pending` to drive - /// the reply to the close frame until [Error::ConnectionClosed] is returned. Once that happens - /// it is safe to drop the underlying connection. - pub fn read_message(&mut self) -> Result<Message> { - self.context.read_message(&mut self.socket) + /// You should continue calling [`read`](Self::read), [`write`](Self::write) or + /// [`flush`](Self::flush) to drive the reply to the close frame until [`Error::ConnectionClosed`] + /// is returned. Once that happens it is safe to drop the underlying connection. + pub fn read(&mut self) -> Result<Message> { + self.context.read(&mut self.socket) } - /// Send a message to stream, if possible. + /// Writes and immediately flushes a message. + /// Equivalent to calling [`write`](Self::write) then [`flush`](Self::flush). + pub fn send(&mut self, message: Message) -> Result<()> { + self.write(message)?; + self.flush() + } + + /// Write a message to the provided stream, if possible. + /// + /// A subsequent call should be made to [`flush`](Self::flush) to flush writes. /// - /// WebSocket will buffer a configurable number of messages at a time, except to reply to Ping - /// requests. A Pong reply will jump the queue because the - /// [websocket RFC](https://tools.ietf.org/html/rfc6455#section-5.5.2) specifies it should be sent - /// as soon as is practical. + /// In the event of stream write failure the message frame will be stored + /// in the write buffer and will try again on the next call to [`write`](Self::write) + /// or [`flush`](Self::flush). /// - /// Note that upon receiving a ping message, tungstenite cues a pong reply automatically. - /// When you call either `read_message`, `write_message` or `write_pending` next it will try to send - /// that pong out if the underlying connection can take more data. This means you should not - /// respond to ping frames manually. + /// If the write buffer would exceed the configured [`WebSocketConfig::max_write_buffer_size`] + /// [`Err(WriteBufferFull(msg_frame))`](Error::WriteBufferFull) is returned. + /// + /// This call will generally not flush. However, if there are queued automatic messages + /// they will be written and eagerly flushed. + /// + /// For example, upon receiving ping messages tungstenite queues pong replies automatically. + /// The next call to [`read`](Self::read), [`write`](Self::write) or [`flush`](Self::flush) + /// will write & flush the pong reply. This means you should not respond to ping frames manually. /// /// You can however send pong frames manually in order to indicate a unidirectional heartbeat /// as described in [RFC 6455](https://tools.ietf.org/html/rfc6455#section-5.5.3). Note that - /// if `read_message` returns a ping, you should call `write_pending` until it doesn't return - /// WouldBlock before passing a pong to `write_message`, otherwise the response to the - /// ping will not be sent, but rather replaced by your custom pong message. - /// - /// ## Errors - /// - If the WebSocket's send queue is full, `SendQueueFull` will be returned - /// along with the passed message. Otherwise, the message is queued and Ok(()) is returned. - /// - If the connection is closed and should be dropped, this will return [Error::ConnectionClosed]. - /// - If you try again after [Error::ConnectionClosed] was returned either from here or from `read_message`, - /// [Error::AlreadyClosed] will be returned. This indicates a program error on your part. - /// - [Error::Io] is returned if the underlying connection returns an error + /// if [`read`](Self::read) returns a ping, you should [`flush`](Self::flush) before passing + /// a custom pong to [`write`](Self::write), otherwise the automatic queued response to the + /// ping will not be sent as it will be replaced by your custom pong message. + /// + /// # Errors + /// - If the WebSocket's write buffer is full, [`Error::WriteBufferFull`] will be returned + /// along with the equivalent passed message frame. + /// - If the connection is closed and should be dropped, this will return [`Error::ConnectionClosed`]. + /// - If you try again after [`Error::ConnectionClosed`] was returned either from here or from + /// [`read`](Self::read), [`Error::AlreadyClosed`] will be returned. This indicates a program + /// error on your part. + /// - [`Error::Io`] is returned if the underlying connection returns an error /// (consider these fatal except for WouldBlock). - /// - [Error::Capacity] if your message size is bigger than the configured max message size. - pub fn write_message(&mut self, message: Message) -> Result<()> { - self.context.write_message(&mut self.socket, message) + /// - [`Error::Capacity`] if your message size is bigger than the configured max message size. + pub fn write(&mut self, message: Message) -> Result<()> { + self.context.write(&mut self.socket, message) } - /// Flush the pending send queue. - pub fn write_pending(&mut self) -> Result<()> { - self.context.write_pending(&mut self.socket) + /// Flush writes. + /// + /// Ensures all messages previously passed to [`write`](Self::write) and automatic + /// queued pong responses are written & flushed into the underlying stream. + pub fn flush(&mut self) -> Result<()> { + self.context.flush(&mut self.socket) } /// Close the connection. /// /// This function guarantees that the close frame will be queued. /// There is no need to call it again. Calling this function is - /// the same as calling `write_message(Message::Close(..))`. + /// the same as calling `write(Message::Close(..))`. /// - /// After queuing the close frame you should continue calling `read_message` or - /// `write_pending` to drive the close handshake to completion. + /// After queuing the close frame you should continue calling [`read`](Self::read) or + /// [`flush`](Self::flush) to drive the close handshake to completion. /// /// The websocket RFC defines that the underlying connection should be closed /// by the server. Tungstenite takes care of this asymmetry for you. /// /// When the close handshake is finished (we have both sent and received - /// a close message), `read_message` or `write_pending` will return + /// a close message), [`read`](Self::read) or [`flush`](Self::flush) will return /// [Error::ConnectionClosed] if this endpoint is the server. /// /// If this endpoint is a client, [Error::ConnectionClosed] will only be /// returned after the server has closed the underlying connection. /// /// It is thus safe to drop the underlying connection as soon as [Error::ConnectionClosed] - /// is returned from `read_message` or `write_pending`. + /// is returned from [`read`](Self::read) or [`flush`](Self::flush). pub fn close(&mut self, code: Option<CloseFrame>) -> Result<()> { self.context.close(&mut self.socket, code) } + + /// Old name for [`read`](Self::read). + #[deprecated(note = "Use `read`")] + pub fn read_message(&mut self) -> Result<Message> { + self.read() + } + + /// Old name for [`send`](Self::send). + #[deprecated(note = "Use `send`")] + pub fn write_message(&mut self, message: Message) -> Result<()> { + self.send(message) + } + + /// Old name for [`flush`](Self::flush). + #[deprecated(note = "Use `flush`")] + pub fn write_pending(&mut self) -> Result<()> { + self.flush() + } } /// A context for managing WebSocket stream. @@ -235,39 +308,56 @@ pub struct WebSocketContext { state: WebSocketState, /// Receive: an incomplete message being processed. incomplete: Option<IncompleteMessage>, - /// Send: a data send queue. - send_queue: VecDeque<Frame>, - /// Send: an OOB pong message. - pong: Option<Frame>, + /// Send in addition to regular messages E.g. "pong" or "close". + additional_send: Option<Frame>, + /// True indicates there is an additional message (like a pong) + /// that failed to flush previously and we should try again. + unflushed_additional: bool, /// The configuration for the websocket session. config: WebSocketConfig, } impl WebSocketContext { /// Create a WebSocket context that manages a post-handshake stream. + /// + /// # Panics + /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`. pub fn new(role: Role, config: Option<WebSocketConfig>) -> Self { - WebSocketContext { - role, - frame: FrameCodec::new(), - state: WebSocketState::Active, - incomplete: None, - send_queue: VecDeque::new(), - pong: None, - config: config.unwrap_or_default(), - } + Self::_new(role, FrameCodec::new(), config.unwrap_or_default()) } /// Create a WebSocket context that manages an post-handshake stream. + /// + /// # Panics + /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`. pub fn from_partially_read(part: Vec<u8>, role: Role, config: Option<WebSocketConfig>) -> Self { - WebSocketContext { - frame: FrameCodec::from_partially_read(part), - ..WebSocketContext::new(role, config) + Self::_new(role, FrameCodec::from_partially_read(part), config.unwrap_or_default()) + } + + fn _new(role: Role, mut frame: FrameCodec, config: WebSocketConfig) -> Self { + config.assert_valid(); + frame.set_max_out_buffer_len(config.max_write_buffer_size); + frame.set_out_buffer_write_len(config.write_buffer_size); + Self { + role, + frame, + state: WebSocketState::Active, + incomplete: None, + additional_send: None, + unflushed_additional: false, + config, } } /// Change the configuration. + /// + /// # Panics + /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`. pub fn set_config(&mut self, set_func: impl FnOnce(&mut WebSocketConfig)) { - set_func(&mut self.config) + set_func(&mut self.config); + self.config.assert_valid(); + self.frame.set_max_out_buffer_len(self.config.max_write_buffer_size); + self.frame.set_out_buffer_write_len(self.config.write_buffer_size); } /// Read the configuration. @@ -294,17 +384,29 @@ impl WebSocketContext { /// /// This function sends pong and close responses automatically. /// However, it never blocks on write. - pub fn read_message<Stream>(&mut self, stream: &mut Stream) -> Result<Message> + pub fn read<Stream>(&mut self, stream: &mut Stream) -> Result<Message> where Stream: Read + Write, { // Do not read from already closed connections. - self.state.check_active()?; + self.state.check_not_terminated()?; loop { - // Since we may get ping or close, we need to reply to the messages even during read. - // Thus we call write_pending() but ignore its blocking. - self.write_pending(stream).no_block()?; + if self.additional_send.is_some() || self.unflushed_additional { + // Since we may get ping or close, we need to reply to the messages even during read. + match self.flush(stream) { + Ok(_) => {} + Err(Error::Io(err)) if err.kind() == io::ErrorKind::WouldBlock => { + // If blocked continue reading, but try again later + self.unflushed_additional = true; + } + Err(err) => return Err(err), + } + } else if self.role == Role::Server && !self.state.can_read() { + self.state = WebSocketState::Terminated; + return Err(Error::ConnectionClosed); + } + // If we get here, either write blocks or we have nothing to write. // Thus if read blocks, just let it return WouldBlock. if let Some(message) = self.read_message_frame(stream)? { @@ -314,78 +416,96 @@ impl WebSocketContext { } } - /// Send a message to the provided stream, if possible. + /// Write a message to the provided stream. /// - /// WebSocket will buffer a configurable number of messages at a time, except to reply to Ping - /// and Close requests. If the WebSocket's send queue is full, `SendQueueFull` will be returned - /// along with the passed message. Otherwise, the message is queued and Ok(()) is returned. + /// A subsequent call should be made to [`flush`](Self::flush) to flush writes. /// - /// Note that only the last pong frame is stored to be sent, and only the - /// most recent pong frame is sent if multiple pong frames are queued. - pub fn write_message<Stream>(&mut self, stream: &mut Stream, message: Message) -> Result<()> + /// In the event of stream write failure the message frame will be stored + /// in the write buffer and will try again on the next call to [`write`](Self::write) + /// or [`flush`](Self::flush). + /// + /// If the write buffer would exceed the configured [`WebSocketConfig::max_write_buffer_size`] + /// [`Err(WriteBufferFull(msg_frame))`](Error::WriteBufferFull) is returned. + pub fn write<Stream>(&mut self, stream: &mut Stream, message: Message) -> Result<()> where Stream: Read + Write, { // When terminated, return AlreadyClosed. - self.state.check_active()?; + self.state.check_not_terminated()?; // Do not write after sending a close frame. if !self.state.is_active() { return Err(Error::Protocol(ProtocolError::SendAfterClosing)); } - if let Some(max_send_queue) = self.config.max_send_queue { - if self.send_queue.len() >= max_send_queue { - // Try to make some room for the new message. - // Do not return here if write would block, ignore WouldBlock silently - // since we must queue the message anyway. - self.write_pending(stream).no_block()?; - } - - if self.send_queue.len() >= max_send_queue { - return Err(Error::SendQueueFull(message)); - } - } - let frame = match message { Message::Text(data) => Frame::message(data.into(), OpCode::Data(OpData::Text), true), Message::Binary(data) => Frame::message(data, OpCode::Data(OpData::Binary), true), Message::Ping(data) => Frame::ping(data), Message::Pong(data) => { - self.pong = Some(Frame::pong(data)); - return self.write_pending(stream); + self.set_additional(Frame::pong(data)); + // Note: user pongs can be user flushed so no need to flush here + return self._write(stream, None).map(|_| ()); } Message::Close(code) => return self.close(stream, code), Message::Frame(f) => f, }; - self.send_queue.push_back(frame); - self.write_pending(stream) + let should_flush = self._write(stream, Some(frame))?; + if should_flush { + self.flush(stream)?; + } + Ok(()) } - /// Flush the pending send queue. - pub fn write_pending<Stream>(&mut self, stream: &mut Stream) -> Result<()> + /// Flush writes. + /// + /// Ensures all messages previously passed to [`write`](Self::write) and automatically + /// queued pong responses are written & flushed into the `stream`. + #[inline] + pub fn flush<Stream>(&mut self, stream: &mut Stream) -> Result<()> where Stream: Read + Write, { - // First, make sure we have no pending frame sending. - self.frame.write_pending(stream)?; + self._write(stream, None)?; + self.frame.write_out_buffer(stream)?; + stream.flush()?; + self.unflushed_additional = false; + Ok(()) + } + + /// Writes any data in the out_buffer, `additional_send` and given `data`. + /// + /// Does **not** flush. + /// + /// Returns true if the write contents indicate we should flush immediately. + fn _write<Stream>(&mut self, stream: &mut Stream, data: Option<Frame>) -> Result<bool> + where + Stream: Read + Write, + { + if let Some(data) = data { + self.buffer_frame(stream, data)?; + } // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in // response, unless it already received a Close frame. It SHOULD // respond with Pong frame as soon as is practical. (RFC 6455) - if let Some(pong) = self.pong.take() { - trace!("Sending pong reply"); - self.send_one_frame(stream, pong)?; - } - // If we have any unsent frames, send them. - trace!("Frames still in queue: {}", self.send_queue.len()); - while let Some(data) = self.send_queue.pop_front() { - self.send_one_frame(stream, data)?; - } - - // If we get to this point, the send queue is empty and the underlying socket is still - // willing to take more data. + let should_flush = if let Some(msg) = self.additional_send.take() { + trace!("Sending pong/close"); + match self.buffer_frame(stream, msg) { + Err(Error::WriteBufferFull(Message::Frame(msg))) => { + // if an system message would exceed the buffer put it back in + // `additional_send` for retry. Otherwise returning this error + // may not make sense to the user, e.g. calling `flush`. + self.set_additional(msg); + false + } + Err(err) => return Err(err), + Ok(_) => true, + } + } else { + self.unflushed_additional + }; // If we're closing and there is nothing to send anymore, we should close the connection. if self.role == Role::Server && !self.state.can_read() { @@ -395,10 +515,11 @@ impl WebSocketContext { // maximum segment lifetimes (2MSL), while there is no corresponding // server impact as a TIME_WAIT connection is immediately reopened upon // a new SYN with a higher seq number). (RFC 6455) + self.frame.write_out_buffer(stream)?; self.state = WebSocketState::Terminated; Err(Error::ConnectionClosed) } else { - Ok(()) + Ok(should_flush) } } @@ -406,7 +527,7 @@ impl WebSocketContext { /// /// This function guarantees that the close frame will be queued. /// There is no need to call it again. Calling this function is - /// the same as calling `write(Message::Close(..))`. + /// the same as calling `send(Message::Close(..))`. pub fn close<Stream>(&mut self, stream: &mut Stream, code: Option<CloseFrame>) -> Result<()> where Stream: Read + Write, @@ -414,11 +535,9 @@ impl WebSocketContext { if let WebSocketState::Active = self.state { self.state = WebSocketState::ClosedByUs; let frame = Frame::close(code); - self.send_queue.push_back(frame); - } else { - // Already closed, nothing to do. + self._write(stream, Some(frame))?; } - self.write_pending(stream) + self.flush(stream) } /// Try to decode one message frame. May return None. @@ -487,7 +606,7 @@ impl WebSocketContext { let data = frame.into_data(); // No ping processing after we sent a close frame. if self.state.is_active() { - self.pong = Some(Frame::pong(data.clone())); + self.set_additional(Frame::pong(data.clone())); } Ok(Some(Message::Ping(data))) } @@ -571,7 +690,7 @@ impl WebSocketContext { let reply = Frame::close(close.clone()); debug!("Replying to close with {:?}", reply); - self.send_queue.push_back(reply); + self.set_additional(reply); Some(close) } @@ -588,8 +707,8 @@ impl WebSocketContext { } } - /// Send a single pending frame. - fn send_one_frame<Stream>(&mut self, stream: &mut Stream, mut frame: Frame) -> Result<()> + /// Write a single frame into the write-buffer. + fn buffer_frame<Stream>(&mut self, stream: &mut Stream, mut frame: Frame) -> Result<()> where Stream: Read + Write, { @@ -603,7 +722,18 @@ impl WebSocketContext { } trace!("Sending frame: {:?}", frame); - self.frame.write_frame(stream, frame).check_connection_reset(self.state) + self.frame.buffer_frame(stream, frame).check_connection_reset(self.state) + } + + /// Replace `additional_send` if it is currently a `Pong` message. + fn set_additional(&mut self, add: Frame) { + let empty_or_pong = self + .additional_send + .as_ref() + .map_or(true, |f| f.header().opcode == OpCode::Control(OpCtl::Pong)); + if empty_or_pong { + self.additional_send.replace(add); + } } } @@ -636,7 +766,7 @@ impl WebSocketState { } /// Check if the state is active, return error if not. - fn check_active(self) -> Result<()> { + fn check_not_terminated(self) -> Result<()> { match self { WebSocketState::Terminated => Err(Error::AlreadyClosed), _ => Ok(()), @@ -653,7 +783,7 @@ impl<T> CheckConnectionReset for Result<T> { fn check_connection_reset(self, state: WebSocketState) -> Self { match self { Err(Error::Io(io_error)) => Err({ - if !state.can_read() && io_error.kind() == IoErrorKind::ConnectionReset { + if !state.can_read() && io_error.kind() == io::ErrorKind::ConnectionReset { Error::ConnectionClosed } else { Error::Io(io_error) @@ -688,64 +818,6 @@ mod tests { } } - struct WouldBlockStreamMoc; - - impl io::Write for WouldBlockStreamMoc { - fn write(&mut self, _: &[u8]) -> io::Result<usize> { - Err(io::Error::new(io::ErrorKind::WouldBlock, "would block")) - } - fn flush(&mut self) -> io::Result<()> { - Err(io::Error::new(io::ErrorKind::WouldBlock, "would block")) - } - } - - impl io::Read for WouldBlockStreamMoc { - fn read(&mut self, _: &mut [u8]) -> io::Result<usize> { - Err(io::Error::new(io::ErrorKind::WouldBlock, "would block")) - } - } - - #[test] - fn queue_logic() { - // Create a socket with the queue size of 1. - let mut socket = WebSocket::from_raw_socket( - WouldBlockStreamMoc, - Role::Client, - Some(WebSocketConfig { max_send_queue: Some(1), ..Default::default() }), - ); - - // Test message that we're going to send. - let message = Message::Binary(vec![0xFF; 1024]); - - // Helper to check the error. - let assert_would_block = |error| { - if let Error::Io(io_error) = error { - assert_eq!(io_error.kind(), io::ErrorKind::WouldBlock); - } else { - panic!("Expected WouldBlock error"); - } - }; - - // The first attempt of writing must not fail, since the queue is empty at start. - // But since the underlying mock object always returns `WouldBlock`, so is the result. - assert_would_block(dbg!(socket.write_message(message.clone()).unwrap_err())); - - // Any subsequent attempts must return an error telling that the queue is full. - for _i in 0..100 { - assert!(matches!( - socket.write_message(message.clone()).unwrap_err(), - Error::SendQueueFull(..) - )); - } - - // The size of the output buffer must not be bigger than the size of that message - // that we managed to write to the output buffer at first. Since we could not make - // any progress (because of the logic of the moc buffer), the size remains unchanged. - if socket.context.frame.output_buffer_len() > message.len() { - panic!("Too many frames in the queue"); - } - } - #[test] fn receive_messages() { let incoming = Cursor::new(vec![ @@ -754,10 +826,10 @@ mod tests { 0x03, ]); let mut socket = WebSocket::from_raw_socket(WriteMoc(incoming), Role::Client, None); - assert_eq!(socket.read_message().unwrap(), Message::Ping(vec![1, 2])); - assert_eq!(socket.read_message().unwrap(), Message::Pong(vec![3])); - assert_eq!(socket.read_message().unwrap(), Message::Text("Hello, World!".into())); - assert_eq!(socket.read_message().unwrap(), Message::Binary(vec![0x01, 0x02, 0x03])); + assert_eq!(socket.read().unwrap(), Message::Ping(vec![1, 2])); + assert_eq!(socket.read().unwrap(), Message::Pong(vec![3])); + assert_eq!(socket.read().unwrap(), Message::Text("Hello, World!".into())); + assert_eq!(socket.read().unwrap(), Message::Binary(vec![0x01, 0x02, 0x03])); } #[test] @@ -770,7 +842,7 @@ mod tests { let mut socket = WebSocket::from_raw_socket(WriteMoc(incoming), Role::Client, Some(limit)); assert!(matches!( - socket.read_message(), + socket.read(), Err(Error::Capacity(CapacityError::MessageTooLong { size: 13, max_size: 10 })) )); } @@ -782,7 +854,7 @@ mod tests { let mut socket = WebSocket::from_raw_socket(WriteMoc(incoming), Role::Client, Some(limit)); assert!(matches!( - socket.read_message(), + socket.read(), Err(Error::Capacity(CapacityError::MessageTooLong { size: 3, max_size: 2 })) )); } @@ -70,7 +70,8 @@ mod encryption { #[cfg(feature = "__rustls-tls")] pub mod rustls { - use rustls::{ClientConfig, ClientConnection, RootCertStore, ServerName, StreamOwned}; + use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned}; + use rustls_pki_types::ServerName; use std::{ convert::TryFrom, @@ -104,35 +105,27 @@ mod encryption { #[cfg(feature = "rustls-tls-native-roots")] { - for cert in rustls_native_certs::load_native_certs()? { - root_store - .add(&rustls::Certificate(cert.0)) - .map_err(TlsError::Rustls)?; - } + let native_certs = rustls_native_certs::load_native_certs()?; + let total_number = native_certs.len(); + let (number_added, number_ignored) = + root_store.add_parsable_certificates(native_certs); + log::debug!("Added {number_added}/{total_number} native root certificates (ignored {number_ignored})"); } #[cfg(feature = "rustls-tls-webpki-roots")] { - root_store.add_server_trust_anchors( - webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { - rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) - }) - ); + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); } Arc::new( ClientConfig::builder() - .with_safe_defaults() .with_root_certificates(root_store) .with_no_client_auth(), ) } }; - let domain = - ServerName::try_from(domain).map_err(|_| TlsError::InvalidDnsName)?; + let domain = ServerName::try_from(domain) + .map_err(|_| TlsError::InvalidDnsName)? + .to_owned(); let client = ClientConnection::new(config, domain).map_err(TlsError::Rustls)?; let stream = StreamOwned::new(client, socket); |