diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20cdb31..f6f2300 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,20 +30,27 @@ 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 + run: | + cargo build + cargo build --manifest-path ./test/rust_grpc/Cargo.toml - name: Tests shell: bash run: | + cargo test cd test - ./test_script.sh - + cargo test --manifest-path ./rust_grpc/Cargo.toml + ./test_script.sh style: name: Fmt & Clippy @@ -58,14 +65,22 @@ 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 + 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/.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/Cargo.lock b/Cargo.lock index 42e72f9..5a1c41f 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" @@ -199,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" @@ -241,7 +473,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 +535,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 +697,24 @@ 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", + "crossbeam-skiplist", + "os-id", + "portpicker", + "prost", + "prost-types", + "tokio", + "tokio-shutdown", + "tonic", + "tonic-build", + "windows", +] + [[package]] name = "h2" version = "0.3.22" @@ -455,6 +746,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 +767,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 +787,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 +814,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" @@ -508,7 +867,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.51.1", ] [[package]] @@ -540,6 +899,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 +935,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 +957,33 @@ 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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -613,9 +1008,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" @@ -701,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" @@ -727,7 +1137,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -745,6 +1155,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" @@ -791,17 +1207,72 @@ dependencies = [ ] [[package]] -name = "pin-project-lite" -version = "0.2.13" +name = "petgraph" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.1.0", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pin-project" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - +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" +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 = "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 +1282,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 +1355,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 +1394,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 +1446,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 +1501,7 @@ dependencies = [ "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -954,6 +1541,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 +1566,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 +1625,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 +1744,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 +1761,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 +1804,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 +1872,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 +1909,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 +1944,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 +2023,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 +2047,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 +2108,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 +2130,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 +2219,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" @@ -1460,13 +2268,32 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "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]] @@ -1475,7 +2302,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 +2320,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 +2350,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/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 + } +} 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..a3ec707 --- /dev/null +++ b/test/rust_grpc/Cargo.lock @@ -0,0 +1,1413 @@ +# 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 = "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" +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 = "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" +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 = [ + "atomic-counter", + "clap", + "crossbeam-skiplist", + "os-id", + "portpicker", + "prost", + "prost-types", + "tokio", + "tokio-shutdown", + "tonic", + "tonic-build", + "windows", +] + +[[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 = "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" +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 = "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" +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 = "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", +] + +[[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 = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[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" +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" +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..80387a6 --- /dev/null +++ b/test/rust_grpc/Cargo.toml @@ -0,0 +1,43 @@ +[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" +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" +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"] } + +[profile.release] +debug = true 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..d3b637f --- /dev/null +++ b/test/rust_grpc/build.rs @@ -0,0 +1,5 @@ +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..74cb944 --- /dev/null +++ b/test/rust_grpc/proto/rust_grpc.proto @@ -0,0 +1,85 @@ +// 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"; + +import "google/protobuf/duration.proto"; + +/// 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 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. + google.protobuf.Duration uptime = 1; + + /// 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 { +} + +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..cd13818 --- /dev/null +++ b/test/rust_grpc/src/generator.rs @@ -0,0 +1,241 @@ +use clap::{arg, Parser}; +use rust_grpc_private::DiagnosticsRequest; +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 +{ + tonic::include_proto!("rust_grpc"); +} + +/// Simple program to greet a person. +#[derive(Parser, Debug, Clone)] +#[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, + + /// 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 +{ + generator: Option>, + stop: UnboundedSender<()>, +} + +impl GrpcGenerator +{ + /// Stars a new gRPC generator. + 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 = 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(); + 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: args.address, + period: args.period, + tasks: args.tasks, + }) + .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_clone) + .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 generator.join().is_err() { + 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> +{ + // 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 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; } + } + + #[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> +{ + 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 +{ + 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, processes: {} with {} threads", + call_rate, + diagnostics.clients.len(), + diagnostics + .clients + .iter() + .map(|c| c.threads.len() as u64) + .sum::() + ); + + 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 new file mode 100644 index 0000000..9e6dad5 --- /dev/null +++ b/test/rust_grpc/src/lib.rs @@ -0,0 +1,231 @@ +use crate::server::GrpcServer; +pub use rust_grpc::{ + ClientProcess, DiagnosticsRequest, DiagnosticsResponse, SendMessageRequest, SendMessageResponse, +}; +use std::time; +use std::time::Duration; +use tonic::transport::channel::Channel; + +pub mod generator; +pub mod server; + +pub mod rust_grpc +{ + tonic::include_proto!("rust_grpc"); +} + +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, + + /// Information about the clients that have contacted the server. + pub clients: Vec, +} + +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() + } + + 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_with_args(args: Args) -> Result> + { + let server = server::GrpcServer::start().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 { + server, + generator, + client, + }) + } + + /// 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 + .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, + clients: diagnostics + .clients + .clone() + .into_iter() + .map(|c| ClientProcess { + id: c.id, + threads: c.threads, + }) + .collect(), + }) + } + + /// Stops the gRPC Server + pub fn stop(&mut self) -> Result<(), Box> + { + self.generator.stop()?; + self.server.stop()?; + + Ok(()) + } + + /// Stops the message generator and returns server which is left running. + pub fn stop_generator(mut self) -> Result> + { + self.generator.stop()?; + Ok(self.server) + } +} + +#[cfg(test)] +mod test +{ + use crate::{Args, GrpcTester}; + use std::time::Duration; + + #[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."); + } + + #[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."); + } + + #[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 new file mode 100644 index 0000000..e605bbb --- /dev/null +++ b/test/rust_grpc/src/server.rs @@ -0,0 +1,332 @@ +use atomic_counter::AtomicCounter; +use clap::Parser; +use rust_grpc_private::test_service_server::{TestService, TestServiceServer}; +use rust_grpc_private::{ + 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}; + +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 = 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(); + 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 server's address. + pub fn address(&self) -> &SocketAddr + { + &self.address + } + + /// 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 server.join().is_err() { + 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."); + } +} + +/// A message sink for the message generator. +/// +/// 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, + + /// 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 +{ + /// 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, + 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); + Server::builder() + .concurrency_limit_per_connection(128) + .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| { + #[allow(clippy::bool_comparison)] + if *value == false { + *value = true; + true + } else { + false + } + }); + 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 {})) + } + + async fn get_diagnostics( + &self, + _request: Request, + ) -> 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.")), + }; + + 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, + })) + } + + /// Waits and blocks until the server has received at least one send_message call. + 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(()) +} + +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")), + } +}