diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d6fe3e2..96cfe80 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -8,6 +8,8 @@ on: env: CARGO_TERM_COLOR: always + MSRV: "1.63" + RUST_BACKTRACE: 1 jobs: build: @@ -30,3 +32,17 @@ jobs: run: cargo build --locked --verbose - name: Run tests run: cargo test --all-features --locked --verbose + + msrv: + name: Minimal Supported Rust Version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: "${{ env.MSRV }}" + override: true + - name: Check MSRV all features + run: | + cargo +$MSRV check --workspace --all-targets --no-default-features diff --git a/Cargo.lock b/Cargo.lock index 7cf924f..3f0e23a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "arrayref" @@ -91,6 +91,12 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-waker" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" + [[package]] name = "autocfg" version = "1.1.0" @@ -115,6 +121,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.5.3" @@ -162,9 +174,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byteorder" @@ -174,15 +186,21 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "cc" -version = "1.0.77" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] @@ -204,6 +222,7 @@ dependencies = [ "rustls", "serde", "tokio", + "tracing-subscriber", "types", ] @@ -391,9 +410,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ "signature", ] @@ -426,9 +445,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "enum-ordinalize" @@ -467,9 +486,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -490,7 +509,7 @@ dependencies = [ "futures-sink", "nanorand", "pin-project", - "spin 0.9.4", + "spin 0.9.5", ] [[package]] @@ -510,9 +529,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -525,9 +544,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -535,15 +554,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -553,15 +572,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -581,15 +600,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-timer" @@ -599,9 +618,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -685,15 +704,15 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] @@ -740,9 +759,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", @@ -793,9 +812,9 @@ dependencies = [ [[package]] name = "intrusive-collections" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe531a7789d7120f3e17d4f3f2cd95f54418ba7354f60b7b622b6644a07888a" +checksum = "3f4f90afb01281fdeffb0f8e082d230cbe4f888f837cc90759696b858db1a700" dependencies = [ "memoffset", ] @@ -811,9 +830,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "jobserver" @@ -826,9 +845,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -841,9 +860,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.138" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libm" @@ -930,9 +949,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.5.6" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg", ] @@ -945,14 +964,14 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -1042,14 +1061,24 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.2" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1094,9 +1123,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi", "libc", @@ -1113,9 +1142,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "opaque-debug" @@ -1142,6 +1171,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1154,24 +1189,24 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] name = "pem" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ - "base64", + "base64 0.13.1", ] [[package]] @@ -1182,9 +1217,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", "indexmap", @@ -1290,18 +1325,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" +checksum = "21dc42e00223fc37204bd4aa177e69420c604ca4a183209a8f9de30c6d934698" dependencies = [ "bytes", "prost-derive", @@ -1309,9 +1344,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6" +checksum = "a3f8ad728fb08fe212df3c05169e940fbb6d9d16a877ddde14644a983ba2012e" dependencies = [ "bytes", "heck", @@ -1331,9 +1366,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" +checksum = "8bda8c0881ea9f722eb9629376db3d0b903b462477c1aafcb0566610ac28ac5d" dependencies = [ "anyhow", "itertools", @@ -1344,9 +1379,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091" +checksum = "a5e0526209433e96d83d750dd81a99118edbc55739e7e61a46764fd2ad537788" dependencies = [ "bytes", "prost", @@ -1354,7 +1389,7 @@ dependencies = [ [[package]] name = "quic-rpc" -version = "0.3.2" +version = "0.5.0" dependencies = [ "anyhow", "async-stream", @@ -1377,6 +1412,7 @@ dependencies = [ "tokio-serde", "tokio-util", "tracing", + "tracing-subscriber", ] [[package]] @@ -1431,9 +1467,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -1595,9 +1631,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -1619,11 +1655,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] @@ -1639,9 +1675,9 @@ dependencies = [ [[package]] name = "s2n-codec" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76a52949171a784415293428d2c9315c249ba2091d49b27279601a55375bbb2" +checksum = "732bd05755d783122ccc2d26273be50944da4422709011bdce4914b5c7eb81b8" dependencies = [ "byteorder", "bytes", @@ -1651,9 +1687,9 @@ dependencies = [ [[package]] name = "s2n-quic" -version = "1.14.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf9239d46070261d9ad1bddadac94999a1b2b2edbe53184440734130eeaf7bf" +checksum = "5d94364c9b08237e7282f4a1d453698a05777c274953d17e14feb983a8bd60a7" dependencies = [ "bytes", "cfg-if", @@ -1677,12 +1713,14 @@ dependencies = [ [[package]] name = "s2n-quic-core" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62fa73b53d55f77ceced204afe7961ea37e3b989e70ce3ab2ff09bac97f21b54" +checksum = "8d46c9c0ec80c1d21ade9fd0078cbfd52406914f075130d8faac1035e1527da2" dependencies = [ + "atomic-waker", "byteorder", "bytes", + "cache-padded", "hex-literal", "num-rational", "num-traits", @@ -1695,9 +1733,9 @@ dependencies = [ [[package]] name = "s2n-quic-crypto" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e0bc8d5e3381125cc1a98cd08a30b65d65b6124fb529aa16d9e9c25e4e9e73" +checksum = "c87540c3f7bbc5af8212b000757468a97cb163aa20dd912cf726a5ec11619c82" dependencies = [ "cfg-if", "lazy_static", @@ -1709,9 +1747,9 @@ dependencies = [ [[package]] name = "s2n-quic-platform" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07d630b137fde296b906e8d743359b28c86574259dd653caf8e4ef3c1a3bdb" +checksum = "4a65e5a9ddf732e2070231fbf35e267e0e2028f603924d617fd4987bf9450224" dependencies = [ "cfg-if", "errno", @@ -1727,9 +1765,9 @@ dependencies = [ [[package]] name = "s2n-quic-rustls" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c133b24436ae7069d32f0f2efe51e23648cdab93f27e08d8bb914ef6317555" +checksum = "c293cb1c3b65544ae4a0f5e799f82454bdd7a57e8e56d533e49b781bc987b132" dependencies = [ "bytes", "rustls", @@ -1741,9 +1779,9 @@ dependencies = [ [[package]] name = "s2n-quic-tls" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96265f6fa3c8a33674019b1d93d8e69511fd3f09cc916ad0d48922fca3e4b12" +checksum = "00f0fc77245ed8d497b7df71f9214c4c3a65524ac4bc1edc5552f2471c5ecbde" dependencies = [ "bytes", "errno", @@ -1756,9 +1794,9 @@ dependencies = [ [[package]] name = "s2n-quic-tls-default" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921c8a01dee19faec425d01114c495ef7c898865cb4e06069dcb290abcab405e" +checksum = "2d01ef7433ab5401269daf34d1803d0b07db85c38b05966990941a3fe2003d39" dependencies = [ "s2n-quic-rustls", "s2n-quic-tls", @@ -1766,9 +1804,9 @@ dependencies = [ [[package]] name = "s2n-quic-transport" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48eae03679c9ca43d7e7f978771e9f0fbd9fd9ec680b483be04171a48d077a7" +checksum = "fb6a4e0f623e6f464ede902870adfe3cb5d9beb985d9f9c65aedcea6a670e936" dependencies = [ "bytes", "futures-channel", @@ -1808,12 +1846,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys 0.42.0", ] [[package]] @@ -1847,9 +1884,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.7.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", @@ -1860,9 +1897,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -1870,24 +1907,24 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" [[package]] name = "serde" -version = "1.0.149" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.149" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1907,6 +1944,7 @@ dependencies = [ "rustls", "serde", "tokio", + "tracing-subscriber", "types", ] @@ -1934,11 +1972,20 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -1988,9 +2035,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" dependencies = [ "lock_api", ] @@ -2019,9 +2066,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -2056,18 +2103,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -2080,11 +2127,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "53250a3b3fed8ff8fd988587d8925d26a83ac3845d9e03b220b37f34c2b8d6c2" dependencies = [ "itoa", "serde", @@ -2100,9 +2157,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "a460aeb8de6dcb0f381e1ee05f1cd56fcf5a5f6eb8187ff3d8f0b11078d38b7c" dependencies = [ "time-core", ] @@ -2118,15 +2175,15 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.23.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", "bytes", @@ -2170,9 +2227,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -2184,9 +2241,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] @@ -2227,13 +2284,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" @@ -2253,15 +2336,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -2301,6 +2384,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2343,9 +2432,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2353,9 +2442,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -2368,9 +2457,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2378,9 +2467,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -2391,15 +2480,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -2417,9 +2506,9 @@ dependencies = [ [[package]] name = "which" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ "either", "libc", @@ -2448,19 +2537,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.42.0" @@ -2468,85 +2544,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] [[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" +name = "windows-targets" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +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", +] [[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" +name = "windows_aarch64_gnullvm" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] -name = "windows_i686_gnu" -version = "0.36.1" +name = "windows_aarch64_msvc" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "x509-parser" @@ -2555,7 +2625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" dependencies = [ "asn1-rs", - "base64", + "base64 0.13.1", "data-encoding", "der-parser", "lazy_static", @@ -2568,9 +2638,9 @@ dependencies = [ [[package]] name = "yasna" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346d34a236c9d3e5f3b9b74563f238f955bbd05fa0b8b4efa53c130c43982f4c" +checksum = "aed2e7a52e3744ab4d0c05c20aa065258e84c49fd4226f5191b2ed29712710b4" dependencies = [ "time", ] diff --git a/Cargo.toml b/Cargo.toml index 8128952..17c2b95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic-rpc" -version = "0.3.2" +version = "0.5.0" edition = "2021" authors = ["RĂ¼diger Klaehn "] keywords = ["api", "protocol", "network", "rpc"] @@ -9,22 +9,24 @@ license = "Apache-2.0/MIT" repository = "https://github.com/n0-computer/quic-rpc" description = "A streaming rpc system based on quic" +# Sadly this also needs to be updated in .github/workflows/ci.yml +rust-version = "1.63" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bincode = { version = "1.3.3", optional = true } -bytes = { version = "1.3.0", optional = true } -flume = "0.10.14" -futures = "0.3.25" -hyper = { version = "0.14.23", features = ["full"], optional = true } +bincode = { version = "1.3", optional = true } +bytes = { version = "1", optional = true } +flume = { version = "0.10", optional = true } +futures = "0.3" +hyper = { version = "0.14", features = ["full"], optional = true } pin-project = "1" -quinn = { version = "0.9.0", optional = true } -s2n-quic = "1.14.0" +s2n-quic = { version = "1.14.0", optional = true } +quinn = { version = "0.9", optional = true } serde = { version = "1" } tokio = { version = "1", features = ["full"] } -tokio-serde = { version = "0.8.0", features = ["bincode"], optional = true } -tokio-util = { version = "0.7.4", features = ["codec"], optional = true } +tokio-serde = { version = "0.8", features = ["bincode"], optional = true } +tokio-util = { version = "0.7", features = ["codec"], optional = true } tracing = "0.1" [dev-dependencies] @@ -33,18 +35,36 @@ async-stream = "0.3.3" derive_more = "0.99.17" serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } -quinn = "0.9.0" +quinn = "0.9.3" rcgen = "0.10.0" -rustls = "0.20.7" +rustls = "0.20.8" thousands = "0.2.0" -libp2p-core = "0.38.0" -libp2p-tls = "0.1.0-alpha" +libp2p-core = { version = "0.38.0" } +libp2p-tls = { version = "0.1.0-alpha" } s2n-quic = { version = "1.14.0", default-features = false, features = [ "provider-tls-rustls", "provider-address-token-default" ] } +tracing-subscriber = "0.3.16" [features] -http2 = ["hyper", "bincode", "bytes"] -quic = ["quinn", "bincode", "tokio-serde", "tokio-util"] -default = ["quic", "http2"] +hyper-transport = ["flume", "hyper", "bincode", "bytes"] +quinn-transport = ["flume", "quinn", "bincode", "tokio-serde", "tokio-util"] +s2n-quic-transport = ["s2n-quic", "flume", "tokio-serde", "tokio-util"] +flume-transport = ["flume"] +combined-transport = [] +macros = [] +# todo: remove before merge +default = ["s2n-quic-transport", "macros"] + +[[example]] +name = "errors" +required-features = ["flume-transport"] + +[[example]] +name = "macro" +required-features = ["flume-transport", "macros"] + +[[example]] +name = "store" +required-features = ["flume-transport"] [workspace] members = ["examples/split/types", "examples/split/server", "examples/split/client"] diff --git a/README.md b/README.md index d6d8838..15c06c5 100644 --- a/README.md +++ b/README.md @@ -81,11 +81,11 @@ substreams including per substream backpressure. However, I found that for raw data transfer http2/tcp has still superior performance. This is why the http2 transport exists. -Currently you would use the quic transport for cases where you want to have +Currently you would use the quinn transport for cases where you want to have connections to many different peers and can't accept a large per connection overhead, or where you want low latency for small messages. -You would use the http2 transport for cases where you have a small number of +You would use the hyper transport for cases where you have a small number of connections, so per connection overhead does not matter that much, and where you want maximum throughput at the expense of some latency. diff --git a/examples/errors.rs b/examples/errors.rs index ab12567..29f8d9e 100644 --- a/examples/errors.rs +++ b/examples/errors.rs @@ -1,5 +1,5 @@ use derive_more::{Display, From, TryInto}; -use quic_rpc::{message::RpcMsg, transport::mem::MemChannelTypes, RpcClient, RpcServer, Service}; +use quic_rpc::{message::RpcMsg, RpcClient, RpcServer, Service}; use serde::{Deserialize, Serialize}; use std::result; @@ -21,7 +21,7 @@ impl std::error::Error for WriteError {} impl From for WriteError { fn from(e: anyhow::Error) -> Self { - WriteError(format!("{:?}", e)) + WriteError(format!("{e:?}")) } } @@ -55,15 +55,14 @@ impl Fs { #[tokio::main] async fn main() -> anyhow::Result<()> { let fs = Fs; - let (server, client) = quic_rpc::transport::mem::connection(1); - let client = RpcClient::::new(client); - let server = RpcServer::::new(server); + let (server, client) = quic_rpc::transport::flume::connection(1); + let client = RpcClient::::new(client); + let server = RpcServer::::new(server); let handle = tokio::task::spawn(async move { for _ in 0..1 { - let (req, chan) = server.accept_one().await?; - let s = server.clone(); + let (req, chan) = server.accept().await?; match req { - IoRequest::Write(req) => s.rpc_map_err(req, chan, fs, Fs::write).await, + IoRequest::Write(req) => chan.rpc_map_err(req, fs, Fs::write).await, }? } anyhow::Ok(()) diff --git a/examples/macro.rs b/examples/macro.rs index b6955ed..1e46013 100644 --- a/examples/macro.rs +++ b/examples/macro.rs @@ -39,7 +39,6 @@ mod store_rpc { Response = StoreResponse; Service = StoreService; CreateDispatch = create_store_dispatch; - CreateClient = create_store_client; Rpc put = Put, _ -> PutResponse; Rpc get = Get, _ -> GetResponse; @@ -53,7 +52,7 @@ use async_stream::stream; use futures::{SinkExt, Stream, StreamExt}; use quic_rpc::client::RpcClient; use quic_rpc::server::run_server_loop; -use quic_rpc::transport::mem::{self, MemChannelTypes}; +use quic_rpc::transport::flume; use store_rpc::*; #[derive(Clone)] @@ -101,63 +100,55 @@ impl Store { } create_store_dispatch!(Store, dispatch_store_request); -create_store_client!(StoreClient); +// create_store_client!(StoreClient); #[tokio::main] async fn main() -> anyhow::Result<()> { - let (server, client) = mem::connection::(1); + let (server, client) = flume::connection::(1); let server_handle = tokio::task::spawn(async move { let target = Store; - run_server_loop( - StoreService, - MemChannelTypes, - server, - target, - dispatch_store_request, - ) - .await + run_server_loop(StoreService, server, target, dispatch_store_request).await }); - let client = RpcClient::::new(client); - let mut client = StoreClient(client); + let client = RpcClient::::new(client); // a rpc call for i in 0..3 { println!("a rpc call [{i}]"); - let mut client = client.clone(); + let client = client.clone(); tokio::task::spawn(async move { - let res = client.get(Get([0u8; 32])).await; - println!("rpc res [{i}]: {:?}", res); + let res = client.rpc(Get([0u8; 32])).await; + println!("rpc res [{i}]: {res:?}"); }); } // server streaming call println!("a server streaming call"); - let mut s = client.get_file(GetFile([0u8; 32])).await?; + let mut s = client.server_streaming(GetFile([0u8; 32])).await?; while let Some(res) = s.next().await { - println!("streaming res: {:?}", res); + println!("streaming res: {res:?}"); } // client streaming call println!("a client streaming call"); - let (mut send, recv) = client.put_file(PutFile).await?; + let (mut send, recv) = client.client_streaming(PutFile).await?; tokio::task::spawn(async move { for i in 0..3 { send.send(PutFileUpdate(vec![i])).await.unwrap(); } }); let res = recv.await?; - println!("client stremaing res: {:?}", res); + println!("client stremaing res: {res:?}"); // bidi streaming call println!("a bidi streaming call"); - let (mut send, mut recv) = client.convert_file(ConvertFile).await?; + let (mut send, mut recv) = client.bidi(ConvertFile).await?; tokio::task::spawn(async move { for i in 0..3 { send.send(ConvertFileUpdate(vec![i])).await.unwrap(); } }); while let Some(res) = recv.next().await { - println!("bidi res: {:?}", res); + println!("bidi res: {res:?}"); } // dropping the client will cause the server to terminate diff --git a/examples/split/client/Cargo.toml b/examples/split/client/Cargo.toml index e70fba6..3df5e5a 100644 --- a/examples/split/client/Cargo.toml +++ b/examples/split/client/Cargo.toml @@ -7,10 +7,11 @@ edition = "2021" [dependencies] anyhow = "1" -futures = "0.3.25" -quic-rpc = { path = "../../..", features = ["quic"] } -quinn = "0.9.0" -rustls = { version = "0.20.7", features = ["dangerous_configuration"] } +futures = "0.3.26" +quic-rpc = { path = "../../..", features = ["quinn-transport", "macros"] } +quinn = "0.9.3" +rustls = { version = "0.20.8", features = ["dangerous_configuration"] } +tracing-subscriber = "0.3.16" serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } types = { path = "../types" } diff --git a/examples/split/client/src/main.rs b/examples/split/client/src/main.rs index eb69de0..16738fd 100644 --- a/examples/split/client/src/main.rs +++ b/examples/split/client/src/main.rs @@ -1,36 +1,40 @@ use futures::sink::SinkExt; use futures::stream::StreamExt; -use quic_rpc::{transport::QuinnChannelTypes, RpcClient}; +use quic_rpc::RpcClient; use quinn::{ClientConfig, Endpoint}; use std::io; use std::net::SocketAddr; use std::sync::Arc; use types::compute::*; -types::create_compute_client!(ComputeClient); +// types::create_compute_client!(ComputeClient); #[tokio::main] async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt::init(); let server_addr: SocketAddr = "127.0.0.1:12345".parse()?; let endpoint = make_insecure_client_endpoint("0.0.0.0:0".parse()?)?; - let client = endpoint.connect(server_addr, "localhost")?.await?; - let client = quic_rpc::transport::quinn::QuinnClientChannel::new(client); - let client = RpcClient::::new(client); - let mut client = ComputeClient(client); + let client = quic_rpc::transport::quinn::QuinnConnection::new( + endpoint, + server_addr, + "localhost".to_string(), + ); + let client = RpcClient::::new(client); + // let mut client = ComputeClient(client); // a rpc call for i in 0..3 { - let mut client = client.clone(); + let client = client.clone(); tokio::task::spawn(async move { println!("rpc call: square([{i}])"); - let res = client.square(Sqr(i)).await; + let res = client.rpc(Sqr(i)).await; println!("rpc res: square({i}) = {:?}", res.unwrap()); }); } // client streaming call println!("client streaming call: sum()"); - let (mut send, recv) = client.sum(Sum).await?; + let (mut send, recv) = client.client_streaming(Sum).await?; tokio::task::spawn(async move { for i in 2..4 { println!("client streaming update: {i}"); @@ -38,18 +42,18 @@ async fn main() -> anyhow::Result<()> { } }); let res = recv.await?; - println!("client streaming res: {:?}", res); + println!("client streaming res: {res:?}"); // server streaming call println!("server streaming call: fibonacci(10)"); - let mut s = client.fibonacci(Fibonacci(10)).await?; + let mut s = client.server_streaming(Fibonacci(10)).await?; while let Some(res) = s.next().await { println!("server streaming res: {:?}", res?); } // bidi streaming call println!("bidi streaming call: multiply(2)"); - let (mut send, mut recv) = client.multiply(Multiply(2)).await?; + let (mut send, mut recv) = client.bidi(Multiply(2)).await?; tokio::task::spawn(async move { for i in 1..3 { println!("bidi streaming update: {i}"); diff --git a/examples/split/server/Cargo.toml b/examples/split/server/Cargo.toml index 80ae742..a367379 100644 --- a/examples/split/server/Cargo.toml +++ b/examples/split/server/Cargo.toml @@ -6,13 +6,15 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + anyhow = "1" async-stream = "0.3.3" -futures = "0.3.25" -quic-rpc = { path = "../../.." } -quinn = "0.9.0" +futures = "0.3.26" +tracing-subscriber = "0.3.16" +quic-rpc = { path = "../../..", features = ["quinn-transport", "macros"] } +quinn = "0.9.3" rcgen = "0.10.0" -rustls = "0.20.7" +rustls = "0.20.8" serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } types = { path = "../types" } diff --git a/examples/split/server/src/main.rs b/examples/split/server/src/main.rs index 603c07b..e064395 100644 --- a/examples/split/server/src/main.rs +++ b/examples/split/server/src/main.rs @@ -1,7 +1,6 @@ -use anyhow::Context; use async_stream::stream; use futures::stream::{Stream, StreamExt}; -use quic_rpc::{server::run_server_loop, transport::QuinnChannelTypes}; +use quic_rpc::server::run_server_loop; use quinn::{Endpoint, ServerConfig}; use std::net::SocketAddr; use std::sync::Arc; @@ -59,31 +58,19 @@ impl Compute { #[tokio::main] async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt::init(); let server_addr: SocketAddr = "127.0.0.1:12345".parse()?; let (server, _server_certs) = make_server_endpoint(server_addr)?; - let local_addr = server.local_addr()?; - loop { - let accept = server.accept().await.context("accept failed")?.await?; - tokio::task::spawn(async move { - let remote = accept.remote_address(); - eprintln!("new connection from {remote}"); - let connection = - quic_rpc::transport::quinn::QuinnServerChannel::new(accept, local_addr); - let target = Compute; - match run_server_loop( - ComputeService, - QuinnChannelTypes, - connection, - target, - dispatch_compute_request, - ) - .await - { - Ok(_) => eprintln!("connection to {remote} closed"), - Err(err) => eprintln!("connection to {remote} closed: {err:?}"), - } - }); - } + let channel = quic_rpc::transport::quinn::QuinnServerEndpoint::new(server)?; + let target = Compute; + run_server_loop( + ComputeService, + channel.clone(), + target, + dispatch_compute_request, + ) + .await?; + Ok(()) } fn make_server_endpoint(bind_addr: SocketAddr) -> anyhow::Result<(Endpoint, Vec)> { diff --git a/examples/split/types/Cargo.toml b/examples/split/types/Cargo.toml index 21881a2..fd976ab 100644 --- a/examples/split/types/Cargo.toml +++ b/examples/split/types/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" [dependencies] derive_more = "0.99.17" -futures = "0.3.25" -quic-rpc = { path = "../../.." } +futures = "0.3.26" +quic-rpc = { path = "../../..", features = ["macros"] } serde = { version = "1", features = ["derive"] } diff --git a/examples/split/types/src/lib.rs b/examples/split/types/src/lib.rs index b48143c..a80922f 100644 --- a/examples/split/types/src/lib.rs +++ b/examples/split/types/src/lib.rs @@ -36,7 +36,6 @@ pub mod compute { Response = ComputeResponse; Service = ComputeService; CreateDispatch = create_compute_dispatch; - CreateClient = create_compute_client; Rpc square = Sqr, _ -> SqrResponse; ClientStreaming sum = Sum, SumUpdate -> SumResponse; diff --git a/examples/store.rs b/examples/store.rs index 2480ef5..dbbb182 100644 --- a/examples/store.rs +++ b/examples/store.rs @@ -2,12 +2,10 @@ use async_stream::stream; use derive_more::{From, TryInto}; use futures::{SinkExt, Stream, StreamExt}; -use message::RpcMsg; use quic_rpc::{ - message::{BidiStreaming, ClientStreaming, Msg, ServerStreaming}, server::RpcServerError, - transport::mem::{self, MemChannelTypes}, - ClientChannel, *, + transport::{flume, Connection, ServerEndpoint}, + Service, ServiceEndpoint, *, }; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, result}; @@ -107,31 +105,16 @@ impl Service for StoreService { type Res = StoreResponse; } -impl RpcMsg for Put { - type Response = PutResponse; -} - -impl RpcMsg for Get { - type Response = GetResponse; -} - -impl Msg for PutFile { - type Response = PutFileResponse; - type Update = PutFileUpdate; - type Pattern = ClientStreaming; -} - -impl Msg for GetFile { - type Response = GetFileResponse; - type Update = GetFile; - type Pattern = ServerStreaming; -} - -impl Msg for ConvertFile { - type Response = ConvertFileResponse; - type Update = ConvertFileUpdate; - type Pattern = BidiStreaming; -} +declare_rpc!(StoreService, Get, GetResponse); +declare_rpc!(StoreService, Put, PutResponse); +declare_client_streaming!(StoreService, PutFile, PutFileUpdate, PutFileResponse); +declare_server_streaming!(StoreService, GetFile, GetFileResponse); +declare_bidi_streaming!( + StoreService, + ConvertFile, + ConvertFileUpdate, + ConvertFileResponse +); #[derive(Clone)] struct Store; @@ -178,43 +161,43 @@ impl Store { #[tokio::main] async fn main() -> anyhow::Result<()> { - async fn server_future( - server: RpcServer, - ) -> result::Result<(), RpcServerError> { + async fn server_future>( + server: RpcServer, + ) -> result::Result<(), RpcServerError> { let s = server; let store = Store; loop { - let (req, chan) = s.accept_one().await?; + let (req, chan) = s.accept().await?; use StoreRequest::*; let store = store.clone(); #[rustfmt::skip] match req { - Put(msg) => s.rpc(msg, chan, store, Store::put).await, - Get(msg) => s.rpc(msg, chan, store, Store::get).await, - PutFile(msg) => s.client_streaming(msg, chan, store, Store::put_file).await, - GetFile(msg) => s.server_streaming(msg, chan, store, Store::get_file).await, - ConvertFile(msg) => s.bidi_streaming(msg, chan, store, Store::convert_file).await, + Put(msg) => chan.rpc(msg, store, Store::put).await, + Get(msg) => chan.rpc(msg, store, Store::get).await, + PutFile(msg) => chan.client_streaming(msg, store, Store::put_file).await, + GetFile(msg) => chan.server_streaming(msg, store, Store::get_file).await, + ConvertFile(msg) => chan.bidi_streaming(msg, store, Store::convert_file).await, PutFileUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, ConvertFileUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, }?; } } - let (server, client) = mem::connection::(1); - let client = RpcClient::::new(client); - let server = RpcServer::::new(server); + let (server, client) = flume::connection::(1); + let client = RpcClient::::new(client); + let server = RpcServer::::new(server); let server_handle = tokio::task::spawn(server_future(server)); // a rpc call println!("a rpc call"); let res = client.rpc(Get([0u8; 32])).await?; - println!("{:?}", res); + println!("{res:?}"); // server streaming call println!("a server streaming call"); let mut s = client.server_streaming(GetFile([0u8; 32])).await?; while let Some(res) = s.next().await { - println!("{:?}", res); + println!("{res:?}"); } // client streaming call @@ -226,7 +209,7 @@ async fn main() -> anyhow::Result<()> { } }); let res = recv.await?; - println!("{:?}", res); + println!("{res:?}"); // bidi streaming call println!("a bidi streaming call"); @@ -237,7 +220,7 @@ async fn main() -> anyhow::Result<()> { } }); while let Some(res) = recv.next().await { - println!("{:?}", res); + println!("{res:?}"); } // dropping the client will cause the server to terminate @@ -247,12 +230,12 @@ async fn main() -> anyhow::Result<()> { } async fn _main_unsugared() -> anyhow::Result<()> { - let (server, client) = mem::connection::(1); + let (server, client) = flume::connection::(1); let to_string_service = tokio::spawn(async move { let (mut send, mut recv) = server.accept_bi().await?; while let Some(item) = recv.next().await { let item = item?; - println!("server got: {:?}", item); + println!("server got: {item:?}"); send.send(item.to_string()).await?; } anyhow::Ok(()) @@ -261,7 +244,7 @@ async fn _main_unsugared() -> anyhow::Result<()> { let print_result_service = tokio::spawn(async move { while let Some(item) = recv.next().await { let item = item?; - println!("got result: {}", item); + println!("got result: {item}"); } anyhow::Ok(()) }); diff --git a/src/client.rs b/src/client.rs index 42864fd..4735d2b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,9 +1,10 @@ -//! [RpcClient] and support types +//! Client side api //! -//! This defines the RPC client DSL +//! The main entry point is [RpcClient]. use crate::{ - message::{BidiStreaming, ClientStreaming, Msg, Rpc, ServerStreaming}, - ChannelTypes, ClientChannel, Service, + message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, + transport::ConnectionErrors, + Service, ServiceConnection, }; use futures::{ future::BoxFuture, stream::BoxStream, FutureExt, Sink, SinkExt, Stream, StreamExt, TryFutureExt, @@ -20,38 +21,40 @@ use std::{ /// A client for a specific service /// -/// This is a wrapper around a [crate::ClientChannel] that serves as the entry point for the client DSL. -/// `S` is the service type, `C` is the channel type. +/// This is a wrapper around a [ServiceConnection] that serves as the entry point +/// for the client DSL. `S` is the service type, `C` is the substream source. #[derive(Debug)] -pub struct RpcClient { - channel: C::ClientChannel, +pub struct RpcClient { + source: C, + p: PhantomData, } -impl Clone for RpcClient { +impl Clone for RpcClient { fn clone(&self) -> Self { Self { - channel: self.channel.clone(), + source: self.source.clone(), + p: PhantomData, } } } /// Sink that can be used to send updates to the server for the two interaction patterns -/// that support it, [ClientStreaming] and [BidiStreaming]. +/// that support it, [crate::message::ClientStreaming] and [crate::message::BidiStreaming]. #[pin_project] #[derive(Debug)] -pub struct UpdateSink>( - #[pin] C::SendSink, - PhantomData, +pub struct UpdateSink, T: Into>( + #[pin] C::SendSink, + PhantomData, ); -impl> Sink for UpdateSink { +impl, T: Into> Sink for UpdateSink { type Error = C::SendError; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().0.poll_ready_unpin(cx) } - fn start_send(self: Pin<&mut Self>, item: M::Update) -> Result<(), Self::Error> { + fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { let req: S::Req = item.into(); self.project().0.start_send_unpin(req) } @@ -65,26 +68,25 @@ impl> Sink for UpdateSink RpcClient { - /// Create a new rpc client from a channel - pub fn new(channel: C::ClientChannel) -> Self { - Self { channel } - } - - /// Open a bidi connection on an existing channel, or possibly also open a new channel - async fn open_bi( - &self, - ) -> result::Result<(C::SendSink, C::RecvStream), C::OpenBiError> { - self.channel.open_bi().await +impl> RpcClient { + /// Create a new rpc client for a specific [Service] given a compatible + /// [ServiceConnection]. + /// + /// This is where a generic typed connection is converted into a client for a specific service. + pub fn new(source: C) -> Self { + Self { + source, + p: PhantomData, + } } /// RPC call to the server, single request, single response pub async fn rpc(&self, msg: M) -> result::Result> where - M: Msg + Into, + M: RpcMsg, { let msg = msg.into(); - let (mut send, mut recv) = self.open_bi().await.map_err(RpcClientError::Open)?; + let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; send.send(msg).await.map_err(RpcClientError::::Send)?; let res = recv .next() @@ -105,10 +107,14 @@ impl RpcClient { StreamingResponseError, > where - M: Msg + Into, + M: ServerStreamingMsg, { let msg = msg.into(); - let (mut send, recv) = self.open_bi().await.map_err(StreamingResponseError::Open)?; + let (mut send, recv) = self + .source + .open_bi() + .await + .map_err(StreamingResponseError::Open)?; send.send(msg) .map_err(StreamingResponseError::::Send) .await?; @@ -129,18 +135,22 @@ impl RpcClient { msg: M, ) -> result::Result< ( - UpdateSink, + UpdateSink, BoxFuture<'static, result::Result>>, ), ClientStreamingError, > where - M: Msg + Into, + M: ClientStreamingMsg, { let msg = msg.into(); - let (mut send, mut recv) = self.open_bi().await.map_err(ClientStreamingError::Open)?; + let (mut send, mut recv) = self + .source + .open_bi() + .await + .map_err(ClientStreamingError::Open)?; send.send(msg).map_err(ClientStreamingError::Send).await?; - let send = UpdateSink::(send, PhantomData); + let send = UpdateSink::(send, PhantomData); let recv = async move { let item = recv .next() @@ -164,16 +174,16 @@ impl RpcClient { msg: M, ) -> result::Result< ( - UpdateSink, + UpdateSink, BoxStream<'static, result::Result>>, ), BidiError, > where - M: Msg + Into, + M: BidiStreamingMsg, { let msg = msg.into(); - let (mut send, recv) = self.open_bi().await.map_err(BidiError::Open)?; + let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; send.send(msg).await.map_err(BidiError::::Send)?; let send = UpdateSink(send, PhantomData); let recv = recv @@ -188,9 +198,9 @@ impl RpcClient { /// Client error. All client DSL methods return a `Result` with this error type. #[derive(Debug)] -pub enum RpcClientError { - /// Unable to open a stream to the server - Open(C::OpenBiError), +pub enum RpcClientError { + /// Unable to open a substream at all + Open(C::OpenError), /// Unable to send the request to the server Send(C::SendError), /// Server closed the stream before sending a response @@ -201,68 +211,68 @@ pub enum RpcClientError { DowncastError, } -impl fmt::Display for RpcClientError { +impl fmt::Display for RpcClientError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for RpcClientError {} +impl error::Error for RpcClientError {} /// Server error when accepting a bidi request #[derive(Debug)] -pub enum BidiError { - /// Unable to open a stream to the server - Open(C::OpenBiError), +pub enum BidiError { + /// Unable to open a substream at all + Open(C::OpenError), /// Unable to send the request to the server Send(C::SendError), } -impl fmt::Display for BidiError { +impl fmt::Display for BidiError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for BidiError {} +impl error::Error for BidiError {} /// Server error when receiving an item for a bidi request #[derive(Debug)] -pub enum BidiItemError { +pub enum BidiItemError { /// Unable to receive the response from the server RecvError(C::RecvError), /// Unexpected response from the server DowncastError, } -impl fmt::Display for BidiItemError { +impl fmt::Display for BidiItemError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for BidiItemError {} +impl error::Error for BidiItemError {} /// Server error when accepting a client streaming request #[derive(Debug)] -pub enum ClientStreamingError { - /// Unable to open a stream to the server - Open(C::OpenBiError), +pub enum ClientStreamingError { + /// Unable to open a substream at all + Open(C::OpenError), /// Unable to send the request to the server Send(C::SendError), } -impl fmt::Display for ClientStreamingError { +impl fmt::Display for ClientStreamingError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for ClientStreamingError {} +impl error::Error for ClientStreamingError {} /// Server error when receiving an item for a client streaming request #[derive(Debug)] -pub enum ClientStreamingItemError { +pub enum ClientStreamingItemError { /// Connection was closed before receiving the first message EarlyClose, /// Unable to receive the response from the server @@ -271,47 +281,47 @@ pub enum ClientStreamingItemError { DowncastError, } -impl fmt::Display for ClientStreamingItemError { +impl fmt::Display for ClientStreamingItemError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for ClientStreamingItemError {} +impl error::Error for ClientStreamingItemError {} /// Server error when accepting a server streaming request #[derive(Debug)] -pub enum StreamingResponseError { - /// Unable to open a stream to the server - Open(C::OpenBiError), +pub enum StreamingResponseError { + /// Unable to open a substream at all + Open(C::OpenError), /// Unable to send the request to the server Send(C::SendError), } -impl fmt::Display for StreamingResponseError { +impl fmt::Display for StreamingResponseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for StreamingResponseError {} +impl error::Error for StreamingResponseError {} /// Client error when handling responses from a server streaming request #[derive(Debug)] -pub enum StreamingResponseItemError { +pub enum StreamingResponseItemError { /// Unable to receive the response from the server - RecvError(C::RecvError), + RecvError(S::RecvError), /// Unexpected response from the server DowncastError, } -impl fmt::Display for StreamingResponseItemError { +impl fmt::Display for StreamingResponseItemError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for StreamingResponseItemError {} +impl error::Error for StreamingResponseItemError {} /// Wrap a stream with an additional item that is kept alive until the stream is dropped #[pin_project] diff --git a/src/lib.rs b/src/lib.rs index 2946530..4195b3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,10 @@ -//! A streaming rpc system based on quic +//! A streaming rpc system for transports that support multiple bidirectional +//! streams, such as QUIC and HTTP2. +//! +//! A lightweight memory transport is provided for cases where you want have +//! multiple cleanly separated substreams in the same process. +//! +//! For supported transports, see the [transport] module. //! //! # Motivation //! @@ -7,7 +13,7 @@ //! # Example //! ``` //! # async fn example() -> anyhow::Result<()> { -//! use quic_rpc::{message::RpcMsg, Service, RpcClient, transport::MemChannelTypes}; +//! use quic_rpc::{message::RpcMsg, Service, RpcClient, RpcServer}; //! use serde::{Serialize, Deserialize}; //! use derive_more::{From, TryInto}; //! @@ -19,6 +25,8 @@ //! struct Pong; //! //! // Define your RPC service and its request/response types +//! #[derive(Debug, Clone)] +//! struct PingService; //! //! #[derive(Debug, Serialize, Deserialize, From, TryInto)] //! enum PingRequest { @@ -30,9 +38,6 @@ //! Pong(Pong), //! } //! -//! #[derive(Debug, Clone)] -//! struct PingService; -//! //! impl Service for PingService { //! type Req = PingRequest; //! type Res = PingResponse; @@ -43,32 +48,59 @@ //! type Response = Pong; //! } //! -//! // create a transport channel -//! let (server, client) = quic_rpc::transport::mem::connection::(1); +//! // create a transport channel, here a memory channel for testing +//! let (server, client) = quic_rpc::transport::flume::connection::(1); //! +//! // client side //! // create the rpc client given the channel and the service type -//! let mut client = RpcClient::::new(client); +//! let mut client = RpcClient::::new(client); //! //! // call the service //! let res = client.rpc(Ping).await?; +//! +//! // server side +//! // create the rpc server given the channel and the service type +//! let mut server = RpcServer::::new(server); +//! +//! let handler = Handler; +//! loop { +//! // accept connections +//! let (msg, chan) = server.accept().await?; +//! // dispatch the message to the appropriate handler +//! match msg { +//! PingRequest::Ping(ping) => chan.rpc(ping, handler, Handler::ping).await?, +//! } +//! } +//! +//! // the handler. For a more complex example, this would contain any state +//! // needed to handle the request. +//! #[derive(Debug, Clone, Copy)] +//! struct Handler; +//! +//! impl Handler { +//! // the handle fn for a Ping request. +//! +//! // The return type is the response type for the service. +//! // Note that this must take self by value, not by reference. +//! async fn ping(self, _req: Ping) -> Pong { +//! Pong +//! } +//! } //! # Ok(()) //! # } //! ``` #![deny(missing_docs)] #![deny(rustdoc::broken_intra_doc_links)] -use futures::{Future, Sink, Stream}; use serde::{de::DeserializeOwned, Serialize}; -use std::{ - fmt::{self, Debug, Display}, - net::SocketAddr, - result, -}; +use std::fmt::{Debug, Display}; +use transport::{Connection, ServerEndpoint}; pub mod client; pub mod message; pub mod server; pub mod transport; pub use client::RpcClient; pub use server::RpcServer; +#[cfg(feature = "macros")] mod macros; /// Requirements for a RPC message @@ -87,12 +119,34 @@ impl RpcMessage for T where /// Requirements for an internal error /// -/// All errors have to be Send and 'static so they can be sent across threads. +/// All errors have to be Send, Sync and 'static so they can be sent across threads. +/// They also have to be Debug and Display so they can be logged. +/// +/// We don't require them to implement [std::error::Error] so we can use +/// anyhow::Error as an error type. pub trait RpcError: Debug + Display + Send + Sync + Unpin + 'static {} impl RpcError for T where T: Debug + Display + Send + Sync + Unpin + 'static {} /// A service +/// +/// A service has request and response message types. These types have to be the +/// union of all possible request and response types for all interactions with +/// the service. +/// +/// Usually you will define an enum for the request and response +/// type, and use the [derive_more](https://crates.io/crates/derive_more) crate to +/// define the conversions between the enum and the actual request and response types. +/// +/// To make a message type usable as a request for a service, implement [message::Msg] +/// for it. This is how you define the interaction patterns for each request type. +/// +/// Depending on the interaction type, you might need to implement traits that further +/// define details of the interaction. +/// +/// A message type can be used for multiple services. E.g. you might have a +/// Status request that is understood by multiple services and returns a +/// standard status response. pub trait Service: Send + Sync + Debug + Clone + 'static { /// Type of request messages type Req: RpcMessage; @@ -100,98 +154,21 @@ pub trait Service: Send + Sync + Debug + Clone + 'static { type Res: RpcMessage; } -/// Defines a set of types for a kind of channel +/// A connection to a specific service on a specific remote machine /// -/// Every distinct kind of channel has its own ChannelType. See e.g. -/// [crate::transport::MemChannelTypes]. -pub trait ChannelTypes: Debug + Sized + Send + Sync + Unpin + Clone + 'static { - /// The sink used for sending either requests or responses on this channel - type SendSink: Sink + Send + Unpin + 'static; - /// The stream used for receiving either requests or responses on this channel - type RecvStream: Stream> - + Send - + Unpin - + 'static; - /// Error you might get while sending messages to a sink - type SendError: RpcError; - /// Error you might get while receiving messages from a stream - type RecvError: RpcError; - /// Error you might get when opening a new connection to the server - type OpenBiError: RpcError; - /// Future returned by open_bi - type OpenBiFuture<'a, In: RpcMessage, Out: RpcMessage>: Future< - Output = result::Result<(Self::SendSink, Self::RecvStream), Self::OpenBiError>, - > + Send - + 'a - where - Self: 'a; - - /// Error you might get when waiting for new streams on the server side - type AcceptBiError: RpcError; - /// Future returned by accept_bi - type AcceptBiFuture<'a, In: RpcMessage, Out: RpcMessage>: Future< - Output = result::Result< - (Self::SendSink, Self::RecvStream), - Self::AcceptBiError, - >, - > + Send - + 'a - where - Self: 'a; - - /// Channel type - type ClientChannel: ClientChannel; - - /// Channel type - type ServerChannel: ServerChannel; -} - -/// An abstract client channel with typed input and output -pub trait ClientChannel: - Debug + Clone + Send + Sync + 'static -{ - /// Open a bidirectional stream - fn open_bi(&self) -> T::OpenBiFuture<'_, In, Out>; -} - -/// An abstract server with typed input and output -pub trait ServerChannel: - Debug + Clone + Send + Sync + 'static -{ - /// Accepts a bidirectional stream. - /// - /// This returns a future who's `Output` is a tuple of a sender sink and receiver stream - /// to send and receive messages to and from the client respectively. The sink and - /// stream `Item`s are the whole `In` and `Out` messages, an [`RpcMessage`]. - fn accept_bi(&self) -> T::AcceptBiFuture<'_, In, Out>; +/// This is just a trait alias for a [Connection] with the right types. +/// +/// This can be used to create a [RpcClient] that can be used to send requests. +pub trait ServiceConnection: Connection {} - /// The local addresses this server is bound to. - /// - /// This is useful for publicly facing addresses when you start the server with a random - /// port, `:0` and let the kernel choose the real bind address. This will return the - /// address with the actual port used. - fn local_addr(&self) -> &[LocalAddr]; -} +impl, S: Service> ServiceConnection for T {} -/// The kinds of local addresses a [`ServerChannel`] can be bound to. +/// A server endpoint for a specific service /// -/// Returned by [`ServerChannel::local_addr`]. +/// This is just a trait alias for a [ServerEndpoint] with the right types. /// -/// [`Display`]: fmt::Display -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum LocalAddr { - /// A local socket. - Socket(SocketAddr), - /// An in-memory address. - Mem, -} +/// This can be used to create a [RpcServer] that can be used to handle +/// requests. +pub trait ServiceEndpoint: ServerEndpoint {} -impl Display for LocalAddr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - LocalAddr::Socket(sockaddr) => write!(f, "{sockaddr}"), - LocalAddr::Mem => write!(f, "mem"), - } - } -} +impl, S: Service> ServiceEndpoint for T {} diff --git a/src/macros.rs b/src/macros.rs index 5d9a981..650e406 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -40,8 +40,6 @@ /// // Optional, if not needed pass _ (underscore) as name. /// CreateDispatch = create_my_dispatch; /// // Name of the macro to create an RPC client. -/// // Optional, if not needed pass _ (underscore) as name. -/// CreateClient = create_my_client; /// /// Rpc add = Add, _ -> Sum; /// BidiStreaming multiply = Multiply, MultiplyUpdate -> MultiplyOutput @@ -59,7 +57,7 @@ /// ```ignore /// create_store_client!(MyClient); /// let client = quic_rpc::quinn::Channel::new(client); -/// let client = quic_rpc::client::RpcClient::::new(client); +/// let client = quic_rpc::client::RpcClient::::new(client); /// let mut client = MyClient(client); /// let sum = client.add(Add(3, 4)).await?; /// // Sum(7) @@ -140,7 +138,6 @@ macro_rules! rpc_service { Response = $response:ident; Service = $service:ident; CreateDispatch = $create_dispatch:tt; - CreateClient = $create_client:tt; $($m_pattern:ident $m_name:ident = $m_input:ident, $m_update:tt -> $m_output:ident);+$(;)? ) => { @@ -179,12 +176,6 @@ macro_rules! rpc_service { $create_dispatch, [ $($m_pattern $m_name = $m_input, $m_update -> $m_output);+ ] ); - - $crate::__derive_create_client!( - $service, - $create_client, - [ $($m_pattern $m_name = $m_input, $m_update -> $m_output);+ ] - ); }; } @@ -207,20 +198,19 @@ macro_rules! __derive_create_dispatch { #[macro_export] macro_rules! $create_dispatch { ($target:ident, $handler:ident) => { - pub async fn $handler( - mut server: $crate::server::RpcServer<$service, C>, + pub async fn $handler>( + mut chan: $crate::server::RpcChannel<$service, C>, msg: <$service as $crate::Service>::Req, - chan: (C::SendSink<<$service as $crate::Service>::Res>, C::RecvStream<<$service as $crate::Service>::Req>), target: $target, - ) -> Result<$crate::server::RpcServer<$service, C>, $crate::server::RpcServerError> { + ) -> Result<(), $crate::server::RpcServerError> { let res = match msg { $( - $request::$m_input(msg) => { $crate::__rpc_invoke!($m_pattern, $m_name, $target, server, msg, chan, target) }, + $request::$m_input(msg) => { $crate::__rpc_invoke!($m_pattern, $m_name, $target, msg, chan, target) }, )* _ => Err($crate::server::RpcServerError::::UnexpectedStartMessage), }; res?; - Ok(server) + Ok(()) } } } @@ -256,6 +246,118 @@ macro_rules! __request_enum { }; } +/// Declare a message to be a rpc message for a service. +/// +/// Example: +/// ```ignore +/// declare_rpc!(TestService, TestRequest, TestResponse); +/// ``` +/// +/// This is equivalent to: +/// ```ignore +/// impl RpcMsg for TestRequest { +/// type Response = TestResponse; +/// } +/// ``` +#[macro_export] +macro_rules! declare_rpc { + ($service:ty, $m_input:ty, $m_output:ty) => { + impl $crate::message::RpcMsg<$service> for $m_input { + type Response = $m_output; + } + }; +} + +/// Declare a message to be a server streaming message for a service. +/// +/// Example: +/// ```ignore +/// declare_server_streaming!(TestService, TestRequest, TestResponse); +/// ``` +/// +/// This is equivalent to: +/// ```ignore +/// impl Msg for TestRequest { +/// type Pattern = ServerStreamingPattern; +/// } +/// +/// impl ServerStreamingMsg for TestRequest { +/// type Response = TestResponse; +/// } +#[macro_export] +macro_rules! declare_server_streaming { + ($service:ident, $m_input:ident, $m_output:ident) => { + impl $crate::message::Msg<$service> for $m_input { + type Pattern = $crate::message::ServerStreaming; + } + impl $crate::message::ServerStreamingMsg<$service> for $m_input { + type Response = $m_output; + } + }; +} + +/// Declare a message to be a server streaming message for a service. +/// +/// Example: +/// ```ignore +/// declare_client_streaming!(TestService, TestRequest, TestUpdate, TestResponse); +/// ``` +/// +/// This is equivalent to: +/// ```ignore +/// impl Msg for TestRequest { +/// type Pattern = ClientStreamingPattern; +/// } +/// +/// impl ClientStreamingMsg for TestRequest { +/// type Update = TestUpdate; +/// type Response = TestResponse; +/// } +/// ``` +#[macro_export] +macro_rules! declare_client_streaming { + ($service:ident, $m_input:ident, $m_update:ident, $m_output:ident) => { + impl $crate::message::Msg<$service> for $m_input { + type Pattern = $crate::message::ClientStreaming; + } + impl $crate::message::ClientStreamingMsg<$service> for $m_input { + type Update = $m_update; + type Response = $m_output; + } + }; +} + +/// Declare a message to be a server streaming message for a service. +/// +/// Example: +/// ```ignore +/// declare_bidi_streaming!(TestService, TestRequest, TestUpdate, TestResponse); +/// ``` +/// +/// This is equivalent to: +/// ```ignore +/// impl Msg for TestRequest { +/// type Pattern = BidiStreamingPattern; +/// } +/// +/// impl BidiStreamingMsg for TestRequest { +/// type Update = TestUpdate; +/// type Response = TestResponse; +/// } +/// ``` +#[macro_export] +macro_rules! declare_bidi_streaming { + ($service:ident, $m_input:ident, $m_update:ident, $m_output:ident) => { + impl $crate::message::Msg<$service> for $m_input { + type Pattern = $crate::message::BidiStreaming; + } + impl $crate::message::BidiStreamingMsg<$service> for $m_input { + type Update = $m_update; + type Response = $m_output; + } + }; +} + #[doc(hidden)] #[macro_export] macro_rules! __rpc_message { @@ -267,13 +369,25 @@ macro_rules! __rpc_message { ($service:ident, ServerStreaming, $m_input:ident, _, $m_output:ident) => { impl $crate::message::Msg<$service> for $m_input { type Pattern = $crate::message::ServerStreaming; + } + impl $crate::message::ServerStreamingMsg<$service> for $m_input { + type Response = $m_output; + } + }; + ($service:ident, ClientStreaming, $m_input:ident, $m_update:ident, $m_output:ident) => { + impl $crate::message::Msg<$service> for $m_input { + type Pattern = $crate::message::ClientStreaming; + } + impl $crate::message::ClientStreamingMsg<$service> for $m_input { type Response = $m_output; - type Update = $m_input; + type Update = $m_update; } }; - ($service:ident, $m_pattern:ident, $m_input:ident, $m_update:ident, $m_output:ident) => { + ($service:ident, BidiStreaming, $m_input:ident, $m_update:ident, $m_output:ident) => { impl $crate::message::Msg<$service> for $m_input { - type Pattern = $crate::message::$m_pattern; + type Pattern = $crate::message::BidiStreaming; + } + impl $crate::message::BidiStreamingMsg<$service> for $m_input { type Response = $m_output; type Update = $m_update; } @@ -283,22 +397,22 @@ macro_rules! __rpc_message { #[doc(hidden)] #[macro_export] macro_rules! __rpc_invoke { - (Rpc, $m_name:ident, $target_ty:ident, $server:ident, $msg:ident, $chan:ident, $target:ident) => { - $server.rpc($msg, $chan, $target, $target_ty::$m_name).await + (Rpc, $m_name:ident, $target_ty:ident, $msg:ident, $chan:ident, $target:ident) => { + $chan.rpc($msg, $target, $target_ty::$m_name).await }; - (ClientStreaming, $m_name:ident, $target_ty:ident, $server:ident, $msg:ident, $chan:ident, $target:ident) => { - $server - .client_streaming($msg, $chan, $target, $target_ty::$m_name) + (ClientStreaming, $m_name:ident, $target_ty:ident, $msg:ident, $chan:ident, $target:ident) => { + $chan + .client_streaming($msg, $target, $target_ty::$m_name) .await }; - (ServerStreaming, $m_name:ident, $target_ty:ident, $server:ident, $msg:ident, $chan:ident, $target:ident) => { - $server - .server_streaming($msg, $chan, $target, $target_ty::$m_name) + (ServerStreaming, $m_name:ident, $target_ty:ident, $msg:ident, $chan:ident, $target:ident) => { + $chan + .server_streaming($msg, $target, $target_ty::$m_name) .await }; - (BidiStreaming, $m_name:ident, $target_ty:ident, $server:ident, $msg:ident, $chan:ident, $target:ident) => { - $server - .bidi_streaming($msg, $chan, $target, $target_ty::$m_name) + (BidiStreaming, $m_name:ident, $target_ty:ident, $msg:ident, $chan:ident, $target:ident) => { + $chan + .bidi_streaming($msg, $target, $target_ty::$m_name) .await }; } @@ -321,9 +435,9 @@ macro_rules! __derive_create_client{ macro_rules! $create_client { ($struct:ident) => { #[derive(::std::clone::Clone, ::std::fmt::Debug)] - pub struct $struct(pub $crate::client::RpcClient<$service, C>); + pub struct $struct>(pub $crate::client::RpcClient<$service, C>); - impl $struct { + impl> $struct { $( $crate::__rpc_method!($m_pattern, $service, $m_name, $m_input, $m_output, $m_update); )* diff --git a/src/message.rs b/src/message.rs index 7825164..83b6bee 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,11 +1,36 @@ +//! Service definition +//! //! Traits to define the behaviour of messages for services use crate::Service; use std::fmt::Debug; -/// Defines interaction pattern, update type and return type for a RPC message +/// Declares the interaction pattern for a message and a service. /// /// For each server and each message, only one interaction pattern can be defined. pub trait Msg: Into + TryFrom + Send + 'static { + /// The interaction pattern for this message with this service. + type Pattern: InteractionPattern; +} + +/// Defines the response type for a rpc message. +/// +/// Since this is the most common interaction pattern, this also implements [Msg] for you +/// automatically, with the interaction pattern set to [Rpc]. This is to reduce boilerplate +/// when defining rpc messages. +pub trait RpcMsg: Msg { + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Response: Into + TryFrom + Send + 'static; +} + +/// We can only do this for one trait, so we do it for RpcMsg since it is the most common +impl, S: Service> Msg for T { + type Pattern = Rpc; +} + +/// Defines update type and response type for a client streaming message. +pub trait ClientStreamingMsg: Msg { /// The type for request updates /// /// For a request that does not support updates, this can be safely set to any type, including @@ -13,53 +38,70 @@ pub trait Msg: Into + TryFrom + Send + 'static { type Update: Into + TryFrom + Send + 'static; /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). type Response: Into + TryFrom + Send + 'static; - - /// The interaction pattern for this message with this service. - type Pattern: InteractionPattern; } -/// Shortcut to define just the return type for the very common RPC interaction pattern -pub trait RpcMsg: Into + TryFrom + Send + 'static { +/// Defines response type for a server streaming message. +pub trait ServerStreamingMsg: Msg { /// The type for the response /// - /// This is the only type that is required for the RPC interaction pattern. + /// For requests that can produce errors, this can be set to [Result](std::result::Result). type Response: Into + TryFrom + Send + 'static; } -impl> Msg for T { - type Update = Self; - - type Response = T::Response; +/// Defines update type and response type for a bidi streaming message. +pub trait BidiStreamingMsg: Msg { + /// The type for request updates + /// + /// For a request that does not support updates, this can be safely set to any type, including + /// the message type itself. Any update for such a request will result in an error. + type Update: Into + TryFrom + Send + 'static; - type Pattern = Rpc; + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Response: Into + TryFrom + Send + 'static; } /// Trait defining interaction pattern. /// /// Currently there are 4 patterns: -/// - `RPC`: 1 request, 1 response -/// - `ClientStreaming`: 1 request, stream of updates, 1 response -/// - `ServerStreaming`: 1 request, stream of responses -/// - `BidiStreaming`: 1 request, stream of updates, stream of responses +/// - [Rpc]: 1 request, 1 response +/// - [ClientStreaming]: 1 request, stream of updates, 1 response +/// - [ServerStreaming]: 1 request, stream of responses +/// - [BidiStreaming]: 1 request, stream of updates, stream of responses +/// +/// You could define your own interaction patterns such as OneWay. pub trait InteractionPattern: Debug + Clone + Send + Sync + 'static {} -/// RPC interaction pattern +/// Rpc interaction pattern +/// +/// There is only one request and one response. #[derive(Debug, Clone, Copy)] pub struct Rpc; impl InteractionPattern for Rpc {} /// Client streaming interaction pattern +/// +/// After the initial request, the client can send updates, but there is only +/// one response. #[derive(Debug, Clone, Copy)] pub struct ClientStreaming; impl InteractionPattern for ClientStreaming {} /// Server streaming interaction pattern +/// +/// After the initial request, the server can send a stream of responses. #[derive(Debug, Clone, Copy)] pub struct ServerStreaming; impl InteractionPattern for ServerStreaming {} /// Bidirectional streaming interaction pattern +/// +/// After the initial request, the client can send updates and the server can +/// send responses. #[derive(Debug, Clone, Copy)] pub struct BidiStreaming; impl InteractionPattern for BidiStreaming {} diff --git a/src/server.rs b/src/server.rs index 7bfe8ce..a55aaf7 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,9 +1,10 @@ -//! [RpcServer] and support types +//! Server side api //! -//! This defines the RPC server DSL +//! The main entry point is [RpcServer] use crate::{ - message::{BidiStreaming, ClientStreaming, Msg, Rpc, ServerStreaming}, - ChannelTypes, LocalAddr, ServerChannel, Service, + message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, + transport::ConnectionErrors, + Service, ServiceEndpoint, }; use futures::{channel::oneshot, task, task::Poll, Future, FutureExt, SinkExt, Stream, StreamExt}; use pin_project::pin_project; @@ -11,122 +12,85 @@ use std::{error, fmt, fmt::Debug, marker::PhantomData, pin::Pin, result}; /// A server channel for a specific service. /// -/// This is a wrapper around a [crate::ServerChannel] that serves as the entry point for the server DSL. +/// This is a wrapper around a [ServiceEndpoint](crate::ServiceEndpoint) that serves as the entry point for the server DSL. /// `S` is the service type, `C` is the channel type. #[derive(Debug)] -pub struct RpcServer { +pub struct RpcServer { /// The channel on which new requests arrive. /// /// Each new request is a receiver and channel pair on which messages for this request /// are received and responses sent. - channel: C::ServerChannel, + source: C, + p: PhantomData, } -impl Clone for RpcServer { +impl Clone for RpcServer { fn clone(&self) -> Self { Self { - channel: self.channel.clone(), + source: self.source.clone(), + p: PhantomData, } } } -impl RpcServer { - /// Create a new server channel from a channel and a service type. - pub fn new(channel: C::ServerChannel) -> Self { - Self { channel } - } - - /// The local addresses this server is bound to. +impl> RpcServer { + /// Create a new rpc server for a specific service for a [Service] given a compatible + /// [ServiceEndpoint]. /// - /// This is useful for publicly facing addresses when you start the server with a random - /// port, `:0` and let the kernel choose the real bind address. This will return the - /// address with the actual port used. - pub fn local_addr(&self) -> &[LocalAddr] { - self.channel.local_addr() + /// This is where a generic typed endpoint is converted into a server for a specific service. + pub fn new(source: C) -> Self { + Self { + source, + p: PhantomData, + } } } -impl RpcServer { - /// Accepts a connection, handling the first request. - /// - /// This accepts a new client connection, which is represented as a tuple of a sender - /// [`Sink`] and receiver [`Stream`] with `Item`s being the [`Service::Req`] and - /// [`Service::Res`] respectively. - /// - /// The return value is a tuple of `(request, (sink, stream))`. Here `request` is the - /// first request which is already read from the stream. The `sink` and `stream` are - /// used to send more requests and/or receive more responses. - /// - /// [`Sink`]: futures::sink::Sink - /// [`Stream`]: futures::stream::Stream - pub async fn accept_one( - &self, - ) -> result::Result<(S::Req, (C::SendSink, C::RecvStream)), RpcServerError> - where - C::RecvStream: Unpin, - { - let (sink, mut stream) = self - .channel - .accept_bi() - .await - .map_err(RpcServerError::AcceptBiError)?; - - // get the first message from the client. This will tell us what it wants to do. - let request: S::Req = stream - .next() - .await - // no msg => early close - .ok_or(RpcServerError::EarlyClose)? - // recv error - .map_err(RpcServerError::RecvError)?; - Ok((request, (sink, stream))) - } +/// A channel for requests and responses for a specific service. +/// +/// This just groups the sink and stream into a single type, and attaches the +/// information about the service type. +/// +/// Sink and stream are independent, so you can take the channel apart and use +/// them independently. +#[derive(Debug)] +pub struct RpcChannel> { + /// Sink to send responses to the client. + pub send: C::SendSink, + /// Stream to receive requests from the client. + pub recv: C::RecvStream, + /// Phantom data to make the type parameter `S` non-instantiable. + p: PhantomData, +} - /// A rpc call that also maps the error from the user type to the wire type - /// - /// This is useful if you want to write your function with a convenient error type like anyhow::Error, - /// yet still use a serializable error type on the wire. - pub async fn rpc_map_err( - &self, - req: M, - chan: (C::SendSink, C::RecvStream), - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: Msg>, - F: FnOnce(T, M) -> Fut, - Fut: Future>, - E2: From, - T: Send + 'static, - { - let fut = |target: T, msg: M| async move { - // call the inner fn - let res: Result = f(target, msg).await; - // convert the error type - let res: Result = res.map_err(E2::from); - res - }; - self.rpc(req, chan, target, fut).await +impl> RpcChannel { + /// Create a new channel from a sink and a stream. + pub fn new(send: C::SendSink, recv: C::RecvStream) -> Self { + Self { + send, + recv, + p: PhantomData, + } } - /// handle the message M using the given function on the target object + /// handle the message of type `M` using the given function on the target object /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. pub async fn rpc( - &self, + self, req: M, - chan: (C::SendSink, C::RecvStream), target: T, f: F, ) -> result::Result<(), RpcServerError> where - M: Msg, + M: RpcMsg, F: FnOnce(T, M) -> Fut, Fut: Future, T: Send + 'static, { - let (mut send, mut recv) = chan; + let Self { + mut send, mut recv, .. + } = self; // cancel if we get an update, no matter what it is let cancel = recv .next() @@ -147,19 +111,18 @@ impl RpcServer { /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. pub async fn client_streaming( - &self, + self, req: M, - c: (C::SendSink, C::RecvStream), target: T, f: F, ) -> result::Result<(), RpcServerError> where - M: Msg, - F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, + M: ClientStreamingMsg, + F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, Fut: Future + Send + 'static, T: Send + 'static, { - let (mut send, recv) = c; + let Self { mut send, recv, .. } = self; let (updates, read_error) = UpdateStream::new(recv); race2(read_error.map(Err), async move { // get the response @@ -176,19 +139,18 @@ impl RpcServer { /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. pub async fn bidi_streaming( - &self, + self, req: M, - c: (C::SendSink, C::RecvStream), target: T, f: F, ) -> result::Result<(), RpcServerError> where - M: Msg, - F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, + M: BidiStreamingMsg, + F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, { - let (mut send, recv) = c; + let Self { mut send, recv, .. } = self; // downcast the updates let (updates, read_error) = UpdateStream::new(recv); // get the response @@ -212,19 +174,20 @@ impl RpcServer { /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. pub async fn server_streaming( - &self, + self, req: M, - c: (C::SendSink, C::RecvStream), target: T, f: F, ) -> result::Result<(), RpcServerError> where - M: Msg, + M: ServerStreamingMsg, F: FnOnce(T, M) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, { - let (mut send, mut recv) = c; + let Self { + mut send, mut recv, .. + } = self; // cancel if we get an update, no matter what it is let cancel = recv .next() @@ -246,6 +209,62 @@ impl RpcServer { }) .await } + + /// A rpc call that also maps the error from the user type to the wire type + /// + /// This is useful if you want to write your function with a convenient error type like anyhow::Error, + /// yet still use a serializable error type on the wire. + pub async fn rpc_map_err( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: RpcMsg>, + F: FnOnce(T, M) -> Fut, + Fut: Future>, + E2: From, + T: Send + 'static, + { + let fut = |target: T, msg: M| async move { + // call the inner fn + let res: Result = f(target, msg).await; + // convert the error type + let res: Result = res.map_err(E2::from); + res + }; + self.rpc(req, target, fut).await + } +} + +impl> RpcServer { + /// Accepts a new channel from a client, and reads the first request. + /// + /// The return value is a tuple of `(request, channel)`. Here `request` is the + /// first request which is already read from the stream. The `channel` is a + /// [RpcChannel] that has `sink` and `stream` fields that can be used to send more + /// requests and/or receive more responses. + /// + /// Often sink and stream will wrap an an underlying byte stream. In this case you can + /// call into_inner() on them to get it back to perform byte level reads and writes. + pub async fn accept(&self) -> result::Result<(S::Req, RpcChannel), RpcServerError> { + let (send, mut recv) = self + .source + .accept_bi() + .await + .map_err(RpcServerError::Accept)?; + + // get the first message from the client. This will tell us what it wants to do. + let request: S::Req = recv + .next() + .await + // no msg => early close + .ok_or(RpcServerError::EarlyClose)? + // recv error + .map_err(RpcServerError::RecvError)?; + Ok((request, RpcChannel::new(send, recv))) + } } /// A stream of updates @@ -253,28 +272,32 @@ impl RpcServer { /// If there is any error with receiving or with decoding the updates, the stream will stall and the error will /// cause a termination of the RPC call. #[pin_project] -pub struct UpdateStream>( - #[pin] C::RecvStream, +#[derive(Debug)] +pub struct UpdateStream, T>( + #[pin] C::RecvStream, Option>>, - PhantomData, + PhantomData, ); -impl> UpdateStream { - fn new(recv: C::RecvStream) -> (Self, UnwrapToPending>) { +impl, T> UpdateStream { + fn new(recv: C::RecvStream) -> (Self, UnwrapToPending>) { let (error_send, error_recv) = oneshot::channel(); let error_recv = UnwrapToPending(error_recv); (Self(recv, Some(error_send), PhantomData), error_recv) } } -impl> Stream for UpdateStream { - type Item = M::Update; +impl, T> Stream for UpdateStream +where + T: TryFrom, +{ + type Item = T; fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { let mut this = self.project(); match this.0.poll_next_unpin(cx) { Poll::Ready(Some(msg)) => match msg { - Ok(msg) => match M::Update::try_from(msg) { + Ok(msg) => match T::try_from(msg) { Ok(msg) => Poll::Ready(Some(msg)), Err(_cause) => { // we were unable to downcast, so we need to send an error @@ -299,9 +322,9 @@ impl> Stream for UpdateStream { } /// Server error. All server DSL methods return a `Result` with this error type. -pub enum RpcServerError { +pub enum RpcServerError { /// Unable to open a new channel - AcceptBiError(C::AcceptBiError), + Accept(C::OpenError), /// Recv side for a channel was closed before getting the first message EarlyClose, /// Got an unexpected first message, e.g. an update message @@ -314,10 +337,10 @@ pub enum RpcServerError { UnexpectedUpdateMessage, } -impl fmt::Debug for RpcServerError { +impl fmt::Debug for RpcServerError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::AcceptBiError(arg0) => f.debug_tuple("AcceptBiError").field(arg0).finish(), + Self::Accept(arg0) => f.debug_tuple("Open").field(arg0).finish(), Self::EarlyClose => write!(f, "EarlyClose"), Self::RecvError(arg0) => f.debug_tuple("RecvError").field(arg0).finish(), Self::SendError(arg0) => f.debug_tuple("SendError").field(arg0).finish(), @@ -327,13 +350,13 @@ impl fmt::Debug for RpcServerError { } } -impl fmt::Display for RpcServerError { +impl fmt::Display for RpcServerError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fmt::Debug::fmt(&self, f) } } -impl error::Error for RpcServerError {} +impl error::Error for RpcServerError {} /// Take an oneshot receiver and just return Pending the underlying future returns `Err(oneshot::Canceled)` struct UnwrapToPending(oneshot::Receiver); @@ -362,24 +385,21 @@ async fn race2, B: Future>(f1: A, f2: B) -> /// Requests will be handled sequentially. pub async fn run_server_loop( _service_type: S, - _channel_type: C, - conn: C::ServerChannel, + conn: C, target: T, mut handler: F, ) -> Result<(), RpcServerError> where S: Service, - C: ChannelTypes, + C: ServiceEndpoint, T: Clone + Send + 'static, - F: FnMut(RpcServer, S::Req, (C::SendSink, C::RecvStream), T) -> Fut - + Send - + 'static, - Fut: Future, RpcServerError>> + Send + 'static, + F: FnMut(RpcChannel, S::Req, T) -> Fut + Send + 'static, + Fut: Future>> + Send + 'static, { - let mut server = RpcServer::::new(conn); + let server = RpcServer::::new(conn); loop { - let (req, chan) = server.accept_one().await?; + let (req, chan) = server.accept().await?; let target = target.clone(); - server = handler(server, req, chan, target).await?; + handler(chan, req, target).await?; } } diff --git a/src/transport/combined.rs b/src/transport/combined.rs index 2f2687b..df0cedb 100644 --- a/src/transport/combined.rs +++ b/src/transport/combined.rs @@ -1,5 +1,6 @@ -//! Channel that combines two other channels -use crate::{ChannelTypes as CT, LocalAddr, RpcMessage, ServerChannel as ServerChannelTrait}; +//! Transport that combines two other transports +use super::{Connection, ConnectionCommon, ConnectionErrors, LocalAddr, ServerEndpoint}; +use crate::RpcMessage; use futures::{ future::{self, BoxFuture}, FutureExt, Sink, Stream, TryFutureExt, @@ -14,102 +15,141 @@ use std::{ task::{Context, Poll}, }; -/// A channel that combines two other channels -pub struct CombinedClientChannel { - a: Option>, - b: Option>, +/// A connection that combines two other connections +pub struct CombinedConnection { + /// First connection + pub a: Option, + /// Second connection + pub b: Option, + /// Phantom data so we can have `In` and `Out` as type parameters + _p: PhantomData<(In, Out)>, } -impl CombinedClientChannel { - /// Create a combined channel from two other channels - /// - /// When opening a channel with [`crate::ClientChannel::open_bi`], the first configured channel will be used, - /// and no attempt will be made to use the second channel in case of failure. If no channels are - /// configred, open_bi will immediately fail with [`OpenBiError::NoChannel`]. +impl, B: Connection, In: RpcMessage, Out: RpcMessage> + CombinedConnection +{ + /// Create a combined connection from two other connections /// - /// When listening for incoming channels with [`crate::ServerChannel::accept_bi`], all configured channels will - /// be listened on, and the first to receive a connection will be used. If no channels are - /// configured, accept_bi will wait forever. - pub fn new(a: Option>, b: Option>) -> Self { - Self { a, b } + /// It will always use the first connection that is not `None`. + pub fn new(a: Option, b: Option) -> Self { + Self { + a, + b, + _p: PhantomData, + } } } -impl Clone for CombinedClientChannel { +impl Clone + for CombinedConnection +{ fn clone(&self) -> Self { Self { a: self.a.clone(), b: self.b.clone(), + _p: PhantomData, } } } -impl Debug for CombinedClientChannel { +impl Debug + for CombinedConnection +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Channel") + f.debug_struct("CombinedConnection") .field("a", &self.a) .field("b", &self.b) .finish() } } -/// A channel that combines two other channels -pub struct CombinedServerChannel { - a: Option>, - b: Option>, +/// An endpoint that combines two other endpoints +pub struct CombinedServerEndpoint { + /// First endpoint + pub a: Option, + /// Second endpoint + pub b: Option, + /// Local addresses from all endpoints local_addr: Vec, + /// Phantom data so we can have `In` and `Out` as type parameters + _p: PhantomData<(In, Out)>, } -impl CombinedServerChannel { - /// Create a combined channel from two other channels - /// - /// When opening a channel with [`crate::ClientChannel::open_bi`], the first configured channel will be used, - /// and no attempt will be made to use the second channel in case of failure. If no channels are - /// configred, open_bi will immediately fail with [`OpenBiError::NoChannel`]. +impl, B: ServerEndpoint, In: RpcMessage, Out: RpcMessage> + CombinedServerEndpoint +{ + /// Create a combined server endpoint from two other server endpoints /// - /// When listening for incoming channels with [`crate::ServerChannel::accept_bi`], all configured channels will - /// be listened on, and the first to receive a connection will be used. If no channels are - /// configured, accept_bi will wait forever. - pub fn new(a: Option>, b: Option>) -> Self { - let mut local_addr = Vec::new(); - if let Some(ref a) = a { - local_addr.extend_from_slice(a.local_addr()); - } - if let Some(ref b) = b { - local_addr.extend_from_slice(b.local_addr()); + /// When listening for incoming connections with + /// [crate::ServerEndpoint::accept_bi], all configured channels will be listened on, + /// and the first to receive a connection will be used. If no channels are configured, + /// accept_bi will not throw an error but wait forever. + pub fn new(a: Option, b: Option) -> Self { + let mut local_addr = Vec::with_capacity(2); + if let Some(a) = &a { + local_addr.extend(a.local_addr().iter().cloned()) + }; + if let Some(b) = &b { + local_addr.extend(b.local_addr().iter().cloned()) + }; + Self { + a, + b, + local_addr, + _p: PhantomData, } - Self { a, b, local_addr } + } + + /// Get back the inner endpoints + pub fn into_inner(self) -> (Option, Option) { + (self.a, self.b) } } -impl Clone for CombinedServerChannel { +impl Clone + for CombinedServerEndpoint +{ fn clone(&self) -> Self { Self { a: self.a.clone(), b: self.b.clone(), local_addr: self.local_addr.clone(), + _p: PhantomData, } } } -impl Debug for CombinedServerChannel { +impl Debug + for CombinedServerEndpoint +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Channel") + f.debug_struct("CombinedServerEndpoint") .field("a", &self.a) .field("b", &self.b) .finish() } } -/// SendSink for combined channels +/// Send sink for combined channels #[pin_project(project = SendSinkProj)] -pub enum SendSink { +pub enum SendSink< + A: ConnectionCommon, + B: ConnectionCommon, + In: RpcMessage, + Out: RpcMessage, +> { /// A variant - A(#[pin] A::SendSink), + A(#[pin] A::SendSink), /// B variant - B(#[pin] B::SendSink), + B(#[pin] B::SendSink), } -impl Sink for SendSink { +impl< + A: ConnectionCommon, + B: ConnectionCommon, + In: RpcMessage, + Out: RpcMessage, + > Sink for SendSink +{ type Error = self::SendError; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -143,14 +183,25 @@ impl Sink for SendSink { /// RecvStream for combined channels #[pin_project(project = ResStreamProj)] -pub enum RecvStream { +pub enum RecvStream< + A: ConnectionCommon, + B: ConnectionCommon, + In: RpcMessage, + Out: RpcMessage, +> { /// A variant - A(#[pin] A::RecvStream), + A(#[pin] A::RecvStream), /// B variant - B(#[pin] B::RecvStream), + B(#[pin] B::RecvStream), } -impl Stream for RecvStream { +impl< + A: ConnectionCommon, + B: ConnectionCommon, + In: RpcMessage, + Out: RpcMessage, + > Stream for RecvStream +{ type Item = Result>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -163,127 +214,113 @@ impl Stream for RecvStream { /// SendError for combined channels #[derive(Debug)] -pub enum SendError { +pub enum SendError { /// A variant A(A::SendError), /// B variant B(B::SendError), } -impl fmt::Display for SendError { +impl fmt::Display for SendError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for SendError {} +impl error::Error for SendError {} /// RecvError for combined channels #[derive(Debug)] -pub enum RecvError { +pub enum RecvError { /// A variant A(A::RecvError), /// B variant B(B::RecvError), } -impl fmt::Display for RecvError { +impl fmt::Display for RecvError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for RecvError {} +impl error::Error for RecvError {} /// OpenBiError for combined channels #[derive(Debug)] -pub enum OpenBiError { +pub enum OpenBiError { /// A variant - A(A::OpenBiError), + A(A::OpenError), /// B variant - B(B::OpenBiError), + B(B::OpenError), /// None of the two channels is configured NoChannel, } -impl fmt::Display for OpenBiError { +impl fmt::Display for OpenBiError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for OpenBiError {} +impl error::Error for OpenBiError {} /// AcceptBiError for combined channels #[derive(Debug)] -pub enum AcceptBiError { +pub enum AcceptBiError { /// A variant - A(A::AcceptBiError), + A(A::OpenError), /// B variant - B(B::AcceptBiError), + B(B::OpenError), } -impl fmt::Display for AcceptBiError { +impl fmt::Display for AcceptBiError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } -impl error::Error for AcceptBiError {} +impl error::Error for AcceptBiError {} /// Future returned by open_bi -pub type OpenBiFuture<'a, A, B, In, Out> = - BoxFuture<'a, result::Result, self::OpenBiError>>; +pub type OpenBiFuture = + BoxFuture<'static, result::Result, self::OpenBiError>>; /// Future returned by accept_bi -pub type AcceptBiFuture<'a, A, B, In, Out> = - BoxFuture<'a, result::Result, self::AcceptBiError>>; - -type Socket = (self::SendSink, self::RecvStream); - -/// Channel types for combined channels -/// -/// `A` and `B` are the channel types for the two channels. -/// `In` and `Out` are the message types for the two channels. -#[derive(Debug, Clone, Copy)] -pub struct CombinedChannelTypes(PhantomData<(A, B)>); +pub type AcceptBiFuture = + BoxFuture<'static, result::Result, self::AcceptBiError>>; -impl CT for CombinedChannelTypes { - type SendSink = self::SendSink; - - type RecvStream = self::RecvStream; +type Socket = ( + self::SendSink, + self::RecvStream, +); +impl ConnectionErrors + for CombinedConnection +{ type SendError = self::SendError; - type RecvError = self::RecvError; + type OpenError = self::OpenBiError; +} - type OpenBiError = self::OpenBiError; - - type OpenBiFuture<'a, In: RpcMessage, Out: RpcMessage> = self::OpenBiFuture<'a, A, B, In, Out>; - - type AcceptBiError = self::AcceptBiError; - - type AcceptBiFuture<'a, In: RpcMessage, Out: RpcMessage> = - self::AcceptBiFuture<'a, A, B, In, Out>; - - type ClientChannel = - self::CombinedClientChannel; - - type ServerChannel = - self::CombinedServerChannel; +impl, B: Connection, In: RpcMessage, Out: RpcMessage> + ConnectionCommon for CombinedConnection +{ + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; } -impl - crate::ClientChannel> - for CombinedClientChannel +impl, B: Connection, In: RpcMessage, Out: RpcMessage> + Connection for CombinedConnection { - fn open_bi(&self) -> OpenBiFuture<'_, A, B, In, Out> { + fn open_bi(&self) -> OpenBiFuture { + let this = self.clone(); async { // try a first, then b - if let Some(a) = &self.a { + if let Some(a) = this.a { let (send, recv) = a.open_bi().await.map_err(OpenBiError::A)?; Ok((SendSink::A(send), RecvStream::A(recv))) - } else if let Some(b) = &self.b { + } else if let Some(b) = this.b { let (send, recv) = b.open_bi().await.map_err(OpenBiError::B)?; Ok((SendSink::B(send), RecvStream::B(recv))) } else { @@ -292,23 +329,35 @@ impl } .boxed() } + + type OpenBiFut = OpenBiFuture; } -impl - crate::ServerChannel> - for CombinedServerChannel +impl ConnectionErrors + for CombinedServerEndpoint { - fn local_addr(&self) -> &[crate::LocalAddr] { - &self.local_addr - } + type SendError = self::SendError; + type RecvError = self::RecvError; + type OpenError = self::AcceptBiError; +} + +impl, B: ServerEndpoint, In: RpcMessage, Out: RpcMessage> + ConnectionCommon for CombinedServerEndpoint +{ + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; +} - fn accept_bi(&self) -> AcceptBiFuture<'_, A, B, In, Out> { +impl, B: ServerEndpoint, In: RpcMessage, Out: RpcMessage> + ServerEndpoint for CombinedServerEndpoint +{ + fn accept_bi(&self) -> AcceptBiFuture { let a_fut = if let Some(a) = &self.a { a.accept_bi() .map_ok(|(send, recv)| { ( - SendSink::::A(send), - RecvStream::::A(recv), + SendSink::::A(send), + RecvStream::::A(recv), ) }) .map_err(AcceptBiError::A) @@ -320,8 +369,8 @@ impl b.accept_bi() .map_ok(|(send, recv)| { ( - SendSink::::B(send), - RecvStream::::B(recv), + SendSink::::B(send), + RecvStream::::B(recv), ) }) .map_err(AcceptBiError::B) @@ -337,21 +386,29 @@ impl } .boxed() } + + type AcceptBiFut = AcceptBiFuture; + + fn local_addr(&self) -> &[LocalAddr] { + &self.local_addr + } } #[cfg(test)] mod tests { - use super::*; use crate::{ - transport::{combined, mem}, - ClientChannel, + transport::{ + combined::{self, OpenBiError}, + flume, + }, + Connection, }; #[tokio::test] async fn open_empty_channel() { - let channel = combined::CombinedClientChannel::< - mem::MemChannelTypes, - mem::MemChannelTypes, + let channel = combined::CombinedConnection::< + flume::FlumeConnection<(), ()>, + flume::FlumeConnection<(), ()>, (), (), >::new(None, None); diff --git a/src/transport/flume.rs b/src/transport/flume.rs new file mode 100644 index 0000000..9c86843 --- /dev/null +++ b/src/transport/flume.rs @@ -0,0 +1,341 @@ +//! Memory transport implementation using [flume] +//! +//! [flume]: https://docs.rs/flume/ +use crate::{ + transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}, + RpcMessage, +}; +use core::fmt; +use futures::{Future, FutureExt, Sink, SinkExt, Stream, StreamExt}; +use std::{error, fmt::Display, marker::PhantomData, pin::Pin, result, task::Poll}; + +use super::ConnectionCommon; + +/// Error when receiving from a channel +/// +/// This type has zero inhabitants, so it is always safe to unwrap a result with this error type. +#[derive(Debug)] +pub enum RecvError {} + +impl fmt::Display for RecvError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +/// Sink for memory channels +pub struct SendSink(flume::r#async::SendSink<'static, T>); + +impl fmt::Debug for SendSink { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SendSink").finish() + } +} + +impl Sink for SendSink { + type Error = self::SendError; + + fn poll_ready( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + self.0 + .poll_ready_unpin(cx) + .map_err(|_| SendError::ReceiverDropped) + } + + fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { + self.0 + .start_send_unpin(item) + .map_err(|_| SendError::ReceiverDropped) + } + + fn poll_flush( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + self.0 + .poll_flush_unpin(cx) + .map_err(|_| SendError::ReceiverDropped) + } + + fn poll_close( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + self.0 + .poll_close_unpin(cx) + .map_err(|_| SendError::ReceiverDropped) + } +} + +/// Stream for memory channels +pub struct RecvStream(flume::r#async::RecvStream<'static, T>); + +impl fmt::Debug for RecvStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RecvStream").finish() + } +} + +impl Stream for RecvStream { + type Item = result::Result; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + match self.0.poll_next_unpin(cx) { + Poll::Ready(Some(v)) => Poll::Ready(Some(Ok(v))), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } +} + +impl error::Error for RecvError {} + +/// A flume based server endpoint. +/// +/// Created using [connection]. +pub struct FlumeServerEndpoint { + stream: flume::Receiver<(SendSink, RecvStream)>, +} + +impl Clone for FlumeServerEndpoint { + fn clone(&self) -> Self { + Self { + stream: self.stream.clone(), + } + } +} + +impl fmt::Debug for FlumeServerEndpoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlumeServerEndpoint") + .field("stream", &self.stream) + .finish() + } +} + +impl ConnectionErrors for FlumeServerEndpoint { + type SendError = self::SendError; + + type RecvError = self::RecvError; + + type OpenError = self::AcceptBiError; +} + +type Socket = (self::SendSink, self::RecvStream); + +/// Future returned by [FlumeConnection::open_bi] +pub struct OpenBiFuture { + inner: flume::r#async::SendFut<'static, Socket>, + res: Option>, +} + +impl fmt::Debug for OpenBiFuture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OpenBiFuture").finish() + } +} + +impl OpenBiFuture { + fn new(inner: flume::r#async::SendFut<'static, Socket>, res: Socket) -> Self { + Self { + inner, + res: Some(res), + } + } +} + +impl Future for OpenBiFuture { + type Output = result::Result, self::OpenBiError>; + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + match self.inner.poll_unpin(cx) { + Poll::Ready(Ok(())) => self + .res + .take() + .map(|x| Poll::Ready(Ok(x))) + .unwrap_or(Poll::Pending), + Poll::Ready(Err(_)) => Poll::Ready(Err(self::OpenBiError::RemoteDropped)), + Poll::Pending => Poll::Pending, + } + } +} + +/// Future returned by [FlumeServerEndpoint::accept_bi] +pub struct AcceptBiFuture { + wrapped: flume::r#async::RecvFut<'static, (SendSink, RecvStream)>, + _p: PhantomData<(In, Out)>, +} + +impl fmt::Debug for AcceptBiFuture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AcceptBiFuture").finish() + } +} + +impl Future for AcceptBiFuture { + type Output = result::Result<(SendSink, RecvStream), AcceptBiError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + match self.wrapped.poll_unpin(cx) { + Poll::Ready(Ok((send, recv))) => Poll::Ready(Ok((send, recv))), + Poll::Ready(Err(_)) => Poll::Ready(Err(AcceptBiError::RemoteDropped)), + Poll::Pending => Poll::Pending, + } + } +} + +impl ConnectionCommon for FlumeServerEndpoint { + type SendSink = SendSink; + type RecvStream = RecvStream; +} + +impl ServerEndpoint for FlumeServerEndpoint { + type AcceptBiFut = AcceptBiFuture; + + fn accept_bi(&self) -> Self::AcceptBiFut { + AcceptBiFuture { + wrapped: self.stream.clone().into_recv_async(), + _p: PhantomData, + } + } + + fn local_addr(&self) -> &[LocalAddr] { + &[LocalAddr::Mem] + } +} + +impl ConnectionErrors for FlumeConnection { + type SendError = self::SendError; + + type RecvError = self::RecvError; + + type OpenError = self::OpenBiError; +} + +impl ConnectionCommon for FlumeConnection { + type SendSink = SendSink; + type RecvStream = RecvStream; +} + +impl Connection for FlumeConnection { + type OpenBiFut = OpenBiFuture; + + fn open_bi(&self) -> Self::OpenBiFut { + let (local_send, remote_recv) = flume::bounded::(128); + let (remote_send, local_recv) = flume::bounded::(128); + let remote_chan = ( + SendSink(remote_send.into_sink()), + RecvStream(remote_recv.into_stream()), + ); + let local_chan = ( + SendSink(local_send.into_sink()), + RecvStream(local_recv.into_stream()), + ); + OpenBiFuture::new(self.sink.clone().into_send_async(remote_chan), local_chan) + } +} + +/// A flume based connection to a server endpoint. +/// +/// Created using [connection]. +pub struct FlumeConnection { + sink: flume::Sender<(SendSink, RecvStream)>, +} + +impl Clone for FlumeConnection { + fn clone(&self) -> Self { + Self { + sink: self.sink.clone(), + } + } +} + +impl fmt::Debug for FlumeConnection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlumeClientChannel") + .field("sink", &self.sink) + .finish() + } +} + +/// AcceptBiError for mem channels. +/// +/// There is not much that can go wrong with mem channels. +#[derive(Debug)] +pub enum AcceptBiError { + /// The remote side of the channel was dropped + RemoteDropped, +} + +impl fmt::Display for AcceptBiError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for AcceptBiError {} + +/// SendError for mem channels. +/// +/// There is not much that can go wrong with mem channels. +#[derive(Debug)] +pub enum SendError { + /// Receiver was dropped + ReceiverDropped, +} + +impl Display for SendError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl std::error::Error for SendError {} + +/// OpenBiError for mem channels. +#[derive(Debug)] +pub enum OpenBiError { + /// The remote side of the channel was dropped + RemoteDropped, +} + +impl Display for OpenBiError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl std::error::Error for OpenBiError {} + +/// CreateChannelError for mem channels. +/// +/// You can always create a mem channel, so there is no possible error. +/// Nevertheless we need a type for it. +#[derive(Debug, Clone, Copy)] +pub enum CreateChannelError {} + +impl Display for CreateChannelError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl std::error::Error for CreateChannelError {} + +/// Create a flume server endpoint and a connected flume client channel. +/// +/// `buffer` the size of the buffer for each channel. Keep this at a low value to get backpressure +pub fn connection( + buffer: usize, +) -> (FlumeServerEndpoint, FlumeConnection) { + let (sink, stream) = flume::bounded(buffer); + (FlumeServerEndpoint { stream }, FlumeConnection { sink }) +} diff --git a/src/transport/http2.rs b/src/transport/hyper.rs similarity index 86% rename from src/transport/http2.rs rename to src/transport/hyper.rs index b080374..5b28024 100644 --- a/src/transport/http2.rs +++ b/src/transport/hyper.rs @@ -1,13 +1,13 @@ -//! http2 transport +//! http2 transport using [hyper] //! -//! Note that we are using the framing from http2, so we have to make sure that -//! the parameters on both client and server side are big enough. +//! [hyper]: https://crates.io/crates/hyper/ use std::{ convert::Infallible, error, fmt, io, marker::PhantomData, net::SocketAddr, pin::Pin, result, sync::Arc, task::Poll, }; -use crate::{ClientChannel, LocalAddr, RpcMessage, ServerChannel}; +use crate::transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}; +use crate::RpcMessage; use bytes::Bytes; use flume::{r#async::RecvFut, Receiver, Sender}; use futures::{future::FusedFuture, Future, FutureExt, Sink, SinkExt, StreamExt}; @@ -22,15 +22,17 @@ use tokio::sync::mpsc; use tokio::task::JoinHandle; use tracing::{debug, event, trace, Level}; -struct ClientChannelInner { +use super::ConnectionCommon; + +struct HyperConnectionInner { client: Box, config: Arc, uri: Uri, } -/// Client channel -pub struct Http2ClientChannel { - inner: Arc, +/// Hyper based connection to a server +pub struct HyperConnection { + inner: Arc, _p: PhantomData<(In, Out)>, } @@ -45,7 +47,7 @@ impl Requester for Client { } } -impl Http2ClientChannel { +impl HyperConnection { /// create a client given an uri and the default configuration pub fn new(uri: Uri) -> Self { Self::with_config(uri, ChannelConfig::default()) @@ -72,7 +74,7 @@ impl Http2ClientChannel { .http2_max_send_buf_size(config.max_frame_size.try_into().unwrap()) .build(connector); Self { - inner: Arc::new(ClientChannelInner { + inner: Arc::new(HyperConnectionInner { client: Box::new(client), uri, config, @@ -82,7 +84,7 @@ impl Http2ClientChannel { } } -impl fmt::Debug for Http2ClientChannel { +impl fmt::Debug for HyperConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ClientChannel") .field("uri", &self.inner.uri) @@ -91,7 +93,7 @@ impl fmt::Debug for Http2ClientChannel } } -impl Clone for Http2ClientChannel { +impl Clone for HyperConnection { fn clone(&self) -> Self { Self { inner: self.inner.clone(), @@ -168,7 +170,7 @@ impl Default for ChannelConfig { } } -/// A server-side channel using a hyper connection +/// A server endpoint using a hyper server /// /// Each request made by the any client connection this channel will yield a `(recv, send)` /// pair which allows receiving the request and sending the response. Both these are @@ -177,7 +179,7 @@ impl Default for ChannelConfig { /// Creating this spawns a tokio task which runs the server, once dropped this task is shut /// down: no new connections will be accepted and existing channels will stop. #[derive(Debug)] -pub struct Http2ServerChannel { +pub struct HyperServerEndpoint { /// The channel. channel: Receiver>, /// The configuration. @@ -196,7 +198,7 @@ pub struct Http2ServerChannel { _p: PhantomData<(In, Out)>, } -impl Http2ServerChannel { +impl HyperServerEndpoint { /// Creates a server listening on the [`SocketAddr`], with the default configuration. pub fn serve(addr: &SocketAddr) -> hyper::Result { Self::serve_with_config(addr, Default::default()) @@ -369,7 +371,7 @@ fn spawn_recv_forwarder( // This does not want or need RpcMessage to be clone but still want to clone the // ServerChannel and it's containing channels itself. The derive macro can't cope with this // so this needs to be written by hand. -impl Clone for Http2ServerChannel { +impl Clone for HyperServerEndpoint { fn clone(&self) -> Self { Self { channel: self.channel.clone(), @@ -381,9 +383,9 @@ impl Clone for Http2ServerChannel { } } -/// Receive stream for http2 channels. +/// Receive stream for hyper channels. /// -/// This is a newtype wrapper around a [`flume::r#async::RecvStream`] of deserialized +/// This is a newtype wrapper around a [`flume::async::RecvStream`] of deserialized /// messages. pub struct RecvStream { recv: flume::r#async::RecvStream<'static, result::Result>, @@ -396,6 +398,9 @@ impl RecvStream { recv: recv.into_stream(), } } + + // we can not write into_inner, since all we got is a stream of already + // framed and deserialize messages. Might want to change that... } impl Clone for RecvStream { @@ -417,7 +422,7 @@ impl futures::Stream for RecvStream { } } -/// SendSink for http2 channels +/// Send sink for hyper channels pub struct SendSink { sink: flume::r#async::SendSink<'static, io::Result>, config: Arc, @@ -444,6 +449,14 @@ impl SendSink { data[0..4].copy_from_slice(&len.to_be_bytes()); Ok(data.into()) } + + /// Consumes the [`SendSink`] and returns the underlying [`flume::async::SendSink`]. + /// + /// This is useful if you want to send raw [bytes::Bytes] without framing + /// directly to the channel. + pub fn into_inner(self) -> flume::r#async::SendSink<'static, io::Result> { + self.sink + } } impl Sink for SendSink { @@ -493,7 +506,7 @@ impl Sink for SendSink { } } -/// Send error for http2 channels. +/// Send error for hyper channels. #[derive(Debug)] pub enum SendError { /// Error when bincode serializing the message. @@ -512,7 +525,7 @@ impl fmt::Display for SendError { impl error::Error for SendError {} -/// Receive error for http2 channels. +/// Receive error for hyper channels. #[derive(Debug)] pub enum RecvError { /// Error when bincode deserializing the message. @@ -529,33 +542,7 @@ impl fmt::Display for RecvError { impl error::Error for RecvError {} -/// Http2 channel types -#[derive(Debug, Clone)] -pub struct Http2ChannelTypes; - -impl crate::ChannelTypes for Http2ChannelTypes { - type SendSink = self::SendSink; - - type RecvStream = self::RecvStream; - - type SendError = self::SendError; - - type RecvError = self::RecvError; - - type OpenBiError = self::OpenBiError; - - type OpenBiFuture<'a, In: RpcMessage, Out: RpcMessage> = self::OpenBiFuture<'a, In, Out>; - - type AcceptBiError = self::AcceptBiError; - - type AcceptBiFuture<'a, In: RpcMessage, Out: RpcMessage> = self::AcceptBiFuture<'a, In, Out>; - - type ClientChannel = self::Http2ClientChannel; - - type ServerChannel = self::Http2ServerChannel; -} - -/// OpenBiError for mem channels. +/// OpenBiError for hyper channels. #[derive(Debug)] pub enum OpenBiError { /// Hyper http error @@ -574,10 +561,10 @@ impl fmt::Display for OpenBiError { impl std::error::Error for OpenBiError {} -/// Future returned by `Channel::open_bi`. +/// Future returned by [open_bi](crate::transport::Connection::open_bi). #[allow(clippy::type_complexity)] #[pin_project] -pub struct OpenBiFuture<'a, In, Out> { +pub struct OpenBiFuture { chan: Option< Result< ( @@ -588,11 +575,11 @@ pub struct OpenBiFuture<'a, In, Out> { OpenBiError, >, >, - _p: PhantomData<&'a (In, Out)>, + _p: PhantomData<(In, Out)>, } #[allow(clippy::type_complexity)] -impl<'a, In: RpcMessage, Out: RpcMessage> OpenBiFuture<'a, In, Out> { +impl OpenBiFuture { fn new( value: Result< ( @@ -610,7 +597,7 @@ impl<'a, In: RpcMessage, Out: RpcMessage> OpenBiFuture<'a, In, Out> { } } -impl<'a, In: RpcMessage, Out: RpcMessage> Future for OpenBiFuture<'a, In, Out> { +impl Future for OpenBiFuture { type Output = result::Result, OpenBiError>; fn poll( @@ -651,9 +638,9 @@ impl<'a, In: RpcMessage, Out: RpcMessage> Future for OpenBiFuture<'a, In, Out> { } } -/// AcceptBiError for mem channels. +/// AcceptBiError for hyper channels. /// -/// There is not much that can go wrong with mem channels. +/// There is not much that can go wrong with hyper channels. #[derive(Debug)] pub enum AcceptBiError { /// Hyper error @@ -670,13 +657,13 @@ impl fmt::Display for AcceptBiError { impl error::Error for AcceptBiError {} -/// Future returned by `Channel::accept_bi`. +/// Future returned by [accept_bi](crate::transport::ServerEndpoint::accept_bi). #[allow(clippy::type_complexity)] #[pin_project] -pub struct AcceptBiFuture<'a, In, Out> { +pub struct AcceptBiFuture { chan: Option<( RecvFut< - 'a, + 'static, ( Receiver>, Sender>, @@ -687,11 +674,11 @@ pub struct AcceptBiFuture<'a, In, Out> { _p: PhantomData<(In, Out)>, } -impl<'a, In: RpcMessage, Out: RpcMessage> AcceptBiFuture<'a, In, Out> { +impl AcceptBiFuture { #[allow(clippy::type_complexity)] fn new( fut: RecvFut< - 'a, + 'static, ( Receiver>, Sender>, @@ -706,7 +693,7 @@ impl<'a, In: RpcMessage, Out: RpcMessage> AcceptBiFuture<'a, In, Out> { } } -impl<'a, In: RpcMessage, Out: RpcMessage> Future for AcceptBiFuture<'a, In, Out> { +impl Future for AcceptBiFuture { type Output = result::Result, AcceptBiError>; fn poll( @@ -738,7 +725,7 @@ impl<'a, In: RpcMessage, Out: RpcMessage> Future for AcceptBiFuture<'a, In, Out> } } -impl<'a, In, Out> FusedFuture for AcceptBiFuture<'a, In, Out> +impl FusedFuture for AcceptBiFuture where In: RpcMessage, Out: RpcMessage, @@ -751,12 +738,8 @@ where } } -impl ClientChannel for Http2ClientChannel -where - In: RpcMessage, - Out: RpcMessage, -{ - fn open_bi(&self) -> OpenBiFuture<'_, In, Out> { +impl HyperConnection { + fn open_bi(&self) -> OpenBiFuture { event!(Level::TRACE, "open_bi {}", self.inner.uri); let (out_tx, out_rx) = flume::bounded::>(32); let req: Result, OpenBiError> = Request::post(&self.inner.uri) @@ -773,18 +756,49 @@ where } } -impl ServerChannel - for Http2ServerChannel -{ - /// Accept a bi-directional stream from a client. - /// - /// The [`AcceptBiFuture`] returns a `(sender, receiver)` pair which can be used as - /// channels. - fn accept_bi(&self) -> AcceptBiFuture<'_, In, Out> { - AcceptBiFuture::new(self.channel.recv_async(), self.config.clone()) +impl ConnectionErrors for HyperConnection { + type SendError = self::SendError; + + type RecvError = self::RecvError; + + type OpenError = OpenBiError; +} + +impl ConnectionCommon for HyperConnection { + type RecvStream = self::RecvStream; + + type SendSink = self::SendSink; +} + +impl Connection for HyperConnection { + type OpenBiFut = OpenBiFuture; + + fn open_bi(&self) -> Self::OpenBiFut { + self.open_bi() } +} + +impl ConnectionErrors for HyperServerEndpoint { + type SendError = self::SendError; + + type RecvError = self::RecvError; + + type OpenError = AcceptBiError; +} + +impl ConnectionCommon for HyperServerEndpoint { + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; +} - fn local_addr(&self) -> &[crate::LocalAddr] { +impl ServerEndpoint for HyperServerEndpoint { + type AcceptBiFut = AcceptBiFuture; + + fn local_addr(&self) -> &[LocalAddr] { &self.local_addr } + + fn accept_bi(&self) -> Self::AcceptBiFut { + AcceptBiFuture::new(self.channel.clone().into_recv_async(), self.config.clone()) + } } diff --git a/src/transport/mem.rs b/src/transport/mem.rs deleted file mode 100644 index fb30480..0000000 --- a/src/transport/mem.rs +++ /dev/null @@ -1,317 +0,0 @@ -//! Memory channel implementation -//! -//! This is currently based on [flume], but since no flume types are exposed it can be changed to another -//! mpmc channel implementation, like [crossbeam]. -//! -//! [flume]: https://docs.rs/flume/ -//! [crossbeam]: https://docs.rs/crossbeam/ -use crate::{LocalAddr, RpcMessage}; -use core::fmt; -use futures::{Future, FutureExt, Sink, SinkExt, StreamExt}; -use pin_project::pin_project; -use std::{error, fmt::Display, pin::Pin, result, task::Poll}; - -/// Error when receiving from a channel -/// -/// This type has zero inhabitants, so it is always safe to unwrap a result with this error type. -#[derive(Debug)] -pub enum RecvError {} - -impl fmt::Display for RecvError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for RecvError {} - -/// RecvStream for mem channels -pub struct RecvStream(flume::r#async::RecvStream<'static, Res>); - -impl Clone for RecvStream { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl futures::Stream for RecvStream { - type Item = Result; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - match self.0.poll_next_unpin(cx) { - Poll::Ready(Some(item)) => Poll::Ready(Some(Ok(item))), - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } -} - -type Socket = (self::SendSink, self::RecvStream); - -/// A mem channel -pub struct MemServerChannel { - stream: flume::Receiver>, -} - -impl Clone for MemServerChannel { - fn clone(&self) -> Self { - Self { - stream: self.stream.clone(), - } - } -} - -impl fmt::Debug for MemServerChannel { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ServerChannel") - .field("stream", &self.stream) - .finish() - } -} -/// A mem channel -pub struct MemClientChannel { - sink: flume::Sender>, -} - -impl Clone for MemClientChannel { - fn clone(&self) -> Self { - Self { - sink: self.sink.clone(), - } - } -} - -impl fmt::Debug for MemClientChannel { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ClientChannel") - .field("sink", &self.sink) - .finish() - } -} - -/// AcceptBiError for mem channels. -/// -/// There is not much that can go wrong with mem channels. -#[derive(Debug)] -pub enum AcceptBiError { - /// The remote side of the channel was dropped - RemoteDropped, -} - -impl fmt::Display for AcceptBiError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for AcceptBiError {} - -/// Future returned by accept_bi -#[pin_project] -pub struct OpenBiFuture<'a, In: RpcMessage, Out: RpcMessage> { - #[pin] - inner: flume::r#async::SendFut<'a, Socket>, - res: Option>, -} - -impl<'a, In: RpcMessage, Out: RpcMessage> OpenBiFuture<'a, In, Out> { - fn new(inner: flume::r#async::SendFut<'a, Socket>, res: Socket) -> Self { - Self { - inner, - res: Some(res), - } - } -} - -impl<'a, In: RpcMessage, Out: RpcMessage> Future for OpenBiFuture<'a, In, Out> { - type Output = result::Result, self::OpenBiError>; - - fn poll( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - let mut this = self.project(); - match this.inner.poll_unpin(cx) { - Poll::Ready(Ok(())) => this - .res - .take() - .map(|x| Poll::Ready(Ok(x))) - .unwrap_or(Poll::Pending), - Poll::Ready(Err(_)) => Poll::Ready(Err(self::OpenBiError::RemoteDropped)), - Poll::Pending => Poll::Pending, - } - } -} - -/// Future returned by accept_bi -pub struct AcceptBiFuture<'a, In: RpcMessage, Out: RpcMessage>( - flume::r#async::RecvFut<'a, Socket>, -); - -impl<'a, In: RpcMessage, Out: RpcMessage> Future for AcceptBiFuture<'a, In, Out> { - type Output = result::Result, AcceptBiError>; - - fn poll( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - match self.0.poll_unpin(cx) { - Poll::Ready(Ok(socket)) => Poll::Ready(Ok(socket)), - Poll::Ready(Err(_)) => Poll::Ready(Err(AcceptBiError::RemoteDropped)), - Poll::Pending => Poll::Pending, - } - } -} - -/// SendSink for mem channels -pub struct SendSink(flume::r#async::SendSink<'static, Out>); - -impl Sink for SendSink { - type Error = SendError; - - fn poll_ready( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - self.0 - .poll_ready_unpin(cx) - .map_err(|_| SendError::ReceiverDropped) - } - - fn start_send(mut self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { - self.0 - .start_send_unpin(item) - .map_err(|_| SendError::ReceiverDropped) - } - - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - self.0 - .poll_flush_unpin(cx) - .map_err(|_| SendError::ReceiverDropped) - } - - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - self.0 - .poll_close_unpin(cx) - .map_err(|_| SendError::ReceiverDropped) - } -} - -/// SendError for mem channels. -/// -/// There is not much that can go wrong with mem channels. -#[derive(Debug)] -pub enum SendError { - /// Receiver was dropped - ReceiverDropped, -} - -impl Display for SendError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl std::error::Error for SendError {} - -/// OpenBiError for mem channels. -#[derive(Debug)] -pub enum OpenBiError { - /// The remote side of the channel was dropped - RemoteDropped, -} - -impl Display for OpenBiError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl std::error::Error for OpenBiError {} - -/// CreateChannelError for mem channels. -/// -/// You can always create a mem channel, so there is no possible error. -/// Nevertheless we need a type for it. -#[derive(Debug, Clone, Copy)] -pub enum CreateChannelError {} - -impl Display for CreateChannelError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl std::error::Error for CreateChannelError {} - -/// Types for mem channels. -#[derive(Debug, Clone, Copy)] -pub struct MemChannelTypes; - -impl crate::ChannelTypes for MemChannelTypes { - type SendSink = self::SendSink; - - type RecvStream = self::RecvStream; - - type SendError = self::SendError; - - type RecvError = self::RecvError; - - type OpenBiError = self::OpenBiError; - - type OpenBiFuture<'a, In: RpcMessage, Out: RpcMessage> = self::OpenBiFuture<'a, In, Out>; - - type AcceptBiError = self::AcceptBiError; - - type AcceptBiFuture<'a, In: RpcMessage, Out: RpcMessage> = self::AcceptBiFuture<'a, In, Out>; - - type ClientChannel = self::MemClientChannel; - - type ServerChannel = self::MemServerChannel; -} - -impl crate::ClientChannel - for MemClientChannel -{ - fn open_bi(&self) -> OpenBiFuture<'_, In, Out> { - let (local_send, remote_recv) = flume::bounded::(128); - let (remote_send, local_recv) = flume::bounded::(128); - let remote_recv = RecvStream(remote_recv.into_stream()); - let local_recv = RecvStream(local_recv.into_stream()); - let remote_send = SendSink(remote_send.into_sink()); - let local_send = SendSink(local_send.into_sink()); - let inner = self.sink.send_async((remote_send, remote_recv)); - OpenBiFuture::new(inner, (local_send, local_recv)) - } -} - -impl crate::ServerChannel - for MemServerChannel -{ - fn accept_bi(&self) -> AcceptBiFuture<'_, In, Out> { - AcceptBiFuture(self.stream.recv_async()) - } - - fn local_addr(&self) -> &[crate::LocalAddr] { - &[LocalAddr::Mem] - } -} - -/// Create a channel pair (server, client) for mem channels -/// -/// `buffer` the size of the buffer for each channel. Keep this at a low value to get backpressure -pub fn connection( - buffer: usize, -) -> (MemServerChannel, MemClientChannel) { - let (sink, stream) = flume::bounded::>(buffer); - (MemServerChannel { stream }, MemClientChannel { sink }) -} diff --git a/src/transport/misc/mod.rs b/src/transport/misc/mod.rs new file mode 100644 index 0000000..f48e531 --- /dev/null +++ b/src/transport/misc/mod.rs @@ -0,0 +1,40 @@ +//! Miscellaneous transport utilities + +use crate::{ + transport::{ConnectionErrors, ServerEndpoint}, + RpcMessage, +}; +use futures::{future, stream, Sink}; +use std::convert::Infallible; + +use super::ConnectionCommon; + +/// A dummy server endpoint that does nothing +/// +/// This can be useful as a default if you want to configure +/// an optional server endpoint. +#[derive(Debug, Clone, Default)] +pub struct DummyServerEndpoint; + +impl ConnectionErrors for DummyServerEndpoint { + type OpenError = Infallible; + type RecvError = Infallible; + type SendError = Infallible; +} + +impl ConnectionCommon for DummyServerEndpoint { + type RecvStream = stream::Pending>; + type SendSink = Box + Unpin + Send>; +} + +impl ServerEndpoint for DummyServerEndpoint { + type AcceptBiFut = future::Pending>; + + fn accept_bi(&self) -> Self::AcceptBiFut { + futures::future::pending() + } + + fn local_addr(&self) -> &[super::LocalAddr] { + &[] + } +} diff --git a/src/transport/mod.rs b/src/transport/mod.rs index eacb9a9..6133fda 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,26 +1,93 @@ //! Transports for quic-rpc -//! -//! mem and combined are enabled by default. quic and http2 are enabled by feature flags. +use crate::RpcError; +use futures::{Future, Sink, Stream}; +use std::{ + fmt::{self, Debug, Display}, + net::SocketAddr, +}; +#[cfg(feature = "combined-transport")] pub mod combined; -#[cfg(feature = "http2")] -pub mod http2; -pub mod mem; -#[cfg(feature = "quic")] +#[cfg(feature = "flume-transport")] +pub mod flume; +#[cfg(feature = "hyper-transport")] +pub mod hyper; +#[cfg(feature = "quinn-transport")] pub mod quinn; - -#[cfg(feature = "quic")] +#[cfg(feature = "s2n-quic-transport")] pub mod s2n_quic; -#[cfg(feature = "quic")] -/// Alias for quinn channel types -pub use self::quinn::QuinnChannelTypes; -#[cfg(feature = "quic")] -/// Alias for s2n_quic channel types -pub use self::s2n_quic::ChannelTypes as S2nQuicChannelTypes; -/// Alias for combined channel types -pub use combined::CombinedChannelTypes; -#[cfg(feature = "http2")] -/// Alias for http2 channel types -pub use http2::Http2ChannelTypes; -/// Alias for mem channel types -pub use mem::MemChannelTypes; +pub mod misc; + +#[cfg(any(feature = "quinn-transport", feature = "hyper-transport"))] +mod util; + +/// Errors that can happen when creating and using a [`Connection`] or [`ServerEndpoint`]. +pub trait ConnectionErrors: Debug + Clone + Send + Sync + 'static { + /// Error when opening or accepting a channel + type OpenError: RpcError; + /// Error when sending a message via a channel + type SendError: RpcError; + /// Error when receiving a message via a channel + type RecvError: RpcError; +} + +/// Types that are common to both [`Connection`] and [`ServerEndpoint`]. +/// +/// Having this as a separate trait is useful when writing generic code that works with both. +pub trait ConnectionCommon: ConnectionErrors { + /// Receive side of a bidirectional typed channel + type RecvStream: Stream> + Send + Unpin + 'static; + /// Send side of a bidirectional typed channel + type SendSink: Sink + Send + Unpin + 'static; +} + +/// A connection to a specific remote machine +/// +/// A connection can be used to open bidirectional typed channels using [`Connection::open_bi`]. +pub trait Connection: ConnectionCommon { + /// The future that will resolve to a substream or an error + type OpenBiFut: Future> + + Send; + /// Open a channel to the remote + fn open_bi(&self) -> Self::OpenBiFut; +} + +/// A server endpoint that listens for connections +/// +/// A server endpoint can be used to accept bidirectional typed channels from any of the +/// currently opened connections to clients, using [`ServerEndpoint::accept_bi`]. +pub trait ServerEndpoint: ConnectionCommon { + /// The future that will resolve to a substream or an error + type AcceptBiFut: Future> + + Send; + + /// Accept a new typed bidirectional channel on any of the connections we + /// have currently opened. + fn accept_bi(&self) -> Self::AcceptBiFut; + + /// The local addresses this endpoint is bound to. + fn local_addr(&self) -> &[LocalAddr]; +} + +/// The kinds of local addresses a [ServerEndpoint] can be bound to. +/// +/// Returned by [ServerEndpoint::local_addr]. +/// +/// [`Display`]: fmt::Display +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum LocalAddr { + /// A local socket. + Socket(SocketAddr), + /// An in-memory address. + Mem, +} + +impl Display for LocalAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + LocalAddr::Socket(sockaddr) => write!(f, "{sockaddr}"), + LocalAddr::Mem => write!(f, "mem"), + } + } +} diff --git a/src/transport/quinn.rs b/src/transport/quinn.rs index 586593b..a439850 100644 --- a/src/transport/quinn.rs +++ b/src/transport/quinn.rs @@ -1,91 +1,446 @@ -//! QUIC channel implementation based on quinn -use crate::{LocalAddr, RpcMessage}; +//! QUIC transport implementation based on [quinn](https://crates.io/crates/quinn) +use crate::{ + transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}, + RpcMessage, +}; +use futures::channel::oneshot; use futures::{Future, FutureExt, Sink, SinkExt, Stream, StreamExt}; use pin_project::pin_project; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; +use serde::Serialize; use std::net::SocketAddr; +use std::sync::Arc; +use std::task::{Context, Poll}; use std::{fmt, io, marker::PhantomData, pin::Pin, result}; -use tokio_serde::{formats::SymmetricalBincode, SymmetricallyFramed}; -use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec}; + +use super::{ + util::{FramedBincodeRead, FramedBincodeWrite}, + ConnectionCommon, +}; type Socket = (SendSink, RecvStream); -/// A server channel using a quinn connection +const MAX_FRAME_LENGTH: usize = 1024 * 1024 * 16; + #[derive(Debug)] -pub struct QuinnServerChannel { - connection: quinn::Connection, +struct ServerChannelInner { + endpoint: Option, + task: Option>, local_addr: [LocalAddr; 1], + receiver: flume::Receiver, +} + +impl Drop for ServerChannelInner { + fn drop(&mut self) { + if let Some(endpoint) = self.endpoint.take() { + endpoint.close(0u32.into(), b"server channel dropped"); + } + if let Some(task) = self.task.take() { + task.abort() + } + } +} + +/// A server endpoint using a quinn connection +#[derive(Debug)] +pub struct QuinnServerEndpoint { + inner: Arc, _phantom: PhantomData<(In, Out)>, } -impl QuinnServerChannel { - /// Create a new channel - pub fn new(conn: quinn::Connection, local_addr: SocketAddr) -> Self { +impl QuinnServerEndpoint { + /// handles RPC requests from a connection + /// + /// to cleanly shutdown the handler, drop the receiver side of the sender. + async fn connection_handler(connection: quinn::Connection, sender: flume::Sender) { + loop { + tracing::debug!("Awaiting incoming bidi substream on existing connection..."); + let bidi_stream = match connection.accept_bi().await { + Ok(bidi_stream) => bidi_stream, + Err(quinn::ConnectionError::ApplicationClosed(e)) => { + tracing::debug!("Peer closed the connection {:?}", e); + break; + } + Err(e) => { + tracing::debug!("Error opening stream: {}", e); + break; + } + }; + tracing::debug!("Sending substream to be handled... {}", bidi_stream.0.id()); + if sender.send_async(bidi_stream).await.is_err() { + tracing::debug!("Receiver dropped"); + break; + } + } + } + + async fn endpoint_handler(endpoint: quinn::Endpoint, sender: flume::Sender) { + loop { + tracing::debug!("Waiting for incoming connection..."); + let connecting = match endpoint.accept().await { + Some(connecting) => connecting, + None => break, + }; + tracing::debug!("Awaiting connection from connect..."); + let conection = match connecting.await { + Ok(conection) => conection, + Err(e) => { + tracing::warn!("Error accepting connection: {}", e); + continue; + } + }; + tracing::debug!( + "Connection established from {:?}", + conection.remote_address() + ); + tracing::debug!("Spawning connection handler..."); + tokio::spawn(Self::connection_handler(conection, sender.clone())); + } + } + + /// Create a new server channel, given a quinn endpoint. + /// + /// The endpoint must be a server endpoint. + /// + /// The server channel will take care of listening on the endpoint and spawning + /// handlers for new connections. + pub fn new(endpoint: quinn::Endpoint) -> io::Result { + let local_addr = endpoint.local_addr()?; + let (sender, receiver) = flume::bounded(16); + let task = tokio::spawn(Self::endpoint_handler(endpoint.clone(), sender)); + Ok(Self { + inner: Arc::new(ServerChannelInner { + endpoint: Some(endpoint), + task: Some(task), + local_addr: [LocalAddr::Socket(local_addr)], + receiver, + }), + _phantom: PhantomData, + }) + } + + /// Create a new server channel, given just a source of incoming connections + /// + /// This is useful if you want to manage the quinn endpoint yourself, + /// use multiple endpoints, or use an endpoint for multiple protocols. + pub fn handle_connections( + incoming: flume::Receiver, + local_addr: SocketAddr, + ) -> Self { + let (sender, receiver) = flume::bounded(16); + let task = tokio::spawn(async move { + // just grab all connections and spawn a handler for each one + while let Ok(connection) = incoming.recv_async().await { + tokio::spawn(Self::connection_handler(connection, sender.clone())); + } + }); Self { - connection: conn, - local_addr: [LocalAddr::Socket(local_addr)], + inner: Arc::new(ServerChannelInner { + endpoint: None, + task: Some(task), + local_addr: [LocalAddr::Socket(local_addr)], + receiver, + }), _phantom: PhantomData, } } -} -// impl fmt::Debug for ServerChannel { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// f.debug_tuple("ServerChannel") -// .field(&self.0) -// .field(&self.1) -// .finish() -// } -// } + /// Create a new server channel, given just a source of incoming substreams + /// + /// This is useful if you want to manage the quinn endpoint yourself, + /// use multiple endpoints, or use an endpoint for multiple protocols. + pub fn handle_substreams( + receiver: flume::Receiver, + local_addr: SocketAddr, + ) -> Self { + Self { + inner: Arc::new(ServerChannelInner { + endpoint: None, + task: None, + local_addr: [LocalAddr::Socket(local_addr)], + receiver, + }), + _phantom: PhantomData, + } + } +} -impl Clone for QuinnServerChannel { +impl Clone for QuinnServerEndpoint { fn clone(&self) -> Self { Self { - connection: self.connection.clone(), - local_addr: self.local_addr.clone(), + inner: self.inner.clone(), _phantom: PhantomData, } } } -/// A server channel using a quinn connection -pub struct QuinnClientChannel( - quinn::Connection, - PhantomData<(In, Out)>, -); +impl ConnectionErrors for QuinnServerEndpoint { + type SendError = io::Error; + + type RecvError = io::Error; + + type OpenError = quinn::ConnectionError; +} + +impl ConnectionCommon for QuinnServerEndpoint { + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; +} + +impl ServerEndpoint for QuinnServerEndpoint { + type AcceptBiFut = AcceptBiFuture; + + fn accept_bi(&self) -> Self::AcceptBiFut { + AcceptBiFuture(self.inner.receiver.clone().into_recv_async(), PhantomData) + } + + fn local_addr(&self) -> &[LocalAddr] { + &self.inner.local_addr + } +} + +type SocketInner = (quinn::SendStream, quinn::RecvStream); + +#[derive(Debug)] +struct ClientChannelInner { + /// The quinn endpoint, we just keep a clone of this for information + endpoint: Option, + /// The task that handles creating new connections + task: Option>, + /// The channel to receive new connections + sender: flume::Sender>>, +} + +impl Drop for ClientChannelInner { + fn drop(&mut self) { + tracing::debug!("Dropping client channel"); + if let Some(endpoint) = self.endpoint.take() { + endpoint.close(0u32.into(), b"client channel dropped"); + } + // this should not be necessary, since the task would terminate when the receiver is dropped. + // but just to be on the safe side. + if let Some(task) = self.task.take() { + task.abort(); + } + } +} + +/// A connection using a quinn connection +pub struct QuinnConnection { + inner: Arc, + _phantom: PhantomData<(In, Out)>, +} + +impl QuinnConnection { + async fn single_connection_handler_inner( + connection: quinn::Connection, + requests: flume::Receiver>>, + ) -> result::Result<(), flume::RecvError> { + loop { + tracing::debug!("Awaiting request for new bidi substream..."); + let request = requests.recv_async().await?; + tracing::debug!("Got request for new bidi substream"); + match connection.open_bi().await { + Ok(pair) => { + tracing::debug!("Bidi substream opened"); + if request.send(Ok(pair)).is_err() { + tracing::debug!("requester dropped"); + } + } + Err(e) => { + tracing::warn!("error opening bidi substream: {}", e); + if request.send(Err(e)).is_err() { + tracing::debug!("requester dropped"); + } + } + } + } + } + + async fn single_connection_handler( + connection: quinn::Connection, + requests: flume::Receiver>>, + ) { + if Self::single_connection_handler_inner(connection, requests) + .await + .is_err() + { + tracing::info!("Single connection handler finished"); + } else { + unreachable!() + } + } + + /// Client connection handler. + /// + /// It will run until the send side of the channel is dropped. + /// All other errors are logged and handled internally. + /// It will try to keep a connection open at all times. + async fn reconnect_handler_inner( + endpoint: quinn::Endpoint, + addr: SocketAddr, + name: String, + requests: flume::Receiver>>, + ) -> result::Result<(), flume::RecvError> { + 'outer: loop { + tracing::debug!("Connecting to {} as {}", addr, name); + let connecting = match endpoint.connect(addr, &name) { + Ok(connecting) => connecting, + Err(e) => { + tracing::warn!("error calling connect: {}", e); + // try again. Maybe delay? + continue; + } + }; + let connection = match connecting.await { + Ok(connection) => connection, + Err(e) => { + tracing::warn!("error awaiting connect: {}", e); + // try again. Maybe delay? + continue; + } + }; + loop { + tracing::debug!("Awaiting request for new bidi substream..."); + let request = requests.recv_async().await?; + tracing::debug!("Got request for new bidi substream"); + match connection.open_bi().await { + Ok(pair) => { + tracing::debug!("Bidi substream opened"); + if request.send(Ok(pair)).is_err() { + tracing::debug!("requester dropped"); + } + } + Err(e) => { + tracing::warn!("error opening bidi substream: {}", e); + tracing::warn!("recreating connection"); + // try again. Maybe delay? + continue 'outer; + } + } + } + } + } + + async fn reconnect_handler( + endpoint: quinn::Endpoint, + addr: SocketAddr, + name: String, + requests: flume::Receiver>>, + ) { + if Self::reconnect_handler_inner(endpoint, addr, name, requests) + .await + .is_err() + { + tracing::info!("Reconnect handler finished"); + } else { + unreachable!() + } + } + + /// Create a new channel + pub fn from_connection(connection: quinn::Connection) -> Self { + let (sender, receiver) = flume::bounded(16); + let task = tokio::spawn(Self::single_connection_handler(connection, receiver)); + Self { + inner: Arc::new(ClientChannelInner { + endpoint: None, + task: Some(task), + sender, + }), + _phantom: PhantomData, + } + } -impl QuinnClientChannel { /// Create a new channel - pub fn new(conn: quinn::Connection) -> Self { - Self(conn, PhantomData) + pub fn new(endpoint: quinn::Endpoint, addr: SocketAddr, name: String) -> Self { + let (sender, receiver) = flume::bounded(16); + let task = tokio::spawn(Self::reconnect_handler( + endpoint.clone(), + addr, + name, + receiver, + )); + Self { + inner: Arc::new(ClientChannelInner { + endpoint: Some(endpoint), + task: Some(task), + sender, + }), + _phantom: PhantomData, + } } } -impl fmt::Debug for QuinnClientChannel { +impl fmt::Debug for QuinnConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("ClientChannel") - .field(&self.0) - .field(&self.1) + f.debug_struct("ClientChannel") + .field("inner", &self.inner) .finish() } } -impl Clone for QuinnClientChannel { +impl Clone for QuinnConnection { fn clone(&self) -> Self { - Self(self.0.clone(), PhantomData) + Self { + inner: self.inner.clone(), + _phantom: PhantomData, + } + } +} + +impl ConnectionErrors for QuinnConnection { + type SendError = io::Error; + + type RecvError = io::Error; + + type OpenError = quinn::ConnectionError; +} + +impl ConnectionCommon for QuinnConnection { + type SendSink = self::SendSink; + type RecvStream = self::RecvStream; +} + +impl Connection for QuinnConnection { + type OpenBiFut = OpenBiFuture; + + fn open_bi(&self) -> Self::OpenBiFut { + let (sender, receiver) = oneshot::channel(); + OpenBiFuture( + OpenBiFutureState::Sending(self.inner.sender.clone().into_send_async(sender), receiver), + PhantomData, + ) } } /// A sink that wraps a quinn SendStream with length delimiting and bincode +/// +/// If you want to send bytes directly, use [SendSink::into_inner] to get the +/// underlying [quinn::SendStream]. #[pin_project] -pub struct SendSink( - #[pin] - tokio_serde::SymmetricallyFramed< - FramedWrite<::quinn::SendStream, LengthDelimitedCodec>, - Out, - SymmetricalBincode, - >, -); +pub struct SendSink(#[pin] FramedBincodeWrite); + +impl fmt::Debug for SendSink { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SendSink").finish() + } +} + +impl SendSink { + fn new(inner: quinn::SendStream) -> Self { + let inner = FramedBincodeWrite::new(inner, MAX_FRAME_LENGTH); + Self(inner) + } +} + +impl SendSink { + /// Get the underlying [quinn::SendStream], which implements + /// [tokio::io::AsyncWrite] and can be used to send bytes directly. + pub fn into_inner(self) -> quinn::SendStream { + self.0.into_inner() + } +} impl Sink for SendSink { type Error = io::Error; @@ -117,15 +472,32 @@ impl Sink for SendSink { } /// A stream that wraps a quinn RecvStream with length delimiting and bincode +/// +/// If you want to receive bytes directly, use [RecvStream::into_inner] to get +/// the underlying [quinn::RecvStream]. #[pin_project] -pub struct RecvStream( - #[pin] - tokio_serde::SymmetricallyFramed< - FramedRead<::quinn::RecvStream, LengthDelimitedCodec>, - In, - SymmetricalBincode, - >, -); +pub struct RecvStream(#[pin] FramedBincodeRead); + +impl fmt::Debug for RecvStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RecvStream").finish() + } +} + +impl RecvStream { + fn new(inner: quinn::RecvStream) -> Self { + let inner = FramedBincodeRead::new(inner, MAX_FRAME_LENGTH); + Self(inner) + } +} + +impl RecvStream { + /// Get the underlying [quinn::RecvStream], which implements + /// [tokio::io::AsyncRead] and can be used to receive bytes directly. + pub fn into_inner(self) -> quinn::RecvStream { + self.0.into_inner() + } +} impl Stream for RecvStream { type Item = result::Result; @@ -144,44 +516,87 @@ pub type OpenBiError = quinn::ConnectionError; /// Error for accept_bi. Currently just a quinn::ConnectionError pub type AcceptBiError = quinn::ConnectionError; -/// Types for quinn channels. -/// -/// This exposes the types from quinn directly without attempting to wrap them. -#[derive(Debug, Clone, Copy)] -pub struct QuinnChannelTypes; +enum OpenBiFutureState { + /// Sending the oneshot sender to the server + Sending( + flume::r#async::SendFut< + 'static, + oneshot::Sender>, + >, + oneshot::Receiver>, + ), + /// Receiving the channel from the server + Receiving( + oneshot::Receiver>, + ), + /// Taken or done + Taken, +} + +impl OpenBiFutureState { + fn take(&mut self) -> Self { + std::mem::replace(self, Self::Taken) + } +} /// Future returned by open_bi #[pin_project] -pub struct OpenBiFuture<'a, In, Out>(#[pin] quinn::OpenBi<'a>, PhantomData<(In, Out)>); +pub struct OpenBiFuture(OpenBiFutureState, PhantomData<(In, Out)>); + +impl fmt::Debug for OpenBiFuture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OpenBiFuture").finish() + } +} -impl<'a, In, Out> Future for OpenBiFuture<'a, In, Out> { +impl Future for OpenBiFuture { type Output = result::Result, self::OpenBiError>; - fn poll( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - self.project().0.poll_unpin(cx).map(|conn| { - let (send, recv) = conn?; - // turn chunks of bytes into a stream of messages using length delimited codec - let send = FramedWrite::new(send, LengthDelimitedCodec::new()); - let recv = FramedRead::new(recv, LengthDelimitedCodec::new()); - // now switch to streams of WantRequestUpdate and WantResponse - let recv = SymmetricallyFramed::new(recv, SymmetricalBincode::::default()); - let send = SymmetricallyFramed::new(send, SymmetricalBincode::::default()); - // box so we don't have to write down the insanely long type - let send = SendSink(send); - let recv = RecvStream(recv); - Ok((send, recv)) - }) + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.0.take() { + OpenBiFutureState::Sending(mut fut, recever) => match fut.poll_unpin(cx) { + Poll::Ready(Ok(_)) => { + self.0 = OpenBiFutureState::Receiving(recever); + self.poll(cx) + } + Poll::Pending => { + self.0 = OpenBiFutureState::Sending(fut, recever); + Poll::Pending + } + Poll::Ready(Err(_)) => Poll::Ready(Err(quinn::ConnectionError::LocallyClosed)), + }, + OpenBiFutureState::Receiving(mut fut) => match fut.poll_unpin(cx) { + Poll::Ready(Ok(Ok((send, recv)))) => { + let send = SendSink::new(send); + let recv = RecvStream::new(recv); + Poll::Ready(Ok((send, recv))) + } + Poll::Ready(Ok(Err(cause))) => Poll::Ready(Err(cause)), + Poll::Pending => { + self.0 = OpenBiFutureState::Receiving(fut); + Poll::Pending + } + Poll::Ready(Err(_)) => Poll::Ready(Err(quinn::ConnectionError::LocallyClosed)), + }, + OpenBiFutureState::Taken => unreachable!(), + } } } /// Future returned by accept_bi #[pin_project] -pub struct AcceptBiFuture<'a, In, Out>(#[pin] quinn::AcceptBi<'a>, PhantomData<(In, Out)>); +pub struct AcceptBiFuture( + #[pin] flume::r#async::RecvFut<'static, SocketInner>, + PhantomData<(In, Out)>, +); + +impl fmt::Debug for AcceptBiFuture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AcceptBiFuture").finish() + } +} -impl<'a, In, Out> Future for AcceptBiFuture<'a, In, Out> { +impl Future for AcceptBiFuture { type Output = result::Result, self::OpenBiError>; fn poll( @@ -189,66 +604,17 @@ impl<'a, In, Out> Future for AcceptBiFuture<'a, In, Out> { cx: &mut std::task::Context<'_>, ) -> std::task::Poll { self.project().0.poll_unpin(cx).map(|conn| { - let (send, recv) = conn?; - // turn chunks of bytes into a stream of messages using length delimited codec - let send = FramedWrite::new(send, LengthDelimitedCodec::new()); - let recv = FramedRead::new(recv, LengthDelimitedCodec::new()); - // now switch to streams of WantRequestUpdate and WantResponse - let recv = SymmetricallyFramed::new(recv, SymmetricalBincode::::default()); - let send = SymmetricallyFramed::new(send, SymmetricalBincode::::default()); - // box so we don't have to write down the insanely long type - let send = SendSink(send); - let recv = RecvStream(recv); + let (send, recv) = conn.map_err(|e| { + tracing::warn!("accept_bi: error receiving connection: {}", e); + quinn::ConnectionError::LocallyClosed + })?; + let send = SendSink::new(send); + let recv = RecvStream::new(recv); Ok((send, recv)) }) } } -// pub type AcceptBiFuture<'a, In, Out> = -// BoxFuture<'a, result::Result, self::AcceptBiError>>; - -impl crate::ChannelTypes for QuinnChannelTypes { - type SendSink = self::SendSink; - - type RecvStream = self::RecvStream; - - type OpenBiError = self::OpenBiError; - - type AcceptBiError = self::OpenBiError; - - type SendError = io::Error; - - type RecvError = io::Error; - - type OpenBiFuture<'a, In: RpcMessage, Out: RpcMessage> = self::OpenBiFuture<'a, In, Out>; - - type AcceptBiFuture<'a, In: RpcMessage, Out: RpcMessage> = self::AcceptBiFuture<'a, In, Out>; - - type ClientChannel = self::QuinnClientChannel; - - type ServerChannel = self::QuinnServerChannel; -} - -impl crate::ClientChannel - for self::QuinnClientChannel -{ - fn open_bi(&self) -> OpenBiFuture<'_, In, Out> { - OpenBiFuture(self.0.open_bi(), PhantomData) - } -} - -impl crate::ServerChannel - for self::QuinnServerChannel -{ - fn accept_bi(&self) -> AcceptBiFuture<'_, In, Out> { - AcceptBiFuture(self.connection.accept_bi(), PhantomData) - } - - fn local_addr(&self) -> &[crate::LocalAddr] { - todo!() - } -} - /// CreateChannelError for quinn channels. #[derive(Debug, Clone)] pub enum CreateChannelError { @@ -285,3 +651,15 @@ impl fmt::Display for CreateChannelError { } impl std::error::Error for CreateChannelError {} + +/// Get the handshake data from a quinn connection that uses rustls. +pub fn get_handshake_data( + connection: &quinn::Connection, +) -> Option { + let handshake_data = connection.handshake_data()?; + let tls_connection = handshake_data.downcast_ref::()?; + Some(quinn::crypto::rustls::HandshakeData { + protocol: tls_connection.protocol.clone(), + server_name: tls_connection.server_name.clone(), + }) +} diff --git a/src/transport/s2n_quic.rs b/src/transport/s2n_quic.rs index 3de0e4d..ca2accf 100644 --- a/src/transport/s2n_quic.rs +++ b/src/transport/s2n_quic.rs @@ -1,5 +1,5 @@ //! QUIC channel implementation based on quinn -use crate::{LocalAddr, RpcMessage}; +use crate::RpcMessage; use futures::channel::oneshot; use futures::{Future, FutureExt, Sink, SinkExt, Stream, StreamExt}; use pin_project::pin_project; @@ -7,24 +7,35 @@ use s2n_quic::stream::BidirectionalStream; use serde::{de::DeserializeOwned, Serialize}; use std::net::SocketAddr; use std::sync::Arc; -use std::task::{Poll, ready}; +use std::task::{ready, Poll}; use std::{fmt, io, marker::PhantomData, pin::Pin, result}; use tokio_serde::{formats::SymmetricalBincode, SymmetricallyFramed}; use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec}; +use super::{ConnectionCommon, ConnectionErrors, LocalAddr}; + type Socket = (SendSink, RecvStream); #[derive(Debug)] -struct ServerChannelInner { - task: tokio::task::JoinHandle<()>, - recv: flume::Receiver>, +struct ServerEndpointInner { + task: Option>, + recv: + flume::Receiver>, local_addr: [LocalAddr; 1], } +impl Drop for ServerEndpointInner { + fn drop(&mut self) { + if let Some(task) = self.task.take() { + task.abort(); + } + } +} + /// A server channel using a quinn connection #[derive(Debug)] pub struct ServerChannel { - inner: Arc, + inner: Arc, _phantom: PhantomData<(In, Out)>, } @@ -42,8 +53,8 @@ impl ServerChannel { } }); Self { - inner: Arc::new(ServerChannelInner { - task, + inner: Arc::new(ServerEndpointInner { + task: Some(task), recv, local_addr: [LocalAddr::Socket(local_addr)], }), @@ -63,10 +74,18 @@ impl Clone for ServerChannel { #[derive(Debug)] struct ClientChannelInner { - task: tokio::task::JoinHandle<()>, + task: Option>, send: flume::Sender>, } +impl Drop for ClientChannelInner { + fn drop(&mut self) { + if let Some(task) = self.task.take() { + task.abort(); + } + } +} + /// A server channel using a quinn connection pub struct ClientChannel( Arc, @@ -76,7 +95,8 @@ pub struct ClientChannel( impl ClientChannel { /// Create a new channel pub fn new(client: s2n_quic::Client, connect: s2n_quic::client::Connect) -> Self { - let (send, recv) = flume::bounded::>(1); + let (send, recv) = + flume::bounded::>(1); let task = tokio::spawn(async move { loop { let connect = connect.clone(); @@ -100,7 +120,7 @@ impl ClientChannel { Self( Arc::new(ClientChannelInner { send, - task, + task: Some(task), }), PhantomData, ) @@ -190,20 +210,14 @@ pub type OpenBiError = s2n_quic::connection::Error; /// Error for accept_bi. Currently just a quinn::ConnectionError pub type AcceptBiError = s2n_quic::connection::Error; -/// Types for quinn channels. -/// -/// This exposes the types from quinn directly without attempting to wrap them. -#[derive(Debug, Clone, Copy)] -pub struct ChannelTypes; - /// Future returned by open_bi #[pin_project] -pub struct OpenBiFuture<'a, In, Out> { +pub struct OpenBiFuture { inner: futures::channel::oneshot::Receiver, - p: PhantomData<&'a (In, Out)>, + p: PhantomData<(In, Out)>, } -impl<'a, In, Out> Future for OpenBiFuture<'a, In, Out> { +impl Future for OpenBiFuture { type Output = result::Result, self::OpenBiError>; fn poll( @@ -228,20 +242,15 @@ fn wrap_bidi_stream(stream: BidirectionalStream) -> (SendSink, Rec (send, recv) } -enum ServerConnectionState { - Initial, - Connected, - Final, -} - /// Future returned by accept_bi #[pin_project] -pub struct AcceptBiFuture<'a, In, Out> { - inner: flume::r#async::RecvFut<'a, Result>, +pub struct AcceptBiFuture { + inner: + flume::r#async::RecvFut<'static, Result>, p: PhantomData<(In, Out)>, } -impl<'a, In, Out> Future for AcceptBiFuture<'a, In, Out> { +impl Future for AcceptBiFuture { type Output = result::Result, self::AcceptBiError>; fn poll( @@ -256,32 +265,19 @@ impl<'a, In, Out> Future for AcceptBiFuture<'a, In, Out> { // pub type AcceptBiFuture<'a, In, Out> = // BoxFuture<'a, result::Result, self::AcceptBiError>>; -impl crate::ChannelTypes for ChannelTypes { - type SendSink = self::SendSink; - - type RecvStream = self::RecvStream; - - type OpenBiError = self::OpenBiError; - - type AcceptBiError = self::OpenBiError; - +impl ConnectionErrors for self::ClientChannel { + type OpenError = s2n_quic::connection::Error; type SendError = io::Error; - type RecvError = io::Error; +} - type OpenBiFuture<'a, In: RpcMessage, Out: RpcMessage> = self::OpenBiFuture<'a, In, Out>; - - type AcceptBiFuture<'a, In: RpcMessage, Out: RpcMessage> = self::AcceptBiFuture<'a, In, Out>; - - type ClientChannel = self::ClientChannel; - - type ServerChannel = self::ServerChannel; +impl ConnectionCommon for self::ClientChannel { + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; } -impl crate::ClientChannel - for self::ClientChannel -{ - fn open_bi(&self) -> OpenBiFuture<'_, In, Out> { +impl super::Connection for self::ClientChannel { + fn open_bi(&self) -> Self::OpenBiFut { let (tx, rx) = oneshot::channel(); self.0.send.send(tx).unwrap(); OpenBiFuture { @@ -289,56 +285,34 @@ impl crate::ClientChannel crate::ServerChannel - for self::ServerChannel -{ - fn accept_bi(&self) -> AcceptBiFuture<'_, In, Out> { - AcceptBiFuture { - inner: self.inner.recv.recv_async(), - p: PhantomData, - } - } - - fn local_addr(&self) -> &[crate::LocalAddr] { - todo!() - } + type OpenBiFut = self::OpenBiFuture; } -/// CreateChannelError for quinn channels. -#[derive(Debug, Clone)] -pub enum CreateChannelError { - /// Something went wrong immediately when creating the quinn endpoint - Io(io::ErrorKind, String), - /// Error directly when calling connect on the quinn endpoint - Connect(quinn::ConnectError), - /// Error produced by the future returned by connect - Connection(quinn::ConnectionError), +impl ConnectionErrors for self::ServerChannel { + type OpenError = s2n_quic::connection::Error; + type SendError = io::Error; + type RecvError = io::Error; } -impl From for CreateChannelError { - fn from(e: io::Error) -> Self { - CreateChannelError::Io(e.kind(), e.to_string()) - } +impl ConnectionCommon for self::ServerChannel { + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; } -impl From for CreateChannelError { - fn from(e: quinn::ConnectionError) -> Self { - CreateChannelError::Connection(e) +impl super::ServerEndpoint + for self::ServerChannel +{ + fn accept_bi(&self) -> Self::AcceptBiFut { + AcceptBiFuture { + inner: self.inner.recv.clone().into_recv_async(), + p: PhantomData, + } } -} -impl From for CreateChannelError { - fn from(e: quinn::ConnectError) -> Self { - CreateChannelError::Connect(e) + fn local_addr(&self) -> &[LocalAddr] { + self.inner.local_addr.as_slice() } -} -impl fmt::Display for CreateChannelError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } + type AcceptBiFut = self::AcceptBiFuture; } - -impl std::error::Error for CreateChannelError {} diff --git a/src/transport/util.rs b/src/transport/util.rs new file mode 100644 index 0000000..e477830 --- /dev/null +++ b/src/transport/util.rs @@ -0,0 +1,135 @@ +use std::{ + pin::Pin, + task::{self, Poll}, +}; + +use bincode::Options; +use futures::{Sink, SinkExt, Stream, StreamExt}; +use pin_project::pin_project; +use serde::{de::DeserializeOwned, Serialize}; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio_util::codec::LengthDelimitedCodec; + +type BincodeEncoding = + bincode::config::WithOtherIntEncoding; +/// Wrapper that wraps a bidirectional binary stream in a length delimited codec and bincode with fast fixint encoding +/// to get a bidirectional stream of rpc Messages +#[pin_project] +pub struct FramedBincodeRead( + #[pin] + tokio_serde::SymmetricallyFramed< + tokio_util::codec::FramedRead, + In, + tokio_serde::formats::SymmetricalBincode, + >, +); + +impl FramedBincodeRead { + /// Wrap a socket in a length delimited codec and bincode with fast fixint encoding + pub fn new(inner: T, max_frame_length: usize) -> Self { + // configure length delimited codec with max frame length + let framing = LengthDelimitedCodec::builder() + .max_frame_length(max_frame_length) + .new_codec(); + // create the actual framing. This turns the AsyncRead/AsyncWrite into a Stream/Sink of Bytes/BytesMut + let framed = tokio_util::codec::FramedRead::new(inner, framing); + // configure bincode with fixint encoding + let bincode_options: BincodeEncoding = + bincode::DefaultOptions::new().with_fixint_encoding(); + let bincode = tokio_serde::formats::Bincode::from(bincode_options); + // create the actual framing. This turns the Stream/Sink of Bytes/BytesMut into a Stream/Sink of In/Out + let framed = tokio_serde::Framed::new(framed, bincode); + Self(framed) + } +} + +impl FramedBincodeRead { + /// Get the underlying binary stream + /// + /// This can be useful if you want to drop the framing and use the underlying stream directly + /// after exchanging some messages. + pub fn into_inner(self) -> T { + self.0.into_inner().into_inner() + } +} + +impl Stream for FramedBincodeRead { + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + self.project().0.poll_next_unpin(cx) + } +} + +/// Wrapper that wraps a bidirectional binary stream in a length delimited codec and bincode with fast fixint encoding +/// to get a bidirectional stream of rpc Messages +#[pin_project] +pub struct FramedBincodeWrite( + #[pin] + tokio_serde::SymmetricallyFramed< + tokio_util::codec::FramedWrite, + Out, + tokio_serde::formats::SymmetricalBincode, + >, +); + +impl FramedBincodeWrite { + /// Wrap a socket in a length delimited codec and bincode with fast fixint encoding + pub fn new(inner: T, max_frame_length: usize) -> Self { + // configure length delimited codec with max frame length + let framing = LengthDelimitedCodec::builder() + .max_frame_length(max_frame_length) + .new_codec(); + // create the actual framing. This turns the AsyncRead/AsyncWrite into a Stream/Sink of Bytes/BytesMut + let framed = tokio_util::codec::FramedWrite::new(inner, framing); + // configure bincode with fixint encoding + let bincode_options: BincodeEncoding = + bincode::DefaultOptions::new().with_fixint_encoding(); + let bincode = tokio_serde::formats::SymmetricalBincode::from(bincode_options); + // create the actual framing. This turns the Stream/Sink of Bytes/BytesMut into a Stream/Sink of In/Out + let framed = tokio_serde::SymmetricallyFramed::new(framed, bincode); + Self(framed) + } +} + +impl FramedBincodeWrite { + /// Get the underlying binary stream + /// + /// This can be useful if you want to drop the framing and use the underlying stream directly + /// after exchanging some messages. + pub fn into_inner(self) -> T { + self.0.into_inner().into_inner() + } +} + +impl Sink for FramedBincodeWrite { + type Error = std::io::Error; + + fn poll_ready( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> Poll> { + self.project().0.poll_ready_unpin(cx) + } + + fn start_send(self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { + self.project().0.start_send_unpin(item) + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> Poll> { + self.project().0.poll_flush_unpin(cx) + } + + fn poll_close( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> Poll> { + self.project().0.poll_close_unpin(cx) + } +} + +// fn assert_sink(_: &impl Sink) {} +// fn assert_stream(_: &impl Stream) {} diff --git a/tests/flume.rs b/tests/flume.rs new file mode 100644 index 0000000..13a633b --- /dev/null +++ b/tests/flume.rs @@ -0,0 +1,39 @@ +#![cfg(feature = "flume-transport")] +mod math; +use math::*; +use quic_rpc::{server::RpcServerError, transport::flume, RpcClient, RpcServer}; + +#[tokio::test] +async fn flume_channel_bench() -> anyhow::Result<()> { + tracing_subscriber::fmt::try_init().ok(); + let (server, client) = flume::connection::(1); + + let server = RpcServer::::new(server); + let server_handle = tokio::task::spawn(ComputeService::server(server)); + let client = RpcClient::::new(client); + bench(client, 1000000).await?; + // dropping the client will cause the server to terminate + match server_handle.await? { + Err(RpcServerError::Accept(_)) => {} + e => panic!("unexpected termination result {e:?}"), + } + Ok(()) +} + +/// simple happy path test for all 4 patterns +#[tokio::test] +async fn flume_channel_smoke() -> anyhow::Result<()> { + tracing_subscriber::fmt::try_init().ok(); + let (server, client) = flume::connection::(1); + + let server = RpcServer::::new(server); + let server_handle = tokio::task::spawn(ComputeService::server(server)); + smoke_test(client).await?; + + // dropping the client will cause the server to terminate + match server_handle.await? { + Err(RpcServerError::Accept(_)) => {} + e => panic!("unexpected termination result {e:?}"), + } + Ok(()) +} diff --git a/tests/http2.rs b/tests/hyper.rs similarity index 71% rename from tests/http2.rs rename to tests/hyper.rs index d236c4a..6b26cd5 100644 --- a/tests/http2.rs +++ b/tests/hyper.rs @@ -1,16 +1,14 @@ -#![cfg(feature = "http2")] +#![cfg(feature = "hyper-transport")] use std::{net::SocketAddr, result}; +use ::hyper::Uri; use derive_more::{From, TryInto}; use flume::Receiver; -use hyper::Uri; use quic_rpc::{ client::RpcClientError, - message::{Msg, Rpc}, + declare_rpc, server::RpcServerError, - transport::http2::{ - self, Http2ChannelTypes, Http2ClientChannel, Http2ServerChannel, RecvError, - }, + transport::hyper::{self, HyperConnection, HyperServerEndpoint, RecvError}, RpcClient, RpcServer, Service, }; use serde::{Deserialize, Serialize}; @@ -21,8 +19,8 @@ use math::*; mod util; fn run_server(addr: &SocketAddr) -> JoinHandle> { - let channel = Http2ServerChannel::::serve(addr).unwrap(); - let server = RpcServer::::new(channel); + let channel = HyperServerEndpoint::::serve(addr).unwrap(); + let server = RpcServer::::new(channel); tokio::spawn(async move { loop { let server = server.clone(); @@ -34,13 +32,12 @@ fn run_server(addr: &SocketAddr) -> JoinHandle> { } #[tokio::test] -async fn http2_channel_bench() -> anyhow::Result<()> { - type C = Http2ChannelTypes; +async fn hyper_channel_bench() -> anyhow::Result<()> { let addr: SocketAddr = "127.0.0.1:3000".parse()?; let uri: Uri = "http://127.0.0.1:3000".parse()?; let server_handle = run_server(&addr); - let client = Http2ClientChannel::new(uri); - let client = RpcClient::::new(client); + let client = HyperConnection::new(uri); + let client = RpcClient::::new(client); bench(client, 50000).await?; println!("terminating server"); server_handle.abort(); @@ -49,20 +46,21 @@ async fn http2_channel_bench() -> anyhow::Result<()> { } #[tokio::test] -async fn http2_channel_smoke() -> anyhow::Result<()> { - type C = Http2ChannelTypes; +async fn hyper_channel_smoke() -> anyhow::Result<()> { let addr: SocketAddr = "127.0.0.1:3001".parse()?; let uri: Uri = "http://127.0.0.1:3001".parse()?; let server_handle = run_server(&addr); - let client = Http2ClientChannel::new(uri); - smoke_test::(client).await?; + let client = HyperConnection::new(uri); + smoke_test(client).await?; server_handle.abort(); let _ = server_handle.await; Ok(()) } #[tokio::test] -async fn http2_channel_errors() -> anyhow::Result<()> { +async fn hyper_channel_errors() -> anyhow::Result<()> { + type SC = HyperServerEndpoint; + /// request that can be too big #[derive(Debug, Serialize, Deserialize)] pub struct BigRequest(Vec); @@ -160,82 +158,46 @@ async fn http2_channel_errors() -> anyhow::Result<()> { } } - impl Msg for BigRequest { - type Response = (); - type Update = Self; - type Pattern = Rpc; - } - - impl Msg for NoSerRequest { - type Response = (); - type Update = Self; - type Pattern = Rpc; - } - - impl Msg for NoDeserRequest { - type Response = (); - type Update = Self; - type Pattern = Rpc; - } - - impl Msg for NoSerResponseRequest { - type Response = NoSer; - type Update = Self; - type Pattern = Rpc; - } - - impl Msg for NoDeserResponseRequest { - type Response = NoDeser; - type Update = Self; - type Pattern = Rpc; - } - - impl Msg for BigResponseRequest { - type Response = Vec; - type Update = Self; - type Pattern = Rpc; - } + declare_rpc!(TestService, BigRequest, ()); + declare_rpc!(TestService, NoSerRequest, ()); + declare_rpc!(TestService, NoDeserRequest, ()); + declare_rpc!(TestService, NoSerResponseRequest, NoSer); + declare_rpc!(TestService, NoDeserResponseRequest, NoDeser); + declare_rpc!(TestService, BigResponseRequest, Vec); #[allow(clippy::type_complexity)] fn run_test_server( addr: &SocketAddr, ) -> ( JoinHandle>, - Receiver>>, + Receiver>>, ) { - let channel = Http2ServerChannel::serve(addr).unwrap(); - let server = RpcServer::::new(channel); + let channel = HyperServerEndpoint::serve(addr).unwrap(); + let server = RpcServer::::new(channel); let (res_tx, res_rx) = flume::unbounded(); let handle = tokio::spawn(async move { loop { - let x = server.accept_one().await; + let x = server.accept().await; let res = match x { Ok((req, chan)) => match req { TestRequest::BigRequest(req) => { - server.rpc(req, chan, TestService, TestService::big).await + chan.rpc(req, TestService, TestService::big).await } TestRequest::NoSerRequest(req) => { - server.rpc(req, chan, TestService, TestService::noser).await + chan.rpc(req, TestService, TestService::noser).await } TestRequest::NoDeserRequest(req) => { - server - .rpc(req, chan, TestService, TestService::nodeser) - .await + chan.rpc(req, TestService, TestService::nodeser).await } TestRequest::NoSerResponseRequest(req) => { - server - .rpc(req, chan, TestService, TestService::noserresponse) - .await + chan.rpc(req, TestService, TestService::noserresponse).await } TestRequest::NoDeserResponseRequest(req) => { - server - .rpc(req, chan, TestService, TestService::nodeserresponse) + chan.rpc(req, TestService, TestService::nodeserresponse) .await } TestRequest::BigResponseRequest(req) => { - server - .rpc(req, chan, TestService, TestService::bigresponse) - .await + chan.rpc(req, TestService, TestService::bigresponse).await } }, Err(e) => Err(e), @@ -248,12 +210,11 @@ async fn http2_channel_errors() -> anyhow::Result<()> { (handle, res_rx) } - type C = Http2ChannelTypes; let addr: SocketAddr = "127.0.0.1:3002".parse()?; let uri: Uri = "http://127.0.0.1:3002".parse()?; let (server_handle, server_results) = run_test_server(&addr); - let client = Http2ClientChannel::new(uri); - let client = RpcClient::::new(client); + let client = HyperConnection::new(uri); + let client = RpcClient::::new(client); macro_rules! assert_matches { ($e:expr, $p:pat) => { @@ -286,7 +247,7 @@ async fn http2_channel_errors() -> anyhow::Result<()> { let res = client.rpc(BigRequest(vec![0; 20_000_000])).await; assert_matches!( res, - Err(RpcClientError::Send(http2::SendError::SizeError(_))) + Err(RpcClientError::Send(hyper::SendError::SizeError(_))) ); assert_server_result!(Err(RpcServerError::EarlyClose)); @@ -294,7 +255,7 @@ async fn http2_channel_errors() -> anyhow::Result<()> { let res = client.rpc(NoSerRequest(NoSer)).await; assert_matches!( res, - Err(RpcClientError::Send(http2::SendError::SerializeError(_))) + Err(RpcClientError::Send(hyper::SendError::SerializeError(_))) ); assert_server_result!(Err(RpcServerError::EarlyClose)); @@ -302,14 +263,14 @@ async fn http2_channel_errors() -> anyhow::Result<()> { let res = client.rpc(NoDeserRequest(NoDeser)).await; assert_matches!(res, Err(RpcClientError::EarlyClose)); assert_server_result!(Err(RpcServerError::RecvError( - http2::RecvError::DeserializeError(_) + hyper::RecvError::DeserializeError(_) ))); // response not serializable - should fail on the server side let res = client.rpc(NoSerResponseRequest).await; assert_matches!(res, Err(RpcClientError::EarlyClose)); assert_server_result!(Err(RpcServerError::SendError( - http2::SendError::SerializeError(_) + hyper::SendError::SerializeError(_) ))); // response not deserializable - should succeed on the server side fail on the client side @@ -328,7 +289,7 @@ async fn http2_channel_errors() -> anyhow::Result<()> { // response big - should fail let res = client.rpc(BigResponseRequest(20_000_000)).await; assert_matches!(res, Err(RpcClientError::EarlyClose)); - assert_server_result!(Err(RpcServerError::SendError(http2::SendError::SizeError( + assert_server_result!(Err(RpcServerError::SendError(hyper::SendError::SizeError( _ )))); diff --git a/tests/math.rs b/tests/math.rs index 69a5adb..24252fe 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -1,11 +1,16 @@ +#![cfg(any( + feature = "flume-transport", + feature = "hyper-transport", + feature = "quinn-transport", + feature = "s2n-quic-transport", +))] #![allow(dead_code)] use async_stream::stream; use derive_more::{From, TryInto}; use futures::{SinkExt, Stream, StreamExt, TryStreamExt}; use quic_rpc::{ - message::{BidiStreaming, ClientStreaming, Msg, RpcMsg, ServerStreaming}, - server::RpcServerError, - ChannelTypes, RpcClient, RpcServer, Service, + declare_bidi_streaming, declare_client_streaming, declare_rpc, declare_server_streaming, + server::RpcServerError, RpcClient, RpcServer, Service, ServiceConnection, ServiceEndpoint, }; use serde::{Deserialize, Serialize}; use std::{ @@ -77,27 +82,10 @@ impl Service for ComputeService { type Res = ComputeResponse; } -impl RpcMsg for Sqr { - type Response = SqrResponse; -} - -impl Msg for Sum { - type Response = SumResponse; - type Update = SumUpdate; - type Pattern = ClientStreaming; -} - -impl Msg for Fibonacci { - type Response = FibonacciResponse; - type Update = Self; - type Pattern = ServerStreaming; -} - -impl Msg for Multiply { - type Response = MultiplyResponse; - type Update = MultiplyUpdate; - type Pattern = BidiStreaming; -} +declare_rpc!(ComputeService, Sqr, SqrResponse); +declare_client_streaming!(ComputeService, Sum, SumUpdate, SumResponse); +declare_server_streaming!(ComputeService, Fibonacci, FibonacciResponse); +declare_bidi_streaming!(ComputeService, Multiply, MultiplyUpdate, MultiplyResponse); impl ComputeService { async fn sqr(self, req: Sqr) -> SqrResponse { @@ -142,23 +130,22 @@ impl ComputeService { } } - pub async fn server( + pub async fn server>( server: RpcServer, ) -> result::Result<(), RpcServerError> { let s = server; let service = ComputeService; loop { - let (req, chan) = s.accept_one().await?; - let s = s.clone(); + let (req, chan) = s.accept().await?; let service = service.clone(); tokio::spawn(async move { use ComputeRequest::*; #[rustfmt::skip] match req { - Sqr(msg) => s.rpc(msg, chan, service, ComputeService::sqr).await, - Sum(msg) => s.client_streaming(msg, chan, service, ComputeService::sum).await, - Fibonacci(msg) => s.server_streaming(msg, chan, service, ComputeService::fibonacci).await, - Multiply(msg) => s.bidi_streaming(msg, chan, service, ComputeService::multiply).await, + Sqr(msg) => chan.rpc(msg, service, ComputeService::sqr).await, + Sum(msg) => chan.client_streaming(msg, service, ComputeService::sum).await, + Fibonacci(msg) => chan.server_streaming(msg, service, ComputeService::fibonacci).await, + Multiply(msg) => chan.bidi_streaming(msg, service, ComputeService::multiply).await, SumUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, MultiplyUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, }?; @@ -167,7 +154,7 @@ impl ComputeService { } } - pub async fn server_par( + pub async fn server_par>( server: RpcServer, parallelism: usize, ) -> result::Result<(), RpcServerError> { @@ -176,21 +163,20 @@ impl ComputeService { let service = ComputeService; let request_stream = stream! { loop { - yield s2.accept_one().await; + yield s2.accept().await; } }; let process_stream = request_stream.map(move |r| { let service = service.clone(); - let s = s.clone(); async move { let (req, chan) = r?; use ComputeRequest::*; #[rustfmt::skip] match req { - Sqr(msg) => s.rpc(msg, chan, service, ComputeService::sqr).await, - Sum(msg) => s.client_streaming(msg, chan, service, ComputeService::sum).await, - Fibonacci(msg) => s.server_streaming(msg, chan, service, ComputeService::fibonacci).await, - Multiply(msg) => s.bidi_streaming(msg, chan, service, ComputeService::multiply).await, + Sqr(msg) => chan.rpc(msg, service, ComputeService::sqr).await, + Sum(msg) => chan.client_streaming(msg, service, ComputeService::sum).await, + Fibonacci(msg) => chan.server_streaming(msg, service, ComputeService::fibonacci).await, + Multiply(msg) => chan.bidi_streaming(msg, service, ComputeService::multiply).await, SumUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, MultiplyUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, }?; @@ -201,7 +187,7 @@ impl ComputeService { .buffer_unordered(parallelism) .for_each(|x| async move { if let Err(e) = x { - eprintln!("error: {:?}", e); + eprintln!("error: {e:?}"); } }) .await; @@ -209,15 +195,16 @@ impl ComputeService { } } -pub async fn smoke_test( - client: C::ClientChannel, -) -> anyhow::Result<()> { +pub async fn smoke_test>(client: C) -> anyhow::Result<()> { let client = RpcClient::::new(client); // a rpc call + tracing::debug!("calling rpc S(1234)"); let res = client.rpc(Sqr(1234)).await?; + tracing::debug!("got response {:?}", res); assert_eq!(res, SqrResponse(1522756)); // client streaming call + tracing::debug!("calling client_streaming Sum"); let (mut send, recv) = client.client_streaming(Sum).await?; tokio::task::spawn(async move { for i in 1..=3 { @@ -226,14 +213,18 @@ pub async fn smoke_test( Ok::<_, C::SendError>(()) }); let res = recv.await?; + tracing::debug!("got response {:?}", res); assert_eq!(res, SumResponse(6)); // server streaming call + tracing::debug!("calling server_streaming Fibonacci(10)"); let s = client.server_streaming(Fibonacci(10)).await?; let res = s.map_ok(|x| x.0).try_collect::>().await?; + tracing::debug!("got response {:?}", res); assert_eq!(res, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]); // bidi streaming call + tracing::debug!("calling bidi Multiply(2)"); let (mut send, recv) = client.bidi(Multiply(2)).await?; tokio::task::spawn(async move { for i in 1..=3 { @@ -242,7 +233,10 @@ pub async fn smoke_test( Ok::<_, C::SendError>(()) }); let res = recv.map_ok(|x| x.0).try_collect::>().await?; + tracing::debug!("got response {:?}", res); assert_eq!(res, vec![2, 4, 6]); + + tracing::debug!("dropping client!"); Ok(()) } @@ -250,7 +244,7 @@ fn clear_line() { print!("\r{}\r", " ".repeat(80)); } -pub async fn bench( +pub async fn bench>( client: RpcClient, n: u64, ) -> anyhow::Result<()> diff --git a/tests/mem.rs b/tests/mem.rs deleted file mode 100644 index 364ff1a..0000000 --- a/tests/mem.rs +++ /dev/null @@ -1,37 +0,0 @@ -mod math; -use math::*; -use quic_rpc::{server::RpcServerError, transport::mem, RpcClient, RpcServer}; - -#[tokio::test] -async fn mem_channel_bench() -> anyhow::Result<()> { - type C = mem::MemChannelTypes; - let (server, client) = mem::connection::(1); - - let server = RpcServer::::new(server); - let server_handle = tokio::task::spawn(ComputeService::server(server)); - let client = RpcClient::::new(client); - bench(client, 1000000).await?; - // dropping the client will cause the server to terminate - match server_handle.await? { - Err(RpcServerError::AcceptBiError(_)) => {} - e => panic!("unexpected termination result {:?}", e), - } - Ok(()) -} - -/// simple happy path test for all 4 patterns -#[tokio::test] -async fn mem_channel_smoke() -> anyhow::Result<()> { - let (server, client) = mem::connection::(1); - - let server = RpcServer::::new(server); - let server_handle = tokio::task::spawn(ComputeService::server(server)); - smoke_test::(client).await?; - - // dropping the client will cause the server to terminate - match server_handle.await? { - Err(RpcServerError::AcceptBiError(_)) => {} - e => panic!("unexpected termination result {:?}", e), - } - Ok(()) -} diff --git a/tests/quinn.rs b/tests/quinn.rs index 4346e9b..a5a0126 100644 --- a/tests/quinn.rs +++ b/tests/quinn.rs @@ -1,18 +1,16 @@ -#![cfg(feature = "quic")] +#![cfg(feature = "quinn-transport")] use std::{ net::{Ipv4Addr, SocketAddr, SocketAddrV4}, sync::Arc, }; -use anyhow::Context; -use quic_rpc::{transport::QuinnChannelTypes, RpcClient, RpcServer}; +use quic_rpc::{RpcClient, RpcServer}; use quinn::{ClientConfig, Endpoint, ServerConfig}; use tokio::task::JoinHandle; mod math; use math::*; mod util; -use util::*; /// Constructs a QUIC endpoint configured for use a client only. /// @@ -54,7 +52,6 @@ fn configure_client(server_certs: &[&[u8]]) -> anyhow::Result { for cert in server_certs { certs.add(&rustls::Certificate(cert.to_vec()))?; } - Ok(ClientConfig::with_root_certificates(certs)) } @@ -81,8 +78,8 @@ pub struct Endpoints { server_addr: SocketAddr, } -pub fn make_endpoints() -> anyhow::Result { - let server_addr: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 12345)); +pub fn make_endpoints(port: u16) -> anyhow::Result { + let server_addr: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)); let (server, server_certs) = make_server_endpoint(server_addr)?; let client = make_client_endpoint("0.0.0.0:0".parse()?, &[&server_certs])?; Ok(Endpoints { @@ -94,46 +91,46 @@ pub fn make_endpoints() -> anyhow::Result { fn run_server(server: quinn::Endpoint) -> JoinHandle> { tokio::task::spawn(async move { - let local_addr = server.local_addr()?; - let conn = server.accept().await.context("accept failed")?.await?; - let connection = quic_rpc::transport::quinn::QuinnServerChannel::new(conn, local_addr); - let server = RpcServer::::new(connection); + let connection = quic_rpc::transport::quinn::QuinnServerEndpoint::new(server)?; + let server = RpcServer::::new(connection); ComputeService::server(server).await?; anyhow::Ok(()) }) } +// #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test] async fn quinn_channel_bench() -> anyhow::Result<()> { - type C = QuinnChannelTypes; + tracing_subscriber::fmt::try_init().ok(); let Endpoints { client, server, server_addr, - } = make_endpoints()?; + } = make_endpoints(12345)?; + tracing::debug!("Starting server"); let server_handle = run_server(server); - let client = client.connect(server_addr, "localhost")?.await?; - let client = quic_rpc::transport::quinn::QuinnClientChannel::new(client); - let client = RpcClient::::new(client); + tracing::debug!("Starting client"); + let client = + quic_rpc::transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); + let client = RpcClient::::new(client); + tracing::debug!("Starting benchmark"); bench(client, 50000).await?; - println!("waiting for server"); - check_termination_anyhow::(server_handle).await?; + server_handle.abort(); Ok(()) } #[tokio::test] -#[ignore] async fn quinn_channel_smoke() -> anyhow::Result<()> { - type C = QuinnChannelTypes; + tracing_subscriber::fmt::try_init().ok(); let Endpoints { client, server, server_addr, - } = make_endpoints()?; + } = make_endpoints(12346)?; let server_handle = run_server(server); - let client_connection = client.connect(server_addr, "localhost")?.await?; - let client_connection = quic_rpc::transport::quinn::QuinnClientChannel::new(client_connection); - smoke_test::(client_connection).await?; - check_termination_anyhow::(server_handle).await?; + let client_connection = + quic_rpc::transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); + smoke_test(client_connection).await?; + server_handle.abort(); Ok(()) } diff --git a/tests/s2n_quic.rs b/tests/s2n_quic.rs index f2279f5..81bd8aa 100644 --- a/tests/s2n_quic.rs +++ b/tests/s2n_quic.rs @@ -1,17 +1,18 @@ +#![cfg(feature = "s2n-quic-transport")] mod math; use std::{net::SocketAddr, path::Path}; use libp2p_core::identity::ed25519::Keypair; use math::*; mod util; -use quic_rpc::{transport::S2nQuicChannelTypes, RpcClient, RpcServer}; +use quic_rpc::{RpcClient, RpcServer}; use tokio::task::JoinHandle; fn run_server(server: s2n_quic::Server) -> JoinHandle> { tokio::task::spawn(async move { let local_addr = server.local_addr()?; let channel = quic_rpc::transport::s2n_quic::ServerChannel::new(server, local_addr); - let server = RpcServer::::new(channel); + let server = RpcServer::::new(channel); ComputeService::server(server).await?; anyhow::Ok(()) }) @@ -72,11 +73,10 @@ async fn make_client_and_server_builtin() -> anyhow::Result<( #[tokio::test] #[ignore] async fn s2n_quic_channel_smoke() -> anyhow::Result<()> { - type C = quic_rpc::transport::s2n_quic::ChannelTypes; let (client, server, connect) = make_client_and_server_builtin().await?; let server_handle = run_server(server); let client = quic_rpc::transport::s2n_quic::ClientChannel::new(client, connect); - smoke_test::(client).await?; + smoke_test(client).await?; server_handle.abort(); let _ = server_handle.await; Ok(()) @@ -84,11 +84,10 @@ async fn s2n_quic_channel_smoke() -> anyhow::Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn s2n_quic_channel_bench() -> anyhow::Result<()> { - type C = quic_rpc::transport::s2n_quic::ChannelTypes; let (client, server, connect) = make_client_and_server_builtin().await?; let server_handle = run_server(server); let client = quic_rpc::transport::s2n_quic::ClientChannel::new(client, connect); - let client = RpcClient::::new(client); + let client = RpcClient::::new(client); bench(client, 5).await?; server_handle.abort(); let _ = server_handle.await; diff --git a/tests/slow_math.rs b/tests/slow_math.rs index 2ff487e..fca09c5 100644 --- a/tests/slow_math.rs +++ b/tests/slow_math.rs @@ -1,3 +1,8 @@ +#![cfg(any( + feature = "flume-transport", + feature = "hyper-transport", + feature = "quinn-transport" +))] mod math; use std::result; @@ -5,9 +10,8 @@ use async_stream::stream; use futures::{Stream, StreamExt}; use math::*; use quic_rpc::{ - message::{BidiStreaming, ClientStreaming, Msg, RpcMsg, ServerStreaming}, - server::RpcServerError, - ChannelTypes, RpcServer, Service, + declare_bidi_streaming, declare_client_streaming, declare_rpc, declare_server_streaming, + server::RpcServerError, RpcServer, Service, ServiceEndpoint, }; #[derive(Debug, Clone)] @@ -18,27 +22,10 @@ impl Service for ComputeService { type Res = ComputeResponse; } -impl RpcMsg for Sqr { - type Response = SqrResponse; -} - -impl Msg for Sum { - type Response = SumResponse; - type Update = SumUpdate; - type Pattern = ClientStreaming; -} - -impl Msg for Fibonacci { - type Response = FibonacciResponse; - type Update = Self; - type Pattern = ServerStreaming; -} - -impl Msg for Multiply { - type Response = MultiplyResponse; - type Update = MultiplyUpdate; - type Pattern = BidiStreaming; -} +declare_rpc!(ComputeService, Sqr, SqrResponse); +declare_client_streaming!(ComputeService, Sum, SumUpdate, SumResponse); +declare_server_streaming!(ComputeService, Fibonacci, FibonacciResponse); +declare_bidi_streaming!(ComputeService, Multiply, MultiplyUpdate, MultiplyResponse); async fn sleep_ms(ms: u64) { tokio::time::sleep(std::time::Duration::from_millis(ms)).await; @@ -91,21 +78,21 @@ impl ComputeService { } } - pub async fn server( + pub async fn server>( server: RpcServer, ) -> result::Result<(), RpcServerError> { let s = server; let service = ComputeService; loop { - let (req, chan) = s.accept_one().await?; + let (req, chan) = s.accept().await?; use ComputeRequest::*; let service = service.clone(); #[rustfmt::skip] match req { - Sqr(msg) => s.rpc(msg, chan, service, ComputeService::sqr).await, - Sum(msg) => s.client_streaming(msg, chan, service, ComputeService::sum).await, - Fibonacci(msg) => s.server_streaming(msg, chan, service, ComputeService::fibonacci).await, - Multiply(msg) => s.bidi_streaming(msg, chan, service, ComputeService::multiply).await, + Sqr(msg) => chan.rpc(msg, service, ComputeService::sqr).await, + Sum(msg) => chan.client_streaming(msg, service, ComputeService::sum).await, + Fibonacci(msg) => chan.server_streaming(msg, service, ComputeService::fibonacci).await, + Multiply(msg) => chan.bidi_streaming(msg, service, ComputeService::multiply).await, SumUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, MultiplyUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, }?; diff --git a/tests/util.rs b/tests/util.rs index 1008843..428aa76 100644 --- a/tests/util.rs +++ b/tests/util.rs @@ -1,8 +1,8 @@ use anyhow::Context; -use quic_rpc::{server::RpcServerError, ChannelTypes}; +use quic_rpc::{server::RpcServerError, transport::Connection, RpcMessage}; #[allow(unused)] -pub async fn check_termination_anyhow( +pub async fn check_termination_anyhow>( server_handle: tokio::task::JoinHandle>, ) -> anyhow::Result<()> { // dropping the client will cause the server to terminate @@ -10,11 +10,11 @@ pub async fn check_termination_anyhow( Err(e) => { let err: RpcServerError = e.downcast().context("unexpected termination result")?; match err { - RpcServerError::AcceptBiError(_) => {} - e => panic!("unexpected termination error {:?}", e), + RpcServerError::Accept(_) => {} + e => panic!("unexpected termination error {e:?}"), } } - e => panic!("server should have terminated with an error {:?}", e), + e => panic!("server should have terminated with an error {e:?}"), } Ok(()) }