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")),
+ }
+}