From c9e0030df5117260bc95a95746badea18b931eba Mon Sep 17 00:00:00 2001 From: fluxie Date: Sat, 16 Dec 2023 11:55:31 +0200 Subject: [PATCH 1/6] Install Protoc --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20cdb31..3028271 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,10 +30,14 @@ jobs: profile: minimal override: true + - name: Install Protoc + uses: arduino/setup-protoc@v2 + - name: Environment run: | cargo --version rustc --version + protoc --version - name: Build run: cargo build @@ -58,11 +62,17 @@ jobs: profile: minimal override: true components: rustfmt, clippy + + - name: Install Protoc + uses: arduino/setup-protoc@v2 + - name: Environment run: | cargo --version cargo fmt -- --version cargo clippy -- --version + protoc --version + - name: Run rustfmt run: | cargo fmt --all -- --check From 09e4fb7ed6e542d0ef87efea53c9a3931ead9b43 Mon Sep 17 00:00:00 2001 From: fluxie Date: Wed, 13 Dec 2023 13:18:15 +0200 Subject: [PATCH 2/6] Rust gRPC tester with tonic The tester consists of three parts: grpc-tester, grpc-generator and grpc-server. Tester is a library which enables units tests to start and stop the testing framework. The generator sends messages to server and server will collect statistics of the messages it receives. This initial implementation does not allow injecting the proxide proxy between the grpc-generator and grpc-server. This functionality will be added in another pull request. #### grpc-tester The tester is a Rust library with an interface for starting and tying the generator and server together. During the startup it ensures the connection between the generator and the server are ready before returning the tester to ensure unit tests utilizing the tester won't experience random timing related failures. Both the generator and the server are started in their own tokio runtimes to isolate the runtime used for testing. The threads associated with the generator and the server have named threads so that they can be distinguished in the debugger. #### grpc-generator The generator periodically sends gRPC requests (SendMessage) to the server. The purpose of the generator is to provide a constant stream of message that can be observed with the proxide proxy. #### grpc-server The server's main purpose is to receive and then ignore the messages it receives. A support for retrieving statistics of the received messages will be added in the future. --- .github/workflows/ci.yml | 8 +- .idea/.gitignore | 8 + .idea/inspectionProfiles/Project_Default.xml | 37 + .idea/modules.xml | 8 + .idea/proxide.iml | 13 + .idea/vcs.xml | 6 + test/rust_grpc/.gitignore | 1 + test/rust_grpc/Cargo.lock | 1316 ++++++++++++++++++ test/rust_grpc/Cargo.toml | 27 + test/rust_grpc/LICENSE-APACHE | 177 +++ test/rust_grpc/LICENSE-MIT | 15 + test/rust_grpc/build.rs | 4 + test/rust_grpc/proto/rust_grpc.proto | 64 + test/rust_grpc/src/generator.rs | 141 ++ test/rust_grpc/src/lib.rs | 63 + test/rust_grpc/src/server.rs | 229 +++ 16 files changed, 2114 insertions(+), 3 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/proxide.iml create mode 100644 .idea/vcs.xml create mode 100644 test/rust_grpc/.gitignore create mode 100644 test/rust_grpc/Cargo.lock create mode 100644 test/rust_grpc/Cargo.toml create mode 100644 test/rust_grpc/LICENSE-APACHE create mode 100644 test/rust_grpc/LICENSE-MIT create mode 100644 test/rust_grpc/build.rs create mode 100644 test/rust_grpc/proto/rust_grpc.proto create mode 100644 test/rust_grpc/src/generator.rs create mode 100644 test/rust_grpc/src/lib.rs create mode 100644 test/rust_grpc/src/server.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3028271..7b17edf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,14 +40,16 @@ jobs: protoc --version - name: Build - run: cargo build + run: | + cargo build + cargo build --manifest-path ./test/rust_grpc/Cargo.toml - name: Tests shell: bash run: | cd test - ./test_script.sh - + cargo test --manifest-path ./rust_grpc/Cargo.toml + ./test_script.sh style: name: Fmt & Clippy diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..eb178e7 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,37 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f80d8d6 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/proxide.iml b/.idea/proxide.iml new file mode 100644 index 0000000..1651999 --- /dev/null +++ b/.idea/proxide.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/rust_grpc/.gitignore b/test/rust_grpc/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/test/rust_grpc/.gitignore @@ -0,0 +1 @@ +/target diff --git a/test/rust_grpc/Cargo.lock b/test/rust_grpc/Cargo.lock new file mode 100644 index 0000000..8a903b7 --- /dev/null +++ b/test/rust_grpc/Cargo.lock @@ -0,0 +1,1316 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "grpc-tester" +version = "0.1.0" +dependencies = [ + "clap", + "prost", + "tokio", + "tokio-shutdown", + "tonic", + "tonic-build", +] + +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.1.0", +] + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[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 = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.5", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-shutdown" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f30b85501e483eff3fd482c5b8ca6540fd3764fecf31bd2a7f1fcb77c648bb" +dependencies = [ + "tokio", + "tracing", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/test/rust_grpc/Cargo.toml b/test/rust_grpc/Cargo.toml new file mode 100644 index 0000000..5f7adec --- /dev/null +++ b/test/rust_grpc/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "grpc-tester" +version = "0.1.0" # Keep the version near the top for CI purposes (release.yml) +authors = ["Juha Lepola "] +edition = "2021" +license = "MIT OR Apache-2.0" + +[lib] +name="grpc_tester" + +[[bin]] +name="grpc-generator" +path= "src/generator.rs" + +[[bin]] +name="grpc-server" +path="src/server.rs" + +[dependencies] +tonic = "0.10.2" +prost = "0.12.3" +tokio = { version = "1.35.0", features = ["macros", "rt-multi-thread"] } +tokio-shutdown = "0.1.4" +clap = { version= "4.4.11", features = ["derive"] } + +[build-dependencies] +tonic-build = { version = "0.10.2", features = ["prost"] } \ No newline at end of file diff --git a/test/rust_grpc/LICENSE-APACHE b/test/rust_grpc/LICENSE-APACHE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/test/rust_grpc/LICENSE-APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/test/rust_grpc/LICENSE-MIT b/test/rust_grpc/LICENSE-MIT new file mode 100644 index 0000000..ef2eaae --- /dev/null +++ b/test/rust_grpc/LICENSE-MIT @@ -0,0 +1,15 @@ +// Copyright © 2022 Juha Lepola +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. diff --git a/test/rust_grpc/build.rs b/test/rust_grpc/build.rs new file mode 100644 index 0000000..d188817 --- /dev/null +++ b/test/rust_grpc/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("proto/rust_grpc.proto")?; + Ok(()) +} diff --git a/test/rust_grpc/proto/rust_grpc.proto b/test/rust_grpc/proto/rust_grpc.proto new file mode 100644 index 0000000..8d9e292 --- /dev/null +++ b/test/rust_grpc/proto/rust_grpc.proto @@ -0,0 +1,64 @@ +// Copyright © 2022 Juha Lepola +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +syntax = "proto3"; + +package rust_grpc; + + +option java_multiple_files = true; +option java_package = "io.grpc.examples.rust_grpc"; +option java_outer_classname = "RustGrpcProto"; + +/// Test server +service TestService { + + /// A test message sent from client to the server. + rpc SendMessage (SendMessageRequest) returns (SendMessageResponse) {} + + /// Gets diagnostics from the server such number of messages received. + rpc GetDiagnostics (DiagnosticsRequest) returns (DiagnosticsResponse) {} + + /// Blocks until the service has received its first message vai SendMessage. + rpc WaitForFirstMessage (WaitForFirstMessageRequest) returns (WaitForFirstMessageResponse) {} + + /// Pings the server / ensures it is available. + rpc Ping (PingRequest) returns (PingResponse) {} +} + +message SendMessageRequest { +} + +message SendMessageResponse { +} + +message DiagnosticsRequest { +} + +message DiagnosticsResponse { +} + +message WaitForFirstMessageRequest { +} + +message WaitForFirstMessageResponse { +} + +message PingRequest { +} + +message PingResponse { +} \ No newline at end of file diff --git a/test/rust_grpc/src/generator.rs b/test/rust_grpc/src/generator.rs new file mode 100644 index 0000000..5a97091 --- /dev/null +++ b/test/rust_grpc/src/generator.rs @@ -0,0 +1,141 @@ +use clap::Parser; +use rust_grpc_private::{SendMessageRequest, WaitForFirstMessageRequest}; +use std::thread; +use tokio::sync::mpsc::UnboundedSender; + +mod rust_grpc_private { + tonic::include_proto!("rust_grpc"); +} + +/// Simple program to greet a person. +#[derive(Parser, Debug)] +#[command(author, version)] +pub struct Args { + /// Name of the person to greet. + #[arg(short, long, default_value = "http://[::1]:50051")] + pub address: String, + + /// Period / delay between the messages sent to the server. + #[arg(short, long, value_parser = parse_period)] + pub period: std::time::Duration, +} + +/// A gRPC message generator that periodically sends messages to the target server. +pub struct GrpcGenerator { + generator: Option>, + stop: UnboundedSender<()>, +} + +impl GrpcGenerator { + /// Stars a new gRPC generator. + pub async fn start(address: &str) -> Result> { + // Start the generator in a separate tokio runtime to ensure its tasks won't interfere with the tests. + let address_clone = address.to_string(); + let (generator_started_send, mut generator_started_recv) = + tokio::sync::mpsc::unbounded_channel(); + let (stop_requested_send, mut stop_requested_recv) = tokio::sync::mpsc::unbounded_channel(); + let generator = thread::spawn(move || { + let rt = tokio::runtime::Builder::new_multi_thread() + .thread_name("grpc-generator") + .enable_all() + .build() + .expect("Starting runtime for message generator failed."); + rt.block_on(async move { + spawn(Args { + address: address_clone, + period: std::time::Duration::from_millis(100), + }) + .expect("Starting generator failed."); + generator_started_send + .send(()) + .expect("Sending generator ready failed."); + tokio::select! { + _chosen = stop_requested_recv.recv() => {}, + _chosen = tokio::signal::ctrl_c() => {}, + } + }); + }); + let _ = generator_started_recv.recv().await; + + // Wait for the first message to reach the server. + // This improve the robustness of the tests utilizing the generator as the environment is guaranteed to work after this. + { + let mut client = rust_grpc_private::test_service_client::TestServiceClient::connect( + address.to_string(), + ) + .await?; + let _ = client + .wait_for_first_message(WaitForFirstMessageRequest {}) + .await; + } + + Ok(GrpcGenerator { + generator: Some(generator), + stop: stop_requested_send, + }) + } + + /// Stops the gRPC message generation. + pub fn stop(&mut self) -> Result<(), Box> { + let _ = self.stop.send(()); // Fails when called repeatedly as the channel gets dropped. + if let Some(generator) = self.generator.take() { + if let Err(_) = generator.join() { + return Err(Box::::from( + "Waiting for the generator to stop failed.", + )); + } + } + Ok(()) + } +} + +impl Drop for GrpcGenerator { + fn drop(&mut self) { + self.stop().expect("Dropping the generator failed. "); + } +} + +/// Spawns a new asynchronous message generation tasks. +fn spawn(args: Args) -> Result<(), Box> { + tokio::spawn(async move { + generate_messages(args) + .await + .expect("Spawning gRPC client failed.") + }); + Ok(()) +} + +/// Starts sending messages to the server, +async fn generate_messages(args: Args) -> Result<(), Box> { + let mut client = + rust_grpc_private::test_service_client::TestServiceClient::connect(args.address).await?; + + loop { + let request = tonic::Request::new(SendMessageRequest {}); + + tokio::select! { + chosen = client.send_message(request) => { chosen?; }, + _chosen = tokio::signal::ctrl_c() => { break; } + } + + if args.period.is_zero() == false { + tokio::time::sleep(args.period).await; + } + } + + Ok(()) +} + +#[tokio::main] +#[allow(dead_code)] +async fn main() -> Result<(), Box> { + spawn(Args::parse())?; + tokio::signal::ctrl_c().await?; + Ok(()) +} + +/// Reads period from the command line and converts it into duration. +fn parse_period(arg: &str) -> Result { + let seconds = arg.parse()?; + Ok(std::time::Duration::from_millis(seconds)) +} diff --git a/test/rust_grpc/src/lib.rs b/test/rust_grpc/src/lib.rs new file mode 100644 index 0000000..22a93b6 --- /dev/null +++ b/test/rust_grpc/src/lib.rs @@ -0,0 +1,63 @@ +pub use rust_grpc::{ + DiagnosticsRequest, DiagnosticsResponse, SendMessageRequest, SendMessageResponse, +}; +use tonic::transport::channel::Channel; + +mod generator; +mod server; + +pub mod rust_grpc { + tonic::include_proto!("rust_grpc"); +} + +pub struct GrpcTester { + server: server::GrpcServer, + generator: generator::GrpcGenerator, + client: rust_grpc::test_service_client::TestServiceClient, +} + +impl GrpcTester { + /// Gets gRPC client for communicating with the server associated with the tester. + pub fn client(&self) -> rust_grpc::test_service_client::TestServiceClient { + self.client.clone() + } + + /// Creates a new testes which internally pipes data from client to the server. + pub async fn pipe() -> Result> { + let server = server::GrpcServer::start().await?; + let http_address = server.http(); + let generator = generator::GrpcGenerator::start(&http_address).await?; + let client = + rust_grpc::test_service_client::TestServiceClient::connect(server.http()).await?; + Ok(GrpcTester { + server, + generator, + client, + }) + } + + /// Stops the gRPC Server + pub fn stop(&mut self) -> Result<(), Box> { + self.generator.stop()?; + self.server.stop()?; + + Ok(()) + } +} + +impl Drop for GrpcTester { + fn drop(&mut self) { + self.stop().expect("Dropping the tester failed."); + } +} + +#[cfg(test)] +mod test { + use crate::GrpcTester; + + #[tokio::test] + async fn starting_and_stopping_tester_succeeds() { + let mut tester = GrpcTester::pipe().await.expect("Starting tester failed."); + tester.stop().expect("Stopping tester failed."); + } +} diff --git a/test/rust_grpc/src/server.rs b/test/rust_grpc/src/server.rs new file mode 100644 index 0000000..8336fa7 --- /dev/null +++ b/test/rust_grpc/src/server.rs @@ -0,0 +1,229 @@ +use clap::Parser; +use rust_grpc_private::test_service_server::{TestService, TestServiceServer}; +use rust_grpc_private::{ + DiagnosticsRequest, DiagnosticsResponse, PingRequest, PingResponse, SendMessageRequest, + SendMessageResponse, WaitForFirstMessageRequest, WaitForFirstMessageResponse, +}; +use std::net::SocketAddr; +use std::thread; +use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::watch; +use tokio::sync::watch::{Receiver, Sender}; +use tonic::transport::Server; +use tonic::{Request, Response, Status}; + +mod rust_grpc_private { + tonic::include_proto!("rust_grpc"); +} + +/// A simple gRPC Server for receiving messages +#[derive(Parser, Debug)] +#[command(author, version)] +struct Args { + /// Network address to listen. + #[arg(short, long, default_value = "[::1]:50051")] + pub address: String, +} + +/// A gRPC server ready to accept messages +pub struct GrpcServer { + address: SocketAddr, + server: Option>, + stop: UnboundedSender<()>, +} + +impl GrpcServer { + /// Starts a new gRPC server. + pub async fn start() -> Result> { + // Start the server in a separate tokio runtime to ensure its tasks won't interfere with the tests. + let address: SocketAddr = "[::1]:50051".parse()?; + let address_clone = address.clone(); + let (server_listening_send, mut server_listening_recv) = + tokio::sync::mpsc::unbounded_channel(); + let (stop_requested_send, mut stop_requested_recv) = tokio::sync::mpsc::unbounded_channel(); + let server = thread::spawn(move || { + let rt = tokio::runtime::Builder::new_multi_thread() + .thread_name("grpc-server") + .enable_all() + .build() + .expect("Failed to start tokio runtime for grpc-server."); + rt.block_on(async move { + LocalTestService::spawn(address_clone).expect("gRPC Server failed."); + server_listening_send + .send(()) + .expect("Sending server ready failed."); + tokio::select! { + _chosen = stop_requested_recv.recv() => {}, + _chosen = tokio::signal::ctrl_c() => {}, + } + }); + }); + let _ = server_listening_recv.recv().await; + + // Ensure the server is ready. + let server = GrpcServer { + address, + server: Some(server), + stop: stop_requested_send, + }; + server.wait_for_server_to_listen().await?; + Ok(server) + } + + /// Gets the HTTP address of the server. + pub fn http(&self) -> String { + format!("http://{}", &self.address) + } + + /// Stops the gRPC server. + pub fn stop(&mut self) -> Result<(), Box> { + let _ = self.stop.send(()); // Fails when called repeatedly as the channel gets dropped. + if let Some(server) = self.server.take() { + if let Err(_) = server.join() { + return Err(Box::::from( + "Waiting for the server to stop failed.", + )); + } + } + Ok(()) + } + + /// Pings the server and ensures it is listening. + async fn wait_for_server_to_listen(&self) -> Result<(), Box> { + // Try to establish connection to the server. + const MAX_ATTEMPTS: u32 = 100; + for attempt in 1.. { + let mut client = + match rust_grpc_private::test_service_client::TestServiceClient::connect( + self.http(), + ) + .await + { + Ok(client) => client, + Err(_) if attempt < MAX_ATTEMPTS => { + tokio::time::sleep(std::time::Duration::from_millis(20)).await; + continue; + } + Err(error) => return Err(Box::new(error)), + }; + match client.ping(PingRequest {}).await { + Ok(_) => { + break; // A message was sent to the server. + } + Err(_) if attempt < MAX_ATTEMPTS => { + tokio::time::sleep(std::time::Duration::from_millis(20)).await; + continue; + } + Err(error) => { + return Err(Box::new(error)); + } + }; + } + Ok(()) + } +} + +impl Drop for GrpcServer { + fn drop(&mut self) { + self.stop().expect("Dropping GrpcServer failed."); + } +} + +/// +struct LocalTestService { + /// A watcher for acknowledging that the first "SendMessage" call has been received by the server. + message_received_notify: Sender, + + /// A watcher for checking whether the server has received "SendMessage" call, + message_received_check: Receiver, +} + +impl LocalTestService { + /// Spawns the test service in a new asynchronous task. + fn spawn(address: SocketAddr) -> Result<(), Box> { + tokio::spawn(async move { + LocalTestService::run(address) + .await + .expect("Spawning gRPC server failed.") + }); + Ok(()) + } + + /// Launches the the test service. + async fn run(address: SocketAddr) -> Result<(), Box> { + let (tx, rx) = watch::channel(false); + let service = LocalTestService { + message_received_notify: tx, + message_received_check: rx, + }; + println!("Test server listening on {}", address); + Server::builder() + .add_service(TestServiceServer::new(service)) + .serve_with_shutdown(address, async { + tokio::signal::ctrl_c() + .await + .expect("Waiting shutdown failed"); + }) + .await?; + + Ok(()) + } +} + +impl Drop for LocalTestService { + fn drop(&mut self) { + let _ = self.message_received_notify.send(true); + } +} + +#[tonic::async_trait] +impl TestService for LocalTestService { + async fn send_message( + &self, + _request: Request, + ) -> Result, Status> { + // Avoid unnecessary notifications to reduce CPU <-> CPU communication. + self.message_received_notify + .send_if_modified(|value: &mut bool| { + if *value == false { + *value = true; + return true; + } else { + return false; + } + }); + Ok(Response::new(SendMessageResponse {})) + } + + async fn get_diagnostics( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("")) + } + + async fn wait_for_first_message( + &self, + _request: Request, + ) -> Result, Status> { + self.message_received_check + .clone() + .wait_for(|value| value == &true) + .await + .expect("Waiting for the first SendMessage call failed."); + Ok(Response::new(WaitForFirstMessageResponse {})) + } + + async fn ping(&self, _request: Request) -> Result, Status> { + Ok(Response::new(PingResponse {})) + } +} + +#[tokio::main] +#[allow(dead_code)] +async fn main() -> Result<(), Box> { + let args = Args::parse(); + LocalTestService::spawn(args.address.parse()?)?; + tokio::signal::ctrl_c().await?; + Ok(()) +} From 1b21cc3ad558982259336da629638866ef8138d1 Mon Sep 17 00:00:00 2001 From: fluxie Date: Mon, 18 Dec 2023 10:14:50 +0200 Subject: [PATCH 3/6] Support for parallel message generation and basic statistics The generator now takes "tasks" parameter which tells it how many tasks it should launch. The server counts the number of send_message calls it has processed and the generator periodically retrieves this value and reports how many such calls have been processed in one millisecond. The goal of this is to enable performance assessment of the proxide proxy. Includes linting support in the pipeline. --- .github/workflows/ci.yml | 2 + test/rust_grpc/Cargo.lock | 18 ++++ test/rust_grpc/Cargo.toml | 8 +- test/rust_grpc/build.rs | 3 +- test/rust_grpc/proto/rust_grpc.proto | 8 ++ test/rust_grpc/src/generator.rs | 130 +++++++++++++++++++------ test/rust_grpc/src/lib.rs | 139 ++++++++++++++++++++++++--- test/rust_grpc/src/server.rs | 111 +++++++++++++++------ 8 files changed, 343 insertions(+), 76 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b17edf..c5a6f87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,6 +78,8 @@ jobs: - name: Run rustfmt run: | cargo fmt --all -- --check + cargo fmt --all --manifest-path ./test/rust_grpc/Cargo.toml -- --check - name: Run clippy run: | cargo clippy --all -- -D warnings + cargo clippy --all --manifest-path ./test/rust_grpc/Cargo.toml -- -D warnings diff --git a/test/rust_grpc/Cargo.lock b/test/rust_grpc/Cargo.lock index 8a903b7..b65b8e6 100644 --- a/test/rust_grpc/Cargo.lock +++ b/test/rust_grpc/Cargo.lock @@ -113,6 +113,12 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-counter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f447d68cfa5a9ab0c1c862a703da2a65b5ed1b7ce1153c9eb0169506d56019" + [[package]] name = "autocfg" version = "1.1.0" @@ -364,8 +370,11 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" name = "grpc-tester" version = "0.1.0" dependencies = [ + "atomic-counter", "clap", + "portpicker", "prost", + "prost-types", "tokio", "tokio-shutdown", "tonic", @@ -664,6 +673,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portpicker" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +dependencies = [ + "rand", +] + [[package]] name = "ppv-lite86" version = "0.2.17" diff --git a/test/rust_grpc/Cargo.toml b/test/rust_grpc/Cargo.toml index 5f7adec..8f93f09 100644 --- a/test/rust_grpc/Cargo.toml +++ b/test/rust_grpc/Cargo.toml @@ -19,9 +19,15 @@ path="src/server.rs" [dependencies] tonic = "0.10.2" prost = "0.12.3" +prost-types = "0.12.3" tokio = { version = "1.35.0", features = ["macros", "rt-multi-thread"] } tokio-shutdown = "0.1.4" clap = { version= "4.4.11", features = ["derive"] } +portpicker = "0.1.1" +atomic-counter = "1.0.1" [build-dependencies] -tonic-build = { version = "0.10.2", features = ["prost"] } \ No newline at end of file +tonic-build = { version = "0.10.2", features = ["prost"] } + +[profile.release] +debug = true diff --git a/test/rust_grpc/build.rs b/test/rust_grpc/build.rs index d188817..d3b637f 100644 --- a/test/rust_grpc/build.rs +++ b/test/rust_grpc/build.rs @@ -1,4 +1,5 @@ -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> +{ tonic_build::compile_protos("proto/rust_grpc.proto")?; Ok(()) } diff --git a/test/rust_grpc/proto/rust_grpc.proto b/test/rust_grpc/proto/rust_grpc.proto index 8d9e292..ba7457c 100644 --- a/test/rust_grpc/proto/rust_grpc.proto +++ b/test/rust_grpc/proto/rust_grpc.proto @@ -23,6 +23,8 @@ option java_multiple_files = true; option java_package = "io.grpc.examples.rust_grpc"; option java_outer_classname = "RustGrpcProto"; +import "google/protobuf/duration.proto"; + /// Test server service TestService { @@ -49,6 +51,12 @@ message DiagnosticsRequest { } message DiagnosticsResponse { + + /// Uptime of the server. + google.protobuf.Duration uptime = 1; + + /// Number of times SendMessage has been called since the server was started. + uint64 send_message_calls = 2; } message WaitForFirstMessageRequest { diff --git a/test/rust_grpc/src/generator.rs b/test/rust_grpc/src/generator.rs index 5a97091..7bbc2f2 100644 --- a/test/rust_grpc/src/generator.rs +++ b/test/rust_grpc/src/generator.rs @@ -1,16 +1,21 @@ -use clap::Parser; +use clap::{arg, Parser}; +use rust_grpc_private::DiagnosticsRequest; use rust_grpc_private::{SendMessageRequest, WaitForFirstMessageRequest}; -use std::thread; +use std::{thread, time}; use tokio::sync::mpsc::UnboundedSender; +use tokio::task::JoinSet; +use tonic::transport::Channel; -mod rust_grpc_private { +mod rust_grpc_private +{ tonic::include_proto!("rust_grpc"); } /// Simple program to greet a person. -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Clone)] #[command(author, version)] -pub struct Args { +pub struct Args +{ /// Name of the person to greet. #[arg(short, long, default_value = "http://[::1]:50051")] pub address: String, @@ -18,19 +23,26 @@ pub struct Args { /// Period / delay between the messages sent to the server. #[arg(short, long, value_parser = parse_period)] pub period: std::time::Duration, + + /// The number of asynchronous tasks used to send the messages in parallel. + #[arg(short, long, default_value_t = 1)] + pub tasks: u16, } /// A gRPC message generator that periodically sends messages to the target server. -pub struct GrpcGenerator { +pub struct GrpcGenerator +{ generator: Option>, stop: UnboundedSender<()>, } -impl GrpcGenerator { +impl GrpcGenerator +{ /// Stars a new gRPC generator. - pub async fn start(address: &str) -> Result> { + pub async fn start(args: Args) -> Result> + { // Start the generator in a separate tokio runtime to ensure its tasks won't interfere with the tests. - let address_clone = address.to_string(); + let address_clone = args.address.to_string(); let (generator_started_send, mut generator_started_recv) = tokio::sync::mpsc::unbounded_channel(); let (stop_requested_send, mut stop_requested_recv) = tokio::sync::mpsc::unbounded_channel(); @@ -42,8 +54,9 @@ impl GrpcGenerator { .expect("Starting runtime for message generator failed."); rt.block_on(async move { spawn(Args { - address: address_clone, - period: std::time::Duration::from_millis(100), + address: args.address, + period: args.period, + tasks: args.tasks, }) .expect("Starting generator failed."); generator_started_send @@ -60,10 +73,9 @@ impl GrpcGenerator { // Wait for the first message to reach the server. // This improve the robustness of the tests utilizing the generator as the environment is guaranteed to work after this. { - let mut client = rust_grpc_private::test_service_client::TestServiceClient::connect( - address.to_string(), - ) - .await?; + let mut client = + rust_grpc_private::test_service_client::TestServiceClient::connect(address_clone) + .await?; let _ = client .wait_for_first_message(WaitForFirstMessageRequest {}) .await; @@ -76,10 +88,11 @@ impl GrpcGenerator { } /// Stops the gRPC message generation. - pub fn stop(&mut self) -> Result<(), Box> { + pub fn stop(&mut self) -> Result<(), Box> + { let _ = self.stop.send(()); // Fails when called repeatedly as the channel gets dropped. if let Some(generator) = self.generator.take() { - if let Err(_) = generator.join() { + if generator.join().is_err() { return Err(Box::::from( "Waiting for the generator to stop failed.", )); @@ -89,14 +102,17 @@ impl GrpcGenerator { } } -impl Drop for GrpcGenerator { - fn drop(&mut self) { +impl Drop for GrpcGenerator +{ + fn drop(&mut self) + { self.stop().expect("Dropping the generator failed. "); } } /// Spawns a new asynchronous message generation tasks. -fn spawn(args: Args) -> Result<(), Box> { +fn spawn(args: Args) -> Result<(), Box> +{ tokio::spawn(async move { generate_messages(args) .await @@ -106,36 +122,88 @@ fn spawn(args: Args) -> Result<(), Box> { } /// Starts sending messages to the server, -async fn generate_messages(args: Args) -> Result<(), Box> { - let mut client = - rust_grpc_private::test_service_client::TestServiceClient::connect(args.address).await?; +async fn generate_messages(args: Args) -> Result<(), Box> +{ + // Start the requested number of tasks. + // Each task is given a unique client as the generator did not scale properly when the channel was shared + // between the clients. The number of requests sent to the peaked at around <6 tasks. (Very rough approximation.) + // TODO: Investigate further. + let mut tasks: JoinSet>> = JoinSet::new(); + for _t in 0..args.tasks { + let client = rust_grpc_private::test_service_client::TestServiceClient::connect( + args.address.to_string(), + ) + .await?; + tasks.spawn(async move { + generate_messages_task(client, args.period).await?; + Ok(()) + }); + } + while let Some(result) = tasks.join_next().await { + match result { + Ok(_) => {} + Err(error) if error.is_cancelled() => {} + Err(error) => return Err(Box::new(error)), + } + } + Ok(()) +} + +/// An asynchronous function which sends messages to the server. +async fn generate_messages_task( + mut client: rust_grpc_private::test_service_client::TestServiceClient, + period: std::time::Duration, +) -> Result<(), Box> +{ loop { let request = tonic::Request::new(SendMessageRequest {}); - tokio::select! { chosen = client.send_message(request) => { chosen?; }, _chosen = tokio::signal::ctrl_c() => { break; } } - if args.period.is_zero() == false { - tokio::time::sleep(args.period).await; + #[allow(clippy::bool_comparison)] + if period.is_zero() == false { + tokio::time::sleep(period).await; } } - Ok(()) } #[tokio::main] #[allow(dead_code)] -async fn main() -> Result<(), Box> { - spawn(Args::parse())?; - tokio::signal::ctrl_c().await?; +async fn main() -> Result<(), Box> +{ + let args = Args::parse(); + spawn(args.clone())?; + tokio::select! { + result = report_statistics( args ) => result?, + result = tokio::signal::ctrl_c() => result?, + } Ok(()) } /// Reads period from the command line and converts it into duration. -fn parse_period(arg: &str) -> Result { +fn parse_period(arg: &str) -> Result +{ let seconds = arg.parse()?; Ok(std::time::Duration::from_millis(seconds)) } + +/// Reports server statistics to the console. +async fn report_statistics(args: Args) -> Result<(), Box> +{ + let mut client = + rust_grpc_private::test_service_client::TestServiceClient::connect(args.address).await?; + + loop { + let response = client.get_diagnostics(DiagnosticsRequest {}).await?; + let diagnostics = response.get_ref(); + let server_uptime = time::Duration::try_from(diagnostics.uptime.clone().unwrap())?; + let call_rate = diagnostics.send_message_calls as u128 / server_uptime.as_millis(); + println!("Call rate: {} calls / ms", call_rate); + + tokio::time::sleep(time::Duration::from_secs(2)).await; + } +} diff --git a/test/rust_grpc/src/lib.rs b/test/rust_grpc/src/lib.rs index 22a93b6..922f529 100644 --- a/test/rust_grpc/src/lib.rs +++ b/test/rust_grpc/src/lib.rs @@ -1,32 +1,71 @@ pub use rust_grpc::{ DiagnosticsRequest, DiagnosticsResponse, SendMessageRequest, SendMessageResponse, }; +use std::time; +use std::time::Duration; use tonic::transport::channel::Channel; mod generator; mod server; -pub mod rust_grpc { +pub mod rust_grpc +{ tonic::include_proto!("rust_grpc"); } -pub struct GrpcTester { +pub struct Args +{ + /// Period / delay between the messages sent to the server. + pub period: std::time::Duration, + + /// The number of asynchronous tasks used to send the messages in parallel. + pub tasks: u16, +} + +/// Snapshot of statistics of a test run. +pub struct Statistics +{ + /// Uptime of the server associated with the tester. + pub server_uptime: std::time::Duration, + + /// Number of "SendMessage" calls the tester has processed. + pub send_message_calls_processed: u64, +} + +pub struct GrpcTester +{ server: server::GrpcServer, generator: generator::GrpcGenerator, client: rust_grpc::test_service_client::TestServiceClient, } -impl GrpcTester { +impl GrpcTester +{ /// Gets gRPC client for communicating with the server associated with the tester. - pub fn client(&self) -> rust_grpc::test_service_client::TestServiceClient { + pub fn client(&self) -> rust_grpc::test_service_client::TestServiceClient + { self.client.clone() } + pub async fn pipe() -> Result> + { + Self::pipe_with_args(Args { + tasks: 1, + period: Duration::from_secs(1), + }) + .await + } + /// Creates a new testes which internally pipes data from client to the server. - pub async fn pipe() -> Result> { + pub async fn pipe_with_args(args: Args) -> Result> + { let server = server::GrpcServer::start().await?; - let http_address = server.http(); - let generator = generator::GrpcGenerator::start(&http_address).await?; + let generator = generator::GrpcGenerator::start(generator::Args { + address: server.http(), + period: args.period, + tasks: args.tasks, + }) + .await?; let client = rust_grpc::test_service_client::TestServiceClient::connect(server.http()).await?; Ok(GrpcTester { @@ -36,8 +75,24 @@ impl GrpcTester { }) } + pub async fn get_statistics(&self) -> Result> + { + let diagnostics = self + .client + .clone() + .get_diagnostics(DiagnosticsRequest {}) + .await?; + let diagnostics = diagnostics.get_ref(); + + Ok(Statistics { + server_uptime: time::Duration::try_from(diagnostics.uptime.clone().unwrap())?, + send_message_calls_processed: diagnostics.send_message_calls, + }) + } + /// Stops the gRPC Server - pub fn stop(&mut self) -> Result<(), Box> { + pub fn stop(&mut self) -> Result<(), Box> + { self.generator.stop()?; self.server.stop()?; @@ -45,19 +100,77 @@ impl GrpcTester { } } -impl Drop for GrpcTester { - fn drop(&mut self) { +impl Drop for GrpcTester +{ + fn drop(&mut self) + { self.stop().expect("Dropping the tester failed."); } } #[cfg(test)] -mod test { - use crate::GrpcTester; +mod test +{ + use crate::{Args, GrpcTester}; + use std::time::Duration; #[tokio::test] - async fn starting_and_stopping_tester_succeeds() { + async fn starting_and_stopping_tester_succeeds() + { let mut tester = GrpcTester::pipe().await.expect("Starting tester failed."); tester.stop().expect("Stopping tester failed."); } + + #[tokio::test] + async fn server_has_valid_uptime() + { + let mut tester = GrpcTester::pipe().await.expect("Starting tester failed."); + + let statistics = tester + .get_statistics() + .await + .expect("Fetching tester statistics failed."); + if statistics.server_uptime.is_zero() { + panic!("Uptime of the server cannot be zero.") + } + + tester.stop().expect("Stopping tester failed."); + } + + #[tokio::test] + async fn server_receives_messages() + { + // Ensure the generator sends messages constantly to minimize the test duration. + let mut tester = GrpcTester::pipe_with_args(Args { + tasks: 1, + period: Duration::from_secs(0), + }) + .await + .expect("Starting tester failed."); + + // Ensure the server is reporting increase in the number of processed send_message calls. + let statistics_base = tester + .get_statistics() + .await + .expect("Fetching tester statistics failed."); + for attempt in 0.. { + let statistics = tester + .get_statistics() + .await + .expect("Fetching tester statistics failed."); + if statistics.server_uptime <= statistics_base.server_uptime { + panic!("Server's uptime should be increasing.") + } + if statistics.send_message_calls_processed + > statistics_base.send_message_calls_processed + { + break; + } + if attempt > 100 { + panic!("Server did not report any increase in send_message calls.") + } + tokio::time::sleep(Duration::from_millis(20)).await; + } + tester.stop().expect("Stopping tester failed."); + } } diff --git a/test/rust_grpc/src/server.rs b/test/rust_grpc/src/server.rs index 8336fa7..ed219d8 100644 --- a/test/rust_grpc/src/server.rs +++ b/test/rust_grpc/src/server.rs @@ -1,3 +1,4 @@ +use atomic_counter::AtomicCounter; use clap::Parser; use rust_grpc_private::test_service_server::{TestService, TestServiceServer}; use rust_grpc_private::{ @@ -6,38 +7,48 @@ use rust_grpc_private::{ }; use std::net::SocketAddr; use std::thread; +use std::time::Instant; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::watch; use tokio::sync::watch::{Receiver, Sender}; use tonic::transport::Server; use tonic::{Request, Response, Status}; -mod rust_grpc_private { +mod rust_grpc_private +{ tonic::include_proto!("rust_grpc"); } /// A simple gRPC Server for receiving messages #[derive(Parser, Debug)] #[command(author, version)] -struct Args { +struct Args +{ /// Network address to listen. #[arg(short, long, default_value = "[::1]:50051")] pub address: String, } /// A gRPC server ready to accept messages -pub struct GrpcServer { +pub struct GrpcServer +{ address: SocketAddr, server: Option>, stop: UnboundedSender<()>, } -impl GrpcServer { +impl GrpcServer +{ /// Starts a new gRPC server. - pub async fn start() -> Result> { + pub async fn start() -> Result> + { // Start the server in a separate tokio runtime to ensure its tasks won't interfere with the tests. - let address: SocketAddr = "[::1]:50051".parse()?; - let address_clone = address.clone(); + let address: SocketAddr = format!( + "[::1]:{}", + portpicker::pick_unused_port().expect("No TCP ports available.") + ) + .parse()?; + let address_clone = address; let (server_listening_send, mut server_listening_recv) = tokio::sync::mpsc::unbounded_channel(); let (stop_requested_send, mut stop_requested_recv) = tokio::sync::mpsc::unbounded_channel(); @@ -71,15 +82,17 @@ impl GrpcServer { } /// Gets the HTTP address of the server. - pub fn http(&self) -> String { + pub fn http(&self) -> String + { format!("http://{}", &self.address) } /// Stops the gRPC server. - pub fn stop(&mut self) -> Result<(), Box> { - let _ = self.stop.send(()); // Fails when called repeatedly as the channel gets dropped. + pub fn stop(&mut self) -> Result<(), Box> + { + let _ = self.stop.send(()); // Fails when called repeatedly as the channel gets dropped. if let Some(server) = self.server.take() { - if let Err(_) = server.join() { + if server.join().is_err() { return Err(Box::::from( "Waiting for the server to stop failed.", )); @@ -89,7 +102,8 @@ impl GrpcServer { } /// Pings the server and ensures it is listening. - async fn wait_for_server_to_listen(&self) -> Result<(), Box> { + async fn wait_for_server_to_listen(&self) -> Result<(), Box> + { // Try to establish connection to the server. const MAX_ATTEMPTS: u32 = 100; for attempt in 1.. { @@ -108,7 +122,7 @@ impl GrpcServer { }; match client.ping(PingRequest {}).await { Ok(_) => { - break; // A message was sent to the server. + break; // A message was sent to the server. } Err(_) if attempt < MAX_ATTEMPTS => { tokio::time::sleep(std::time::Duration::from_millis(20)).await; @@ -123,24 +137,37 @@ impl GrpcServer { } } -impl Drop for GrpcServer { - fn drop(&mut self) { +impl Drop for GrpcServer +{ + fn drop(&mut self) + { self.stop().expect("Dropping GrpcServer failed."); } } +/// A message sink for the message generator. /// -struct LocalTestService { +/// Collects statistics about the calls it receives. +struct LocalTestService +{ /// A watcher for acknowledging that the first "SendMessage" call has been received by the server. message_received_notify: Sender, /// A watcher for checking whether the server has received "SendMessage" call, message_received_check: Receiver, + + /// Timestamp when the service was created. + started: Instant, + + /// The number of send_message calls the service has received. + send_message_calls_served: atomic_counter::RelaxedCounter, } -impl LocalTestService { +impl LocalTestService +{ /// Spawns the test service in a new asynchronous task. - fn spawn(address: SocketAddr) -> Result<(), Box> { + fn spawn(address: SocketAddr) -> Result<(), Box> + { tokio::spawn(async move { LocalTestService::run(address) .await @@ -150,14 +177,19 @@ impl LocalTestService { } /// Launches the the test service. - async fn run(address: SocketAddr) -> Result<(), Box> { + async fn run(address: SocketAddr) -> Result<(), Box> + { let (tx, rx) = watch::channel(false); let service = LocalTestService { message_received_notify: tx, message_received_check: rx, + started: Instant::now(), + send_message_calls_served: atomic_counter::RelaxedCounter::new(0), }; + #[cfg(not(test))] println!("Test server listening on {}", address); Server::builder() + .concurrency_limit_per_connection(128) .add_service(TestServiceServer::new(service)) .serve_with_shutdown(address, async { tokio::signal::ctrl_c() @@ -170,42 +202,59 @@ impl LocalTestService { } } -impl Drop for LocalTestService { - fn drop(&mut self) { +impl Drop for LocalTestService +{ + fn drop(&mut self) + { let _ = self.message_received_notify.send(true); } } #[tonic::async_trait] -impl TestService for LocalTestService { +impl TestService for LocalTestService +{ async fn send_message( &self, _request: Request, - ) -> Result, Status> { + ) -> Result, Status> + { // Avoid unnecessary notifications to reduce CPU <-> CPU communication. self.message_received_notify .send_if_modified(|value: &mut bool| { + #[allow(clippy::bool_comparison)] if *value == false { *value = true; - return true; + true } else { - return false; + false } }); + self.send_message_calls_served.inc(); Ok(Response::new(SendMessageResponse {})) } async fn get_diagnostics( &self, _request: Request, - ) -> Result, Status> { - Err(Status::unimplemented("")) + ) -> Result, Status> + { + let duration = Instant::now().duration_since(self.started); + let duration = match prost_types::Duration::try_from(duration) { + Ok(d) => d, + Err(_) => return Err(Status::internal("Calculating server uptime failed.")), + }; + + Ok(Response::new(DiagnosticsResponse { + uptime: Some(duration), + send_message_calls: self.send_message_calls_served.get() as u64, + })) } async fn wait_for_first_message( &self, _request: Request, - ) -> Result, Status> { + ) -> Result, Status> + { self.message_received_check .clone() .wait_for(|value| value == &true) @@ -214,14 +263,16 @@ impl TestService for LocalTestService { Ok(Response::new(WaitForFirstMessageResponse {})) } - async fn ping(&self, _request: Request) -> Result, Status> { + async fn ping(&self, _request: Request) -> Result, Status> + { Ok(Response::new(PingResponse {})) } } #[tokio::main] #[allow(dead_code)] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), Box> +{ let args = Args::parse(); LocalTestService::spawn(args.address.parse()?)?; tokio::signal::ctrl_c().await?; From bec7baf3602df8c1d8b3f6167c183fb5d58369f1 Mon Sep 17 00:00:00 2001 From: fluxie Date: Tue, 19 Dec 2023 15:34:30 +0200 Subject: [PATCH 4/6] Implemented basic verification unit test for proxide The first unit test for proxide is now implemented with the help of the grpc_tester library which enables the unit test to start and stop gRPC server and gRPC message generation client. The new server and generator allows the test to check whether proxide is able to intercept messages. In this first iteration the test only verifies that this basic principle works. --- .github/workflows/ci.yml | 1 + Cargo.lock | 858 ++++++++++++++++++++++++++++++++++- Cargo.toml | 9 + src/main.rs | 239 ++++++++++ test/rust_grpc/src/lib.rs | 36 +- test/rust_grpc/src/server.rs | 7 + 6 files changed, 1124 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5a6f87..f6f2300 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,7 @@ jobs: - name: Tests shell: bash run: | + cargo test cd test cargo test --manifest-path ./rust_grpc/Cargo.toml ./test_script.sh diff --git a/Cargo.lock b/Cargo.lock index 42e72f9..76c14f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -32,6 +41,99 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.40", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.40", +] + +[[package]] +name = "atomic-counter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f447d68cfa5a9ab0c1c862a703da2a65b5ed1b7ce1153c9eb0169506d56019" + [[package]] name = "atty" version = "0.2.14" @@ -49,6 +151,51 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes 1.5.0", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes 1.5.0", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -76,6 +223,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + [[package]] name = "bitflags" version = "1.3.2" @@ -157,7 +310,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -168,13 +321,47 @@ checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags 1.3.2", - "clap_lex", + "clap_lex 0.2.4", "indexmap 1.9.3", "strsim", "termcolor", "textwrap", ] +[[package]] +name = "clap" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex 0.6.0", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.40", +] + [[package]] name = "clap_lex" version = "0.2.4" @@ -184,6 +371,18 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -241,7 +440,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" dependencies = [ "nix", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] @@ -290,12 +502,40 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" @@ -424,6 +664,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "grpc-tester" +version = "0.1.0" +dependencies = [ + "atomic-counter", + "clap 4.4.11", + "portpicker", + "prost", + "prost-types", + "tokio", + "tokio-shutdown", + "tonic", + "tonic-build", +] + [[package]] name = "h2" version = "0.3.22" @@ -455,6 +710,12 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -470,6 +731,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "0.2.11" @@ -481,6 +751,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes 1.5.0", + "http", + "pin-project-lite", +] + [[package]] name = "http-serde" version = "1.1.3" @@ -497,6 +778,48 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes 1.5.0", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -540,6 +863,15 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -567,6 +899,12 @@ version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + [[package]] name = "lock_api" version = "0.4.11" @@ -583,12 +921,24 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -613,9 +963,15 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "nix" version = "0.27.1" @@ -727,7 +1083,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -745,6 +1101,12 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pest" version = "2.7.5" @@ -790,6 +1152,36 @@ dependencies = [ "sha2", ] +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.1.0", +] + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.40", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -802,6 +1194,31 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portpicker" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +dependencies = [ + "rand", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.40", +] + [[package]] name = "proc-macro2" version = "1.0.70" @@ -811,6 +1228,60 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes 1.5.0", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +dependencies = [ + "bytes 1.5.0", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.40", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.40", +] + +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost", +] + [[package]] name = "protofish" version = "0.2.5" @@ -830,23 +1301,26 @@ dependencies = [ "base64 0.11.0", "bytes 1.5.0", "chrono", - "clap", + "clap 3.2.25", "crossterm", "ctrlc", "futures", "glob", + "grpc-tester", "h2", "http", "http-serde", "httparse", "lazy_static", "log", + "portpicker", "protofish", "rcgen", "rmp-serde", "rustls", "serde", "serde_json", + "serial_test", "shell-words", "simplelog", "snafu", @@ -866,6 +1340,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rcgen" version = "0.8.14" @@ -888,6 +1392,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "ring" version = "0.16.20" @@ -914,7 +1447,7 @@ dependencies = [ "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -954,6 +1487,19 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.20.9" @@ -966,6 +1512,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.16" @@ -1019,6 +1571,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.40", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1113,6 +1690,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.5" @@ -1120,7 +1707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1163,6 +1750,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -1212,9 +1818,19 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.5", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", ] [[package]] @@ -1239,6 +1855,27 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-shutdown" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f30b85501e483eff3fd482c5b8ca6540fd3764fecf31bd2a7f1fcb77c648bb" +dependencies = [ + "tokio", + "tracing", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.10" @@ -1253,6 +1890,78 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.5", + "bytes 1.5.0", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 2.0.40", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.40" @@ -1260,9 +1969,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.40", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -1272,6 +1993,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tui" version = "0.19.0" @@ -1327,6 +2054,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "0.8.2" @@ -1343,6 +2076,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1423,6 +2165,18 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "wildmatch" version = "1.1.0" @@ -1466,7 +2220,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1475,7 +2229,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -1484,13 +2247,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "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]] @@ -1499,42 +2277,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "x509-parser" version = "0.12.0" diff --git a/Cargo.toml b/Cargo.toml index d424ab0..df08256 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,12 @@ base64 = "0.11" wildmatch = "1" glob = "0.3" shell-words = "1" + +[dev-dependencies] +portpicker = "0.1.1" +grpc-tester = { version = "0.1.0", path = "test/rust_grpc"} +serial_test = "2.0.0" +lazy_static = "1.4.0" + +[profile.release] +debug = true \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 43ec730..5f37324 100644 --- a/src/main.rs +++ b/src/main.rs @@ -331,6 +331,16 @@ async fn tokio_main( abort_rx: oneshot::Receiver<()>, ui_tx: Sender, ) -> Result<(), Error> +{ + launch_proxide(options, abort_rx, ui_tx).await?; + Ok(()) +} + +async fn launch_proxide( + options: Arc, + abort_rx: oneshot::Receiver<()>, + ui_tx: Sender, +) -> Result<(), Error> { // We'll want to listen for both IPv4 and IPv6. These days 'localhost' will first resolve to the // IPv6 address if that is available. If we did not bind to it, all the connections would first @@ -400,3 +410,232 @@ fn new_connection( }); } } + +#[cfg(test)] +mod test +{ + use grpc_tester::server::GrpcServer; + use lazy_static::lazy_static; + use log::SetLoggerError; + use serial_test::serial; + use std::io::{ErrorKind, Write}; + use std::str::FromStr; + use std::sync::{Arc, Mutex}; + use std::time::Duration; + use tokio::sync::broadcast::error::TryRecvError; + use tokio::sync::broadcast::Receiver; + use tokio::sync::mpsc::UnboundedReceiver; + use tokio::sync::oneshot; + + use crate::session::events::SessionEvent; + use crate::ConnectionOptions; + + lazy_static! { + + // Logging must be enabled to detect errors inside proxide. + // Failure to monitor logs may cause the test to hang as errors that stop processing get silently ignored. + // It is only possible to initialize one logger => the logger is shared between the tests => + // the tests are execute sequentially. + // + // Call get_error_monitor to access the monitor inside a test. + // It drains the monitor from messages ensuring it has room for errors. + static ref ERROR_MONITOR: Mutex> = create_error_monitor().expect( "Initializing error monitoring failed."); + } + + #[tokio::test] + #[serial] + async fn proxide_captures_messages() + { + // Logging must be enabled to detect errors inside proxide. + // Failure to monitor logs may cause the test to hang as errors that stop processing get silently ignored. + let mut error_monitor = get_error_monitor().expect("Acquiring error monitor failed."); + + // Server + let server = GrpcServer::start() + .await + .expect("Starting test server failed."); + + // Proxide + let options = get_proxide_options(&server); + let (abort_tx, abort_rx) = tokio::sync::oneshot::channel::<()>(); + let (ui_tx, ui_rx_std) = std::sync::mpsc::channel(); + let proxide_port = u16::from_str(&options.listen_port.to_string()).unwrap(); + let proxide = tokio::spawn(crate::launch_proxide(options, abort_rx, ui_tx)); + + // Message generator and tester. + let tester = grpc_tester::GrpcTester::with_proxide( + server, + proxide_port, + grpc_tester::Args { + period: std::time::Duration::from_secs(0), + tasks: 1, + }, + ) + .await + .expect("Starting tester failed."); + let mut message_rx = async_from_sync(ui_rx_std); + tokio::select! { + _result = message_rx.recv() => {}, + result = error_monitor.recv() => panic!( "{:?}", result ), + } + let mut server = tester.stop_generator().expect("Stopping generator failed."); + abort_tx.send(()).expect("Stopping proxide failed."); + proxide + .await + .expect("Waiting for proxide to stop failed.") + .expect("Waiting for proxide to stop failed."); + server.stop().expect("Stopping server failed"); + } + + #[tokio::test] + #[serial] + async fn error_monitor_catches_errors() + { + // Logging must be enabled to detect errors inside proxide. + // Failure to monitor logs may cause the test to hang as errors that stop processing get silently ignored. + let mut error_monitor = get_error_monitor().expect("Acquiring error monitor failed."); + + // Proxide + let options = ConnectionOptions { + ca: None, + allow_remote: false, + listen_port: portpicker::pick_unused_port() + .expect("Getting free port for proxide failed.") + .to_string(), + target_server: Some("Invalid address".to_string()), + proxy: None, + }; + let (abort_tx, abort_rx) = oneshot::channel::<()>(); + let (ui_tx, _) = std::sync::mpsc::channel(); + let proxide_port = u16::from_str(&options.listen_port.to_string()).unwrap(); + let proxide = tokio::spawn(crate::launch_proxide(Arc::new(options), abort_rx, ui_tx)); + + // Request proxide to connect to the dummy server. This triggers the expected failure. + let mut generator = + grpc_tester::generator::GrpcGenerator::start(grpc_tester::generator::Args { + address: format!("http://[::1]:{}", proxide_port), + period: Duration::from_secs(0), + tasks: 1, + }) + .await + .expect("Starting the genrator failed."); + + // The invalid address given as the target server's address should trigger an error within the proxide proxy. + tokio::select! { + _result = tokio::time::sleep( Duration::from_secs(30)) => panic!("Error monitor did not receive errors."), + _result = error_monitor.recv() => {}, + } + abort_tx.send(()).expect("Stopping proxide failed."); + generator.stop().expect("Stopping the generator failed."); + proxide + .await + .expect("Waiting for proxide to stop failed.") + .expect("Waiting for proxide to stop failed."); + } + + /// Gets options for launching proxide. + fn get_proxide_options(server: &GrpcServer) -> Arc + { + let options = ConnectionOptions { + ca: None, + allow_remote: false, + listen_port: portpicker::pick_unused_port() + .expect("Getting free port for proxide failed.") + .to_string(), + target_server: Some(server.address().to_string()), + proxy: None, + }; + Arc::new(options) + } + + /// Converts a synchronous channel into an asynchronous channel with a helper thread. + fn async_from_sync( + sync_received: std::sync::mpsc::Receiver, + ) -> UnboundedReceiver + { + let (async_tx, async_rx) = tokio::sync::mpsc::unbounded_channel(); + std::thread::spawn( + move || -> Result<(), Box> { + loop { + async_tx.send(sync_received.recv()?)?; + } + }, + ); + async_rx + } + + /// Gets an error monitor for a test. + fn get_error_monitor() -> Result, Box> + { + // Gets a clean monitor for the tests. + // The monitor is drained to guarantee the error monitor channel is empty before the tests. + // Re-subscribe would not work as it doesn't remove the existing messages from the channel. + let mut monitor = ERROR_MONITOR.lock().unwrap(); + loop { + match monitor.try_recv() { + Ok(_) => {} + Err(TryRecvError::Empty) => { + break; + } + Err(e) => return Err(Box::from(e)), + } + } + Ok(monitor.resubscribe()) + } + + /// Initializes logging for the tests. + fn create_error_monitor() -> Result>, SetLoggerError> + { + let (log_tx, log_rx) = tokio::sync::broadcast::channel(256); + simplelog::WriteLogger::init( + simplelog::LevelFilter::Error, + simplelog::ConfigBuilder::new().build(), + ChannelLogger { + target: log_tx, + data: Vec::new(), + }, + )?; + Ok(Mutex::new(log_rx)) + } + + /// A logger that sends the log messages into a channel. + struct ChannelLogger + { + /// Target channel for sending log messages. + target: tokio::sync::broadcast::Sender, + + /// Data buffer for the error messages. + data: Vec, + } + + impl Write for ChannelLogger + { + fn write(&mut self, buf: &[u8]) -> std::io::Result + { + // Split the log at line breaks. + if let Some(terminator) = buf.iter().position(|&b| b == 0 || b == 0x0a) { + let (current, _) = buf.split_at(terminator); + self.data.extend_from_slice(current); + let log_entry = String::from_utf8_lossy(&self.data).to_string(); + self.data.clear(); + match self.target.send(log_entry) { + Ok(_) => Ok(current.len() + 1), + Err(_) => Err(std::io::Error::from(ErrorKind::BrokenPipe)), + } + } else { + self.data.extend_from_slice(buf); + Ok(buf.len()) + } + } + + fn flush(&mut self) -> std::io::Result<()> + { + let log = String::from_utf8_lossy(self.data.as_slice()).to_string(); + self.data.clear(); + match self.target.send(log.to_string()) { + Ok(_) => Ok(()), + Err(_) => Err(std::io::Error::from(ErrorKind::BrokenPipe)), + } + } + } +} diff --git a/test/rust_grpc/src/lib.rs b/test/rust_grpc/src/lib.rs index 922f529..35e4bc6 100644 --- a/test/rust_grpc/src/lib.rs +++ b/test/rust_grpc/src/lib.rs @@ -1,3 +1,4 @@ +use crate::server::GrpcServer; pub use rust_grpc::{ DiagnosticsRequest, DiagnosticsResponse, SendMessageRequest, SendMessageResponse, }; @@ -5,8 +6,8 @@ use std::time; use std::time::Duration; use tonic::transport::channel::Channel; -mod generator; -mod server; +pub mod generator; +pub mod server; pub mod rust_grpc { @@ -75,6 +76,28 @@ impl GrpcTester }) } + /// Creates a new testes with proxide prozy in-between + pub async fn with_proxide( + server: GrpcServer, + proxide_port: u16, + args: Args, + ) -> Result> + { + let generator = generator::GrpcGenerator::start(generator::Args { + address: format!("http://[::1]:{}", proxide_port), + period: args.period, + tasks: args.tasks, + }) + .await?; + let client = + rust_grpc::test_service_client::TestServiceClient::connect(server.http()).await?; + Ok(GrpcTester { + server, + generator, + client, + }) + } + pub async fn get_statistics(&self) -> Result> { let diagnostics = self @@ -98,13 +121,12 @@ impl GrpcTester Ok(()) } -} -impl Drop for GrpcTester -{ - fn drop(&mut self) + /// Stops the message generator and returns server which is left running. + pub fn stop_generator(mut self) -> Result> { - self.stop().expect("Dropping the tester failed."); + self.generator.stop()?; + Ok(self.server) } } diff --git a/test/rust_grpc/src/server.rs b/test/rust_grpc/src/server.rs index ed219d8..3575569 100644 --- a/test/rust_grpc/src/server.rs +++ b/test/rust_grpc/src/server.rs @@ -81,6 +81,12 @@ impl GrpcServer Ok(server) } + /// Gets the server's address. + pub fn address(&self) -> &SocketAddr + { + &self.address + } + /// Gets the HTTP address of the server. pub fn http(&self) -> String { @@ -250,6 +256,7 @@ impl TestService for LocalTestService })) } + /// Waits and blocks until the server has received at least one send_message call. async fn wait_for_first_message( &self, _request: Request, From a53835a73e613231567b61d9298d74d9d6ac4215 Mon Sep 17 00:00:00 2001 From: fluxie Date: Wed, 20 Dec 2023 16:24:56 +0200 Subject: [PATCH 5/6] Generator now adds client thread and process ids headers This step prepares for the client stack capture support implementation for the proxide proxy. The goal is for proxide to capture the callstacks of the threads of client process when the traffic from the client goes through the proxide. This enables analysis of the client application behavior when unexpected calls pass through the proxy. Functionality will be available when the proxide proxy is on the same host machine as the client. --- Cargo.lock | 75 +++++++++++++++++++++++++- test/rust_grpc/Cargo.lock | 79 ++++++++++++++++++++++++++++ test/rust_grpc/Cargo.toml | 10 ++++ test/rust_grpc/proto/rust_grpc.proto | 13 +++++ test/rust_grpc/src/generator.rs | 36 ++++++++++++- test/rust_grpc/src/lib.rs | 35 +++++++++++- test/rust_grpc/src/server.rs | 51 ++++++++++++++++-- 7 files changed, 292 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76c14f3..5a1c41f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -398,6 +398,39 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", +] + +[[package]] +name = "crossbeam-skiplist" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883a5821d7d079fcf34ac55f27a833ee61678110f6b97637cc74513c0d0b42fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossterm" version = "0.25.0" @@ -670,6 +703,8 @@ version = "0.1.0" dependencies = [ "atomic-counter", "clap 4.4.11", + "crossbeam-skiplist", + "os-id", "portpicker", "prost", "prost-types", @@ -677,6 +712,7 @@ dependencies = [ "tokio-shutdown", "tonic", "tonic-build", + "windows", ] [[package]] @@ -831,7 +867,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.51.1", ] [[package]] @@ -933,6 +969,15 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1057,6 +1102,15 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "os-id" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510856ec55c552d86db0d675df95c32b87f28cfe1cdc47d3eba2342c39a0a5f6" +dependencies = [ + "libc", +] + [[package]] name = "os_str_bytes" version = "6.6.1" @@ -2214,6 +2268,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.0", +] + [[package]] name = "windows-core" version = "0.51.1" @@ -2223,6 +2287,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/test/rust_grpc/Cargo.lock b/test/rust_grpc/Cargo.lock index b65b8e6..a3ec707 100644 --- a/test/rust_grpc/Cargo.lock +++ b/test/rust_grpc/Cargo.lock @@ -270,6 +270,39 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "crossbeam-epoch" +version = "0.9.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", +] + +[[package]] +name = "crossbeam-skiplist" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883a5821d7d079fcf34ac55f27a833ee61678110f6b97637cc74513c0d0b42fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +dependencies = [ + "cfg-if", +] + [[package]] name = "either" version = "1.9.0" @@ -372,6 +405,8 @@ version = "0.1.0" dependencies = [ "atomic-counter", "clap", + "crossbeam-skiplist", + "os-id", "portpicker", "prost", "prost-types", @@ -379,6 +414,7 @@ dependencies = [ "tokio-shutdown", "tonic", "tonic-build", + "windows", ] [[package]] @@ -568,6 +604,15 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -625,6 +670,15 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "os-id" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510856ec55c552d86db0d675df95c32b87f28cfe1cdc47d3eba2342c39a0a5f6" +dependencies = [ + "libc", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -863,6 +917,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.193" @@ -1201,6 +1261,25 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/test/rust_grpc/Cargo.toml b/test/rust_grpc/Cargo.toml index 8f93f09..80387a6 100644 --- a/test/rust_grpc/Cargo.toml +++ b/test/rust_grpc/Cargo.toml @@ -25,6 +25,16 @@ tokio-shutdown = "0.1.4" clap = { version= "4.4.11", features = ["derive"] } portpicker = "0.1.1" atomic-counter = "1.0.1" +os-id = "3.0.1" +crossbeam-skiplist = "0.1.1" + +[dependencies.windows] +version = "0.52.0" +features = [ + "Win32_Foundation", + "Win32_System_Threading", +] + [build-dependencies] tonic-build = { version = "0.10.2", features = ["prost"] } diff --git a/test/rust_grpc/proto/rust_grpc.proto b/test/rust_grpc/proto/rust_grpc.proto index ba7457c..74cb944 100644 --- a/test/rust_grpc/proto/rust_grpc.proto +++ b/test/rust_grpc/proto/rust_grpc.proto @@ -50,6 +50,16 @@ message SendMessageResponse { message DiagnosticsRequest { } + +message ClientProcess { + + /// Identifies the process in the system. + uint32 id = 1; + + /// Identifies the threads that have called the server from this process. + repeated uint64 threads = 2; +} + message DiagnosticsResponse { /// Uptime of the server. @@ -57,6 +67,9 @@ message DiagnosticsResponse { /// Number of times SendMessage has been called since the server was started. uint64 send_message_calls = 2; + + /// Information about the clients that have called the server. + repeated ClientProcess clients = 3; } message WaitForFirstMessageRequest { diff --git a/test/rust_grpc/src/generator.rs b/test/rust_grpc/src/generator.rs index 7bbc2f2..cd13818 100644 --- a/test/rust_grpc/src/generator.rs +++ b/test/rust_grpc/src/generator.rs @@ -4,7 +4,10 @@ use rust_grpc_private::{SendMessageRequest, WaitForFirstMessageRequest}; use std::{thread, time}; use tokio::sync::mpsc::UnboundedSender; use tokio::task::JoinSet; +use tonic::metadata::MetadataValue; use tonic::transport::Channel; +#[cfg(target_os = "windows")] +use windows; mod rust_grpc_private { @@ -157,7 +160,15 @@ async fn generate_messages_task( ) -> Result<(), Box> { loop { - let request = tonic::Request::new(SendMessageRequest {}); + let mut request = tonic::Request::new(SendMessageRequest {}); + request.metadata_mut().append( + "proxide-client-process-id", + MetadataValue::from(std::process::id()), + ); + request.metadata_mut().append( + "proxide-client-thread-id", + MetadataValue::from(get_current_native_thread_id()), + ); tokio::select! { chosen = client.send_message(request) => { chosen?; }, _chosen = tokio::signal::ctrl_c() => { break; } @@ -202,8 +213,29 @@ async fn report_statistics(args: Args) -> Result<(), Box() + ); tokio::time::sleep(time::Duration::from_secs(2)).await; } } + +/// Gets the current native thread id. +pub fn get_current_native_thread_id() -> i64 +{ + #[cfg(not(target_os = "windows"))] + return os_id::thread::get_raw_id() as i64; + + #[cfg(target_os = "windows")] + unsafe { + return windows::Win32::System::Threading::GetCurrentThreadId() as i64; + } +} diff --git a/test/rust_grpc/src/lib.rs b/test/rust_grpc/src/lib.rs index 35e4bc6..9e6dad5 100644 --- a/test/rust_grpc/src/lib.rs +++ b/test/rust_grpc/src/lib.rs @@ -1,6 +1,6 @@ use crate::server::GrpcServer; pub use rust_grpc::{ - DiagnosticsRequest, DiagnosticsResponse, SendMessageRequest, SendMessageResponse, + ClientProcess, DiagnosticsRequest, DiagnosticsResponse, SendMessageRequest, SendMessageResponse, }; use std::time; use std::time::Duration; @@ -31,6 +31,9 @@ pub struct Statistics /// Number of "SendMessage" calls the tester has processed. pub send_message_calls_processed: u64, + + /// Information about the clients that have contacted the server. + pub clients: Vec, } pub struct GrpcTester @@ -110,6 +113,15 @@ impl GrpcTester Ok(Statistics { server_uptime: time::Duration::try_from(diagnostics.uptime.clone().unwrap())?, send_message_calls_processed: diagnostics.send_message_calls, + clients: diagnostics + .clients + .clone() + .into_iter() + .map(|c| ClientProcess { + id: c.id, + threads: c.threads, + }) + .collect(), }) } @@ -195,4 +207,25 @@ mod test } tester.stop().expect("Stopping tester failed."); } + + #[tokio::test] + async fn server_collects_generator_thread_id() + { + // Ensure the generator sends messages constantly to minimize the test duration. + let tester = GrpcTester::pipe_with_args(Args { + tasks: 1, + period: Duration::from_secs(0), + }) + .await + .expect("Starting tester failed."); + + // The server should have now received the first send_message call as the tester waits for it before returning. + let statistics = tester + .get_statistics() + .await + .expect("Retrieving statistics failed."); + assert_eq!(statistics.clients.len(), 1); + assert_eq!(statistics.clients[0].id, std::process::id()); + assert!(statistics.clients[0].threads.len() > 0); + } } diff --git a/test/rust_grpc/src/server.rs b/test/rust_grpc/src/server.rs index 3575569..e605bbb 100644 --- a/test/rust_grpc/src/server.rs +++ b/test/rust_grpc/src/server.rs @@ -2,15 +2,18 @@ use atomic_counter::AtomicCounter; use clap::Parser; use rust_grpc_private::test_service_server::{TestService, TestServiceServer}; use rust_grpc_private::{ - DiagnosticsRequest, DiagnosticsResponse, PingRequest, PingResponse, SendMessageRequest, - SendMessageResponse, WaitForFirstMessageRequest, WaitForFirstMessageResponse, + ClientProcess, DiagnosticsRequest, DiagnosticsResponse, PingRequest, PingResponse, + SendMessageRequest, SendMessageResponse, WaitForFirstMessageRequest, + WaitForFirstMessageResponse, }; use std::net::SocketAddr; +use std::str::FromStr; use std::thread; use std::time::Instant; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::watch; use tokio::sync::watch::{Receiver, Sender}; +use tonic::metadata::{Ascii, MetadataValue}; use tonic::transport::Server; use tonic::{Request, Response, Status}; @@ -167,6 +170,10 @@ struct LocalTestService /// The number of send_message calls the service has received. send_message_calls_served: atomic_counter::RelaxedCounter, + + /// Collection of client processes and threads as reported with + /// the proxide-client-process-id and proxide-client-thread-id headers + clients: crossbeam_skiplist::SkipMap>, } impl LocalTestService @@ -191,6 +198,7 @@ impl LocalTestService message_received_check: rx, started: Instant::now(), send_message_calls_served: atomic_counter::RelaxedCounter::new(0), + clients: crossbeam_skiplist::SkipMap::new(), }; #[cfg(not(test))] println!("Test server listening on {}", address); @@ -221,7 +229,7 @@ impl TestService for LocalTestService { async fn send_message( &self, - _request: Request, + request: Request, ) -> Result, Status> { // Avoid unnecessary notifications to reduce CPU <-> CPU communication. @@ -236,6 +244,19 @@ impl TestService for LocalTestService } }); self.send_message_calls_served.inc(); + + // Collect the client info. + let process_id = request.metadata().get("proxide-client-process-id"); + let thread_id = request.metadata().get("proxide-client-thread-id"); + if process_id.is_some() && thread_id.is_some() { + let threads = self.clients.get_or_insert( + number_from_client(process_id.unwrap())?, + crossbeam_skiplist::SkipSet::new(), + ); + threads + .value() + .insert(number_from_client(thread_id.unwrap())?); + } Ok(Response::new(SendMessageResponse {})) } @@ -250,9 +271,19 @@ impl TestService for LocalTestService Err(_) => return Err(Status::internal("Calculating server uptime failed.")), }; + let clients = self + .clients + .iter() + .map(|c| ClientProcess { + id: *c.key(), + threads: c.value().iter().map(|e| *e.value()).collect(), + }) + .collect(); + Ok(Response::new(DiagnosticsResponse { uptime: Some(duration), send_message_calls: self.send_message_calls_served.get() as u64, + clients, })) } @@ -285,3 +316,17 @@ async fn main() -> Result<(), Box> tokio::signal::ctrl_c().await?; Ok(()) } + +fn number_from_client(value: &MetadataValue) -> Result +where + N: FromStr, +{ + let value = match value.to_str() { + Ok(v) => v, + Err(_) => return Err(Status::invalid_argument("Header was not a string.")), + }; + match N::from_str(value) { + Ok(numbert) => Ok(numbert), + Err(_) => Err(Status::invalid_argument("Expected number")), + } +} From c604697352da62125da19eb72234338216d1baa0 Mon Sep 17 00:00:00 2001 From: fluxie Date: Sat, 23 Dec 2023 10:23:46 +0200 Subject: [PATCH 6/6] Added area for the client callstack to the UI The area becomes visible if the client included both the process id and the thread id of the calling thread in the headers of a request. Support for capturing and displaying the callstack will be added in another commit. --- src/ui/sub_views/details_pane.rs | 38 +++++++++- src/ui/views.rs | 3 + src/ui/views/callstack_view.rs | 125 +++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 src/ui/views/callstack_view.rs diff --git a/src/ui/sub_views/details_pane.rs b/src/ui/sub_views/details_pane.rs index 9827382..45bc0d7 100644 --- a/src/ui/sub_views/details_pane.rs +++ b/src/ui/sub_views/details_pane.rs @@ -1,3 +1,4 @@ +use std::convert::TryFrom; use tui::layout::{Constraint, Direction, Layout}; use tui::text::{Span, Spans, Text}; use tui::widgets::Paragraph; @@ -6,7 +7,7 @@ use uuid::Uuid; use crate::ui::prelude::*; use crate::session::{EncodedRequest, RequestPart}; -use crate::ui::views::MessageView; +use crate::ui::views::{CallstackView, ClientThreadId, MessageView}; #[derive(Clone, Default)] pub struct DetailsPane; @@ -22,6 +23,7 @@ impl DetailsPane match key.code { KeyCode::Char('q') => self.create_message_view(req, RequestPart::Request), KeyCode::Char('e') => self.create_message_view(req, RequestPart::Response), + KeyCode::Char('s') => self.create_callstack_view(req), _ => None, } } else { @@ -61,11 +63,20 @@ impl DetailsPane c.x -= 1; c.width += 2; c.height += 1; + let vertical_chunks: Vec = if ClientThreadId::try_from(&request.request_msg).is_ok() { + Layout::default() + .direction(Direction::Vertical) + .margin(0) + .constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref()) + .split(block.inner(c)) + } else { + Vec::from([block.inner(c)]) + }; let req_resp_chunks = Layout::default() .direction(Direction::Horizontal) .margin(0) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(block.inner(c)); + .split(vertical_chunks[0]); f.render_widget(block, chunk); @@ -114,6 +125,16 @@ impl DetailsPane offset: 0, } .draw(ctx, f, req_resp_chunks[1]); + + // The right side view is split vertically only if the client included its process id and thread id in the request + // enabling the callstack capture. + if vertical_chunks.len() > 1 { + CallstackView { + request: request.request_data.uuid, + offset: 0, + } + .draw(ctx, f, vertical_chunks[1]); + } } fn create_message_view( @@ -128,4 +149,17 @@ impl DetailsPane offset: 0, }))) } + + fn create_callstack_view(&mut self, req: &EncodedRequest) + -> Option> + { + if ClientThreadId::try_from(&req.request_msg).is_ok() { + Some(HandleResult::PushView(Box::new(CallstackView { + request: req.request_data.uuid, + offset: 0, + }))) + } else { + None + } + } } diff --git a/src/ui/views.rs b/src/ui/views.rs index b319503..f2c76c2 100644 --- a/src/ui/views.rs +++ b/src/ui/views.rs @@ -6,6 +6,9 @@ pub use main_view::MainView; mod message_view; pub use message_view::MessageView; +mod callstack_view; +pub use callstack_view::{CallstackView, ClientThreadId}; + pub trait View { fn draw(&mut self, ctx: &UiContext, f: &mut Frame, chunk: Rect); diff --git a/src/ui/views/callstack_view.rs b/src/ui/views/callstack_view.rs new file mode 100644 index 0000000..056c03d --- /dev/null +++ b/src/ui/views/callstack_view.rs @@ -0,0 +1,125 @@ +use super::prelude::*; +use crossterm::event::KeyCode; +use http::HeaderValue; +use std::convert::TryFrom; +use tui::widgets::{Paragraph, Wrap}; +use uuid::Uuid; + +use crate::session::MessageData; + +/// When available, identifies the thread in the calling or client process. +/// The client should reports its process id with the proxide-client-process-id" header and +/// the thread id with the "proxide-client-thread-id" header. +/// This enables the proxide proxy to capture client's callstack when it is making the call if the proxide +/// and the client are running on the same host. +pub struct ClientThreadId +{ + process_id: u32, + thread_id: i64, +} + +pub struct CallstackView +{ + pub request: Uuid, + pub offset: u16, +} + +impl CallstackView {} + +impl View for CallstackView +{ + fn draw(&mut self, ctx: &UiContext, f: &mut Frame, chunk: Rect) + { + let request = match ctx.data.requests.get_by_uuid(self.request) { + Some(r) => r, + None => return, + }; + + let client_thread = match ClientThreadId::try_from(&request.request_msg) { + Ok(thread_id) => thread_id, + Err(_) => return, + }; + + let title = format!( + "Client call[s]tack, Process: {}, Thread: {}", + client_thread.process_id, client_thread.thread_id + ); + let block = create_block(&title); + let request_data = Paragraph::new("Unimplemented.") + .block(block) + .wrap(Wrap { trim: false }) + .scroll((self.offset, 0)); + f.render_widget(request_data, chunk); + } + + fn on_input(&mut self, _ctx: &UiContext, e: &CTEvent, size: Rect) -> Option> + { + match e { + CTEvent::Key(key) => match key.code { + KeyCode::Char('k') | KeyCode::Up => self.offset = self.offset.saturating_sub(1), + KeyCode::Char('j') | KeyCode::Down => self.offset = self.offset.saturating_add(1), + KeyCode::PageDown => self.offset = self.offset.saturating_add(size.height - 5), + KeyCode::PageUp => self.offset = self.offset.saturating_sub(size.height - 5), + KeyCode::F(12) => { + return None; + } + _ => return None, + }, + _ => return None, + }; + Some(HandleResult::Update) + } + + fn on_change(&mut self, _ctx: &UiContext, change: &SessionChange) -> bool + { + match change { + SessionChange::NewConnection { .. } => false, + SessionChange::Connection { .. } => false, + SessionChange::NewRequest { .. } => false, + SessionChange::Request { .. } => false, + SessionChange::NewMessage { .. } => false, + SessionChange::Message { .. } => false, + } + } + + fn help_text(&self, _state: &UiContext, _size: Rect) -> String + { + format!( + "{}\n{}", + "[Up/Down, j/k, PgUp/PgDn]: Scroll; [F12]: Export to file", "[Esc]: Back to main view" + ) + } +} + +impl TryFrom<&MessageData> for ClientThreadId +{ + type Error = (); + + fn try_from(value: &MessageData) -> Result + { + let process_id: Option = + number_or_none(&value.headers.get("proxide-client-process-id")); + let thread_id: Option = number_or_none(&value.headers.get("proxide-client-thread-id")); + match (process_id, thread_id) { + (Some(process_id), Some(thread_id)) => Ok(ClientThreadId { + process_id, + thread_id, + }), + _ => Err(()), + } + } +} + +fn number_or_none(header: &Option<&HeaderValue>) -> Option +where + N: std::str::FromStr, +{ + if let Some(value) = header { + value + .to_str() + .map(|s| N::from_str(s).map(|n| Some(n)).unwrap_or(None)) + .unwrap_or(None) + } else { + None + } +}