From 8478eb7ecb283470db9a58263174f70e1aeec341 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 11:02:51 +0200 Subject: [PATCH 01/43] move all the things to old --- CHANGELOG.md => old/CHANGELOG.md | 0 Cargo.lock => old/Cargo.lock | 0 Cargo.toml => old/Cargo.toml | 0 DOCS.md => old/DOCS.md | 0 README.md => old/README.md | 0 cliff.toml => old/cliff.toml | 0 {examples => old/examples}/errors.rs | 0 {examples => old/examples}/macro.rs | 0 {examples => old/examples}/modularize.rs | 0 {examples => old/examples}/split/client/Cargo.toml | 0 {examples => old/examples}/split/client/src/main.rs | 0 {examples => old/examples}/split/server/Cargo.toml | 0 {examples => old/examples}/split/server/src/main.rs | 0 {examples => old/examples}/split/types/Cargo.toml | 0 {examples => old/examples}/split/types/src/lib.rs | 0 {examples => old/examples}/store.rs | 0 {quic-rpc-derive => old/quic-rpc-derive}/Cargo.toml | 0 {quic-rpc-derive => old/quic-rpc-derive}/src/lib.rs | 0 .../quic-rpc-derive}/tests/compile_fail/duplicate_type.rs | 0 .../quic-rpc-derive}/tests/compile_fail/duplicate_type.stderr | 0 .../quic-rpc-derive}/tests/compile_fail/extra_attr_types.rs | 0 .../quic-rpc-derive}/tests/compile_fail/extra_attr_types.stderr | 0 .../quic-rpc-derive}/tests/compile_fail/multiple_fields.rs | 0 .../quic-rpc-derive}/tests/compile_fail/multiple_fields.stderr | 0 .../quic-rpc-derive}/tests/compile_fail/named_enum.rs | 0 .../quic-rpc-derive}/tests/compile_fail/named_enum.stderr | 0 .../quic-rpc-derive}/tests/compile_fail/non_enum.rs | 0 .../quic-rpc-derive}/tests/compile_fail/non_enum.stderr | 0 .../quic-rpc-derive}/tests/compile_fail/wrong_attr_types.rs | 0 .../quic-rpc-derive}/tests/compile_fail/wrong_attr_types.stderr | 0 {quic-rpc-derive => old/quic-rpc-derive}/tests/smoke.rs | 0 {src => old/src}/client.rs | 0 {src => old/src}/lib.rs | 0 {src => old/src}/macros.rs | 0 {src => old/src}/message.rs | 0 {src => old/src}/pattern/bidi_streaming.rs | 0 {src => old/src}/pattern/client_streaming.rs | 0 {src => old/src}/pattern/mod.rs | 0 {src => old/src}/pattern/rpc.rs | 0 {src => old/src}/pattern/server_streaming.rs | 0 {src => old/src}/pattern/try_server_streaming.rs | 0 {src => old/src}/server.rs | 0 {src => old/src}/transport/boxed.rs | 0 {src => old/src}/transport/combined.rs | 0 {src => old/src}/transport/flume.rs | 0 {src => old/src}/transport/hyper.rs | 0 {src => old/src}/transport/iroh.rs | 0 {src => old/src}/transport/mapped.rs | 0 {src => old/src}/transport/misc/mod.rs | 0 {src => old/src}/transport/mod.rs | 0 {src => old/src}/transport/quinn.rs | 0 {src => old/src}/transport/util.rs | 0 {tests => old/tests}/flume.rs | 0 {tests => old/tests}/hyper.rs | 0 {tests => old/tests}/iroh.rs | 0 {tests => old/tests}/math.rs | 0 {tests => old/tests}/quinn.rs | 0 {tests => old/tests}/slow_math.rs | 0 {tests => old/tests}/try.rs | 0 {tests => old/tests}/util.rs | 0 60 files changed, 0 insertions(+), 0 deletions(-) rename CHANGELOG.md => old/CHANGELOG.md (100%) rename Cargo.lock => old/Cargo.lock (100%) rename Cargo.toml => old/Cargo.toml (100%) rename DOCS.md => old/DOCS.md (100%) rename README.md => old/README.md (100%) rename cliff.toml => old/cliff.toml (100%) rename {examples => old/examples}/errors.rs (100%) rename {examples => old/examples}/macro.rs (100%) rename {examples => old/examples}/modularize.rs (100%) rename {examples => old/examples}/split/client/Cargo.toml (100%) rename {examples => old/examples}/split/client/src/main.rs (100%) rename {examples => old/examples}/split/server/Cargo.toml (100%) rename {examples => old/examples}/split/server/src/main.rs (100%) rename {examples => old/examples}/split/types/Cargo.toml (100%) rename {examples => old/examples}/split/types/src/lib.rs (100%) rename {examples => old/examples}/store.rs (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/Cargo.toml (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/src/lib.rs (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/duplicate_type.rs (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/duplicate_type.stderr (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/extra_attr_types.rs (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/extra_attr_types.stderr (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/multiple_fields.rs (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/multiple_fields.stderr (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/named_enum.rs (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/named_enum.stderr (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/non_enum.rs (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/non_enum.stderr (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/wrong_attr_types.rs (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/compile_fail/wrong_attr_types.stderr (100%) rename {quic-rpc-derive => old/quic-rpc-derive}/tests/smoke.rs (100%) rename {src => old/src}/client.rs (100%) rename {src => old/src}/lib.rs (100%) rename {src => old/src}/macros.rs (100%) rename {src => old/src}/message.rs (100%) rename {src => old/src}/pattern/bidi_streaming.rs (100%) rename {src => old/src}/pattern/client_streaming.rs (100%) rename {src => old/src}/pattern/mod.rs (100%) rename {src => old/src}/pattern/rpc.rs (100%) rename {src => old/src}/pattern/server_streaming.rs (100%) rename {src => old/src}/pattern/try_server_streaming.rs (100%) rename {src => old/src}/server.rs (100%) rename {src => old/src}/transport/boxed.rs (100%) rename {src => old/src}/transport/combined.rs (100%) rename {src => old/src}/transport/flume.rs (100%) rename {src => old/src}/transport/hyper.rs (100%) rename {src => old/src}/transport/iroh.rs (100%) rename {src => old/src}/transport/mapped.rs (100%) rename {src => old/src}/transport/misc/mod.rs (100%) rename {src => old/src}/transport/mod.rs (100%) rename {src => old/src}/transport/quinn.rs (100%) rename {src => old/src}/transport/util.rs (100%) rename {tests => old/tests}/flume.rs (100%) rename {tests => old/tests}/hyper.rs (100%) rename {tests => old/tests}/iroh.rs (100%) rename {tests => old/tests}/math.rs (100%) rename {tests => old/tests}/quinn.rs (100%) rename {tests => old/tests}/slow_math.rs (100%) rename {tests => old/tests}/try.rs (100%) rename {tests => old/tests}/util.rs (100%) diff --git a/CHANGELOG.md b/old/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to old/CHANGELOG.md diff --git a/Cargo.lock b/old/Cargo.lock similarity index 100% rename from Cargo.lock rename to old/Cargo.lock diff --git a/Cargo.toml b/old/Cargo.toml similarity index 100% rename from Cargo.toml rename to old/Cargo.toml diff --git a/DOCS.md b/old/DOCS.md similarity index 100% rename from DOCS.md rename to old/DOCS.md diff --git a/README.md b/old/README.md similarity index 100% rename from README.md rename to old/README.md diff --git a/cliff.toml b/old/cliff.toml similarity index 100% rename from cliff.toml rename to old/cliff.toml diff --git a/examples/errors.rs b/old/examples/errors.rs similarity index 100% rename from examples/errors.rs rename to old/examples/errors.rs diff --git a/examples/macro.rs b/old/examples/macro.rs similarity index 100% rename from examples/macro.rs rename to old/examples/macro.rs diff --git a/examples/modularize.rs b/old/examples/modularize.rs similarity index 100% rename from examples/modularize.rs rename to old/examples/modularize.rs diff --git a/examples/split/client/Cargo.toml b/old/examples/split/client/Cargo.toml similarity index 100% rename from examples/split/client/Cargo.toml rename to old/examples/split/client/Cargo.toml diff --git a/examples/split/client/src/main.rs b/old/examples/split/client/src/main.rs similarity index 100% rename from examples/split/client/src/main.rs rename to old/examples/split/client/src/main.rs diff --git a/examples/split/server/Cargo.toml b/old/examples/split/server/Cargo.toml similarity index 100% rename from examples/split/server/Cargo.toml rename to old/examples/split/server/Cargo.toml diff --git a/examples/split/server/src/main.rs b/old/examples/split/server/src/main.rs similarity index 100% rename from examples/split/server/src/main.rs rename to old/examples/split/server/src/main.rs diff --git a/examples/split/types/Cargo.toml b/old/examples/split/types/Cargo.toml similarity index 100% rename from examples/split/types/Cargo.toml rename to old/examples/split/types/Cargo.toml diff --git a/examples/split/types/src/lib.rs b/old/examples/split/types/src/lib.rs similarity index 100% rename from examples/split/types/src/lib.rs rename to old/examples/split/types/src/lib.rs diff --git a/examples/store.rs b/old/examples/store.rs similarity index 100% rename from examples/store.rs rename to old/examples/store.rs diff --git a/quic-rpc-derive/Cargo.toml b/old/quic-rpc-derive/Cargo.toml similarity index 100% rename from quic-rpc-derive/Cargo.toml rename to old/quic-rpc-derive/Cargo.toml diff --git a/quic-rpc-derive/src/lib.rs b/old/quic-rpc-derive/src/lib.rs similarity index 100% rename from quic-rpc-derive/src/lib.rs rename to old/quic-rpc-derive/src/lib.rs diff --git a/quic-rpc-derive/tests/compile_fail/duplicate_type.rs b/old/quic-rpc-derive/tests/compile_fail/duplicate_type.rs similarity index 100% rename from quic-rpc-derive/tests/compile_fail/duplicate_type.rs rename to old/quic-rpc-derive/tests/compile_fail/duplicate_type.rs diff --git a/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr b/old/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr similarity index 100% rename from quic-rpc-derive/tests/compile_fail/duplicate_type.stderr rename to old/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr diff --git a/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs b/old/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs similarity index 100% rename from quic-rpc-derive/tests/compile_fail/extra_attr_types.rs rename to old/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs diff --git a/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr b/old/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr similarity index 100% rename from quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr rename to old/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr diff --git a/quic-rpc-derive/tests/compile_fail/multiple_fields.rs b/old/quic-rpc-derive/tests/compile_fail/multiple_fields.rs similarity index 100% rename from quic-rpc-derive/tests/compile_fail/multiple_fields.rs rename to old/quic-rpc-derive/tests/compile_fail/multiple_fields.rs diff --git a/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr b/old/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr similarity index 100% rename from quic-rpc-derive/tests/compile_fail/multiple_fields.stderr rename to old/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr diff --git a/quic-rpc-derive/tests/compile_fail/named_enum.rs b/old/quic-rpc-derive/tests/compile_fail/named_enum.rs similarity index 100% rename from quic-rpc-derive/tests/compile_fail/named_enum.rs rename to old/quic-rpc-derive/tests/compile_fail/named_enum.rs diff --git a/quic-rpc-derive/tests/compile_fail/named_enum.stderr b/old/quic-rpc-derive/tests/compile_fail/named_enum.stderr similarity index 100% rename from quic-rpc-derive/tests/compile_fail/named_enum.stderr rename to old/quic-rpc-derive/tests/compile_fail/named_enum.stderr diff --git a/quic-rpc-derive/tests/compile_fail/non_enum.rs b/old/quic-rpc-derive/tests/compile_fail/non_enum.rs similarity index 100% rename from quic-rpc-derive/tests/compile_fail/non_enum.rs rename to old/quic-rpc-derive/tests/compile_fail/non_enum.rs diff --git a/quic-rpc-derive/tests/compile_fail/non_enum.stderr b/old/quic-rpc-derive/tests/compile_fail/non_enum.stderr similarity index 100% rename from quic-rpc-derive/tests/compile_fail/non_enum.stderr rename to old/quic-rpc-derive/tests/compile_fail/non_enum.stderr diff --git a/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs b/old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs similarity index 100% rename from quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs rename to old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs diff --git a/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr b/old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr similarity index 100% rename from quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr rename to old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr diff --git a/quic-rpc-derive/tests/smoke.rs b/old/quic-rpc-derive/tests/smoke.rs similarity index 100% rename from quic-rpc-derive/tests/smoke.rs rename to old/quic-rpc-derive/tests/smoke.rs diff --git a/src/client.rs b/old/src/client.rs similarity index 100% rename from src/client.rs rename to old/src/client.rs diff --git a/src/lib.rs b/old/src/lib.rs similarity index 100% rename from src/lib.rs rename to old/src/lib.rs diff --git a/src/macros.rs b/old/src/macros.rs similarity index 100% rename from src/macros.rs rename to old/src/macros.rs diff --git a/src/message.rs b/old/src/message.rs similarity index 100% rename from src/message.rs rename to old/src/message.rs diff --git a/src/pattern/bidi_streaming.rs b/old/src/pattern/bidi_streaming.rs similarity index 100% rename from src/pattern/bidi_streaming.rs rename to old/src/pattern/bidi_streaming.rs diff --git a/src/pattern/client_streaming.rs b/old/src/pattern/client_streaming.rs similarity index 100% rename from src/pattern/client_streaming.rs rename to old/src/pattern/client_streaming.rs diff --git a/src/pattern/mod.rs b/old/src/pattern/mod.rs similarity index 100% rename from src/pattern/mod.rs rename to old/src/pattern/mod.rs diff --git a/src/pattern/rpc.rs b/old/src/pattern/rpc.rs similarity index 100% rename from src/pattern/rpc.rs rename to old/src/pattern/rpc.rs diff --git a/src/pattern/server_streaming.rs b/old/src/pattern/server_streaming.rs similarity index 100% rename from src/pattern/server_streaming.rs rename to old/src/pattern/server_streaming.rs diff --git a/src/pattern/try_server_streaming.rs b/old/src/pattern/try_server_streaming.rs similarity index 100% rename from src/pattern/try_server_streaming.rs rename to old/src/pattern/try_server_streaming.rs diff --git a/src/server.rs b/old/src/server.rs similarity index 100% rename from src/server.rs rename to old/src/server.rs diff --git a/src/transport/boxed.rs b/old/src/transport/boxed.rs similarity index 100% rename from src/transport/boxed.rs rename to old/src/transport/boxed.rs diff --git a/src/transport/combined.rs b/old/src/transport/combined.rs similarity index 100% rename from src/transport/combined.rs rename to old/src/transport/combined.rs diff --git a/src/transport/flume.rs b/old/src/transport/flume.rs similarity index 100% rename from src/transport/flume.rs rename to old/src/transport/flume.rs diff --git a/src/transport/hyper.rs b/old/src/transport/hyper.rs similarity index 100% rename from src/transport/hyper.rs rename to old/src/transport/hyper.rs diff --git a/src/transport/iroh.rs b/old/src/transport/iroh.rs similarity index 100% rename from src/transport/iroh.rs rename to old/src/transport/iroh.rs diff --git a/src/transport/mapped.rs b/old/src/transport/mapped.rs similarity index 100% rename from src/transport/mapped.rs rename to old/src/transport/mapped.rs diff --git a/src/transport/misc/mod.rs b/old/src/transport/misc/mod.rs similarity index 100% rename from src/transport/misc/mod.rs rename to old/src/transport/misc/mod.rs diff --git a/src/transport/mod.rs b/old/src/transport/mod.rs similarity index 100% rename from src/transport/mod.rs rename to old/src/transport/mod.rs diff --git a/src/transport/quinn.rs b/old/src/transport/quinn.rs similarity index 100% rename from src/transport/quinn.rs rename to old/src/transport/quinn.rs diff --git a/src/transport/util.rs b/old/src/transport/util.rs similarity index 100% rename from src/transport/util.rs rename to old/src/transport/util.rs diff --git a/tests/flume.rs b/old/tests/flume.rs similarity index 100% rename from tests/flume.rs rename to old/tests/flume.rs diff --git a/tests/hyper.rs b/old/tests/hyper.rs similarity index 100% rename from tests/hyper.rs rename to old/tests/hyper.rs diff --git a/tests/iroh.rs b/old/tests/iroh.rs similarity index 100% rename from tests/iroh.rs rename to old/tests/iroh.rs diff --git a/tests/math.rs b/old/tests/math.rs similarity index 100% rename from tests/math.rs rename to old/tests/math.rs diff --git a/tests/quinn.rs b/old/tests/quinn.rs similarity index 100% rename from tests/quinn.rs rename to old/tests/quinn.rs diff --git a/tests/slow_math.rs b/old/tests/slow_math.rs similarity index 100% rename from tests/slow_math.rs rename to old/tests/slow_math.rs diff --git a/tests/try.rs b/old/tests/try.rs similarity index 100% rename from tests/try.rs rename to old/tests/try.rs diff --git a/tests/util.rs b/old/tests/util.rs similarity index 100% rename from tests/util.rs rename to old/tests/util.rs From 44a8840cea91239c0e7adc229db7ea4b5ed37595 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 11:27:48 +0200 Subject: [PATCH 02/43] new, more low level approach to rpc --- Cargo.lock | 1728 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 23 + examples/storage.rs | 249 ++++++ old/.vscode/settings.json | 3 + src/lib.rs | 582 +++++++++++++ src/util.rs | 343 ++++++++ 6 files changed, 2928 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 examples/storage.rs create mode 100644 old/.vscode/settings.json create mode 100644 src/lib.rs create mode 100644 src/util.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f6cec69 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1728 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "cordyceps" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec10f0a762d93c4498d2e97a333805cb6250d60bead623f71d8034f9a4152ba3" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "diatomic-waker" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "futures-buffered" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe940397c8b744b9c2c974791c2c08bca2c3242ce0290393249e98f215a00472" +dependencies = [ + "cordyceps", + "diatomic-waker", + "futures-core", + "pin-project-lite", + "spin", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generator" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "iroh-quinn" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c6245c9ed906506ab9185e8d7f64857129aee4f935e899f398a3bd3b70338d" +dependencies = [ + "bytes", + "cfg_aliases", + "iroh-quinn-proto", + "iroh-quinn-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "iroh-quinn-proto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "929d5d8fa77d5c304d3ee7cae9aede31f13908bd049f9de8c7c0094ad6f7c535" +dependencies = [ + "bytes", + "getrandom", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "iroh-quinn-udp" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53afaa1049f7c83ea1331f5ebb9e6ebc5fdd69c468b7a22dd598b02c9bcc973" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "n0-future" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399e11dc3b0e8d9d65b27170d22f5d779d52d9bed888db70d7e0c2c7ce3dfc52" +dependencies = [ + "cfg_aliases", + "derive_more 1.0.0", + "futures-buffered", + "futures-lite", + "futures-util", + "js-sys", + "pin-project", + "send_wrapper", + "tokio", + "tokio-util", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + +[[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-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "postcard" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qrpc2" +version = "0.1.0" +dependencies = [ + "anyhow", + "derive_more 2.0.1", + "iroh-quinn", + "n0-future", + "postcard", + "rcgen", + "rustls", + "serde", + "smallvec", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e012c45844a1790332c9386ed4ca3a06def221092eda277e6f079728f8ea99da" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "hashbrown", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "zerocopy" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..32d36ee --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "qrpc2" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1" +derive_more = { version = "2", features = ["debug", "display", "from"] } +n0-future = "0.1.2" +tracing = "0.1.41" +serde = { version = "1.0.219", features = ["derive"] } +tokio = { version = "1.44.1", features = ["full"] } +postcard = { version = "1.1.1", features = ["alloc", "use-std"], optional = true } +quinn = { version = "0.13.0", package = "iroh-quinn", optional = true } +tracing-subscriber = { version = "0.3.19", features = ["fmt"] } +rustls = { version = "0.23.5", default-features = false, features = ["std"], optional = true } +rcgen = { version = "0.13.2", optional = true } +smallvec = { version = "1.14.0", features = ["write"] } + +[features] +quinn = ["dep:quinn", "dep:postcard"] +default = ["quinn", "test"] +test = ["rustls", "rcgen"] diff --git a/examples/storage.rs b/examples/storage.rs new file mode 100644 index 0000000..6ef5130 --- /dev/null +++ b/examples/storage.rs @@ -0,0 +1,249 @@ +use std::{ + collections::BTreeMap, + marker::PhantomData, + net::{Ipv4Addr, SocketAddr, SocketAddrV4}, + sync::Arc, +}; + +use n0_future::task::AbortOnDropHandle; +use qrpc2::{ + Channels, Handler, LocalRequest, Service, ServiceRequest, ServiceSender, WithChannels, + channel::{mpsc, none::NoReceiver, oneshot}, + listen, + util::{make_client_endpoint, make_server_endpoint}, +}; +use serde::{Deserialize, Serialize}; +use tracing::info; + +/// A simple storage service, just to try it out +#[derive(Debug, Clone, Copy)] +struct StorageService; + +impl Service for StorageService {} + +#[derive(Debug, Serialize, Deserialize)] +struct Get { + key: String, +} + +impl Channels for Get { + type Rx = NoReceiver; + type Tx = oneshot::Sender>; +} + +#[derive(Debug, Serialize, Deserialize)] +struct List; + +impl Channels for List { + type Rx = NoReceiver; + type Tx = mpsc::Sender; +} + +#[derive(Debug, Serialize, Deserialize)] +struct Set { + key: String, + value: String, +} + +impl Channels for Set { + type Rx = NoReceiver; + type Tx = oneshot::Sender<()>; +} + +#[derive(derive_more::From, Serialize, Deserialize)] +enum StorageProtocol { + Get(Get), + Set(Set), + List(List), +} + +#[derive(derive_more::From)] +enum StorageMessage { + Get(WithChannels), + Set(WithChannels), + List(WithChannels), +} + +struct StorageActor { + recv: tokio::sync::mpsc::Receiver, + state: BTreeMap, +} + +impl StorageActor { + pub fn local() -> StorageApi { + let (tx, rx) = tokio::sync::mpsc::channel(1); + let actor = Self { + recv: rx, + state: BTreeMap::new(), + }; + tokio::spawn(actor.run()); + StorageApi { + inner: ServiceSender::::Local(tx), + } + } + + async fn run(mut self) { + while let Some(msg) = self.recv.recv().await { + self.handle(msg).await; + } + } + + async fn handle(&mut self, msg: StorageMessage) { + match msg { + StorageMessage::Get(get) => { + info!("get {:?}", get); + let WithChannels { tx, inner, .. } = get; + tx.send(self.state.get(&inner.key).cloned()).await.ok(); + } + StorageMessage::Set(set) => { + info!("set {:?}", set); + let WithChannels { tx, inner, .. } = set; + self.state.insert(inner.key, inner.value); + tx.send(()).await.ok(); + } + StorageMessage::List(list) => { + info!("list {:?}", list); + let WithChannels { mut tx, .. } = list; + for (key, value) in &self.state { + if tx.send(format!("{key}={value}")).await.is_err() { + break; + } + } + } + } + } +} +struct StorageApi { + inner: ServiceSender, +} + +impl StorageApi { + pub fn connect(endpoint: quinn::Endpoint, addr: SocketAddr) -> anyhow::Result { + Ok(StorageApi { + inner: ServiceSender::Remote(endpoint, addr, PhantomData), + }) + } + + pub fn listen(&self, endpoint: quinn::Endpoint) -> anyhow::Result> { + match &self.inner { + ServiceSender::Local(local) => { + let local = LocalRequest::from(local.clone()); + let fun: Handler = Arc::new(move |msg, _, tx| { + let local = local.clone(); + Box::pin(async move { + match msg { + StorageProtocol::Get(msg) => { + local.send((msg, tx)).await?; + } + StorageProtocol::Set(msg) => { + local.send((msg, tx)).await?; + } + StorageProtocol::List(msg) => { + local.send((msg, tx)).await?; + } + }; + Ok(()) + }) + }); + Ok(listen(endpoint, fun)) + } + ServiceSender::Remote(_, _, _) => { + Err(anyhow::anyhow!("cannot listen on a remote service")) + } + } + } + + pub async fn get(&self, key: String) -> anyhow::Result>> { + let msg = Get { key }; + match self.inner.request().await? { + ServiceRequest::Local(request) => { + let (tx, rx) = oneshot::channel(); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } + + pub async fn list(&self) -> anyhow::Result> { + let msg = List; + match self.inner.request().await? { + ServiceRequest::Local(request) => { + let (tx, rx) = mpsc::channel(10); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } + + pub async fn set(&self, key: String, value: String) -> anyhow::Result> { + let msg = Set { key, value }; + match self.inner.request().await? { + ServiceRequest::Local(request) => { + let (tx, rx) = oneshot::channel(); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } +} + +async fn local() -> anyhow::Result<()> { + let api = StorageActor::local(); + api.set("hello".to_string(), "world".to_string()) + .await? + .await?; + let value = api.get("hello".to_string()).await?.await?; + let mut list = api.list().await?; + while let Some(value) = list.recv().await? { + println!("list value = {:?}", value); + } + println!("value = {:?}", value); + Ok(()) +} + +async fn remote() -> anyhow::Result<()> { + let port = 10113; + let (server, cert) = + make_server_endpoint(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port).into())?; + let client = + make_client_endpoint(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0).into(), &[&cert])?; + let store = StorageActor::local(); + let handle = store.listen(server)?; + let api = StorageApi::connect(client, SocketAddrV4::new(Ipv4Addr::LOCALHOST, port).into())?; + api.set("hello".to_string(), "world".to_string()) + .await? + .await?; + api.set("goodbye".to_string(), "world".to_string()) + .await? + .await?; + let value = api.get("hello".to_string()).await?.await?; + println!("value = {:?}", value); + let mut list = api.list().await?; + while let Some(value) = list.recv().await? { + println!("list value = {:?}", value); + } + drop(handle); + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt().init(); + println!("Local use"); + local().await?; + println!("Remote use"); + remote().await?; + Ok(()) +} diff --git a/old/.vscode/settings.json b/old/.vscode/settings.json new file mode 100644 index 0000000..3f22f4c --- /dev/null +++ b/old/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.cargo.features": ["hyper-transport", "quinn-transport", "iroh-transport", "test-utils"] +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0764283 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,582 @@ +use std::{fmt::Debug, io, marker::PhantomData, net::SocketAddr, ops::Deref, pin::Pin, sync::Arc}; + +use channel::{ + mpsc::{self, NetworkReceiver, NetworkSender}, + none::NoReceiver, + oneshot, +}; +use n0_future::task::AbortOnDropHandle; +use serde::{Serialize, de::DeserializeOwned}; +use smallvec::SmallVec; +use tokio::task::JoinSet; +use tracing::warn; +use util::{AsyncReadVarintExt, WriteVarintExt}; +pub mod util; + +/// Requirements for a RPC message +/// +/// Even when just using the mem transport, we require messages to be Serializable and Deserializable. +/// Likewise, even when using the quinn transport, we require messages to be Send. +/// +/// This does not seem like a big restriction. If you want a pure memory channel without the possibility +/// to also use the quinn transport, you might want to use a mpsc channel directly. +pub trait RpcMessage: Debug + Serialize + DeserializeOwned + Send + Sync + Unpin + 'static {} + +impl RpcMessage for T where + T: Debug + Serialize + DeserializeOwned + Send + Sync + Unpin + 'static +{ +} + +/// Marker trait for a service +pub trait Service: Debug + Clone {} + +/// Marker trait for a sender +pub trait Sender: Debug {} + +/// Marker trait for a receiver +pub trait Receiver: Debug {} + +/// Channels to be used for a message and service +pub trait Channels { + type Tx: Sender; + type Rx: Receiver; +} + +/// Channels that abstract over local or remote sending +pub mod channel { + /// Oneshot channel, similar to tokio's oneshot channel + pub mod oneshot { + use std::{fmt::Debug, io, pin::Pin}; + + pub fn channel() -> (Sender, Receiver) { + let (tx, rx) = tokio::sync::oneshot::channel(); + (tx.into(), rx.into()) + } + + pub enum Sender { + Tokio(tokio::sync::oneshot::Sender), + Boxed( + Box< + dyn FnOnce(T) -> n0_future::future::Boxed> + + Send + + Sync + + 'static, + >, + ), + } + + impl Debug for Sender { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Tokio(_) => f.debug_tuple("Tokio").finish(), + Self::Boxed(_) => f.debug_tuple("Boxed").finish(), + } + } + } + + impl From> for Sender { + fn from(tx: tokio::sync::oneshot::Sender) -> Self { + Self::Tokio(tx) + } + } + + #[derive(Debug, derive_more::From, derive_more::Display)] + pub enum SendError { + ReceiverClosed, + Io(std::io::Error), + } + + impl std::error::Error for SendError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + SendError::Io(e) => Some(e), + _ => None, + } + } + } + + impl Sender { + pub async fn send(self, value: T) -> std::result::Result<(), SendError> { + match self { + Sender::Tokio(tx) => tx.send(value).map_err(|_| SendError::ReceiverClosed), + Sender::Boxed(f) => f(value).await.map_err(SendError::from), + } + } + } + + impl crate::Sender for Sender {} + + pub enum Receiver { + Tokio(tokio::sync::oneshot::Receiver), + Boxed(n0_future::future::Boxed>), + } + + impl Future for Receiver { + type Output = std::io::Result; + + fn poll( + self: Pin<&mut Self>, + cx: &mut std::task::Context, + ) -> std::task::Poll { + match self.get_mut() { + Self::Tokio(rx) => Pin::new(rx) + .poll(cx) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e)), + Self::Boxed(rx) => Pin::new(rx).poll(cx), + } + } + } + + /// Convert a tokio oneshot receiver to a receiver for this crate + impl From> for Receiver { + fn from(rx: tokio::sync::oneshot::Receiver) -> Self { + Self::Tokio(rx) + } + } + + /// Convert a function that produces a future to a receiver for this crate + impl From for Receiver + where + F: FnOnce() -> Fut, + Fut: Future> + Send + 'static, + { + fn from(f: F) -> Self { + Self::Boxed(Box::pin(f())) + } + } + + impl Debug for Receiver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Tokio(_) => f.debug_tuple("Tokio").finish(), + Self::Boxed(_) => f.debug_tuple("Boxed").finish(), + } + } + } + + impl crate::Receiver for Receiver {} + } + + /// MPSC channel, similar to tokio's mpsc channel + pub mod mpsc { + use std::{fmt::Debug, io, pin::Pin}; + + use crate::RpcMessage; + + pub fn channel(buffer: usize) -> (Sender, Receiver) { + let (tx, rx) = tokio::sync::mpsc::channel(buffer); + (tx.into(), rx.into()) + } + + #[derive(Debug, derive_more::From, derive_more::Display)] + pub enum SendError { + ReceiverClosed, + Io(std::io::Error), + } + + impl std::error::Error for SendError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + SendError::Io(e) => Some(e), + _ => None, + } + } + } + + pub enum Sender { + Tokio(tokio::sync::mpsc::Sender), + Boxed(Box>), + } + + impl From> for Sender { + fn from(tx: tokio::sync::mpsc::Sender) -> Self { + Self::Tokio(tx) + } + } + + pub trait NetworkSender: Debug + Send + Sync + 'static { + fn send( + &mut self, + value: T, + ) -> Pin> + Send + '_>>; + } + + pub trait NetworkReceiver: Debug + Send + Sync + 'static { + fn recv(&mut self) -> Pin>> + Send + '_>>; + } + + impl Debug for Sender { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Tokio(_) => f.debug_tuple("Tokio").finish(), + Self::Boxed(_) => f.debug_tuple("Boxed").finish(), + } + } + } + + impl Sender { + pub async fn send(&mut self, value: T) -> std::result::Result<(), SendError> { + match self { + Sender::Tokio(tx) => { + tx.send(value).await.map_err(|_| SendError::ReceiverClosed) + } + Sender::Boxed(sink) => sink.send(value).await.map_err(SendError::from), + } + } + } + + impl crate::Sender for Sender {} + + pub enum Receiver { + Tokio(tokio::sync::mpsc::Receiver), + Boxed(Box>), + } + + impl Receiver { + pub async fn recv(&mut self) -> io::Result> { + match self { + Self::Tokio(rx) => Ok(rx.recv().await), + Self::Boxed(rx) => rx.recv().await, + } + } + } + + impl From> for Receiver { + fn from(rx: tokio::sync::mpsc::Receiver) -> Self { + Self::Tokio(rx) + } + } + + impl Debug for Receiver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Tokio(_) => f.debug_tuple("Tokio").finish(), + Self::Boxed(_) => f.debug_tuple("Boxed").finish(), + } + } + } + + impl crate::Receiver for Receiver {} + } + + /// No channels, used when no communication is needed + pub mod none { + use crate::{Receiver, Sender}; + + #[derive(Debug)] + pub struct NoSender; + + impl Sender for NoSender {} + + #[derive(Debug)] + pub struct NoReceiver; + + impl Receiver for NoReceiver {} + } +} + +/// A wrapper for a message with channels to send and receive it +/// +/// rx and tx can be set to an appropriate channel kind. +#[derive(Debug)] +pub struct WithChannels, S: Service> { + /// The inner message. + pub inner: I, + /// The return channel to send the response to. Can be set to [`NoSender`] if not needed. + pub tx: >::Tx, + /// The request channel to receive the request from. Can be set to [`NoReceiver`] if not needed. + pub rx: >::Rx, +} + +/// Tuple conversion from inner message and tx/rx channels to a WithChannels struct +/// +/// For the case where you want both tx and rx channels. +impl, S: Service, Tx, Rx> From<(I, Tx, Rx)> for WithChannels +where + I: Channels, + >::Tx: From, + >::Rx: From, +{ + fn from(inner: (I, Tx, Rx)) -> Self { + let (inner, tx, rx) = inner; + Self { + inner, + tx: tx.into(), + rx: rx.into(), + } + } +} + +/// Tuple conversion from inner message and tx channel to a WithChannels struct +/// +/// For the very common case where you just need a tx channel to send the response to. +impl From<(I, Tx)> for WithChannels +where + I: Channels, + S: Service, + >::Tx: From, +{ + fn from(inner: (I, Tx)) -> Self { + let (inner, tx) = inner; + Self { + inner, + tx: tx.into(), + rx: NoReceiver, + } + } +} + +/// Deref so you can access the inner fields directly +impl, S: Service> Deref for WithChannels { + type Target = I; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +pub enum ServiceSender { + Local(tokio::sync::mpsc::Sender), + Remote(quinn::Endpoint, SocketAddr, PhantomData<(R, S)>), +} + +impl ServiceSender { + pub async fn request(&self) -> anyhow::Result> { + match self { + Self::Local(tx) => Ok(ServiceRequest::Local(LocalRequest(tx.clone(), PhantomData))), + Self::Remote(endpoint, addr, _) => { + let connection = endpoint.connect(*addr, "localhost")?.await?; + let (send, recv) = connection.open_bi().await?; + Ok(ServiceRequest::Remote(RemoteRequest( + send, + recv, + PhantomData, + ))) + } + } + } +} + +#[derive(Debug)] +pub struct LocalRequest(tokio::sync::mpsc::Sender, std::marker::PhantomData); + +impl From> for LocalRequest { + fn from(tx: tokio::sync::mpsc::Sender) -> Self { + Self(tx, PhantomData) + } +} + +impl Clone for LocalRequest { + fn clone(&self) -> Self { + Self(self.0.clone(), PhantomData) + } +} + +pub struct RemoteRequest( + quinn::SendStream, + quinn::RecvStream, + std::marker::PhantomData<(R, S)>, +); + +#[derive(Debug, derive_more::From)] +pub struct RemoteRead(quinn::RecvStream); + +impl From for oneshot::Receiver { + fn from(read: RemoteRead) -> Self { + let fut = async move { + let mut read = read.0; + let size = read.read_varint_u64().await?.ok_or(io::Error::new( + io::ErrorKind::UnexpectedEof, + "failed to read size", + ))?; + let rest = read + .read_to_end(size as usize) + .await + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let msg: T = postcard::from_bytes(&rest) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + io::Result::Ok(msg) + }; + oneshot::Receiver::from(|| fut) + } +} + +impl From for mpsc::Receiver { + fn from(read: RemoteRead) -> Self { + let read = read.0; + mpsc::Receiver::Boxed(Box::new(QuinnReceiver { + recv: read, + _marker: PhantomData, + })) + } +} + +#[derive(Debug, derive_more::From)] +pub struct RemoteWrite(quinn::SendStream); + +impl From for oneshot::Sender { + fn from(write: RemoteWrite) -> Self { + let mut writer = write.0; + oneshot::Sender::Boxed(Box::new(move |value| { + Box::pin(async move { + // write via a small buffer to avoid allocation for small values + let mut buf = SmallVec::<[u8; 128]>::new(); + buf.write_length_prefixed(value)?; + writer.write_all(&buf).await?; + io::Result::Ok(()) + }) + })) + } +} + +impl From for mpsc::Sender { + fn from(write: RemoteWrite) -> Self { + let write = write.0; + mpsc::Sender::Boxed(Box::new(QuinnSender { + send: write, + buffer: SmallVec::new(), + _marker: PhantomData, + })) + } +} + +struct QuinnReceiver { + recv: quinn::RecvStream, + _marker: std::marker::PhantomData, +} + +impl Debug for QuinnReceiver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("QuinnReceiver").finish() + } +} + +impl NetworkReceiver for QuinnReceiver { + fn recv(&mut self) -> Pin>> + Send + '_>> { + Box::pin(async { + let read = &mut self.recv; + let Some(size) = read.read_varint_u64().await? else { + return Ok(None); + }; + let mut buf = vec![0; size as usize]; + read.read_exact(&mut buf) + .await + .map_err(|e| io::Error::new(io::ErrorKind::UnexpectedEof, e))?; + let msg: T = postcard::from_bytes(&buf) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok(Some(msg)) + }) + } +} + +impl Drop for QuinnReceiver { + fn drop(&mut self) {} +} + +struct QuinnSender { + send: quinn::SendStream, + buffer: SmallVec<[u8; 128]>, + _marker: std::marker::PhantomData, +} + +impl Debug for QuinnSender { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("QuinnSender").finish() + } +} + +impl NetworkSender for QuinnSender { + fn send(&mut self, value: T) -> Pin> + Send + '_>> { + Box::pin(async { + let value = value; + self.buffer.clear(); + self.buffer.write_length_prefixed(value)?; + self.send.write_all(&self.buffer).await?; + self.buffer.clear(); + Ok(()) + }) + } +} + +impl Drop for QuinnSender { + fn drop(&mut self) { + self.send.finish().ok(); + } +} + +impl RemoteRequest { + pub async fn write(self, msg: impl Into) -> anyhow::Result<(RemoteRead, RemoteWrite)> { + let RemoteRequest(mut send, recv, _) = self; + let msg = msg.into(); + let mut buf = SmallVec::<[u8; 128]>::new(); + buf.write_length_prefixed(msg)?; + send.write_all(&buf).await?; + Ok((RemoteRead(recv), RemoteWrite(send))) + } +} + +#[derive(derive_more::From)] +pub enum ServiceRequest { + Local(LocalRequest), + Remote(RemoteRequest), +} + +impl LocalRequest { + pub async fn send(&self, value: impl Into>) -> anyhow::Result<()> + where + T: Channels, + M: From>, + { + self.0.send(value.into().into()).await?; + Ok(()) + } +} + +/// Type alias for a handler fn for remote requests +pub type Handler = Arc< + dyn Fn(R, RemoteRead, RemoteWrite) -> n0_future::future::Boxed> + + Send + + Sync + + 'static, +>; + +/// Utility function to listen for incoming connections and handle them with the provided handler +pub fn listen( + endpoint: quinn::Endpoint, + handler: Handler, +) -> AbortOnDropHandle<()> { + let task = tokio::spawn(async move { + let mut tasks = JoinSet::new(); + while let Some(incoming) = endpoint.accept().await { + let handler = handler.clone(); + tasks.spawn(async move { + let connection = match incoming.await { + Ok(connection) => connection, + Err(cause) => { + warn!("failed to accept connection {cause:?}"); + return anyhow::Ok(()); + } + }; + loop { + let (send, mut recv) = match connection.accept_bi().await { + Ok((s, r)) => (s, r), + Err(cause) => { + warn!("failed to accept bi stream {cause:?}"); + return anyhow::Ok(()); + } + }; + let size = recv.read_varint_u64().await?.ok_or_else(|| { + io::Error::new(io::ErrorKind::UnexpectedEof, "failed to read size") + })?; + let mut buf = vec![0; size as usize]; + recv.read_exact(&mut buf).await?; + let msg: R = postcard::from_bytes(&buf)?; + let rx = RemoteRead::from(recv); + let tx = RemoteWrite::from(send); + handler(msg, rx, tx).await?; + } + }); + } + }); + AbortOnDropHandle::new(task) +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..f28a157 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,343 @@ +#[cfg(feature = "test")] +mod quinn_setup_utils { + use std::{net::SocketAddr, sync::Arc}; + + use anyhow::Result; + use quinn::{ClientConfig, Endpoint, ServerConfig, crypto::rustls::QuicClientConfig}; + + /// Builds default quinn client config and trusts given certificates. + /// + /// ## Args + /// + /// - server_certs: a list of trusted certificates in DER format. + pub fn configure_client(server_certs: &[&[u8]]) -> Result { + let mut certs = rustls::RootCertStore::empty(); + for cert in server_certs { + let cert = rustls::pki_types::CertificateDer::from(cert.to_vec()); + certs.add(cert)?; + } + + let crypto_client_config = rustls::ClientConfig::builder_with_provider(Arc::new( + rustls::crypto::ring::default_provider(), + )) + .with_protocol_versions(&[&rustls::version::TLS13]) + .expect("valid versions") + .with_root_certificates(certs) + .with_no_client_auth(); + let quic_client_config = + quinn::crypto::rustls::QuicClientConfig::try_from(crypto_client_config)?; + + Ok(ClientConfig::new(Arc::new(quic_client_config))) + } + + /// Constructs a QUIC endpoint configured for use a client only. + /// + /// ## Args + /// + /// - server_certs: list of trusted certificates. + pub fn make_client_endpoint(bind_addr: SocketAddr, server_certs: &[&[u8]]) -> Result { + let client_cfg = configure_client(server_certs)?; + let mut endpoint = Endpoint::client(bind_addr)?; + endpoint.set_default_client_config(client_cfg); + Ok(endpoint) + } + + /// Create a server endpoint with a self-signed certificate + /// + /// Returns the server endpoint and the certificate in DER format + pub fn make_server_endpoint(bind_addr: SocketAddr) -> Result<(Endpoint, Vec)> { + let (server_config, server_cert) = configure_server()?; + let endpoint = Endpoint::server(server_config, bind_addr)?; + Ok((endpoint, server_cert)) + } + + /// Create a quinn server config with a self-signed certificate + /// + /// Returns the server config and the certificate in DER format + pub fn configure_server() -> anyhow::Result<(ServerConfig, Vec)> { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?; + let cert_der = cert.cert.der(); + let priv_key = rustls::pki_types::PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der()); + let cert_chain = vec![cert_der.clone()]; + + let mut server_config = ServerConfig::with_single_cert(cert_chain, priv_key.into())?; + Arc::get_mut(&mut server_config.transport) + .unwrap() + .max_concurrent_uni_streams(0_u8.into()); + + Ok((server_config, cert_der.to_vec())) + } + + /// Constructs a QUIC endpoint that trusts all certificates. + /// + /// This is useful for testing and local connections, but should be used with care. + pub fn make_insecure_client_endpoint(bind_addr: SocketAddr) -> Result { + let crypto = rustls::ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(Arc::new(SkipServerVerification)) + .with_no_client_auth(); + + let client_cfg = QuicClientConfig::try_from(crypto)?; + let client_cfg = ClientConfig::new(Arc::new(client_cfg)); + let mut endpoint = Endpoint::client(bind_addr)?; + endpoint.set_default_client_config(client_cfg); + Ok(endpoint) + } + + #[derive(Debug)] + struct SkipServerVerification; + + impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::pki_types::CertificateDer<'_>, + _intermediates: &[rustls::pki_types::CertificateDer<'_>], + _server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + use rustls::SignatureScheme::*; + // list them all, we don't care. + vec![ + RSA_PKCS1_SHA1, + ECDSA_SHA1_Legacy, + RSA_PKCS1_SHA256, + ECDSA_NISTP256_SHA256, + RSA_PKCS1_SHA384, + ECDSA_NISTP384_SHA384, + RSA_PKCS1_SHA512, + ECDSA_NISTP521_SHA512, + RSA_PSS_SHA256, + RSA_PSS_SHA384, + RSA_PSS_SHA512, + ED25519, + ED448, + ] + } + } +} +#[cfg(feature = "test")] +pub use quinn_setup_utils::*; + +#[cfg(feature = "quinn")] +mod varint_util { + use std::io::{self, Error}; + + use serde::Serialize; + use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; + + /// Reads a u64 varint from an AsyncRead source, using the Postcard/LEB128 format. + /// + /// In Postcard's varint format (LEB128): + /// - Each byte uses 7 bits for the value + /// - The MSB (most significant bit) of each byte indicates if there are more bytes (1) or not (0) + /// - Values are stored in little-endian order (least significant group first) + /// + /// Returns the decoded u64 value. + pub async fn read_varint_u64(reader: &mut R) -> io::Result> + where + R: AsyncRead + Unpin, + { + let mut result: u64 = 0; + let mut shift: u32 = 0; + + loop { + // We can only shift up to 63 bits (for a u64) + if shift >= 64 { + return Err(Error::new( + std::io::ErrorKind::InvalidData, + "Varint is too large for u64", + )); + } + + // Read a single byte + let res = reader.read_u8().await; + if shift == 0 { + if let Err(cause) = res { + if cause.kind() == std::io::ErrorKind::UnexpectedEof { + return Ok(None); + } else { + return Err(cause); + } + } + } + + let byte = res?; + + // Extract the 7 value bits (bits 0-6, excluding the MSB which is the continuation bit) + let value = (byte & 0x7F) as u64; + + // Add the bits to our result at the current shift position + result |= value << shift; + + // If the high bit is not set (0), this is the last byte + if byte & 0x80 == 0 { + break; + } + + // Move to the next 7 bits + shift += 7; + } + + Ok(Some(result)) + } + + /// Writes a u64 varint to an AsyncWrite destination, using the same LEB128 format. + /// + /// The function stages the entire varint in a 9-byte buffer (maximum size needed for u64) + /// and performs just a single write operation. + /// + /// Returns the number of bytes written. + pub async fn write_varint_u64(writer: &mut W, value: u64) -> io::Result + where + W: AsyncWrite + Unpin, + { + // Buffer to stage the varint (max 9 bytes for u64) + let mut buffer = [0u8; 9]; + let mut pos = 0; + + // Handle zero as a special case + if value == 0 { + buffer[0] = 0; + writer.write_all(&buffer[0..1]).await?; + return Ok(1); + } + + // Encode the value using LEB128 + let mut remaining = value; + while remaining > 0 { + // Extract the 7 least significant bits + let mut byte = (remaining & 0x7F) as u8; + remaining >>= 7; + + // Set the continuation bit if there's more data + if remaining > 0 { + byte |= 0x80; + } + + buffer[pos] = byte; + pos += 1; + } + + // Write the entire buffer in one go + writer.write_all(&buffer[0..pos]).await?; + + Ok(pos) + } + + /// Writes a u64 varint to any object that implements the `std::io::Write` trait. + /// + /// This encodes the value using LEB128 encoding. + /// + /// # Arguments + /// * `writer` - Any object implementing `std::io::Write` + /// * `value` - The u64 value to encode as a varint + /// + /// # Returns + /// The number of bytes written or an IO error + pub fn write_varint_u64_sync( + writer: &mut W, + value: u64, + ) -> std::io::Result { + // Handle zero as a special case + if value == 0 { + writer.write_all(&[0])?; + return Ok(1); + } + + let mut bytes_written = 0; + let mut remaining = value; + + while remaining > 0 { + // Extract the 7 least significant bits + let mut byte = (remaining & 0x7F) as u8; + remaining >>= 7; + + // Set the continuation bit if there's more data + if remaining > 0 { + byte |= 0x80; + } + + writer.write_all(&[byte])?; + bytes_written += 1; + } + + Ok(bytes_written) + } + + pub fn write_length_prefixed( + mut write: impl std::io::Write, + value: T, + ) -> io::Result<()> { + let size = postcard::experimental::serialized_size(&value).map_err(|_| { + Error::new( + io::ErrorKind::InvalidData, + "Failed to calculate serialized size", + ) + })? as u64; + write_varint_u64_sync(&mut write, size)?; + postcard::to_io(&value, &mut write) + .map_err(|_| Error::new(io::ErrorKind::InvalidData, "Failed to serialize data"))?; + Ok(()) + } + + pub trait AsyncReadVarintExt: AsyncRead + Unpin { + fn read_varint_u64(&mut self) -> impl Future>>; + } + + impl AsyncReadVarintExt for T { + fn read_varint_u64(&mut self) -> impl Future>> { + read_varint_u64(self) + } + } + + pub trait AsyncWriteVarintExt: AsyncWrite + Unpin { + fn write_varint_u64(&mut self, value: u64) -> impl Future>; + } + + impl AsyncWriteVarintExt for T { + fn write_varint_u64(&mut self, value: u64) -> impl Future> { + write_varint_u64(self, value) + } + } + + pub trait WriteVarintExt: std::io::Write { + fn write_varint_u64(&mut self, value: u64) -> io::Result; + fn write_length_prefixed(&mut self, value: T) -> io::Result<()>; + } + + impl WriteVarintExt for T { + fn write_varint_u64(&mut self, value: u64) -> io::Result { + write_varint_u64_sync(self, value) + } + + fn write_length_prefixed(&mut self, value: V) -> io::Result<()> { + write_length_prefixed(self, value) + } + } +} +pub use varint_util::{ + AsyncReadVarintExt, AsyncWriteVarintExt, WriteVarintExt, read_varint_u64, write_varint_u64, +}; From 3bcdc864bb504336489caa4e4d32ed4aeec7e6ea Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 12:17:43 +0200 Subject: [PATCH 03/43] Add first version of the macro crate --- Cargo.lock | 188 +++++++++++++++-- Cargo.toml | 21 +- examples/storage.rs | 28 ++- quic-rpc-derive/Cargo.toml | 23 ++ quic-rpc-derive/src/lib.rs | 136 ++++++++++++ src/lib.rs | 423 ++++++++++++++++++++----------------- src/util.rs | 10 +- 7 files changed, 594 insertions(+), 235 deletions(-) create mode 100644 quic-rpc-derive/Cargo.toml create mode 100644 quic-rpc-derive/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f6cec69..9cf9e6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,7 +202,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "unicode-xid", ] @@ -214,7 +214,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "unicode-xid", ] @@ -236,6 +236,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "fastrand" version = "2.3.0" @@ -288,7 +294,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -350,6 +356,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + [[package]] name = "hash32" version = "0.2.1" @@ -365,6 +377,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "heapless" version = "0.7.17" @@ -379,6 +397,16 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + [[package]] name = "iroh-quinn" version = "0.13.0" @@ -434,6 +462,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "jni" version = "0.21.1" @@ -662,7 +696,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -715,14 +749,15 @@ dependencies = [ ] [[package]] -name = "qrpc2" -version = "0.1.0" +name = "quic-rpc" +version = "0.18.3" dependencies = [ "anyhow", "derive_more 2.0.1", "iroh-quinn", "n0-future", "postcard", + "quic-rpc-derive", "rcgen", "rustls", "serde", @@ -732,6 +767,18 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "quic-rpc-derive" +version = "0.18.1" +dependencies = [ + "derive_more 1.0.0", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "trybuild", +] + [[package]] name = "quote" version = "1.0.40" @@ -951,6 +998,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -1033,7 +1086,28 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", ] [[package]] @@ -1106,6 +1180,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.100" @@ -1117,6 +1202,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1143,7 +1243,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1154,7 +1254,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1227,7 +1327,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1240,11 +1340,45 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "hashbrown", + "hashbrown 0.14.5", "pin-project-lite", "tokio", ] +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.41" @@ -1265,7 +1399,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1307,6 +1441,21 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trybuild" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ae08be68c056db96f0e6c6dd820727cca756ced9e1f4cc7fdd20e2a55e23898" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1369,7 +1518,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -1404,7 +1553,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1692,6 +1841,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" +dependencies = [ + "memchr", +] + [[package]] name = "yasna" version = "0.5.2" @@ -1718,7 +1876,7 @@ checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 32d36ee..1b8ddf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,16 @@ [package] -name = "qrpc2" -version = "0.1.0" -edition = "2024" +name = "quic-rpc" +version = "0.18.3" +edition = "2021" +authors = ["Rüdiger Klaehn ", "n0 team"] +keywords = ["api", "protocol", "network", "rpc"] +categories = ["network-programming"] +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.76" [dependencies] anyhow = "1" @@ -16,8 +25,12 @@ tracing-subscriber = { version = "0.3.19", features = ["fmt"] } rustls = { version = "0.23.5", default-features = false, features = ["std"], optional = true } rcgen = { version = "0.13.2", optional = true } smallvec = { version = "1.14.0", features = ["write"] } +quic-rpc-derive = { path = "./quic-rpc-derive", optional = true } [features] quinn = ["dep:quinn", "dep:postcard"] default = ["quinn", "test"] -test = ["rustls", "rcgen"] +test = ["rustls", "rcgen", "dep:quic-rpc-derive"] + +[workspace] +members = ["quic-rpc-derive"] diff --git a/examples/storage.rs b/examples/storage.rs index 6ef5130..254f6a2 100644 --- a/examples/storage.rs +++ b/examples/storage.rs @@ -6,12 +6,13 @@ use std::{ }; use n0_future::task::AbortOnDropHandle; -use qrpc2::{ - Channels, Handler, LocalRequest, Service, ServiceRequest, ServiceSender, WithChannels, +use quic_rpc::{ channel::{mpsc, none::NoReceiver, oneshot}, - listen, + rpc::{listen, Handler}, util::{make_client_endpoint, make_server_endpoint}, + Channels, LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, }; +use quic_rpc_derive::message_enum; use serde::{Deserialize, Serialize}; use tracing::info; @@ -51,19 +52,13 @@ impl Channels for Set { } #[derive(derive_more::From, Serialize, Deserialize)] +#[message_enum(StorageMessage, StorageService)] enum StorageProtocol { Get(Get), Set(Set), List(List), } -#[derive(derive_more::From)] -enum StorageMessage { - Get(WithChannels), - Set(WithChannels), - List(WithChannels), -} - struct StorageActor { recv: tokio::sync::mpsc::Receiver, state: BTreeMap, @@ -77,8 +72,9 @@ impl StorageActor { state: BTreeMap::new(), }; tokio::spawn(actor.run()); + let local = LocalMpscChannel::::from(tx); StorageApi { - inner: ServiceSender::::Local(tx), + inner: local.into(), } } @@ -126,8 +122,8 @@ impl StorageApi { pub fn listen(&self, endpoint: quinn::Endpoint) -> anyhow::Result> { match &self.inner { - ServiceSender::Local(local) => { - let local = LocalRequest::from(local.clone()); + ServiceSender::Local(local, _) => { + let local = LocalMpscChannel::from(local.clone()); let fun: Handler = Arc::new(move |msg, _, tx| { let local = local.clone(); Box::pin(async move { @@ -156,7 +152,7 @@ impl StorageApi { pub async fn get(&self, key: String) -> anyhow::Result>> { let msg = Get { key }; match self.inner.request().await? { - ServiceRequest::Local(request) => { + ServiceRequest::Local(request, _) => { let (tx, rx) = oneshot::channel(); request.send((msg, tx)).await?; Ok(rx) @@ -171,7 +167,7 @@ impl StorageApi { pub async fn list(&self) -> anyhow::Result> { let msg = List; match self.inner.request().await? { - ServiceRequest::Local(request) => { + ServiceRequest::Local(request, _) => { let (tx, rx) = mpsc::channel(10); request.send((msg, tx)).await?; Ok(rx) @@ -186,7 +182,7 @@ impl StorageApi { pub async fn set(&self, key: String, value: String) -> anyhow::Result> { let msg = Set { key, value }; match self.inner.request().await? { - ServiceRequest::Local(request) => { + ServiceRequest::Local(request, _) => { let (tx, rx) = oneshot::channel(); request.send((msg, tx)).await?; Ok(rx) diff --git a/quic-rpc-derive/Cargo.toml b/quic-rpc-derive/Cargo.toml new file mode 100644 index 0000000..7525b37 --- /dev/null +++ b/quic-rpc-derive/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "quic-rpc-derive" +version = "0.18.1" +edition = "2021" +authors = ["Rüdiger Klaehn "] +keywords = ["api", "protocol", "network", "rpc", "macro"] +categories = ["network-programming"] +license = "Apache-2.0/MIT" +repository = "https://github.com/n0-computer/quic-rpc" +description = "Macros for quic-rpc" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1", features = ["full"] } +quote = "1" +proc-macro2 = "1" + +[dev-dependencies] +derive_more = { version = "1", features = ["from", "try_into", "display"] } +serde = { version = "1", features = ["serde_derive"] } +trybuild = "1.0" diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs new file mode 100644 index 0000000..7d28e66 --- /dev/null +++ b/quic-rpc-derive/src/lib.rs @@ -0,0 +1,136 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput, Fields, Type}; + +/// A macro to generate a Message enum from a Protocol enum. +/// +/// This macro takes a Protocol enum and generates a corresponding Message enum +/// where each variant is wrapped in a WithChannels type. +/// +/// # Example +/// +/// ``` +/// #[message_enum(StorageMessage, StorageService)] +/// #[derive(derive_more::From, Serialize, Deserialize)] +/// enum StorageProtocol { +/// Get(Get), +/// Set(Set), +/// List(List), +/// } +/// ``` +/// +/// Will generate: +/// +/// ``` +/// #[derive(derive_more::From)] +/// enum StorageMessage { +/// Get(WithChannels), +/// Set(WithChannels), +/// List(WithChannels), +/// } +/// ``` +#[proc_macro_attribute] +pub fn message_enum(attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the input + let input = parse_macro_input!(item as DeriveInput); + let attrs = parse_macro_input!(attr as syn::AttributeArgs); + + // Parse the attribute arguments + if attrs.len() != 2 { + return syn::Error::new( + proc_macro2::Span::call_site(), + "Expected two arguments: message_enum_name and service_type", + ) + .to_compile_error() + .into(); + } + + // Extract message enum name and service type from attributes + let message_enum_name = match &attrs[0] { + syn::NestedMeta::Meta(syn::Meta::Path(path)) => path.get_ident().unwrap(), + _ => { + return syn::Error::new( + proc_macro2::Span::call_site(), + "First argument must be an identifier for the message enum name", + ) + .to_compile_error() + .into(); + } + }; + + let service_type = match &attrs[1] { + syn::NestedMeta::Meta(syn::Meta::Path(path)) => path.get_ident().unwrap(), + _ => { + return syn::Error::new( + proc_macro2::Span::call_site(), + "Second argument must be an identifier for the service type", + ) + .to_compile_error() + .into(); + } + }; + + // Extract the variants from the enum + let data_enum = match &input.data { + Data::Enum(data_enum) => data_enum, + _ => { + return syn::Error::new(input.ident.span(), "This macro only works on enums") + .to_compile_error() + .into(); + } + }; + + // Generate variants for the message enum + let message_variants = data_enum.variants.iter().map(|variant| { + let variant_name = &variant.ident; + + // Extract the inner type from the variant + let inner_type = match &variant.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { + if let Some(last_segment) = type_path.path.segments.last() { + &last_segment.ident + } else { + return syn::Error::new( + variant.ident.span(), + "Unable to extract type name from variant", + ) + .to_compile_error(); + } + } else { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field of a simple type", + ) + .to_compile_error(); + } + } + _ => { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field", + ) + .to_compile_error(); + } + }; + + // Generate the message variant + quote! { + #variant_name(WithChannels<#inner_type, #service_type>) + } + }); + + // Generate the message enum + let expanded = quote! { + // Keep the original protocol enum + #input + + // Generate the message enum + #[derive(derive_more::From)] + enum #message_enum_name { + #(#message_variants),* + } + }; + + expanded.into() +} diff --git a/src/lib.rs b/src/lib.rs index 0764283..b97be73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,7 @@ -use std::{fmt::Debug, io, marker::PhantomData, net::SocketAddr, ops::Deref, pin::Pin, sync::Arc}; - -use channel::{ - mpsc::{self, NetworkReceiver, NetworkSender}, - none::NoReceiver, - oneshot, -}; -use n0_future::task::AbortOnDropHandle; -use serde::{Serialize, de::DeserializeOwned}; -use smallvec::SmallVec; -use tokio::task::JoinSet; -use tracing::warn; -use util::{AsyncReadVarintExt, WriteVarintExt}; +use std::{fmt::Debug, marker::PhantomData, net::SocketAddr, ops::Deref}; + +use channel::none::NoReceiver; +use serde::{de::DeserializeOwned, Serialize}; pub mod util; /// Requirements for a RPC message @@ -46,7 +37,7 @@ pub trait Channels { pub mod channel { /// Oneshot channel, similar to tokio's oneshot channel pub mod oneshot { - use std::{fmt::Debug, io, pin::Pin}; + use std::{fmt::Debug, future::Future, io, pin::Pin}; pub fn channel() -> (Sender, Receiver) { let (tx, rx) = tokio::sync::oneshot::channel(); @@ -159,7 +150,7 @@ pub mod channel { /// MPSC channel, similar to tokio's mpsc channel pub mod mpsc { - use std::{fmt::Debug, io, pin::Pin}; + use std::{fmt::Debug, future::Future, io, pin::Pin}; use crate::RpcMessage; @@ -336,192 +327,279 @@ impl, S: Service> Deref for WithChannels { } pub enum ServiceSender { - Local(tokio::sync::mpsc::Sender), + Local(LocalMpscChannel, PhantomData), + #[cfg(feature = "quinn")] Remote(quinn::Endpoint, SocketAddr, PhantomData<(R, S)>), } +impl From> for ServiceSender { + fn from(tx: LocalMpscChannel) -> Self { + Self::Local(tx, PhantomData) + } +} + impl ServiceSender { pub async fn request(&self) -> anyhow::Result> { match self { - Self::Local(tx) => Ok(ServiceRequest::Local(LocalRequest(tx.clone(), PhantomData))), + Self::Local(tx, _) => Ok(ServiceRequest::from(tx.clone())), + #[cfg(feature = "quinn")] Self::Remote(endpoint, addr, _) => { let connection = endpoint.connect(*addr, "localhost")?.await?; let (send, recv) = connection.open_bi().await?; - Ok(ServiceRequest::Remote(RemoteRequest( - send, - recv, - PhantomData, - ))) + Ok(ServiceRequest::Remote(rpc::RemoteRequest::new(send, recv))) } } } } #[derive(Debug)] -pub struct LocalRequest(tokio::sync::mpsc::Sender, std::marker::PhantomData); +pub struct LocalMpscChannel(tokio::sync::mpsc::Sender, std::marker::PhantomData); -impl From> for LocalRequest { +impl From> for LocalMpscChannel { fn from(tx: tokio::sync::mpsc::Sender) -> Self { Self(tx, PhantomData) } } -impl Clone for LocalRequest { +impl Clone for LocalMpscChannel { fn clone(&self) -> Self { Self(self.0.clone(), PhantomData) } } -pub struct RemoteRequest( - quinn::SendStream, - quinn::RecvStream, - std::marker::PhantomData<(R, S)>, -); - -#[derive(Debug, derive_more::From)] -pub struct RemoteRead(quinn::RecvStream); - -impl From for oneshot::Receiver { - fn from(read: RemoteRead) -> Self { - let fut = async move { - let mut read = read.0; - let size = read.read_varint_u64().await?.ok_or(io::Error::new( - io::ErrorKind::UnexpectedEof, - "failed to read size", - ))?; - let rest = read - .read_to_end(size as usize) - .await - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let msg: T = postcard::from_bytes(&rest) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - io::Result::Ok(msg) - }; - oneshot::Receiver::from(|| fut) +#[cfg(feature = "quinn")] +pub mod rpc { + use std::{fmt::Debug, future::Future, io, marker::PhantomData, pin::Pin, sync::Arc}; + + use n0_future::task::AbortOnDropHandle; + use serde::{de::DeserializeOwned, Serialize}; + use smallvec::SmallVec; + use tokio::task::JoinSet; + use tracing::warn; + + use crate::{ + channel::{ + mpsc::{self, NetworkReceiver, NetworkSender}, + oneshot, + }, + util::{AsyncReadVarintExt, WriteVarintExt}, + RpcMessage, + }; + + #[derive(Debug)] + pub struct RemoteRequest( + quinn::SendStream, + quinn::RecvStream, + std::marker::PhantomData<(R, S)>, + ); + + impl RemoteRequest { + pub fn new(send: quinn::SendStream, recv: quinn::RecvStream) -> Self { + Self(send, recv, PhantomData) + } } -} -impl From for mpsc::Receiver { - fn from(read: RemoteRead) -> Self { - let read = read.0; - mpsc::Receiver::Boxed(Box::new(QuinnReceiver { - recv: read, - _marker: PhantomData, - })) + #[derive(Debug, derive_more::From)] + pub struct RemoteRead(quinn::RecvStream); + + impl From for oneshot::Receiver { + fn from(read: RemoteRead) -> Self { + let fut = async move { + let mut read = read.0; + let size = read.read_varint_u64().await?.ok_or(io::Error::new( + io::ErrorKind::UnexpectedEof, + "failed to read size", + ))?; + let rest = read + .read_to_end(size as usize) + .await + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let msg: T = postcard::from_bytes(&rest) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + io::Result::Ok(msg) + }; + oneshot::Receiver::from(|| fut) + } } -} -#[derive(Debug, derive_more::From)] -pub struct RemoteWrite(quinn::SendStream); - -impl From for oneshot::Sender { - fn from(write: RemoteWrite) -> Self { - let mut writer = write.0; - oneshot::Sender::Boxed(Box::new(move |value| { - Box::pin(async move { - // write via a small buffer to avoid allocation for small values - let mut buf = SmallVec::<[u8; 128]>::new(); - buf.write_length_prefixed(value)?; - writer.write_all(&buf).await?; - io::Result::Ok(()) - }) - })) + impl From for mpsc::Receiver { + fn from(read: RemoteRead) -> Self { + let read = read.0; + mpsc::Receiver::Boxed(Box::new(QuinnReceiver { + recv: read, + _marker: PhantomData, + })) + } } -} -impl From for mpsc::Sender { - fn from(write: RemoteWrite) -> Self { - let write = write.0; - mpsc::Sender::Boxed(Box::new(QuinnSender { - send: write, - buffer: SmallVec::new(), - _marker: PhantomData, - })) + #[derive(Debug, derive_more::From)] + pub struct RemoteWrite(quinn::SendStream); + + impl From for oneshot::Sender { + fn from(write: RemoteWrite) -> Self { + let mut writer = write.0; + oneshot::Sender::Boxed(Box::new(move |value| { + Box::pin(async move { + // write via a small buffer to avoid allocation for small values + let mut buf = SmallVec::<[u8; 128]>::new(); + buf.write_length_prefixed(value)?; + writer.write_all(&buf).await?; + io::Result::Ok(()) + }) + })) + } } -} -struct QuinnReceiver { - recv: quinn::RecvStream, - _marker: std::marker::PhantomData, -} + impl From for mpsc::Sender { + fn from(write: RemoteWrite) -> Self { + let write = write.0; + mpsc::Sender::Boxed(Box::new(QuinnSender { + send: write, + buffer: SmallVec::new(), + _marker: PhantomData, + })) + } + } -impl Debug for QuinnReceiver { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("QuinnReceiver").finish() + struct QuinnReceiver { + recv: quinn::RecvStream, + _marker: std::marker::PhantomData, } -} -impl NetworkReceiver for QuinnReceiver { - fn recv(&mut self) -> Pin>> + Send + '_>> { - Box::pin(async { - let read = &mut self.recv; - let Some(size) = read.read_varint_u64().await? else { - return Ok(None); - }; - let mut buf = vec![0; size as usize]; - read.read_exact(&mut buf) - .await - .map_err(|e| io::Error::new(io::ErrorKind::UnexpectedEof, e))?; - let msg: T = postcard::from_bytes(&buf) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - Ok(Some(msg)) - }) + impl Debug for QuinnReceiver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("QuinnReceiver").finish() + } } -} -impl Drop for QuinnReceiver { - fn drop(&mut self) {} -} + impl NetworkReceiver for QuinnReceiver { + fn recv(&mut self) -> Pin>> + Send + '_>> { + Box::pin(async { + let read = &mut self.recv; + let Some(size) = read.read_varint_u64().await? else { + return Ok(None); + }; + let mut buf = vec![0; size as usize]; + read.read_exact(&mut buf) + .await + .map_err(|e| io::Error::new(io::ErrorKind::UnexpectedEof, e))?; + let msg: T = postcard::from_bytes(&buf) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok(Some(msg)) + }) + } + } -struct QuinnSender { - send: quinn::SendStream, - buffer: SmallVec<[u8; 128]>, - _marker: std::marker::PhantomData, -} + impl Drop for QuinnReceiver { + fn drop(&mut self) {} + } -impl Debug for QuinnSender { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("QuinnSender").finish() + struct QuinnSender { + send: quinn::SendStream, + buffer: SmallVec<[u8; 128]>, + _marker: std::marker::PhantomData, + } + + impl Debug for QuinnSender { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("QuinnSender").finish() + } } -} -impl NetworkSender for QuinnSender { - fn send(&mut self, value: T) -> Pin> + Send + '_>> { - Box::pin(async { - let value = value; - self.buffer.clear(); - self.buffer.write_length_prefixed(value)?; - self.send.write_all(&self.buffer).await?; - self.buffer.clear(); - Ok(()) - }) + impl NetworkSender for QuinnSender { + fn send(&mut self, value: T) -> Pin> + Send + '_>> { + Box::pin(async { + let value = value; + self.buffer.clear(); + self.buffer.write_length_prefixed(value)?; + self.send.write_all(&self.buffer).await?; + self.buffer.clear(); + Ok(()) + }) + } } -} -impl Drop for QuinnSender { - fn drop(&mut self) { - self.send.finish().ok(); + impl Drop for QuinnSender { + fn drop(&mut self) { + self.send.finish().ok(); + } } -} -impl RemoteRequest { - pub async fn write(self, msg: impl Into) -> anyhow::Result<(RemoteRead, RemoteWrite)> { - let RemoteRequest(mut send, recv, _) = self; - let msg = msg.into(); - let mut buf = SmallVec::<[u8; 128]>::new(); - buf.write_length_prefixed(msg)?; - send.write_all(&buf).await?; - Ok((RemoteRead(recv), RemoteWrite(send))) + impl RemoteRequest { + pub async fn write(self, msg: impl Into) -> anyhow::Result<(RemoteRead, RemoteWrite)> { + let RemoteRequest(mut send, recv, _) = self; + let msg = msg.into(); + let mut buf = SmallVec::<[u8; 128]>::new(); + buf.write_length_prefixed(msg)?; + send.write_all(&buf).await?; + Ok((RemoteRead(recv), RemoteWrite(send))) + } + } + + /// Type alias for a handler fn for remote requests + pub type Handler = Arc< + dyn Fn(R, RemoteRead, RemoteWrite) -> n0_future::future::Boxed> + + Send + + Sync + + 'static, + >; + + /// Utility function to listen for incoming connections and handle them with the provided handler + pub fn listen( + endpoint: quinn::Endpoint, + handler: Handler, + ) -> AbortOnDropHandle<()> { + let task = tokio::spawn(async move { + let mut tasks = JoinSet::new(); + while let Some(incoming) = endpoint.accept().await { + let handler = handler.clone(); + tasks.spawn(async move { + let connection = match incoming.await { + Ok(connection) => connection, + Err(cause) => { + warn!("failed to accept connection {cause:?}"); + return anyhow::Ok(()); + } + }; + loop { + let (send, mut recv) = match connection.accept_bi().await { + Ok((s, r)) => (s, r), + Err(cause) => { + warn!("failed to accept bi stream {cause:?}"); + return anyhow::Ok(()); + } + }; + let size = recv.read_varint_u64().await?.ok_or_else(|| { + io::Error::new(io::ErrorKind::UnexpectedEof, "failed to read size") + })?; + let mut buf = vec![0; size as usize]; + recv.read_exact(&mut buf).await?; + let msg: R = postcard::from_bytes(&buf)?; + let rx = RemoteRead::from(recv); + let tx = RemoteWrite::from(send); + handler(msg, rx, tx).await?; + } + }); + } + }); + AbortOnDropHandle::new(task) } } -#[derive(derive_more::From)] +#[derive(Debug)] pub enum ServiceRequest { - Local(LocalRequest), - Remote(RemoteRequest), + Local(LocalMpscChannel, PhantomData), + #[cfg(feature = "quinn")] + Remote(rpc::RemoteRequest), +} + +impl From> for ServiceRequest { + fn from(tx: LocalMpscChannel) -> Self { + Self::Local(tx, PhantomData) + } } -impl LocalRequest { +impl LocalMpscChannel { pub async fn send(&self, value: impl Into>) -> anyhow::Result<()> where T: Channels, @@ -531,52 +609,3 @@ impl LocalRequest { Ok(()) } } - -/// Type alias for a handler fn for remote requests -pub type Handler = Arc< - dyn Fn(R, RemoteRead, RemoteWrite) -> n0_future::future::Boxed> - + Send - + Sync - + 'static, ->; - -/// Utility function to listen for incoming connections and handle them with the provided handler -pub fn listen( - endpoint: quinn::Endpoint, - handler: Handler, -) -> AbortOnDropHandle<()> { - let task = tokio::spawn(async move { - let mut tasks = JoinSet::new(); - while let Some(incoming) = endpoint.accept().await { - let handler = handler.clone(); - tasks.spawn(async move { - let connection = match incoming.await { - Ok(connection) => connection, - Err(cause) => { - warn!("failed to accept connection {cause:?}"); - return anyhow::Ok(()); - } - }; - loop { - let (send, mut recv) = match connection.accept_bi().await { - Ok((s, r)) => (s, r), - Err(cause) => { - warn!("failed to accept bi stream {cause:?}"); - return anyhow::Ok(()); - } - }; - let size = recv.read_varint_u64().await?.ok_or_else(|| { - io::Error::new(io::ErrorKind::UnexpectedEof, "failed to read size") - })?; - let mut buf = vec![0; size as usize]; - recv.read_exact(&mut buf).await?; - let msg: R = postcard::from_bytes(&buf)?; - let rx = RemoteRead::from(recv); - let tx = RemoteWrite::from(send); - handler(msg, rx, tx).await?; - } - }); - } - }); - AbortOnDropHandle::new(task) -} diff --git a/src/util.rs b/src/util.rs index f28a157..4244e41 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,7 +3,7 @@ mod quinn_setup_utils { use std::{net::SocketAddr, sync::Arc}; use anyhow::Result; - use quinn::{ClientConfig, Endpoint, ServerConfig, crypto::rustls::QuicClientConfig}; + use quinn::{crypto::rustls::QuicClientConfig, ClientConfig, Endpoint, ServerConfig}; /// Builds default quinn client config and trusts given certificates. /// @@ -143,7 +143,10 @@ pub use quinn_setup_utils::*; #[cfg(feature = "quinn")] mod varint_util { - use std::io::{self, Error}; + use std::{ + future::Future, + io::{self, Error}, + }; use serde::Serialize; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; @@ -338,6 +341,7 @@ mod varint_util { } } } +#[cfg(feature = "quinn")] pub use varint_util::{ - AsyncReadVarintExt, AsyncWriteVarintExt, WriteVarintExt, read_varint_u64, write_varint_u64, + read_varint_u64, write_varint_u64, AsyncReadVarintExt, AsyncWriteVarintExt, WriteVarintExt, }; From 1607a02b50e4d9962a1b5d6e1eb0fd693dd31bd7 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 12:28:34 +0200 Subject: [PATCH 04/43] keep the first example macro free --- examples/storage.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/storage.rs b/examples/storage.rs index 254f6a2..7d581b6 100644 --- a/examples/storage.rs +++ b/examples/storage.rs @@ -12,7 +12,6 @@ use quic_rpc::{ util::{make_client_endpoint, make_server_endpoint}, Channels, LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, }; -use quic_rpc_derive::message_enum; use serde::{Deserialize, Serialize}; use tracing::info; @@ -52,13 +51,19 @@ impl Channels for Set { } #[derive(derive_more::From, Serialize, Deserialize)] -#[message_enum(StorageMessage, StorageService)] enum StorageProtocol { Get(Get), Set(Set), List(List), } +#[derive(derive_more::From)] +enum StorageMessage { + Get(WithChannels), + Set(WithChannels), + List(WithChannels), +} + struct StorageActor { recv: tokio::sync::mpsc::Receiver, state: BTreeMap, From c28ae8ba5b0ffae47f74d86cd2fa3131e7a4e939 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 13:00:50 +0200 Subject: [PATCH 05/43] WIP macros --- Cargo.lock | 2 +- Cargo.toml | 3 +- old/quic-rpc-derive/src/lib.rs | 554 ++++++++++++------ quic-rpc-derive/Cargo.toml | 1 + quic-rpc-derive/examples/derive.rs | 251 ++++++++ quic-rpc-derive/src/lib.rs | 268 ++++++++- .../tests/compile_fail/duplicate_type.rs | 9 + .../tests/compile_fail/duplicate_type.stderr | 5 + .../tests/compile_fail/extra_attr_types.rs | 9 + .../compile_fail/extra_attr_types.stderr | 5 + .../tests/compile_fail/multiple_fields.rs | 8 + .../tests/compile_fail/multiple_fields.stderr | 5 + .../tests/compile_fail/named_enum.rs | 8 + .../tests/compile_fail/named_enum.stderr | 5 + .../tests/compile_fail/non_enum.rs | 6 + .../tests/compile_fail/non_enum.stderr | 5 + .../tests/compile_fail/wrong_attr_types.rs | 9 + .../compile_fail/wrong_attr_types.stderr | 5 + quic-rpc-derive/tests/smoke.rs | 79 +++ 19 files changed, 1041 insertions(+), 196 deletions(-) create mode 100644 quic-rpc-derive/examples/derive.rs create mode 100644 quic-rpc-derive/tests/compile_fail/duplicate_type.rs create mode 100644 quic-rpc-derive/tests/compile_fail/duplicate_type.stderr create mode 100644 quic-rpc-derive/tests/compile_fail/extra_attr_types.rs create mode 100644 quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr create mode 100644 quic-rpc-derive/tests/compile_fail/multiple_fields.rs create mode 100644 quic-rpc-derive/tests/compile_fail/multiple_fields.stderr create mode 100644 quic-rpc-derive/tests/compile_fail/named_enum.rs create mode 100644 quic-rpc-derive/tests/compile_fail/named_enum.stderr create mode 100644 quic-rpc-derive/tests/compile_fail/non_enum.rs create mode 100644 quic-rpc-derive/tests/compile_fail/non_enum.stderr create mode 100644 quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs create mode 100644 quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr create mode 100644 quic-rpc-derive/tests/smoke.rs diff --git a/Cargo.lock b/Cargo.lock index 9cf9e6f..cfc576d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -757,7 +757,6 @@ dependencies = [ "iroh-quinn", "n0-future", "postcard", - "quic-rpc-derive", "rcgen", "rustls", "serde", @@ -773,6 +772,7 @@ version = "0.18.1" dependencies = [ "derive_more 1.0.0", "proc-macro2", + "quic-rpc", "quote", "serde", "syn 1.0.109", diff --git a/Cargo.toml b/Cargo.toml index 1b8ddf8..29c4a09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,12 +25,11 @@ tracing-subscriber = { version = "0.3.19", features = ["fmt"] } rustls = { version = "0.23.5", default-features = false, features = ["std"], optional = true } rcgen = { version = "0.13.2", optional = true } smallvec = { version = "1.14.0", features = ["write"] } -quic-rpc-derive = { path = "./quic-rpc-derive", optional = true } [features] quinn = ["dep:quinn", "dep:postcard"] default = ["quinn", "test"] -test = ["rustls", "rcgen", "dep:quic-rpc-derive"] +test = ["rustls", "rcgen"] [workspace] members = ["quic-rpc-derive"] diff --git a/old/quic-rpc-derive/src/lib.rs b/old/quic-rpc-derive/src/lib.rs index fee1096..c1028ac 100644 --- a/old/quic-rpc-derive/src/lib.rs +++ b/old/quic-rpc-derive/src/lib.rs @@ -1,8 +1,5 @@ -use std::collections::{BTreeMap, HashSet}; - use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{quote, ToTokens}; +use quote::quote; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, @@ -10,223 +7,396 @@ use syn::{ Data, DeriveInput, Fields, Ident, Token, Type, }; -const SERVER_STREAMING: &str = "server_streaming"; -const CLIENT_STREAMING: &str = "client_streaming"; -const BIDI_STREAMING: &str = "bidi_streaming"; -const RPC: &str = "rpc"; -const TRY_SERVER_STREAMING: &str = "try_server_streaming"; -const IDENTS: [&str; 5] = [ - SERVER_STREAMING, - CLIENT_STREAMING, - BIDI_STREAMING, - RPC, - TRY_SERVER_STREAMING, -]; - -fn generate_rpc_impls( - pat: &str, - mut args: RpcArgs, - service_name: &Ident, - request_type: &Type, - attr_span: Span, -) -> syn::Result { - let res = match pat { - RPC => { - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::pattern::rpc::RpcMsg<#service_name> for #request_type { - type Response = #response; - } - } - } - SERVER_STREAMING => { - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::server_streaming::ServerStreaming; - } - impl ::quic_rpc::pattern::server_streaming::ServerStreamingMsg<#service_name> for #request_type { - type Response = #response; - } - } - } - BIDI_STREAMING => { - let update = args.get("update", pat, attr_span)?; - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::bidi_streaming::BidiStreaming; - } - impl ::quic_rpc::pattern::bidi_streaming::BidiStreamingMsg<#service_name> for #request_type { - type Update = #update; - type Response = #response; - } - } - } - CLIENT_STREAMING => { - let update = args.get("update", pat, attr_span)?; - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::client_streaming::ClientStreaming; - } - impl ::quic_rpc::pattern::client_streaming::ClientStreamingMsg<#service_name> for #request_type { - type Update = #update; - type Response = #response; - } - } - } - TRY_SERVER_STREAMING => { - let create_error = args.get("create_error", pat, attr_span)?; - let item_error = args.get("item_error", pat, attr_span)?; - let item = args.get("item", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::try_server_streaming::TryServerStreaming; - } - impl ::quic_rpc::pattern::try_server_streaming::TryServerStreamingMsg<#service_name> for #request_type { - type CreateError = #create_error; - type ItemError = #item_error; - type Item = #item; - } - } - } - _ => return Err(syn::Error::new(attr_span, "Unknown RPC pattern")), - }; - args.check_empty(attr_span)?; +struct TxArgs { + tx_type: Type, +} - Ok(res) +impl Parse for TxArgs { + fn parse(input: ParseStream) -> syn::Result { + let tx_type: Type = input.parse()?; + Ok(TxArgs { tx_type }) + } } +struct RxArgs { + rx_type: Type, +} + +impl Parse for RxArgs { + fn parse(input: ParseStream) -> syn::Result { + let rx_type: Type = input.parse()?; + Ok(RxArgs { rx_type }) + } +} + +/// A macro to generate a Message enum from a Protocol enum. +/// +/// This macro takes a Protocol enum and generates a corresponding Message enum +/// where each variant is wrapped in a WithChannels type. +/// +/// # Example +/// +/// ``` +/// #[message_enum(StorageMessage, StorageService)] +/// #[derive(derive_more::From, Serialize, Deserialize)] +/// enum StorageProtocol { +/// Get(Get), +/// Set(Set), +/// List(List), +/// } +/// ``` +/// +/// Will generate: +/// +/// ``` +/// #[derive(derive_more::From)] +/// enum StorageMessage { +/// Get(WithChannels), +/// Set(WithChannels), +/// List(WithChannels), +/// } +/// ``` #[proc_macro_attribute] -pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { - let mut input = parse_macro_input!(item as DeriveInput); - let service_name = parse_macro_input!(attr as Ident); +pub fn message_enum(attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the input + let input = parse_macro_input!(item as DeriveInput); + let attrs = parse_macro_input!(attr as syn::AttributeArgs); - let input_span = input.span(); - let data_enum = match &mut input.data { + // Parse the attribute arguments + if attrs.len() != 2 { + return syn::Error::new( + proc_macro2::Span::call_site(), + "Expected two arguments: message_enum_name and service_type", + ) + .to_compile_error() + .into(); + } + + // Extract message enum name and service type from attributes + let message_enum_name = match &attrs[0] { + syn::NestedMeta::Meta(syn::Meta::Path(path)) => path.get_ident().unwrap(), + _ => { + return syn::Error::new( + proc_macro2::Span::call_site(), + "First argument must be an identifier for the message enum name", + ) + .to_compile_error() + .into(); + } + }; + + let service_type = match &attrs[1] { + syn::NestedMeta::Meta(syn::Meta::Path(path)) => path.get_ident().unwrap(), + _ => { + return syn::Error::new( + proc_macro2::Span::call_site(), + "Second argument must be an identifier for the service type", + ) + .to_compile_error() + .into(); + } + }; + + // Extract the variants from the enum + let data_enum = match &input.data { Data::Enum(data_enum) => data_enum, _ => { - return syn::Error::new(input.span(), "RpcRequests can only be applied to enums") + return syn::Error::new(input.ident.span(), "This macro only works on enums") .to_compile_error() - .into() + .into(); } }; - let mut additional_items = Vec::new(); - let mut types = HashSet::new(); + // Generate variants for the message enum + let message_variants = data_enum.variants.iter().map(|variant| { + let variant_name = &variant.ident; - for variant in &mut data_enum.variants { - // Check field structure for every variant - let request_type = match &variant.fields { - Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, + // Extract the inner type from the variant + let inner_type = match &variant.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { + if let Some(last_segment) = type_path.path.segments.last() { + &last_segment.ident + } else { + return syn::Error::new( + variant.ident.span(), + "Unable to extract type name from variant", + ) + .to_compile_error(); + } + } else { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field of a simple type", + ) + .to_compile_error(); + } + } _ => { return syn::Error::new( - variant.span(), - "Each variant must have exactly one unnamed field", + variant.ident.span(), + "Variant must contain exactly one unnamed field", ) - .to_compile_error() - .into() + .to_compile_error(); } }; - if !types.insert(request_type.to_token_stream().to_string()) { - return syn::Error::new(input_span, "Each variant must have a unique request type") - .to_compile_error() - .into(); - } - - // Extract and remove RPC attributes - let mut rpc_attr = Vec::new(); - variant.attrs.retain(|attr| { - for ident in IDENTS { - if attr.path.is_ident(ident) { - rpc_attr.push((ident, attr.clone())); - return false; - } - } - true - }); - - // Fail if there are multiple RPC patterns - if rpc_attr.len() > 1 { - return syn::Error::new(variant.span(), "Each variant can only have one RPC pattern") - .to_compile_error() - .into(); - } - - if let Some((ident, attr)) = rpc_attr.pop() { - let args = match attr.parse_args::() { - Ok(info) => info, - Err(e) => return e.to_compile_error().into(), - }; - - match generate_rpc_impls(ident, args, &service_name, request_type, attr.span()) { - Ok(impls) => additional_items.extend(impls), - Err(e) => return e.to_compile_error().into(), - } + // Generate the message variant + quote! { + #variant_name(WithChannels<#inner_type, #service_type>) } - } + }); - let output = quote! { + // Generate the message enum + let expanded = quote! { + // Keep the original protocol enum #input - #(#additional_items)* + // Generate the message enum + #[derive(derive_more::From)] + enum #message_enum_name { + #(#message_variants),* + } }; - output.into() -} - -struct RpcArgs { - types: BTreeMap, + expanded.into() } -impl RpcArgs { - /// Get and remove a type from the map, failing if it doesn't exist - fn get(&mut self, key: &str, kind: &str, span: Span) -> syn::Result { - self.types - .remove(key) - .ok_or_else(|| syn::Error::new(span, format!("{kind} requires a {key} type"))) +/// A macro to generate a Message enum from a Protocol enum and implement Channels for each variant. +/// +/// This macro: +/// 1. Takes a Protocol enum and generates a corresponding Message enum with variants wrapped in WithChannels +/// 2. Implements the Channels trait for each variant's inner type based on #[tx(...)] and #[rx(...)] attributes +/// +/// Channel receiver (rx) defaults to NoReceiver if not specified. +/// +/// # Example +/// +/// ``` +/// #[rpc_protocol(StorageMessage, StorageService)] +/// enum StorageProtocol { +/// #[tx(oneshot::Sender>)] +/// Get(Get), +/// #[tx(oneshot::Sender<()>)] +/// Set(Set), +/// #[tx(mpsc::Sender)] +/// List(List), +/// #[rx(mpsc::Receiver)] +/// #[tx(mpsc::Sender)] +/// Query(Query), +/// } +/// ``` +#[proc_macro_attribute] +pub fn rpc_protocol(attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the input + let mut input = parse_macro_input!(item as DeriveInput); + let attr_args = parse_macro_input!(attr as syn::AttributeArgs); + + // Extract message enum name and service type from attributes + if attr_args.len() != 2 { + return syn::Error::new( + proc_macro2::Span::call_site(), + "Expected two arguments: message_enum_name and service_type", + ) + .to_compile_error() + .into(); } - - /// Fail if there are any unknown arguments remaining - fn check_empty(&self, span: Span) -> syn::Result<()> { - if self.types.is_empty() { - Ok(()) - } else { - Err(syn::Error::new( - span, - format!( - "Unknown arguments provided: {:?}", - self.types.keys().collect::>() - ), - )) + + let message_enum_name = match &attr_args[0] { + syn::NestedMeta::Meta(syn::Meta::Path(path)) => { + if let Some(ident) = path.get_ident() { + ident + } else { + return syn::Error::new( + proc_macro2::Span::call_site(), + "First argument must be an identifier for the message enum name", + ).to_compile_error().into(); + } + }, + _ => { + return syn::Error::new( + proc_macro2::Span::call_site(), + "First argument must be an identifier for the message enum name", + ).to_compile_error().into(); } - } -} - -/// Parse the rpc args as a comma separated list of name=type pairs -impl Parse for RpcArgs { - fn parse(input: ParseStream) -> syn::Result { - let mut types = BTreeMap::new(); - - loop { - if input.is_empty() { - break; + }; + + let service_type = match &attr_args[1] { + syn::NestedMeta::Meta(syn::Meta::Path(path)) => { + if let Some(ident) = path.get_ident() { + ident + } else { + return syn::Error::new( + proc_macro2::Span::call_site(), + "Second argument must be an identifier for the service type", + ).to_compile_error().into(); } - - let key: Ident = input.parse()?; - let _: Token![=] = input.parse()?; - let value: Type = input.parse()?; - - types.insert(key.to_string(), value); - - if !input.peek(Token![,]) { - break; + }, + _ => { + return syn::Error::new( + proc_macro2::Span::call_site(), + "Second argument must be an identifier for the service type", + ).to_compile_error().into(); + } + }; + + // Extract the variants from the enum + let data_enum = match &mut input.data { + Data::Enum(data_enum) => data_enum, + _ => { + return syn::Error::new( + input.ident.span(), + "This macro only works on enums", + ).to_compile_error().into(); + } + }; + + // Generate variants for the message enum + let message_variants = data_enum.variants.iter().map(|variant| { + let variant_name = &variant.ident; + + // Extract the inner type from the variant + let inner_type = match &variant.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { + if let Some(last_segment) = type_path.path.segments.last() { + &last_segment.ident + } else { + return syn::Error::new( + variant.ident.span(), + "Unable to extract type name from variant", + ).to_compile_error(); + } + } else { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field of a simple type", + ).to_compile_error(); + } + }, + _ => { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field", + ).to_compile_error(); } - let _: Token![,] = input.parse()?; + }; + + quote! { + #variant_name(WithChannels<#inner_type, #service_type>) } - - Ok(RpcArgs { types }) + }); + + // Storage for additional implementations + let mut channel_impls = Vec::new(); + + // Process each variant + for variant in &mut data_enum.variants { + // Extract the inner type from the variant + let inner_type = match &variant.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { + if let Some(last_segment) = type_path.path.segments.last() { + last_segment.ident.clone() + } else { + return syn::Error::new( + variant.ident.span(), + "Unable to extract type name from variant", + ).to_compile_error().into(); + } + } else { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field of a simple type", + ).to_compile_error().into(); + } + }, + _ => { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field", + ).to_compile_error().into(); + } + }; + + // Default rx to NoReceiver + let mut rx_type = quote! { NoReceiver }; + let mut tx_type = None; + + // Extract and remove rx attributes + let mut rx_attr = None; + variant.attrs.retain(|attr| { + if attr.path.is_ident("rx") { + rx_attr = Some(attr.clone()); + false + } else { + true + } + }); + + // Extract and remove tx attributes + let mut tx_attr = None; + variant.attrs.retain(|attr| { + if attr.path.is_ident("tx") { + tx_attr = Some(attr.clone()); + false + } else { + true + } + }); + + // Parse rx attribute if found + if let Some(attr) = rx_attr { + match attr.parse_args::() { + Ok(args) => { + rx_type = quote! { #args.rx_type }; + }, + Err(e) => return e.to_compile_error().into(), + } + } + + // Parse tx attribute - required + let tx_attr = match tx_attr { + Some(attr) => attr, + None => { + return syn::Error::new( + variant.ident.span(), + format!("Missing #[tx(...)] attribute for variant {}", variant.ident), + ).to_compile_error().into(); + } + }; + + match tx_attr.parse_args::() { + Ok(args) => { + tx_type = Some(quote! { #args.tx_type }); + }, + Err(e) => return e.to_compile_error().into(), + } + + // Generate the Channel implementation + let tx_type = tx_type.unwrap(); + channel_impls.push(quote! { + impl Channels<#service_type> for #inner_type { + type Rx = #rx_type; + type Tx = #tx_type; + } + }); } -} + + // Generate the complete output + let expanded = quote! { + // Keep the original protocol enum + #input + + // Generate the message enum + #[derive(derive_more::From)] + enum #message_enum_name { + #(#message_variants),* + } + + // Implement Channels for each type + #(#channel_impls)* + }; + + expanded.into() +} \ No newline at end of file diff --git a/quic-rpc-derive/Cargo.toml b/quic-rpc-derive/Cargo.toml index 7525b37..7c48850 100644 --- a/quic-rpc-derive/Cargo.toml +++ b/quic-rpc-derive/Cargo.toml @@ -16,6 +16,7 @@ proc-macro = true syn = { version = "1", features = ["full"] } quote = "1" proc-macro2 = "1" +quic-rpc = { path = ".." } [dev-dependencies] derive_more = { version = "1", features = ["from", "try_into", "display"] } diff --git a/quic-rpc-derive/examples/derive.rs b/quic-rpc-derive/examples/derive.rs new file mode 100644 index 0000000..741fc87 --- /dev/null +++ b/quic-rpc-derive/examples/derive.rs @@ -0,0 +1,251 @@ +use std::{ + collections::BTreeMap, + marker::PhantomData, + net::{Ipv4Addr, SocketAddr, SocketAddrV4}, + sync::Arc, +}; + +use n0_future::task::AbortOnDropHandle; +use quic_rpc::{ + channel::{mpsc, none::NoReceiver, oneshot}, + rpc::{listen, Handler}, + util::{make_client_endpoint, make_server_endpoint}, + Channels, LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, +}; +use quic_rpc_derive::rpc_protocol; +use serde::{Deserialize, Serialize}; +use tracing::info; + +/// A simple storage service, just to try it out +#[derive(Debug, Clone, Copy)] +struct StorageService; + +impl Service for StorageService {} + +#[derive(Debug, Serialize, Deserialize)] +struct Get { + key: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct List; + +#[derive(Debug, Serialize, Deserialize)] +struct Set { + key: String, + value: String, +} + +// mod manual { +// use super::*; +// impl Channels for Get { +// type Rx = NoReceiver; +// type Tx = oneshot::Sender>; +// } + +// impl Channels for List { +// type Rx = NoReceiver; +// type Tx = mpsc::Sender; +// } + +// impl Channels for Set { +// type Rx = NoReceiver; +// type Tx = oneshot::Sender<()>; +// } +// } + +#[derive(derive_more::From, Serialize, Deserialize)] +#[rpc_protocol(StorageMessage, StorageService)] +enum StorageProtocol { + #[tx(oneshot::Sender>)] + Get(Get), + #[tx(oneshot::Sender<()>)] + Set(Set), + #[tx(mpsc::Sender)] + List(List), +} + +struct StorageActor { + recv: tokio::sync::mpsc::Receiver, + state: BTreeMap, +} + +impl StorageActor { + pub fn local() -> StorageApi { + let (tx, rx) = tokio::sync::mpsc::channel(1); + let actor = Self { + recv: rx, + state: BTreeMap::new(), + }; + tokio::spawn(actor.run()); + let local = LocalMpscChannel::::from(tx); + StorageApi { + inner: local.into(), + } + } + + async fn run(mut self) { + while let Some(msg) = self.recv.recv().await { + self.handle(msg).await; + } + } + + async fn handle(&mut self, msg: StorageMessage) { + match msg { + StorageMessage::Get(get) => { + info!("get {:?}", get); + let WithChannels { tx, inner, .. } = get; + tx.send(self.state.get(&inner.key).cloned()).await.ok(); + } + StorageMessage::Set(set) => { + info!("set {:?}", set); + let WithChannels { tx, inner, .. } = set; + self.state.insert(inner.key, inner.value); + tx.send(()).await.ok(); + } + StorageMessage::List(list) => { + info!("list {:?}", list); + let WithChannels { mut tx, .. } = list; + for (key, value) in &self.state { + if tx.send(format!("{key}={value}")).await.is_err() { + break; + } + } + } + } + } +} +struct StorageApi { + inner: ServiceSender, +} + +impl StorageApi { + pub fn connect(endpoint: quinn::Endpoint, addr: SocketAddr) -> anyhow::Result { + Ok(StorageApi { + inner: ServiceSender::Remote(endpoint, addr, PhantomData), + }) + } + + pub fn listen(&self, endpoint: quinn::Endpoint) -> anyhow::Result> { + match &self.inner { + ServiceSender::Local(local, _) => { + let local = LocalMpscChannel::from(local.clone()); + let fun: Handler = Arc::new(move |msg, _, tx| { + let local = local.clone(); + Box::pin(async move { + match msg { + StorageProtocol::Get(msg) => { + local.send((msg, tx)).await?; + } + StorageProtocol::Set(msg) => { + local.send((msg, tx)).await?; + } + StorageProtocol::List(msg) => { + local.send((msg, tx)).await?; + } + }; + Ok(()) + }) + }); + Ok(listen(endpoint, fun)) + } + ServiceSender::Remote(_, _, _) => { + Err(anyhow::anyhow!("cannot listen on a remote service")) + } + } + } + + pub async fn get(&self, key: String) -> anyhow::Result>> { + let msg = Get { key }; + match self.inner.request().await? { + ServiceRequest::Local(request, _) => { + let (tx, rx) = oneshot::channel(); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } + + pub async fn list(&self) -> anyhow::Result> { + let msg = List; + match self.inner.request().await? { + ServiceRequest::Local(request, _) => { + let (tx, rx) = mpsc::channel(10); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } + + pub async fn set(&self, key: String, value: String) -> anyhow::Result> { + let msg = Set { key, value }; + match self.inner.request().await? { + ServiceRequest::Local(request, _) => { + let (tx, rx) = oneshot::channel(); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } +} + +async fn local() -> anyhow::Result<()> { + let api = StorageActor::local(); + api.set("hello".to_string(), "world".to_string()) + .await? + .await?; + let value = api.get("hello".to_string()).await?.await?; + let mut list = api.list().await?; + while let Some(value) = list.recv().await? { + println!("list value = {:?}", value); + } + println!("value = {:?}", value); + Ok(()) +} + +async fn remote() -> anyhow::Result<()> { + let port = 10113; + let (server, cert) = + make_server_endpoint(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port).into())?; + let client = + make_client_endpoint(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0).into(), &[&cert])?; + let store = StorageActor::local(); + let handle = store.listen(server)?; + let api = StorageApi::connect(client, SocketAddrV4::new(Ipv4Addr::LOCALHOST, port).into())?; + api.set("hello".to_string(), "world".to_string()) + .await? + .await?; + api.set("goodbye".to_string(), "world".to_string()) + .await? + .await?; + let value = api.get("hello".to_string()).await?.await?; + println!("value = {:?}", value); + let mut list = api.list().await?; + while let Some(value) = list.recv().await? { + println!("list value = {:?}", value); + } + drop(handle); + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt().init(); + println!("Local use"); + local().await?; + println!("Remote use"); + remote().await?; + Ok(()) +} diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index 7d28e66..c1028ac 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -1,6 +1,33 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput, Fields, Type}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + spanned::Spanned, + Data, DeriveInput, Fields, Ident, Token, Type, +}; + +struct TxArgs { + tx_type: Type, +} + +impl Parse for TxArgs { + fn parse(input: ParseStream) -> syn::Result { + let tx_type: Type = input.parse()?; + Ok(TxArgs { tx_type }) + } +} + +struct RxArgs { + rx_type: Type, +} + +impl Parse for RxArgs { + fn parse(input: ParseStream) -> syn::Result { + let rx_type: Type = input.parse()?; + Ok(RxArgs { rx_type }) + } +} /// A macro to generate a Message enum from a Protocol enum. /// @@ -134,3 +161,242 @@ pub fn message_enum(attr: TokenStream, item: TokenStream) -> TokenStream { expanded.into() } + +/// A macro to generate a Message enum from a Protocol enum and implement Channels for each variant. +/// +/// This macro: +/// 1. Takes a Protocol enum and generates a corresponding Message enum with variants wrapped in WithChannels +/// 2. Implements the Channels trait for each variant's inner type based on #[tx(...)] and #[rx(...)] attributes +/// +/// Channel receiver (rx) defaults to NoReceiver if not specified. +/// +/// # Example +/// +/// ``` +/// #[rpc_protocol(StorageMessage, StorageService)] +/// enum StorageProtocol { +/// #[tx(oneshot::Sender>)] +/// Get(Get), +/// #[tx(oneshot::Sender<()>)] +/// Set(Set), +/// #[tx(mpsc::Sender)] +/// List(List), +/// #[rx(mpsc::Receiver)] +/// #[tx(mpsc::Sender)] +/// Query(Query), +/// } +/// ``` +#[proc_macro_attribute] +pub fn rpc_protocol(attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the input + let mut input = parse_macro_input!(item as DeriveInput); + let attr_args = parse_macro_input!(attr as syn::AttributeArgs); + + // Extract message enum name and service type from attributes + if attr_args.len() != 2 { + return syn::Error::new( + proc_macro2::Span::call_site(), + "Expected two arguments: message_enum_name and service_type", + ) + .to_compile_error() + .into(); + } + + let message_enum_name = match &attr_args[0] { + syn::NestedMeta::Meta(syn::Meta::Path(path)) => { + if let Some(ident) = path.get_ident() { + ident + } else { + return syn::Error::new( + proc_macro2::Span::call_site(), + "First argument must be an identifier for the message enum name", + ).to_compile_error().into(); + } + }, + _ => { + return syn::Error::new( + proc_macro2::Span::call_site(), + "First argument must be an identifier for the message enum name", + ).to_compile_error().into(); + } + }; + + let service_type = match &attr_args[1] { + syn::NestedMeta::Meta(syn::Meta::Path(path)) => { + if let Some(ident) = path.get_ident() { + ident + } else { + return syn::Error::new( + proc_macro2::Span::call_site(), + "Second argument must be an identifier for the service type", + ).to_compile_error().into(); + } + }, + _ => { + return syn::Error::new( + proc_macro2::Span::call_site(), + "Second argument must be an identifier for the service type", + ).to_compile_error().into(); + } + }; + + // Extract the variants from the enum + let data_enum = match &mut input.data { + Data::Enum(data_enum) => data_enum, + _ => { + return syn::Error::new( + input.ident.span(), + "This macro only works on enums", + ).to_compile_error().into(); + } + }; + + // Generate variants for the message enum + let message_variants = data_enum.variants.iter().map(|variant| { + let variant_name = &variant.ident; + + // Extract the inner type from the variant + let inner_type = match &variant.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { + if let Some(last_segment) = type_path.path.segments.last() { + &last_segment.ident + } else { + return syn::Error::new( + variant.ident.span(), + "Unable to extract type name from variant", + ).to_compile_error(); + } + } else { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field of a simple type", + ).to_compile_error(); + } + }, + _ => { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field", + ).to_compile_error(); + } + }; + + quote! { + #variant_name(WithChannels<#inner_type, #service_type>) + } + }); + + // Storage for additional implementations + let mut channel_impls = Vec::new(); + + // Process each variant + for variant in &mut data_enum.variants { + // Extract the inner type from the variant + let inner_type = match &variant.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { + if let Some(last_segment) = type_path.path.segments.last() { + last_segment.ident.clone() + } else { + return syn::Error::new( + variant.ident.span(), + "Unable to extract type name from variant", + ).to_compile_error().into(); + } + } else { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field of a simple type", + ).to_compile_error().into(); + } + }, + _ => { + return syn::Error::new( + variant.ident.span(), + "Variant must contain exactly one unnamed field", + ).to_compile_error().into(); + } + }; + + // Default rx to NoReceiver + let mut rx_type = quote! { NoReceiver }; + let mut tx_type = None; + + // Extract and remove rx attributes + let mut rx_attr = None; + variant.attrs.retain(|attr| { + if attr.path.is_ident("rx") { + rx_attr = Some(attr.clone()); + false + } else { + true + } + }); + + // Extract and remove tx attributes + let mut tx_attr = None; + variant.attrs.retain(|attr| { + if attr.path.is_ident("tx") { + tx_attr = Some(attr.clone()); + false + } else { + true + } + }); + + // Parse rx attribute if found + if let Some(attr) = rx_attr { + match attr.parse_args::() { + Ok(args) => { + rx_type = quote! { #args.rx_type }; + }, + Err(e) => return e.to_compile_error().into(), + } + } + + // Parse tx attribute - required + let tx_attr = match tx_attr { + Some(attr) => attr, + None => { + return syn::Error::new( + variant.ident.span(), + format!("Missing #[tx(...)] attribute for variant {}", variant.ident), + ).to_compile_error().into(); + } + }; + + match tx_attr.parse_args::() { + Ok(args) => { + tx_type = Some(quote! { #args.tx_type }); + }, + Err(e) => return e.to_compile_error().into(), + } + + // Generate the Channel implementation + let tx_type = tx_type.unwrap(); + channel_impls.push(quote! { + impl Channels<#service_type> for #inner_type { + type Rx = #rx_type; + type Tx = #tx_type; + } + }); + } + + // Generate the complete output + let expanded = quote! { + // Keep the original protocol enum + #input + + // Generate the message enum + #[derive(derive_more::From)] + enum #message_enum_name { + #(#message_variants),* + } + + // Implement Channels for each type + #(#channel_impls)* + }; + + expanded.into() +} \ No newline at end of file diff --git a/quic-rpc-derive/tests/compile_fail/duplicate_type.rs b/quic-rpc-derive/tests/compile_fail/duplicate_type.rs new file mode 100644 index 0000000..45db393 --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/duplicate_type.rs @@ -0,0 +1,9 @@ +use quic_rpc_derive::rpc_requests; + +#[rpc_requests(Service)] +enum Enum { + A(u8), + B(u8), +} + +fn main() {} \ No newline at end of file diff --git a/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr b/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr new file mode 100644 index 0000000..71c5e95 --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr @@ -0,0 +1,5 @@ +error: Each variant must have a unique request type + --> tests/compile_fail/duplicate_type.rs:4:1 + | +4 | enum Enum { + | ^^^^ diff --git a/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs b/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs new file mode 100644 index 0000000..7ca34a9 --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs @@ -0,0 +1,9 @@ +use quic_rpc_derive::rpc_requests; + +#[rpc_requests(Service)] +enum Enum { + #[rpc(response = Bla, fnord = Foo)] + A(u8), +} + +fn main() {} \ No newline at end of file diff --git a/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr b/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr new file mode 100644 index 0000000..2de36f7 --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr @@ -0,0 +1,5 @@ +error: Unknown arguments provided: ["fnord"] + --> tests/compile_fail/extra_attr_types.rs:5:5 + | +5 | #[rpc(response = Bla, fnord = Foo)] + | ^ diff --git a/quic-rpc-derive/tests/compile_fail/multiple_fields.rs b/quic-rpc-derive/tests/compile_fail/multiple_fields.rs new file mode 100644 index 0000000..b4ec1eb --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/multiple_fields.rs @@ -0,0 +1,8 @@ +use quic_rpc_derive::rpc_requests; + +#[rpc_requests(Service)] +enum Enum { + A(u8, u8), +} + +fn main() {} \ No newline at end of file diff --git a/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr b/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr new file mode 100644 index 0000000..80fc879 --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr @@ -0,0 +1,5 @@ +error: Each variant must have exactly one unnamed field + --> tests/compile_fail/multiple_fields.rs:5:5 + | +5 | A(u8, u8), + | ^ diff --git a/quic-rpc-derive/tests/compile_fail/named_enum.rs b/quic-rpc-derive/tests/compile_fail/named_enum.rs new file mode 100644 index 0000000..4bd442c --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/named_enum.rs @@ -0,0 +1,8 @@ +use quic_rpc_derive::rpc_requests; + +#[rpc_requests(Service)] +enum Enum { + A { name: u8 }, +} + +fn main() {} \ No newline at end of file diff --git a/quic-rpc-derive/tests/compile_fail/named_enum.stderr b/quic-rpc-derive/tests/compile_fail/named_enum.stderr new file mode 100644 index 0000000..f13da1d --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/named_enum.stderr @@ -0,0 +1,5 @@ +error: Each variant must have exactly one unnamed field + --> tests/compile_fail/named_enum.rs:5:5 + | +5 | A { name: u8 }, + | ^ diff --git a/quic-rpc-derive/tests/compile_fail/non_enum.rs b/quic-rpc-derive/tests/compile_fail/non_enum.rs new file mode 100644 index 0000000..e80782d --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/non_enum.rs @@ -0,0 +1,6 @@ +use quic_rpc_derive::rpc_requests; + +#[rpc_requests(Service)] +struct Foo; + +fn main() {} \ No newline at end of file diff --git a/quic-rpc-derive/tests/compile_fail/non_enum.stderr b/quic-rpc-derive/tests/compile_fail/non_enum.stderr new file mode 100644 index 0000000..c0286ef --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/non_enum.stderr @@ -0,0 +1,5 @@ +error: RpcRequests can only be applied to enums + --> tests/compile_fail/non_enum.rs:4:1 + | +4 | struct Foo; + | ^^^^^^ diff --git a/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs b/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs new file mode 100644 index 0000000..2daca8d --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs @@ -0,0 +1,9 @@ +use quic_rpc_derive::rpc_requests; + +#[rpc_requests(Service)] +enum Enum { + #[rpc(fnord = Bla)] + A(u8), +} + +fn main() {} \ No newline at end of file diff --git a/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr b/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr new file mode 100644 index 0000000..4c81995 --- /dev/null +++ b/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr @@ -0,0 +1,5 @@ +error: rpc requires a response type + --> tests/compile_fail/wrong_attr_types.rs:5:5 + | +5 | #[rpc(fnord = Bla)] + | ^ diff --git a/quic-rpc-derive/tests/smoke.rs b/quic-rpc-derive/tests/smoke.rs new file mode 100644 index 0000000..b6fe521 --- /dev/null +++ b/quic-rpc-derive/tests/smoke.rs @@ -0,0 +1,79 @@ +use quic_rpc_derive::rpc_requests; +use serde::{Deserialize, Serialize}; + +#[test] +fn simple() { + #[derive(Debug, Serialize, Deserialize)] + struct RpcRequest; + + #[derive(Debug, Serialize, Deserialize)] + struct ServerStreamingRequest; + + #[derive(Debug, Serialize, Deserialize)] + struct ClientStreamingRequest; + + #[derive(Debug, Serialize, Deserialize)] + struct BidiStreamingRequest; + + #[derive(Debug, Serialize, Deserialize)] + struct Update1; + + #[derive(Debug, Serialize, Deserialize)] + struct Update2; + + #[derive(Debug, Serialize, Deserialize)] + struct Response1; + + #[derive(Debug, Serialize, Deserialize)] + struct Response2; + + #[derive(Debug, Serialize, Deserialize)] + struct Response3; + + #[derive(Debug, Serialize, Deserialize)] + struct Response4; + + #[rpc_requests(Service)] + #[derive(Debug, Serialize, Deserialize, derive_more::From, derive_more::TryInto)] + enum Request { + #[rpc(response=Response1)] + Rpc(RpcRequest), + #[server_streaming(response=Response2)] + ServerStreaming(ServerStreamingRequest), + #[bidi_streaming(update= Update1, response = Response3)] + BidiStreaming(BidiStreamingRequest), + #[client_streaming(update = Update2, response = Response4)] + ClientStreaming(ClientStreamingRequest), + Update1(Update1), + Update2(Update2), + } + + #[derive(Debug, Serialize, Deserialize, derive_more::From, derive_more::TryInto)] + enum Response { + Response1(Response1), + Response2(Response2), + Response3(Response3), + Response4(Response4), + } + + #[derive(Debug, Clone)] + struct Service; + + impl quic_rpc::Service for Service { + type Req = Request; + type Res = Response; + } + + let _ = Service; +} + +/// Use +/// +/// TRYBUILD=overwrite cargo test --test smoke +/// +/// to update the snapshots +#[test] +fn compile_fail() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compile_fail/*.rs"); +} From 97d53a3baedf50b603b10690451974666dac621f Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 13:08:30 +0200 Subject: [PATCH 06/43] WIP macros --- Cargo.lock | 60 +++- quic-rpc-derive/Cargo.toml | 2 +- quic-rpc-derive/src/lib.rs | 552 +++++++++++++------------------------ 3 files changed, 251 insertions(+), 363 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfc576d..81f2e49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,6 +224,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -248,6 +257,18 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + [[package]] name = "futures-buffered" version = "0.2.11" @@ -512,6 +533,12 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.12" @@ -597,6 +624,15 @@ dependencies = [ "web-time", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -766,13 +802,35 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "quic-rpc" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee5ffa5a7459678fb545b3666e1f9a08b01b6e6af56bf29cd54d07f29b97aaf2" +dependencies = [ + "anyhow", + "document-features", + "flume", + "futures-lite", + "futures-sink", + "futures-util", + "pin-project", + "serde", + "slab", + "smallvec", + "time", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "quic-rpc-derive" version = "0.18.1" dependencies = [ "derive_more 1.0.0", "proc-macro2", - "quic-rpc", + "quic-rpc 0.18.3 (registry+https://github.com/rust-lang/crates.io-index)", "quote", "serde", "syn 1.0.109", diff --git a/quic-rpc-derive/Cargo.toml b/quic-rpc-derive/Cargo.toml index 7c48850..96dd4dc 100644 --- a/quic-rpc-derive/Cargo.toml +++ b/quic-rpc-derive/Cargo.toml @@ -16,7 +16,7 @@ proc-macro = true syn = { version = "1", features = ["full"] } quote = "1" proc-macro2 = "1" -quic-rpc = { path = ".." } +quic-rpc = { version = "0.18.3" } [dev-dependencies] derive_more = { version = "1", features = ["from", "try_into", "display"] } diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index c1028ac..e31bb58 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -1,5 +1,8 @@ +use std::collections::{BTreeMap, HashSet}; + use proc_macro::TokenStream; -use quote::quote; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, @@ -7,396 +10,223 @@ use syn::{ Data, DeriveInput, Fields, Ident, Token, Type, }; -struct TxArgs { - tx_type: Type, -} - -impl Parse for TxArgs { - fn parse(input: ParseStream) -> syn::Result { - let tx_type: Type = input.parse()?; - Ok(TxArgs { tx_type }) - } -} - -struct RxArgs { - rx_type: Type, -} - -impl Parse for RxArgs { - fn parse(input: ParseStream) -> syn::Result { - let rx_type: Type = input.parse()?; - Ok(RxArgs { rx_type }) - } -} - -/// A macro to generate a Message enum from a Protocol enum. -/// -/// This macro takes a Protocol enum and generates a corresponding Message enum -/// where each variant is wrapped in a WithChannels type. -/// -/// # Example -/// -/// ``` -/// #[message_enum(StorageMessage, StorageService)] -/// #[derive(derive_more::From, Serialize, Deserialize)] -/// enum StorageProtocol { -/// Get(Get), -/// Set(Set), -/// List(List), -/// } -/// ``` -/// -/// Will generate: -/// -/// ``` -/// #[derive(derive_more::From)] -/// enum StorageMessage { -/// Get(WithChannels), -/// Set(WithChannels), -/// List(WithChannels), -/// } -/// ``` -#[proc_macro_attribute] -pub fn message_enum(attr: TokenStream, item: TokenStream) -> TokenStream { - // Parse the input - let input = parse_macro_input!(item as DeriveInput); - let attrs = parse_macro_input!(attr as syn::AttributeArgs); - - // Parse the attribute arguments - if attrs.len() != 2 { - return syn::Error::new( - proc_macro2::Span::call_site(), - "Expected two arguments: message_enum_name and service_type", - ) - .to_compile_error() - .into(); - } - - // Extract message enum name and service type from attributes - let message_enum_name = match &attrs[0] { - syn::NestedMeta::Meta(syn::Meta::Path(path)) => path.get_ident().unwrap(), - _ => { - return syn::Error::new( - proc_macro2::Span::call_site(), - "First argument must be an identifier for the message enum name", - ) - .to_compile_error() - .into(); - } - }; - - let service_type = match &attrs[1] { - syn::NestedMeta::Meta(syn::Meta::Path(path)) => path.get_ident().unwrap(), - _ => { - return syn::Error::new( - proc_macro2::Span::call_site(), - "Second argument must be an identifier for the service type", - ) - .to_compile_error() - .into(); +const SERVER_STREAMING: &str = "server_streaming"; +const CLIENT_STREAMING: &str = "client_streaming"; +const BIDI_STREAMING: &str = "bidi_streaming"; +const RPC: &str = "rpc"; +const TRY_SERVER_STREAMING: &str = "try_server_streaming"; +const IDENTS: [&str; 5] = [ + SERVER_STREAMING, + CLIENT_STREAMING, + BIDI_STREAMING, + RPC, + TRY_SERVER_STREAMING, +]; + +fn generate_rpc_impls( + pat: &str, + mut args: RpcArgs, + service_name: &Ident, + request_type: &Type, + attr_span: Span, +) -> syn::Result { + let res = match pat { + RPC => { + let response = args.get("response", pat, attr_span)?; + quote! { + impl ::quic_rpc::pattern::rpc::RpcMsg<#service_name> for #request_type { + type Response = #response; + } + } } - }; - - // Extract the variants from the enum - let data_enum = match &input.data { - Data::Enum(data_enum) => data_enum, - _ => { - return syn::Error::new(input.ident.span(), "This macro only works on enums") - .to_compile_error() - .into(); + SERVER_STREAMING => { + let response = args.get("response", pat, attr_span)?; + quote! { + impl ::quic_rpc::message::Msg<#service_name> for #request_type { + type Pattern = ::quic_rpc::pattern::server_streaming::ServerStreaming; + } + impl ::quic_rpc::pattern::server_streaming::ServerStreamingMsg<#service_name> for #request_type { + type Response = #response; + } + } } - }; - - // Generate variants for the message enum - let message_variants = data_enum.variants.iter().map(|variant| { - let variant_name = &variant.ident; - - // Extract the inner type from the variant - let inner_type = match &variant.fields { - Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { - if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { - if let Some(last_segment) = type_path.path.segments.last() { - &last_segment.ident - } else { - return syn::Error::new( - variant.ident.span(), - "Unable to extract type name from variant", - ) - .to_compile_error(); - } - } else { - return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field of a simple type", - ) - .to_compile_error(); + BIDI_STREAMING => { + let update = args.get("update", pat, attr_span)?; + let response = args.get("response", pat, attr_span)?; + quote! { + impl ::quic_rpc::message::Msg<#service_name> for #request_type { + type Pattern = ::quic_rpc::pattern::bidi_streaming::BidiStreaming; + } + impl ::quic_rpc::pattern::bidi_streaming::BidiStreamingMsg<#service_name> for #request_type { + type Update = #update; + type Response = #response; } } - _ => { - return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field", - ) - .to_compile_error(); + } + CLIENT_STREAMING => { + let update = args.get("update", pat, attr_span)?; + let response = args.get("response", pat, attr_span)?; + quote! { + impl ::quic_rpc::message::Msg<#service_name> for #request_type { + type Pattern = ::quic_rpc::pattern::client_streaming::ClientStreaming; + } + impl ::quic_rpc::pattern::client_streaming::ClientStreamingMsg<#service_name> for #request_type { + type Update = #update; + type Response = #response; + } } - }; - - // Generate the message variant - quote! { - #variant_name(WithChannels<#inner_type, #service_type>) } - }); - - // Generate the message enum - let expanded = quote! { - // Keep the original protocol enum - #input - - // Generate the message enum - #[derive(derive_more::From)] - enum #message_enum_name { - #(#message_variants),* + TRY_SERVER_STREAMING => { + let create_error = args.get("create_error", pat, attr_span)?; + let item_error = args.get("item_error", pat, attr_span)?; + let item = args.get("item", pat, attr_span)?; + quote! { + impl ::quic_rpc::message::Msg<#service_name> for #request_type { + type Pattern = ::quic_rpc::pattern::try_server_streaming::TryServerStreaming; + } + impl ::quic_rpc::pattern::try_server_streaming::TryServerStreamingMsg<#service_name> for #request_type { + type CreateError = #create_error; + type ItemError = #item_error; + type Item = #item; + } + } } + _ => return Err(syn::Error::new(attr_span, "Unknown RPC pattern")), }; + args.check_empty(attr_span)?; - expanded.into() + Ok(res) } -/// A macro to generate a Message enum from a Protocol enum and implement Channels for each variant. -/// -/// This macro: -/// 1. Takes a Protocol enum and generates a corresponding Message enum with variants wrapped in WithChannels -/// 2. Implements the Channels trait for each variant's inner type based on #[tx(...)] and #[rx(...)] attributes -/// -/// Channel receiver (rx) defaults to NoReceiver if not specified. -/// -/// # Example -/// -/// ``` -/// #[rpc_protocol(StorageMessage, StorageService)] -/// enum StorageProtocol { -/// #[tx(oneshot::Sender>)] -/// Get(Get), -/// #[tx(oneshot::Sender<()>)] -/// Set(Set), -/// #[tx(mpsc::Sender)] -/// List(List), -/// #[rx(mpsc::Receiver)] -/// #[tx(mpsc::Sender)] -/// Query(Query), -/// } -/// ``` #[proc_macro_attribute] -pub fn rpc_protocol(attr: TokenStream, item: TokenStream) -> TokenStream { - // Parse the input +pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { let mut input = parse_macro_input!(item as DeriveInput); - let attr_args = parse_macro_input!(attr as syn::AttributeArgs); - - // Extract message enum name and service type from attributes - if attr_args.len() != 2 { - return syn::Error::new( - proc_macro2::Span::call_site(), - "Expected two arguments: message_enum_name and service_type", - ) - .to_compile_error() - .into(); - } - - let message_enum_name = match &attr_args[0] { - syn::NestedMeta::Meta(syn::Meta::Path(path)) => { - if let Some(ident) = path.get_ident() { - ident - } else { - return syn::Error::new( - proc_macro2::Span::call_site(), - "First argument must be an identifier for the message enum name", - ).to_compile_error().into(); - } - }, - _ => { - return syn::Error::new( - proc_macro2::Span::call_site(), - "First argument must be an identifier for the message enum name", - ).to_compile_error().into(); - } - }; - - let service_type = match &attr_args[1] { - syn::NestedMeta::Meta(syn::Meta::Path(path)) => { - if let Some(ident) = path.get_ident() { - ident - } else { - return syn::Error::new( - proc_macro2::Span::call_site(), - "Second argument must be an identifier for the service type", - ).to_compile_error().into(); - } - }, - _ => { - return syn::Error::new( - proc_macro2::Span::call_site(), - "Second argument must be an identifier for the service type", - ).to_compile_error().into(); - } - }; - - // Extract the variants from the enum + let service_name = parse_macro_input!(attr as Ident); + + let input_span = input.span(); let data_enum = match &mut input.data { Data::Enum(data_enum) => data_enum, _ => { - return syn::Error::new( - input.ident.span(), - "This macro only works on enums", - ).to_compile_error().into(); + return syn::Error::new(input.span(), "RpcRequests can only be applied to enums") + .to_compile_error() + .into() } }; - - // Generate variants for the message enum - let message_variants = data_enum.variants.iter().map(|variant| { - let variant_name = &variant.ident; - - // Extract the inner type from the variant - let inner_type = match &variant.fields { - Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { - if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { - if let Some(last_segment) = type_path.path.segments.last() { - &last_segment.ident - } else { - return syn::Error::new( - variant.ident.span(), - "Unable to extract type name from variant", - ).to_compile_error(); - } - } else { - return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field of a simple type", - ).to_compile_error(); - } - }, - _ => { - return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field", - ).to_compile_error(); - } - }; - - quote! { - #variant_name(WithChannels<#inner_type, #service_type>) - } - }); - - // Storage for additional implementations - let mut channel_impls = Vec::new(); - - // Process each variant + + let mut additional_items = Vec::new(); + let mut types = HashSet::new(); + for variant in &mut data_enum.variants { - // Extract the inner type from the variant - let inner_type = match &variant.fields { - Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { - if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { - if let Some(last_segment) = type_path.path.segments.last() { - last_segment.ident.clone() - } else { - return syn::Error::new( - variant.ident.span(), - "Unable to extract type name from variant", - ).to_compile_error().into(); - } - } else { - return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field of a simple type", - ).to_compile_error().into(); - } - }, + // Check field structure for every variant + let request_type = match &variant.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, _ => { return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field", - ).to_compile_error().into(); + variant.span(), + "Each variant must have exactly one unnamed field", + ) + .to_compile_error() + .into() } }; - - // Default rx to NoReceiver - let mut rx_type = quote! { NoReceiver }; - let mut tx_type = None; - - // Extract and remove rx attributes - let mut rx_attr = None; - variant.attrs.retain(|attr| { - if attr.path.is_ident("rx") { - rx_attr = Some(attr.clone()); - false - } else { - true - } - }); - - // Extract and remove tx attributes - let mut tx_attr = None; + + if !types.insert(request_type.to_token_stream().to_string()) { + return syn::Error::new(input_span, "Each variant must have a unique request type") + .to_compile_error() + .into(); + } + + // Extract and remove RPC attributes + let mut rpc_attr = Vec::new(); variant.attrs.retain(|attr| { - if attr.path.is_ident("tx") { - tx_attr = Some(attr.clone()); - false - } else { - true + for ident in IDENTS { + if attr.path.is_ident(ident) { + rpc_attr.push((ident, attr.clone())); + return false; + } } + true }); - - // Parse rx attribute if found - if let Some(attr) = rx_attr { - match attr.parse_args::() { - Ok(args) => { - rx_type = quote! { #args.rx_type }; - }, - Err(e) => return e.to_compile_error().into(), - } + + // Fail if there are multiple RPC patterns + if rpc_attr.len() > 1 { + return syn::Error::new(variant.span(), "Each variant can only have one RPC pattern") + .to_compile_error() + .into(); } - - // Parse tx attribute - required - let tx_attr = match tx_attr { - Some(attr) => attr, - None => { - return syn::Error::new( - variant.ident.span(), - format!("Missing #[tx(...)] attribute for variant {}", variant.ident), - ).to_compile_error().into(); + + if let Some((ident, attr)) = rpc_attr.pop() { + let args = match attr.parse_args::() { + Ok(info) => info, + Err(e) => return e.to_compile_error().into(), + }; + + match generate_rpc_impls(ident, args, &service_name, request_type, attr.span()) { + Ok(impls) => additional_items.extend(impls), + Err(e) => return e.to_compile_error().into(), } - }; - - match tx_attr.parse_args::() { - Ok(args) => { - tx_type = Some(quote! { #args.tx_type }); - }, - Err(e) => return e.to_compile_error().into(), } - - // Generate the Channel implementation - let tx_type = tx_type.unwrap(); - channel_impls.push(quote! { - impl Channels<#service_type> for #inner_type { - type Rx = #rx_type; - type Tx = #tx_type; - } - }); } - - // Generate the complete output - let expanded = quote! { - // Keep the original protocol enum + + let output = quote! { #input - - // Generate the message enum - #[derive(derive_more::From)] - enum #message_enum_name { - #(#message_variants),* - } - - // Implement Channels for each type - #(#channel_impls)* + + #(#additional_items)* }; - - expanded.into() + + output.into() +} + +struct RpcArgs { + types: BTreeMap, +} + +impl RpcArgs { + /// Get and remove a type from the map, failing if it doesn't exist + fn get(&mut self, key: &str, kind: &str, span: Span) -> syn::Result { + self.types + .remove(key) + .ok_or_else(|| syn::Error::new(span, format!("{kind} requires a {key} type"))) + } + + /// Fail if there are any unknown arguments remaining + fn check_empty(&self, span: Span) -> syn::Result<()> { + if self.types.is_empty() { + Ok(()) + } else { + Err(syn::Error::new( + span, + format!( + "Unknown arguments provided: {:?}", + self.types.keys().collect::>() + ), + )) + } + } +} + +/// Parse the rpc args as a comma separated list of name=type pairs +impl Parse for RpcArgs { + fn parse(input: ParseStream) -> syn::Result { + let mut types = BTreeMap::new(); + + loop { + if input.is_empty() { + break; + } + + let key: Ident = input.parse()?; + let _: Token![=] = input.parse()?; + let value: Type = input.parse()?; + + types.insert(key.to_string(), value); + + if !input.peek(Token![,]) { + break; + } + let _: Token![,] = input.parse()?; + } + + Ok(RpcArgs { types }) + } } \ No newline at end of file From 37c1144baf5ffbda84bc5ae95d786cd2d5541399 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 16:31:47 +0200 Subject: [PATCH 07/43] WIP rename --- Cargo.lock | 60 +--- old/quic-rpc-derive/src/lib.rs | 552 ++++++++++------------------- quic-rpc-derive/Cargo.toml | 2 +- quic-rpc-derive/examples/derive.rs | 251 ------------- quic-rpc-derive/src/lib.rs | 106 ++---- quic-rpc-derive/tests/smoke.rs | 41 +-- 6 files changed, 233 insertions(+), 779 deletions(-) delete mode 100644 quic-rpc-derive/examples/derive.rs diff --git a/Cargo.lock b/Cargo.lock index 81f2e49..cfc576d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,15 +224,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" -[[package]] -name = "document-features" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" -dependencies = [ - "litrs", -] - [[package]] name = "embedded-io" version = "0.4.0" @@ -257,18 +248,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "spin", -] - [[package]] name = "futures-buffered" version = "0.2.11" @@ -533,12 +512,6 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" -[[package]] -name = "litrs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" - [[package]] name = "lock_api" version = "0.4.12" @@ -624,15 +597,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -802,35 +766,13 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "quic-rpc" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee5ffa5a7459678fb545b3666e1f9a08b01b6e6af56bf29cd54d07f29b97aaf2" -dependencies = [ - "anyhow", - "document-features", - "flume", - "futures-lite", - "futures-sink", - "futures-util", - "pin-project", - "serde", - "slab", - "smallvec", - "time", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "quic-rpc-derive" version = "0.18.1" dependencies = [ "derive_more 1.0.0", "proc-macro2", - "quic-rpc 0.18.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quic-rpc", "quote", "serde", "syn 1.0.109", diff --git a/old/quic-rpc-derive/src/lib.rs b/old/quic-rpc-derive/src/lib.rs index c1028ac..e31bb58 100644 --- a/old/quic-rpc-derive/src/lib.rs +++ b/old/quic-rpc-derive/src/lib.rs @@ -1,5 +1,8 @@ +use std::collections::{BTreeMap, HashSet}; + use proc_macro::TokenStream; -use quote::quote; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, @@ -7,396 +10,223 @@ use syn::{ Data, DeriveInput, Fields, Ident, Token, Type, }; -struct TxArgs { - tx_type: Type, -} - -impl Parse for TxArgs { - fn parse(input: ParseStream) -> syn::Result { - let tx_type: Type = input.parse()?; - Ok(TxArgs { tx_type }) - } -} - -struct RxArgs { - rx_type: Type, -} - -impl Parse for RxArgs { - fn parse(input: ParseStream) -> syn::Result { - let rx_type: Type = input.parse()?; - Ok(RxArgs { rx_type }) - } -} - -/// A macro to generate a Message enum from a Protocol enum. -/// -/// This macro takes a Protocol enum and generates a corresponding Message enum -/// where each variant is wrapped in a WithChannels type. -/// -/// # Example -/// -/// ``` -/// #[message_enum(StorageMessage, StorageService)] -/// #[derive(derive_more::From, Serialize, Deserialize)] -/// enum StorageProtocol { -/// Get(Get), -/// Set(Set), -/// List(List), -/// } -/// ``` -/// -/// Will generate: -/// -/// ``` -/// #[derive(derive_more::From)] -/// enum StorageMessage { -/// Get(WithChannels), -/// Set(WithChannels), -/// List(WithChannels), -/// } -/// ``` -#[proc_macro_attribute] -pub fn message_enum(attr: TokenStream, item: TokenStream) -> TokenStream { - // Parse the input - let input = parse_macro_input!(item as DeriveInput); - let attrs = parse_macro_input!(attr as syn::AttributeArgs); - - // Parse the attribute arguments - if attrs.len() != 2 { - return syn::Error::new( - proc_macro2::Span::call_site(), - "Expected two arguments: message_enum_name and service_type", - ) - .to_compile_error() - .into(); - } - - // Extract message enum name and service type from attributes - let message_enum_name = match &attrs[0] { - syn::NestedMeta::Meta(syn::Meta::Path(path)) => path.get_ident().unwrap(), - _ => { - return syn::Error::new( - proc_macro2::Span::call_site(), - "First argument must be an identifier for the message enum name", - ) - .to_compile_error() - .into(); - } - }; - - let service_type = match &attrs[1] { - syn::NestedMeta::Meta(syn::Meta::Path(path)) => path.get_ident().unwrap(), - _ => { - return syn::Error::new( - proc_macro2::Span::call_site(), - "Second argument must be an identifier for the service type", - ) - .to_compile_error() - .into(); +const SERVER_STREAMING: &str = "server_streaming"; +const CLIENT_STREAMING: &str = "client_streaming"; +const BIDI_STREAMING: &str = "bidi_streaming"; +const RPC: &str = "rpc"; +const TRY_SERVER_STREAMING: &str = "try_server_streaming"; +const IDENTS: [&str; 5] = [ + SERVER_STREAMING, + CLIENT_STREAMING, + BIDI_STREAMING, + RPC, + TRY_SERVER_STREAMING, +]; + +fn generate_rpc_impls( + pat: &str, + mut args: RpcArgs, + service_name: &Ident, + request_type: &Type, + attr_span: Span, +) -> syn::Result { + let res = match pat { + RPC => { + let response = args.get("response", pat, attr_span)?; + quote! { + impl ::quic_rpc::pattern::rpc::RpcMsg<#service_name> for #request_type { + type Response = #response; + } + } } - }; - - // Extract the variants from the enum - let data_enum = match &input.data { - Data::Enum(data_enum) => data_enum, - _ => { - return syn::Error::new(input.ident.span(), "This macro only works on enums") - .to_compile_error() - .into(); + SERVER_STREAMING => { + let response = args.get("response", pat, attr_span)?; + quote! { + impl ::quic_rpc::message::Msg<#service_name> for #request_type { + type Pattern = ::quic_rpc::pattern::server_streaming::ServerStreaming; + } + impl ::quic_rpc::pattern::server_streaming::ServerStreamingMsg<#service_name> for #request_type { + type Response = #response; + } + } } - }; - - // Generate variants for the message enum - let message_variants = data_enum.variants.iter().map(|variant| { - let variant_name = &variant.ident; - - // Extract the inner type from the variant - let inner_type = match &variant.fields { - Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { - if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { - if let Some(last_segment) = type_path.path.segments.last() { - &last_segment.ident - } else { - return syn::Error::new( - variant.ident.span(), - "Unable to extract type name from variant", - ) - .to_compile_error(); - } - } else { - return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field of a simple type", - ) - .to_compile_error(); + BIDI_STREAMING => { + let update = args.get("update", pat, attr_span)?; + let response = args.get("response", pat, attr_span)?; + quote! { + impl ::quic_rpc::message::Msg<#service_name> for #request_type { + type Pattern = ::quic_rpc::pattern::bidi_streaming::BidiStreaming; + } + impl ::quic_rpc::pattern::bidi_streaming::BidiStreamingMsg<#service_name> for #request_type { + type Update = #update; + type Response = #response; } } - _ => { - return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field", - ) - .to_compile_error(); + } + CLIENT_STREAMING => { + let update = args.get("update", pat, attr_span)?; + let response = args.get("response", pat, attr_span)?; + quote! { + impl ::quic_rpc::message::Msg<#service_name> for #request_type { + type Pattern = ::quic_rpc::pattern::client_streaming::ClientStreaming; + } + impl ::quic_rpc::pattern::client_streaming::ClientStreamingMsg<#service_name> for #request_type { + type Update = #update; + type Response = #response; + } } - }; - - // Generate the message variant - quote! { - #variant_name(WithChannels<#inner_type, #service_type>) } - }); - - // Generate the message enum - let expanded = quote! { - // Keep the original protocol enum - #input - - // Generate the message enum - #[derive(derive_more::From)] - enum #message_enum_name { - #(#message_variants),* + TRY_SERVER_STREAMING => { + let create_error = args.get("create_error", pat, attr_span)?; + let item_error = args.get("item_error", pat, attr_span)?; + let item = args.get("item", pat, attr_span)?; + quote! { + impl ::quic_rpc::message::Msg<#service_name> for #request_type { + type Pattern = ::quic_rpc::pattern::try_server_streaming::TryServerStreaming; + } + impl ::quic_rpc::pattern::try_server_streaming::TryServerStreamingMsg<#service_name> for #request_type { + type CreateError = #create_error; + type ItemError = #item_error; + type Item = #item; + } + } } + _ => return Err(syn::Error::new(attr_span, "Unknown RPC pattern")), }; + args.check_empty(attr_span)?; - expanded.into() + Ok(res) } -/// A macro to generate a Message enum from a Protocol enum and implement Channels for each variant. -/// -/// This macro: -/// 1. Takes a Protocol enum and generates a corresponding Message enum with variants wrapped in WithChannels -/// 2. Implements the Channels trait for each variant's inner type based on #[tx(...)] and #[rx(...)] attributes -/// -/// Channel receiver (rx) defaults to NoReceiver if not specified. -/// -/// # Example -/// -/// ``` -/// #[rpc_protocol(StorageMessage, StorageService)] -/// enum StorageProtocol { -/// #[tx(oneshot::Sender>)] -/// Get(Get), -/// #[tx(oneshot::Sender<()>)] -/// Set(Set), -/// #[tx(mpsc::Sender)] -/// List(List), -/// #[rx(mpsc::Receiver)] -/// #[tx(mpsc::Sender)] -/// Query(Query), -/// } -/// ``` #[proc_macro_attribute] -pub fn rpc_protocol(attr: TokenStream, item: TokenStream) -> TokenStream { - // Parse the input +pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { let mut input = parse_macro_input!(item as DeriveInput); - let attr_args = parse_macro_input!(attr as syn::AttributeArgs); - - // Extract message enum name and service type from attributes - if attr_args.len() != 2 { - return syn::Error::new( - proc_macro2::Span::call_site(), - "Expected two arguments: message_enum_name and service_type", - ) - .to_compile_error() - .into(); - } - - let message_enum_name = match &attr_args[0] { - syn::NestedMeta::Meta(syn::Meta::Path(path)) => { - if let Some(ident) = path.get_ident() { - ident - } else { - return syn::Error::new( - proc_macro2::Span::call_site(), - "First argument must be an identifier for the message enum name", - ).to_compile_error().into(); - } - }, - _ => { - return syn::Error::new( - proc_macro2::Span::call_site(), - "First argument must be an identifier for the message enum name", - ).to_compile_error().into(); - } - }; - - let service_type = match &attr_args[1] { - syn::NestedMeta::Meta(syn::Meta::Path(path)) => { - if let Some(ident) = path.get_ident() { - ident - } else { - return syn::Error::new( - proc_macro2::Span::call_site(), - "Second argument must be an identifier for the service type", - ).to_compile_error().into(); - } - }, - _ => { - return syn::Error::new( - proc_macro2::Span::call_site(), - "Second argument must be an identifier for the service type", - ).to_compile_error().into(); - } - }; - - // Extract the variants from the enum + let service_name = parse_macro_input!(attr as Ident); + + let input_span = input.span(); let data_enum = match &mut input.data { Data::Enum(data_enum) => data_enum, _ => { - return syn::Error::new( - input.ident.span(), - "This macro only works on enums", - ).to_compile_error().into(); + return syn::Error::new(input.span(), "RpcRequests can only be applied to enums") + .to_compile_error() + .into() } }; - - // Generate variants for the message enum - let message_variants = data_enum.variants.iter().map(|variant| { - let variant_name = &variant.ident; - - // Extract the inner type from the variant - let inner_type = match &variant.fields { - Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { - if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { - if let Some(last_segment) = type_path.path.segments.last() { - &last_segment.ident - } else { - return syn::Error::new( - variant.ident.span(), - "Unable to extract type name from variant", - ).to_compile_error(); - } - } else { - return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field of a simple type", - ).to_compile_error(); - } - }, - _ => { - return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field", - ).to_compile_error(); - } - }; - - quote! { - #variant_name(WithChannels<#inner_type, #service_type>) - } - }); - - // Storage for additional implementations - let mut channel_impls = Vec::new(); - - // Process each variant + + let mut additional_items = Vec::new(); + let mut types = HashSet::new(); + for variant in &mut data_enum.variants { - // Extract the inner type from the variant - let inner_type = match &variant.fields { - Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { - if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { - if let Some(last_segment) = type_path.path.segments.last() { - last_segment.ident.clone() - } else { - return syn::Error::new( - variant.ident.span(), - "Unable to extract type name from variant", - ).to_compile_error().into(); - } - } else { - return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field of a simple type", - ).to_compile_error().into(); - } - }, + // Check field structure for every variant + let request_type = match &variant.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, _ => { return syn::Error::new( - variant.ident.span(), - "Variant must contain exactly one unnamed field", - ).to_compile_error().into(); + variant.span(), + "Each variant must have exactly one unnamed field", + ) + .to_compile_error() + .into() } }; - - // Default rx to NoReceiver - let mut rx_type = quote! { NoReceiver }; - let mut tx_type = None; - - // Extract and remove rx attributes - let mut rx_attr = None; - variant.attrs.retain(|attr| { - if attr.path.is_ident("rx") { - rx_attr = Some(attr.clone()); - false - } else { - true - } - }); - - // Extract and remove tx attributes - let mut tx_attr = None; + + if !types.insert(request_type.to_token_stream().to_string()) { + return syn::Error::new(input_span, "Each variant must have a unique request type") + .to_compile_error() + .into(); + } + + // Extract and remove RPC attributes + let mut rpc_attr = Vec::new(); variant.attrs.retain(|attr| { - if attr.path.is_ident("tx") { - tx_attr = Some(attr.clone()); - false - } else { - true + for ident in IDENTS { + if attr.path.is_ident(ident) { + rpc_attr.push((ident, attr.clone())); + return false; + } } + true }); - - // Parse rx attribute if found - if let Some(attr) = rx_attr { - match attr.parse_args::() { - Ok(args) => { - rx_type = quote! { #args.rx_type }; - }, - Err(e) => return e.to_compile_error().into(), - } + + // Fail if there are multiple RPC patterns + if rpc_attr.len() > 1 { + return syn::Error::new(variant.span(), "Each variant can only have one RPC pattern") + .to_compile_error() + .into(); } - - // Parse tx attribute - required - let tx_attr = match tx_attr { - Some(attr) => attr, - None => { - return syn::Error::new( - variant.ident.span(), - format!("Missing #[tx(...)] attribute for variant {}", variant.ident), - ).to_compile_error().into(); + + if let Some((ident, attr)) = rpc_attr.pop() { + let args = match attr.parse_args::() { + Ok(info) => info, + Err(e) => return e.to_compile_error().into(), + }; + + match generate_rpc_impls(ident, args, &service_name, request_type, attr.span()) { + Ok(impls) => additional_items.extend(impls), + Err(e) => return e.to_compile_error().into(), } - }; - - match tx_attr.parse_args::() { - Ok(args) => { - tx_type = Some(quote! { #args.tx_type }); - }, - Err(e) => return e.to_compile_error().into(), } - - // Generate the Channel implementation - let tx_type = tx_type.unwrap(); - channel_impls.push(quote! { - impl Channels<#service_type> for #inner_type { - type Rx = #rx_type; - type Tx = #tx_type; - } - }); } - - // Generate the complete output - let expanded = quote! { - // Keep the original protocol enum + + let output = quote! { #input - - // Generate the message enum - #[derive(derive_more::From)] - enum #message_enum_name { - #(#message_variants),* - } - - // Implement Channels for each type - #(#channel_impls)* + + #(#additional_items)* }; - - expanded.into() + + output.into() +} + +struct RpcArgs { + types: BTreeMap, +} + +impl RpcArgs { + /// Get and remove a type from the map, failing if it doesn't exist + fn get(&mut self, key: &str, kind: &str, span: Span) -> syn::Result { + self.types + .remove(key) + .ok_or_else(|| syn::Error::new(span, format!("{kind} requires a {key} type"))) + } + + /// Fail if there are any unknown arguments remaining + fn check_empty(&self, span: Span) -> syn::Result<()> { + if self.types.is_empty() { + Ok(()) + } else { + Err(syn::Error::new( + span, + format!( + "Unknown arguments provided: {:?}", + self.types.keys().collect::>() + ), + )) + } + } +} + +/// Parse the rpc args as a comma separated list of name=type pairs +impl Parse for RpcArgs { + fn parse(input: ParseStream) -> syn::Result { + let mut types = BTreeMap::new(); + + loop { + if input.is_empty() { + break; + } + + let key: Ident = input.parse()?; + let _: Token![=] = input.parse()?; + let value: Type = input.parse()?; + + types.insert(key.to_string(), value); + + if !input.peek(Token![,]) { + break; + } + let _: Token![,] = input.parse()?; + } + + Ok(RpcArgs { types }) + } } \ No newline at end of file diff --git a/quic-rpc-derive/Cargo.toml b/quic-rpc-derive/Cargo.toml index 96dd4dc..7c48850 100644 --- a/quic-rpc-derive/Cargo.toml +++ b/quic-rpc-derive/Cargo.toml @@ -16,7 +16,7 @@ proc-macro = true syn = { version = "1", features = ["full"] } quote = "1" proc-macro2 = "1" -quic-rpc = { version = "0.18.3" } +quic-rpc = { path = ".." } [dev-dependencies] derive_more = { version = "1", features = ["from", "try_into", "display"] } diff --git a/quic-rpc-derive/examples/derive.rs b/quic-rpc-derive/examples/derive.rs deleted file mode 100644 index 741fc87..0000000 --- a/quic-rpc-derive/examples/derive.rs +++ /dev/null @@ -1,251 +0,0 @@ -use std::{ - collections::BTreeMap, - marker::PhantomData, - net::{Ipv4Addr, SocketAddr, SocketAddrV4}, - sync::Arc, -}; - -use n0_future::task::AbortOnDropHandle; -use quic_rpc::{ - channel::{mpsc, none::NoReceiver, oneshot}, - rpc::{listen, Handler}, - util::{make_client_endpoint, make_server_endpoint}, - Channels, LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, -}; -use quic_rpc_derive::rpc_protocol; -use serde::{Deserialize, Serialize}; -use tracing::info; - -/// A simple storage service, just to try it out -#[derive(Debug, Clone, Copy)] -struct StorageService; - -impl Service for StorageService {} - -#[derive(Debug, Serialize, Deserialize)] -struct Get { - key: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct List; - -#[derive(Debug, Serialize, Deserialize)] -struct Set { - key: String, - value: String, -} - -// mod manual { -// use super::*; -// impl Channels for Get { -// type Rx = NoReceiver; -// type Tx = oneshot::Sender>; -// } - -// impl Channels for List { -// type Rx = NoReceiver; -// type Tx = mpsc::Sender; -// } - -// impl Channels for Set { -// type Rx = NoReceiver; -// type Tx = oneshot::Sender<()>; -// } -// } - -#[derive(derive_more::From, Serialize, Deserialize)] -#[rpc_protocol(StorageMessage, StorageService)] -enum StorageProtocol { - #[tx(oneshot::Sender>)] - Get(Get), - #[tx(oneshot::Sender<()>)] - Set(Set), - #[tx(mpsc::Sender)] - List(List), -} - -struct StorageActor { - recv: tokio::sync::mpsc::Receiver, - state: BTreeMap, -} - -impl StorageActor { - pub fn local() -> StorageApi { - let (tx, rx) = tokio::sync::mpsc::channel(1); - let actor = Self { - recv: rx, - state: BTreeMap::new(), - }; - tokio::spawn(actor.run()); - let local = LocalMpscChannel::::from(tx); - StorageApi { - inner: local.into(), - } - } - - async fn run(mut self) { - while let Some(msg) = self.recv.recv().await { - self.handle(msg).await; - } - } - - async fn handle(&mut self, msg: StorageMessage) { - match msg { - StorageMessage::Get(get) => { - info!("get {:?}", get); - let WithChannels { tx, inner, .. } = get; - tx.send(self.state.get(&inner.key).cloned()).await.ok(); - } - StorageMessage::Set(set) => { - info!("set {:?}", set); - let WithChannels { tx, inner, .. } = set; - self.state.insert(inner.key, inner.value); - tx.send(()).await.ok(); - } - StorageMessage::List(list) => { - info!("list {:?}", list); - let WithChannels { mut tx, .. } = list; - for (key, value) in &self.state { - if tx.send(format!("{key}={value}")).await.is_err() { - break; - } - } - } - } - } -} -struct StorageApi { - inner: ServiceSender, -} - -impl StorageApi { - pub fn connect(endpoint: quinn::Endpoint, addr: SocketAddr) -> anyhow::Result { - Ok(StorageApi { - inner: ServiceSender::Remote(endpoint, addr, PhantomData), - }) - } - - pub fn listen(&self, endpoint: quinn::Endpoint) -> anyhow::Result> { - match &self.inner { - ServiceSender::Local(local, _) => { - let local = LocalMpscChannel::from(local.clone()); - let fun: Handler = Arc::new(move |msg, _, tx| { - let local = local.clone(); - Box::pin(async move { - match msg { - StorageProtocol::Get(msg) => { - local.send((msg, tx)).await?; - } - StorageProtocol::Set(msg) => { - local.send((msg, tx)).await?; - } - StorageProtocol::List(msg) => { - local.send((msg, tx)).await?; - } - }; - Ok(()) - }) - }); - Ok(listen(endpoint, fun)) - } - ServiceSender::Remote(_, _, _) => { - Err(anyhow::anyhow!("cannot listen on a remote service")) - } - } - } - - pub async fn get(&self, key: String) -> anyhow::Result>> { - let msg = Get { key }; - match self.inner.request().await? { - ServiceRequest::Local(request, _) => { - let (tx, rx) = oneshot::channel(); - request.send((msg, tx)).await?; - Ok(rx) - } - ServiceRequest::Remote(request) => { - let (rx, _tx) = request.write(msg).await?; - Ok(rx.into()) - } - } - } - - pub async fn list(&self) -> anyhow::Result> { - let msg = List; - match self.inner.request().await? { - ServiceRequest::Local(request, _) => { - let (tx, rx) = mpsc::channel(10); - request.send((msg, tx)).await?; - Ok(rx) - } - ServiceRequest::Remote(request) => { - let (rx, _tx) = request.write(msg).await?; - Ok(rx.into()) - } - } - } - - pub async fn set(&self, key: String, value: String) -> anyhow::Result> { - let msg = Set { key, value }; - match self.inner.request().await? { - ServiceRequest::Local(request, _) => { - let (tx, rx) = oneshot::channel(); - request.send((msg, tx)).await?; - Ok(rx) - } - ServiceRequest::Remote(request) => { - let (rx, _tx) = request.write(msg).await?; - Ok(rx.into()) - } - } - } -} - -async fn local() -> anyhow::Result<()> { - let api = StorageActor::local(); - api.set("hello".to_string(), "world".to_string()) - .await? - .await?; - let value = api.get("hello".to_string()).await?.await?; - let mut list = api.list().await?; - while let Some(value) = list.recv().await? { - println!("list value = {:?}", value); - } - println!("value = {:?}", value); - Ok(()) -} - -async fn remote() -> anyhow::Result<()> { - let port = 10113; - let (server, cert) = - make_server_endpoint(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port).into())?; - let client = - make_client_endpoint(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0).into(), &[&cert])?; - let store = StorageActor::local(); - let handle = store.listen(server)?; - let api = StorageApi::connect(client, SocketAddrV4::new(Ipv4Addr::LOCALHOST, port).into())?; - api.set("hello".to_string(), "world".to_string()) - .await? - .await?; - api.set("goodbye".to_string(), "world".to_string()) - .await? - .await?; - let value = api.get("hello".to_string()).await?.await?; - println!("value = {:?}", value); - let mut list = api.list().await?; - while let Some(value) = list.recv().await? { - println!("list value = {:?}", value); - } - drop(handle); - Ok(()) -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - tracing_subscriber::fmt().init(); - println!("Local use"); - local().await?; - println!("Remote use"); - remote().await?; - Ok(()) -} diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index e31bb58..4aa2b2f 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -10,84 +10,38 @@ use syn::{ Data, DeriveInput, Fields, Ident, Token, Type, }; -const SERVER_STREAMING: &str = "server_streaming"; -const CLIENT_STREAMING: &str = "client_streaming"; -const BIDI_STREAMING: &str = "bidi_streaming"; const RPC: &str = "rpc"; -const TRY_SERVER_STREAMING: &str = "try_server_streaming"; -const IDENTS: [&str; 5] = [ - SERVER_STREAMING, - CLIENT_STREAMING, - BIDI_STREAMING, - RPC, - TRY_SERVER_STREAMING, -]; - -fn generate_rpc_impls( +const ATTRS: [&str; 1] = [RPC]; + +fn generate_channels_impl( pat: &str, - mut args: RpcArgs, + mut args: NamedTypeArgs, service_name: &Ident, request_type: &Type, attr_span: Span, ) -> syn::Result { let res = match pat { RPC => { - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::pattern::rpc::RpcMsg<#service_name> for #request_type { - type Response = #response; - } - } - } - SERVER_STREAMING => { - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::server_streaming::ServerStreaming; - } - impl ::quic_rpc::pattern::server_streaming::ServerStreamingMsg<#service_name> for #request_type { - type Response = #response; - } - } - } - BIDI_STREAMING => { - let update = args.get("update", pat, attr_span)?; - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::bidi_streaming::BidiStreaming; + // Try to get rx, default to NoReceiver if not present + let rx = match args.types.remove("rx") { + Some(rx_type) => rx_type, + None => { + // Parse "NoReceiver" into a Type + syn::parse_str::("::quic_rpc::channel::none::NoReceiver").map_err( + |e| { + syn::Error::new( + attr_span, + format!("Failed to parse default rx type: {}", e), + ) + }, + )? } - impl ::quic_rpc::pattern::bidi_streaming::BidiStreamingMsg<#service_name> for #request_type { - type Update = #update; - type Response = #response; - } - } - } - CLIENT_STREAMING => { - let update = args.get("update", pat, attr_span)?; - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::client_streaming::ClientStreaming; - } - impl ::quic_rpc::pattern::client_streaming::ClientStreamingMsg<#service_name> for #request_type { - type Update = #update; - type Response = #response; - } - } - } - TRY_SERVER_STREAMING => { - let create_error = args.get("create_error", pat, attr_span)?; - let item_error = args.get("item_error", pat, attr_span)?; - let item = args.get("item", pat, attr_span)?; + }; + let tx = args.get("tx", pat, attr_span)?; quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::try_server_streaming::TryServerStreaming; - } - impl ::quic_rpc::pattern::try_server_streaming::TryServerStreamingMsg<#service_name> for #request_type { - type CreateError = #create_error; - type ItemError = #item_error; - type Item = #item; + impl ::quic_rpc::Channels<#service_name> for #request_type { + type Tx = #tx; + type Rx = #rx; } } } @@ -139,7 +93,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { // Extract and remove RPC attributes let mut rpc_attr = Vec::new(); variant.attrs.retain(|attr| { - for ident in IDENTS { + for ident in ATTRS { if attr.path.is_ident(ident) { rpc_attr.push((ident, attr.clone())); return false; @@ -156,12 +110,12 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { } if let Some((ident, attr)) = rpc_attr.pop() { - let args = match attr.parse_args::() { + let args = match attr.parse_args::() { Ok(info) => info, Err(e) => return e.to_compile_error().into(), }; - match generate_rpc_impls(ident, args, &service_name, request_type, attr.span()) { + match generate_channels_impl(ident, args, &service_name, request_type, attr.span()) { Ok(impls) => additional_items.extend(impls), Err(e) => return e.to_compile_error().into(), } @@ -177,11 +131,11 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { output.into() } -struct RpcArgs { +struct NamedTypeArgs { types: BTreeMap, } -impl RpcArgs { +impl NamedTypeArgs { /// Get and remove a type from the map, failing if it doesn't exist fn get(&mut self, key: &str, kind: &str, span: Span) -> syn::Result { self.types @@ -206,7 +160,7 @@ impl RpcArgs { } /// Parse the rpc args as a comma separated list of name=type pairs -impl Parse for RpcArgs { +impl Parse for NamedTypeArgs { fn parse(input: ParseStream) -> syn::Result { let mut types = BTreeMap::new(); @@ -227,6 +181,6 @@ impl Parse for RpcArgs { let _: Token![,] = input.parse()?; } - Ok(RpcArgs { types }) + Ok(NamedTypeArgs { types }) } -} \ No newline at end of file +} diff --git a/quic-rpc-derive/tests/smoke.rs b/quic-rpc-derive/tests/smoke.rs index b6fe521..d03f4fe 100644 --- a/quic-rpc-derive/tests/smoke.rs +++ b/quic-rpc-derive/tests/smoke.rs @@ -1,3 +1,8 @@ +use quic_rpc::channel::{ + mpsc, + none::{NoReceiver, NoSender}, + oneshot, +}; use quic_rpc_derive::rpc_requests; use serde::{Deserialize, Serialize}; @@ -36,44 +41,18 @@ fn simple() { #[rpc_requests(Service)] #[derive(Debug, Serialize, Deserialize, derive_more::From, derive_more::TryInto)] enum Request { - #[rpc(response=Response1)] + #[rpc(tx=NoSender)] Rpc(RpcRequest), - #[server_streaming(response=Response2)] + #[rpc(tx=NoSender)] ServerStreaming(ServerStreamingRequest), - #[bidi_streaming(update= Update1, response = Response3)] + #[rpc(tx=NoSender)] BidiStreaming(BidiStreamingRequest), - #[client_streaming(update = Update2, response = Response4)] + #[rpc(tx=NoSender)] ClientStreaming(ClientStreamingRequest), - Update1(Update1), - Update2(Update2), - } - - #[derive(Debug, Serialize, Deserialize, derive_more::From, derive_more::TryInto)] - enum Response { - Response1(Response1), - Response2(Response2), - Response3(Response3), - Response4(Response4), } #[derive(Debug, Clone)] struct Service; - impl quic_rpc::Service for Service { - type Req = Request; - type Res = Response; - } - - let _ = Service; -} - -/// Use -/// -/// TRYBUILD=overwrite cargo test --test smoke -/// -/// to update the snapshots -#[test] -fn compile_fail() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/compile_fail/*.rs"); + impl quic_rpc::Service for Service {} } From 09a77f16fec246c451a9c4d68c7924a8bef3e315 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 16:51:43 +0200 Subject: [PATCH 08/43] works! --- quic-rpc-derive/src/lib.rs | 63 ++++++++++++++++++++++++++++++++-- quic-rpc-derive/tests/smoke.rs | 4 +-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index 4aa2b2f..0e29e4f 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -7,7 +7,7 @@ use syn::{ parse::{Parse, ParseStream}, parse_macro_input, spanned::Spanned, - Data, DeriveInput, Fields, Ident, Token, Type, + Data, DeriveInput, Fields, Ident, Macro, Token, Type, }; const RPC: &str = "rpc"; @@ -52,10 +52,32 @@ fn generate_channels_impl( Ok(res) } +// Parse arguments in the format (MessageEnumName, ServiceType) +struct MacroArgs { + message_enum_name: Ident, + service_name: Ident, +} + +impl Parse for MacroArgs { + fn parse(input: ParseStream) -> syn::Result { + let service_name: Ident = input.parse()?; + let _: Token![,] = input.parse()?; + let message_enum_name: Ident = input.parse()?; + + Ok(MacroArgs { + service_name, + message_enum_name, + }) + } +} + #[proc_macro_attribute] pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { let mut input = parse_macro_input!(item as DeriveInput); - let service_name = parse_macro_input!(attr as Ident); + let MacroArgs { + service_name, + message_enum_name, + } = parse_macro_input!(attr as MacroArgs); let input_span = input.span(); let data_enum = match &mut input.data { @@ -122,9 +144,46 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { } } + let message_variants = data_enum + .variants + .iter() + .map(|variant| { + let variant_name = &variant.ident; + + // Extract the inner type - we know it's already valid + let inner_type = match &variant.fields { + Fields::Unnamed(fields) => { + if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { + if let Some(last_segment) = type_path.path.segments.last() { + &last_segment.ident + } else { + &type_path.path.segments.first().unwrap().ident + } + } else { + panic!("Unexpected type"); // Should never happen due to prior validation + } + } + _ => panic!("Unexpected field type"), // Should never happen due to prior validation + }; + + quote! { + #variant_name(::quic_rpc::WithChannels<#inner_type, #service_name>) + } + }) + .collect::>(); + + let message_enum = quote! { + #[derive(derive_more::From)] + enum #message_enum_name { + #(#message_variants),* + } + }; + let output = quote! { #input + #message_enum + #(#additional_items)* }; diff --git a/quic-rpc-derive/tests/smoke.rs b/quic-rpc-derive/tests/smoke.rs index d03f4fe..c8cf77c 100644 --- a/quic-rpc-derive/tests/smoke.rs +++ b/quic-rpc-derive/tests/smoke.rs @@ -38,10 +38,10 @@ fn simple() { #[derive(Debug, Serialize, Deserialize)] struct Response4; - #[rpc_requests(Service)] + #[rpc_requests(Service, RequestWithChannels)] #[derive(Debug, Serialize, Deserialize, derive_more::From, derive_more::TryInto)] enum Request { - #[rpc(tx=NoSender)] + #[rpc(tx=oneshot::Sender<()>)] Rpc(RpcRequest), #[rpc(tx=NoSender)] ServerStreaming(ServerStreamingRequest), From 7cd6c331ac5ddec56744c09a95efc0b07d790f2f Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 16:54:47 +0200 Subject: [PATCH 09/43] fix comments --- quic-rpc-derive/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index 0e29e4f..6bdb684 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -7,7 +7,7 @@ use syn::{ parse::{Parse, ParseStream}, parse_macro_input, spanned::Spanned, - Data, DeriveInput, Fields, Ident, Macro, Token, Type, + Data, DeriveInput, Fields, Ident, Token, Type, }; const RPC: &str = "rpc"; @@ -112,7 +112,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { .into(); } - // Extract and remove RPC attributes + // Extract and remove our attributes let mut rpc_attr = Vec::new(); variant.attrs.retain(|attr| { for ident in ATTRS { @@ -124,7 +124,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { true }); - // Fail if there are multiple RPC patterns + // Fail if there are multiple attributes if rpc_attr.len() > 1 { return syn::Error::new(variant.span(), "Each variant can only have one RPC pattern") .to_compile_error() From 0bfac3c4ad3c91bc9eded7ec3fd9f7bce0ecbb11 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 16:58:47 +0200 Subject: [PATCH 10/43] one attribute --- quic-rpc-derive/src/lib.rs | 117 ++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 66 deletions(-) diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index 6bdb684..8accc50 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -10,52 +10,51 @@ use syn::{ Data, DeriveInput, Fields, Ident, Token, Type, }; -const RPC: &str = "rpc"; -const ATTRS: [&str; 1] = [RPC]; +// Helper function for error reporting +fn error_tokens(span: Span, message: &str) -> TokenStream { + syn::Error::new(span, message) + .to_compile_error() + .into() +} fn generate_channels_impl( - pat: &str, mut args: NamedTypeArgs, service_name: &Ident, request_type: &Type, attr_span: Span, ) -> syn::Result { - let res = match pat { - RPC => { - // Try to get rx, default to NoReceiver if not present - let rx = match args.types.remove("rx") { - Some(rx_type) => rx_type, - None => { - // Parse "NoReceiver" into a Type - syn::parse_str::("::quic_rpc::channel::none::NoReceiver").map_err( - |e| { - syn::Error::new( - attr_span, - format!("Failed to parse default rx type: {}", e), - ) - }, - )? - } - }; - let tx = args.get("tx", pat, attr_span)?; - quote! { - impl ::quic_rpc::Channels<#service_name> for #request_type { - type Tx = #tx; - type Rx = #rx; - } - } + // Try to get rx, default to NoReceiver if not present + let rx = match args.types.remove("rx") { + Some(rx_type) => rx_type, + None => { + // Parse "NoReceiver" into a Type + syn::parse_str::("::quic_rpc::channel::none::NoReceiver").map_err( + |e| { + syn::Error::new( + attr_span, + format!("Failed to parse default rx type: {}", e), + ) + }, + )? + } + }; + let tx = args.get("tx", attr_span)?; + + let res = quote! { + impl ::quic_rpc::Channels<#service_name> for #request_type { + type Tx = #tx; + type Rx = #rx; } - _ => return Err(syn::Error::new(attr_span, "Unknown RPC pattern")), }; + args.check_empty(attr_span)?; - Ok(res) } -// Parse arguments in the format (MessageEnumName, ServiceType) +// Parse arguments in the format (ServiceType, MessageEnumName) struct MacroArgs { - message_enum_name: Ident, service_name: Ident, + message_enum_name: Ident, } impl Parse for MacroArgs { @@ -82,11 +81,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { let input_span = input.span(); let data_enum = match &mut input.data { Data::Enum(data_enum) => data_enum, - _ => { - return syn::Error::new(input.span(), "RpcRequests can only be applied to enums") - .to_compile_error() - .into() - } + _ => return error_tokens(input.span(), "RpcRequests can only be applied to enums"), }; let mut additional_items = Vec::new(); @@ -96,48 +91,38 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { // Check field structure for every variant let request_type = match &variant.fields { Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, - _ => { - return syn::Error::new( - variant.span(), - "Each variant must have exactly one unnamed field", - ) - .to_compile_error() - .into() - } + _ => return error_tokens( + variant.span(), + "Each variant must have exactly one unnamed field", + ), }; if !types.insert(request_type.to_token_stream().to_string()) { - return syn::Error::new(input_span, "Each variant must have a unique request type") - .to_compile_error() - .into(); + return error_tokens(input_span, "Each variant must have a unique request type"); } - // Extract and remove our attributes - let mut rpc_attr = Vec::new(); + // Find and remove the rpc attribute + let mut rpc_attr = None; variant.attrs.retain(|attr| { - for ident in ATTRS { - if attr.path.is_ident(ident) { - rpc_attr.push((ident, attr.clone())); - return false; + if attr.path.is_ident("rpc") { + if rpc_attr.is_some() { + // This should never happen since we're removing them as we go + panic!("Multiple rpc attributes found"); } + rpc_attr = Some(attr.clone()); + false // Remove this attribute + } else { + true // Keep other attributes } - true }); - // Fail if there are multiple attributes - if rpc_attr.len() > 1 { - return syn::Error::new(variant.span(), "Each variant can only have one RPC pattern") - .to_compile_error() - .into(); - } - - if let Some((ident, attr)) = rpc_attr.pop() { + if let Some(attr) = rpc_attr { let args = match attr.parse_args::() { Ok(info) => info, Err(e) => return e.to_compile_error().into(), }; - match generate_channels_impl(ident, args, &service_name, request_type, attr.span()) { + match generate_channels_impl(args, &service_name, request_type, attr.span()) { Ok(impls) => additional_items.extend(impls), Err(e) => return e.to_compile_error().into(), } @@ -196,10 +181,10 @@ struct NamedTypeArgs { impl NamedTypeArgs { /// Get and remove a type from the map, failing if it doesn't exist - fn get(&mut self, key: &str, kind: &str, span: Span) -> syn::Result { + fn get(&mut self, key: &str, span: Span) -> syn::Result { self.types .remove(key) - .ok_or_else(|| syn::Error::new(span, format!("{kind} requires a {key} type"))) + .ok_or_else(|| syn::Error::new(span, format!("rpc requires a {key} type"))) } /// Fail if there are any unknown arguments remaining @@ -242,4 +227,4 @@ impl Parse for NamedTypeArgs { Ok(NamedTypeArgs { types }) } -} +} \ No newline at end of file From 7567a6697a3e8a03c8ba402d19ec240291201f59 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 17:22:53 +0200 Subject: [PATCH 11/43] Add more tests and derive example --- Cargo.lock | 1 + Cargo.toml | 3 + examples/derive.rs | 236 ++++++++++++++++++ quic-rpc-derive/Cargo.toml | 2 +- quic-rpc-derive/src/lib.rs | 137 +++++----- .../tests/compile_fail/duplicate_type.rs | 2 +- .../tests/compile_fail/duplicate_type.stderr | 7 +- .../tests/compile_fail/extra_attr_types.rs | 4 +- .../compile_fail/extra_attr_types.stderr | 4 +- .../tests/compile_fail/multiple_fields.rs | 2 +- .../tests/compile_fail/multiple_fields.stderr | 2 +- .../tests/compile_fail/named_enum.rs | 2 +- .../tests/compile_fail/named_enum.stderr | 2 +- .../tests/compile_fail/non_enum.rs | 2 +- .../tests/compile_fail/non_enum.stderr | 2 +- .../tests/compile_fail/wrong_attr_types.rs | 2 +- .../compile_fail/wrong_attr_types.stderr | 4 +- quic-rpc-derive/tests/smoke.rs | 17 +- 18 files changed, 345 insertions(+), 86 deletions(-) create mode 100644 examples/derive.rs diff --git a/Cargo.lock b/Cargo.lock index cfc576d..87af660 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -757,6 +757,7 @@ dependencies = [ "iroh-quinn", "n0-future", "postcard", + "quic-rpc-derive", "rcgen", "rustls", "serde", diff --git a/Cargo.toml b/Cargo.toml index 29c4a09..de6c938 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,9 @@ rustls = { version = "0.23.5", default-features = false, features = ["std"], opt rcgen = { version = "0.13.2", optional = true } smallvec = { version = "1.14.0", features = ["write"] } +[dev-dependencies] +quic-rpc-derive = { path = "quic-rpc-derive" } + [features] quinn = ["dep:quinn", "dep:postcard"] default = ["quinn", "test"] diff --git a/examples/derive.rs b/examples/derive.rs new file mode 100644 index 0000000..878916c --- /dev/null +++ b/examples/derive.rs @@ -0,0 +1,236 @@ +use std::{ + collections::BTreeMap, + marker::PhantomData, + net::{Ipv4Addr, SocketAddr, SocketAddrV4}, + sync::Arc, +}; + +use n0_future::task::AbortOnDropHandle; +use quic_rpc::{ + channel::{mpsc, oneshot}, + rpc::{listen, Handler}, + util::{make_client_endpoint, make_server_endpoint}, + LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, +}; +// Import the macro +use quic_rpc_derive::rpc_requests; +use serde::{Deserialize, Serialize}; +use tracing::info; + +/// A simple storage service, just to try it out +#[derive(Debug, Clone, Copy)] +struct StorageService; + +impl Service for StorageService {} + +#[derive(Debug, Serialize, Deserialize)] +struct Get { + key: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct List; + +#[derive(Debug, Serialize, Deserialize)] +struct Set { + key: String, + value: String, +} + +// Use the macro to generate both the StorageProtocol and StorageMessage enums +// plus implement Channels for each type +#[rpc_requests(StorageService, StorageMessage)] +#[derive(derive_more::From, Serialize, Deserialize)] +enum StorageProtocol { + #[rpc(tx=oneshot::Sender>)] + Get(Get), + #[rpc(tx=oneshot::Sender<()>)] + Set(Set), + #[rpc(tx=mpsc::Sender)] + List(List), +} + +struct StorageActor { + recv: tokio::sync::mpsc::Receiver, + state: BTreeMap, +} + +impl StorageActor { + pub fn local() -> StorageApi { + let (tx, rx) = tokio::sync::mpsc::channel(1); + let actor = Self { + recv: rx, + state: BTreeMap::new(), + }; + tokio::spawn(actor.run()); + let local = LocalMpscChannel::::from(tx); + StorageApi { + inner: local.into(), + } + } + + async fn run(mut self) { + while let Some(msg) = self.recv.recv().await { + self.handle(msg).await; + } + } + + async fn handle(&mut self, msg: StorageMessage) { + match msg { + StorageMessage::Get(get) => { + info!("get {:?}", get); + let WithChannels { tx, inner, .. } = get; + tx.send(self.state.get(&inner.key).cloned()).await.ok(); + } + StorageMessage::Set(set) => { + info!("set {:?}", set); + let WithChannels { tx, inner, .. } = set; + self.state.insert(inner.key, inner.value); + tx.send(()).await.ok(); + } + StorageMessage::List(list) => { + info!("list {:?}", list); + let WithChannels { mut tx, .. } = list; + for (key, value) in &self.state { + if tx.send(format!("{key}={value}")).await.is_err() { + break; + } + } + } + } + } +} +struct StorageApi { + inner: ServiceSender, +} + +impl StorageApi { + pub fn connect(endpoint: quinn::Endpoint, addr: SocketAddr) -> anyhow::Result { + Ok(StorageApi { + inner: ServiceSender::Remote(endpoint, addr, PhantomData), + }) + } + + pub fn listen(&self, endpoint: quinn::Endpoint) -> anyhow::Result> { + match &self.inner { + ServiceSender::Local(local, _) => { + let local = LocalMpscChannel::from(local.clone()); + let fun: Handler = Arc::new(move |msg, _, tx| { + let local = local.clone(); + Box::pin(async move { + match msg { + StorageProtocol::Get(msg) => { + local.send((msg, tx)).await?; + } + StorageProtocol::Set(msg) => { + local.send((msg, tx)).await?; + } + StorageProtocol::List(msg) => { + local.send((msg, tx)).await?; + } + }; + Ok(()) + }) + }); + Ok(listen(endpoint, fun)) + } + ServiceSender::Remote(_, _, _) => { + Err(anyhow::anyhow!("cannot listen on a remote service")) + } + } + } + + pub async fn get(&self, key: String) -> anyhow::Result>> { + let msg = Get { key }; + match self.inner.request().await? { + ServiceRequest::Local(request, _) => { + let (tx, rx) = oneshot::channel(); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } + + pub async fn list(&self) -> anyhow::Result> { + let msg = List; + match self.inner.request().await? { + ServiceRequest::Local(request, _) => { + let (tx, rx) = mpsc::channel(10); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } + + pub async fn set(&self, key: String, value: String) -> anyhow::Result> { + let msg = Set { key, value }; + match self.inner.request().await? { + ServiceRequest::Local(request, _) => { + let (tx, rx) = oneshot::channel(); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } +} + +async fn local() -> anyhow::Result<()> { + let api = StorageActor::local(); + api.set("hello".to_string(), "world".to_string()) + .await? + .await?; + let value = api.get("hello".to_string()).await?.await?; + let mut list = api.list().await?; + while let Some(value) = list.recv().await? { + println!("list value = {:?}", value); + } + println!("value = {:?}", value); + Ok(()) +} + +async fn remote() -> anyhow::Result<()> { + let port = 10113; + let (server, cert) = + make_server_endpoint(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port).into())?; + let client = + make_client_endpoint(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0).into(), &[&cert])?; + let store = StorageActor::local(); + let handle = store.listen(server)?; + let api = StorageApi::connect(client, SocketAddrV4::new(Ipv4Addr::LOCALHOST, port).into())?; + api.set("hello".to_string(), "world".to_string()) + .await? + .await?; + api.set("goodbye".to_string(), "world".to_string()) + .await? + .await?; + let value = api.get("hello".to_string()).await?.await?; + println!("value = {:?}", value); + let mut list = api.list().await?; + while let Some(value) = list.recv().await? { + println!("list value = {:?}", value); + } + drop(handle); + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt().init(); + println!("Local use"); + local().await?; + println!("Remote use"); + remote().await?; + Ok(()) +} diff --git a/quic-rpc-derive/Cargo.toml b/quic-rpc-derive/Cargo.toml index 7c48850..0a59a72 100644 --- a/quic-rpc-derive/Cargo.toml +++ b/quic-rpc-derive/Cargo.toml @@ -16,9 +16,9 @@ proc-macro = true syn = { version = "1", features = ["full"] } quote = "1" proc-macro2 = "1" -quic-rpc = { path = ".." } [dev-dependencies] derive_more = { version = "1", features = ["from", "try_into", "display"] } serde = { version = "1", features = ["serde_derive"] } trybuild = "1.0" +quic-rpc = { path = ".." } diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index 8accc50..bd86ecb 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -12,11 +12,18 @@ use syn::{ // Helper function for error reporting fn error_tokens(span: Span, message: &str) -> TokenStream { - syn::Error::new(span, message) - .to_compile_error() - .into() + syn::Error::new(span, message).to_compile_error().into() } +/// The only attribute we care about +const ATTR_NAME: &str = "rpc"; +/// the tx type name +const TX_ATTR: &str = "tx"; +/// the rx type name +const RX_ATTR: &str = "rx"; +/// Fully qualified path to the default rx type +const DEFAULT_RX_TYPE: &str = "::quic_rpc::channel::none::NoReceiver"; + fn generate_channels_impl( mut args: NamedTypeArgs, service_name: &Ident, @@ -24,52 +31,24 @@ fn generate_channels_impl( attr_span: Span, ) -> syn::Result { // Try to get rx, default to NoReceiver if not present - let rx = match args.types.remove("rx") { - Some(rx_type) => rx_type, - None => { - // Parse "NoReceiver" into a Type - syn::parse_str::("::quic_rpc::channel::none::NoReceiver").map_err( - |e| { - syn::Error::new( - attr_span, - format!("Failed to parse default rx type: {}", e), - ) - }, - )? - } - }; - let tx = args.get("tx", attr_span)?; - + // Use unwrap_or_else for a cleaner default + let rx = args.types.remove(RX_ATTR).unwrap_or_else(|| { + // We can safely unwrap here because this is a known valid type + syn::parse_str::(DEFAULT_RX_TYPE).expect("Failed to parse default rx type") + }); + let tx = args.get(TX_ATTR, attr_span)?; + let res = quote! { impl ::quic_rpc::Channels<#service_name> for #request_type { type Tx = #tx; type Rx = #rx; } }; - + args.check_empty(attr_span)?; Ok(res) } -// Parse arguments in the format (ServiceType, MessageEnumName) -struct MacroArgs { - service_name: Ident, - message_enum_name: Ident, -} - -impl Parse for MacroArgs { - fn parse(input: ParseStream) -> syn::Result { - let service_name: Ident = input.parse()?; - let _: Token![,] = input.parse()?; - let message_enum_name: Ident = input.parse()?; - - Ok(MacroArgs { - service_name, - message_enum_name, - }) - } -} - #[proc_macro_attribute] pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { let mut input = parse_macro_input!(item as DeriveInput); @@ -91,31 +70,43 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { // Check field structure for every variant let request_type = match &variant.fields { Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, - _ => return error_tokens( - variant.span(), - "Each variant must have exactly one unnamed field", - ), + _ => { + return error_tokens( + variant.span(), + "Each variant must have exactly one unnamed field", + ) + } }; if !types.insert(request_type.to_token_stream().to_string()) { return error_tokens(input_span, "Each variant must have a unique request type"); } - // Find and remove the rpc attribute let mut rpc_attr = None; + let mut multiple_rpc_attrs = false; + variant.attrs.retain(|attr| { - if attr.path.is_ident("rpc") { + if attr.path.is_ident(ATTR_NAME) { if rpc_attr.is_some() { - // This should never happen since we're removing them as we go - panic!("Multiple rpc attributes found"); + multiple_rpc_attrs = true; + true // Keep this duplicate attribute + } else { + rpc_attr = Some(attr.clone()); + false // Remove this attribute } - rpc_attr = Some(attr.clone()); - false // Remove this attribute } else { true // Keep other attributes } }); + // Check for multiple rpc attributes + if multiple_rpc_attrs { + return error_tokens( + variant.span(), + "Each variant can only have one rpc attribute", + ); + } + if let Some(attr) = rpc_attr { let args = match attr.parse_args::() { Ok(info) => info, @@ -135,21 +126,20 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { .map(|variant| { let variant_name = &variant.ident; - // Extract the inner type - we know it's already valid - let inner_type = match &variant.fields { - Fields::Unnamed(fields) => { - if let Type::Path(type_path) = &fields.unnamed.first().unwrap().ty { - if let Some(last_segment) = type_path.path.segments.last() { - &last_segment.ident - } else { - &type_path.path.segments.first().unwrap().ident - } - } else { - panic!("Unexpected type"); // Should never happen due to prior validation - } - } - _ => panic!("Unexpected field type"), // Should never happen due to prior validation + // Extract the inner type using let-else patterns + let Fields::Unnamed(fields) = &variant.fields else { + unreachable!() + }; + let Some(field) = fields.unnamed.first() else { + unreachable!() + }; + let Type::Path(type_path) = &field.ty else { + unreachable!() + }; + let Some(last_segment) = type_path.path.segments.last() else { + unreachable!() }; + let inner_type = &last_segment.ident; quote! { #variant_name(::quic_rpc::WithChannels<#inner_type, #service_name>) @@ -175,6 +165,25 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { output.into() } +// Parse arguments in the format (ServiceType, MessageEnumName) +struct MacroArgs { + service_name: Ident, + message_enum_name: Ident, +} + +impl Parse for MacroArgs { + fn parse(input: ParseStream) -> syn::Result { + let service_name: Ident = input.parse()?; + let _: Token![,] = input.parse()?; + let message_enum_name: Ident = input.parse()?; + + Ok(MacroArgs { + service_name, + message_enum_name, + }) + } +} + struct NamedTypeArgs { types: BTreeMap, } @@ -227,4 +236,4 @@ impl Parse for NamedTypeArgs { Ok(NamedTypeArgs { types }) } -} \ No newline at end of file +} diff --git a/quic-rpc-derive/tests/compile_fail/duplicate_type.rs b/quic-rpc-derive/tests/compile_fail/duplicate_type.rs index 45db393..5d8710a 100644 --- a/quic-rpc-derive/tests/compile_fail/duplicate_type.rs +++ b/quic-rpc-derive/tests/compile_fail/duplicate_type.rs @@ -1,6 +1,6 @@ use quic_rpc_derive::rpc_requests; -#[rpc_requests(Service)] +#[rpc_requests(Service, Msg)] enum Enum { A(u8), B(u8), diff --git a/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr b/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr index 71c5e95..6815581 100644 --- a/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr +++ b/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr @@ -1,5 +1,8 @@ error: Each variant must have a unique request type --> tests/compile_fail/duplicate_type.rs:4:1 | -4 | enum Enum { - | ^^^^ +4 | / enum Enum { +5 | | A(u8), +6 | | B(u8), +7 | | } + | |_^ diff --git a/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs b/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs index 7ca34a9..5639d45 100644 --- a/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs +++ b/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs @@ -1,8 +1,8 @@ use quic_rpc_derive::rpc_requests; -#[rpc_requests(Service)] +#[rpc_requests(Service, Msg)] enum Enum { - #[rpc(response = Bla, fnord = Foo)] + #[rpc(tx = NoSender, rx = NoReceiver, fnord = Foo)] A(u8), } diff --git a/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr b/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr index 2de36f7..c19048b 100644 --- a/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr +++ b/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr @@ -1,5 +1,5 @@ error: Unknown arguments provided: ["fnord"] --> tests/compile_fail/extra_attr_types.rs:5:5 | -5 | #[rpc(response = Bla, fnord = Foo)] - | ^ +5 | #[rpc(tx = NoSender, rx = NoReceiver, fnord = Foo)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/quic-rpc-derive/tests/compile_fail/multiple_fields.rs b/quic-rpc-derive/tests/compile_fail/multiple_fields.rs index b4ec1eb..ff2257d 100644 --- a/quic-rpc-derive/tests/compile_fail/multiple_fields.rs +++ b/quic-rpc-derive/tests/compile_fail/multiple_fields.rs @@ -1,6 +1,6 @@ use quic_rpc_derive::rpc_requests; -#[rpc_requests(Service)] +#[rpc_requests(Service, Msg)] enum Enum { A(u8, u8), } diff --git a/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr b/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr index 80fc879..c9aa8f4 100644 --- a/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr +++ b/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr @@ -2,4 +2,4 @@ error: Each variant must have exactly one unnamed field --> tests/compile_fail/multiple_fields.rs:5:5 | 5 | A(u8, u8), - | ^ + | ^^^^^^^^^ diff --git a/quic-rpc-derive/tests/compile_fail/named_enum.rs b/quic-rpc-derive/tests/compile_fail/named_enum.rs index 4bd442c..9846410 100644 --- a/quic-rpc-derive/tests/compile_fail/named_enum.rs +++ b/quic-rpc-derive/tests/compile_fail/named_enum.rs @@ -1,6 +1,6 @@ use quic_rpc_derive::rpc_requests; -#[rpc_requests(Service)] +#[rpc_requests(Service, Msg)] enum Enum { A { name: u8 }, } diff --git a/quic-rpc-derive/tests/compile_fail/named_enum.stderr b/quic-rpc-derive/tests/compile_fail/named_enum.stderr index f13da1d..7aa3da2 100644 --- a/quic-rpc-derive/tests/compile_fail/named_enum.stderr +++ b/quic-rpc-derive/tests/compile_fail/named_enum.stderr @@ -2,4 +2,4 @@ error: Each variant must have exactly one unnamed field --> tests/compile_fail/named_enum.rs:5:5 | 5 | A { name: u8 }, - | ^ + | ^^^^^^^^^^^^^^ diff --git a/quic-rpc-derive/tests/compile_fail/non_enum.rs b/quic-rpc-derive/tests/compile_fail/non_enum.rs index e80782d..0afa60a 100644 --- a/quic-rpc-derive/tests/compile_fail/non_enum.rs +++ b/quic-rpc-derive/tests/compile_fail/non_enum.rs @@ -1,6 +1,6 @@ use quic_rpc_derive::rpc_requests; -#[rpc_requests(Service)] +#[rpc_requests(Service, Msg)] struct Foo; fn main() {} \ No newline at end of file diff --git a/quic-rpc-derive/tests/compile_fail/non_enum.stderr b/quic-rpc-derive/tests/compile_fail/non_enum.stderr index c0286ef..c618706 100644 --- a/quic-rpc-derive/tests/compile_fail/non_enum.stderr +++ b/quic-rpc-derive/tests/compile_fail/non_enum.stderr @@ -2,4 +2,4 @@ error: RpcRequests can only be applied to enums --> tests/compile_fail/non_enum.rs:4:1 | 4 | struct Foo; - | ^^^^^^ + | ^^^^^^^^^^^ diff --git a/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs b/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs index 2daca8d..4d0618f 100644 --- a/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs +++ b/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs @@ -1,6 +1,6 @@ use quic_rpc_derive::rpc_requests; -#[rpc_requests(Service)] +#[rpc_requests(Service, Msg)] enum Enum { #[rpc(fnord = Bla)] A(u8), diff --git a/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr b/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr index 4c81995..f679dbd 100644 --- a/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr +++ b/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr @@ -1,5 +1,5 @@ -error: rpc requires a response type +error: rpc requires a tx type --> tests/compile_fail/wrong_attr_types.rs:5:5 | 5 | #[rpc(fnord = Bla)] - | ^ + | ^^^^^^^^^^^^^^^^^^^ diff --git a/quic-rpc-derive/tests/smoke.rs b/quic-rpc-derive/tests/smoke.rs index c8cf77c..f1adeef 100644 --- a/quic-rpc-derive/tests/smoke.rs +++ b/quic-rpc-derive/tests/smoke.rs @@ -1,8 +1,4 @@ -use quic_rpc::channel::{ - mpsc, - none::{NoReceiver, NoSender}, - oneshot, -}; +use quic_rpc::channel::{none::NoSender, oneshot}; use quic_rpc_derive::rpc_requests; use serde::{Deserialize, Serialize}; @@ -56,3 +52,14 @@ fn simple() { impl quic_rpc::Service for Service {} } + +/// Use +/// +/// TRYBUILD=overwrite cargo test --test smoke +/// +/// to update the snapshots +#[test] +fn compile_fail() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compile_fail/*.rs"); +} From 2b55b7b66b952684f2b9ecd8c05f4471ba038af4 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 17:45:23 +0200 Subject: [PATCH 12/43] rename rpc feature --- Cargo.toml | 4 ++-- examples/derive.rs | 1 + src/lib.rs | 8 ++++---- src/util.rs | 8 ++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de6c938..12e816b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,8 @@ smallvec = { version = "1.14.0", features = ["write"] } quic-rpc-derive = { path = "quic-rpc-derive" } [features] -quinn = ["dep:quinn", "dep:postcard"] -default = ["quinn", "test"] +rpc = ["dep:quinn", "dep:postcard"] +default = ["rpc", "test"] test = ["rustls", "rcgen"] [workspace] diff --git a/examples/derive.rs b/examples/derive.rs index 878916c..d55cc1f 100644 --- a/examples/derive.rs +++ b/examples/derive.rs @@ -100,6 +100,7 @@ impl StorageActor { } } } + struct StorageApi { inner: ServiceSender, } diff --git a/src/lib.rs b/src/lib.rs index b97be73..1f35bb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -328,7 +328,7 @@ impl, S: Service> Deref for WithChannels { pub enum ServiceSender { Local(LocalMpscChannel, PhantomData), - #[cfg(feature = "quinn")] + #[cfg(feature = "rpc")] Remote(quinn::Endpoint, SocketAddr, PhantomData<(R, S)>), } @@ -342,7 +342,7 @@ impl ServiceSender { pub async fn request(&self) -> anyhow::Result> { match self { Self::Local(tx, _) => Ok(ServiceRequest::from(tx.clone())), - #[cfg(feature = "quinn")] + #[cfg(feature = "rpc")] Self::Remote(endpoint, addr, _) => { let connection = endpoint.connect(*addr, "localhost")?.await?; let (send, recv) = connection.open_bi().await?; @@ -367,7 +367,7 @@ impl Clone for LocalMpscChannel { } } -#[cfg(feature = "quinn")] +#[cfg(feature = "rpc")] pub mod rpc { use std::{fmt::Debug, future::Future, io, marker::PhantomData, pin::Pin, sync::Arc}; @@ -589,7 +589,7 @@ pub mod rpc { #[derive(Debug)] pub enum ServiceRequest { Local(LocalMpscChannel, PhantomData), - #[cfg(feature = "quinn")] + #[cfg(feature = "rpc")] Remote(rpc::RemoteRequest), } diff --git a/src/util.rs b/src/util.rs index 4244e41..4ff1054 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "test")] +#[cfg(all(feature = "rpc", feature = "test"))] mod quinn_setup_utils { use std::{net::SocketAddr, sync::Arc}; @@ -138,10 +138,10 @@ mod quinn_setup_utils { } } } -#[cfg(feature = "test")] +#[cfg(all(feature = "rpc", feature = "test"))] pub use quinn_setup_utils::*; -#[cfg(feature = "quinn")] +#[cfg(feature = "rpc")] mod varint_util { use std::{ future::Future, @@ -341,7 +341,7 @@ mod varint_util { } } } -#[cfg(feature = "quinn")] +#[cfg(feature = "rpc")] pub use varint_util::{ read_varint_u64, write_varint_u64, AsyncReadVarintExt, AsyncWriteVarintExt, WriteVarintExt, }; From 36d0a81ce78e282fab41695051b8c265db0a23e5 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 18:15:04 +0200 Subject: [PATCH 13/43] Minimize deps even more --- Cargo.lock | 2 +- Cargo.toml | 43 ++++++++---- examples/derive.rs | 3 +- examples/storage.rs | 4 +- src/lib.rs | 157 ++++++++++++++++++++++++++++++-------------- 5 files changed, 145 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87af660..414a891 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -750,7 +750,7 @@ dependencies = [ [[package]] name = "quic-rpc" -version = "0.18.3" +version = "0.5.0" dependencies = [ "anyhow", "derive_more 2.0.1", diff --git a/Cargo.toml b/Cargo.toml index 12e816b..0fffd53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic-rpc" -version = "0.18.3" +version = "0.5.0" edition = "2021" authors = ["Rüdiger Klaehn ", "n0 team"] keywords = ["api", "protocol", "network", "rpc"] @@ -13,26 +13,45 @@ description = "A streaming rpc system based on quic" rust-version = "1.76" [dependencies] -anyhow = "1" -derive_more = { version = "2", features = ["debug", "display", "from"] } -n0-future = "0.1.2" -tracing = "0.1.41" -serde = { version = "1.0.219", features = ["derive"] } -tokio = { version = "1.44.1", features = ["full"] } -postcard = { version = "1.1.1", features = ["alloc", "use-std"], optional = true } +# we require serde even in non-rpc mode +serde = { version = "1", default-features = false } +# just for the oneshot and mpsc queues +tokio = { version = "1", features = ["sync"], default-features = false } + +# used in the endpoint handler code when using rpc +tracing = { version = "0.1.41", optional = true } +# used to ser/de messages when using rpc +postcard = { version = "1", features = ["alloc", "use-std"], optional = true } +# currently only transport when using rpc quinn = { version = "0.13.0", package = "iroh-quinn", optional = true } -tracing-subscriber = { version = "0.3.19", features = ["fmt"] } +# used as a buffer for serialization when using rpc +smallvec = { version = "1.14.0", features = ["write"], optional = true } +# used in the test utils to generate quinn endpoints rustls = { version = "0.23.5", default-features = false, features = ["std"], optional = true } +# used in the test utils to generate quinn endpoints rcgen = { version = "0.13.2", optional = true } -smallvec = { version = "1.14.0", features = ["write"] } +# used in the test utils to generate quinn endpoints +anyhow = { version = "1", optional = true } [dev-dependencies] +tracing-subscriber = { version = "0.3.19", features = ["fmt"] } +# used in the derive example. This must not be a main crate dep or else it will be circular! quic-rpc-derive = { path = "quic-rpc-derive" } +# just convenient for the enum definitions +derive_more = { version = "2", features = ["debug", "display", "from"] } +# we need full for example main etc. +tokio = { version = "1", features = ["full"] } +# for AbortOnDropHandle +n0-future = "0.1.2" [features] -rpc = ["dep:quinn", "dep:postcard"] +# enable the remote transport +rpc = ["dep:quinn", "dep:postcard", "dep:smallvec", "dep:tracing"] +# add test utilities +test = ["dep:rustls", "dep:rcgen", "dep:anyhow"] +# switch on to avoid needing Send on the boxed futures +wasm-browser = [] default = ["rpc", "test"] -test = ["rustls", "rcgen"] [workspace] members = ["quic-rpc-derive"] diff --git a/examples/derive.rs b/examples/derive.rs index d55cc1f..d719f08 100644 --- a/examples/derive.rs +++ b/examples/derive.rs @@ -133,7 +133,8 @@ impl StorageApi { Ok(()) }) }); - Ok(listen(endpoint, fun)) + let x = AbortOnDropHandle::new(tokio::spawn(listen(endpoint, fun))); + Ok(x) } ServiceSender::Remote(_, _, _) => { Err(anyhow::anyhow!("cannot listen on a remote service")) diff --git a/examples/storage.rs b/examples/storage.rs index 7d581b6..fd8eb33 100644 --- a/examples/storage.rs +++ b/examples/storage.rs @@ -129,7 +129,7 @@ impl StorageApi { match &self.inner { ServiceSender::Local(local, _) => { let local = LocalMpscChannel::from(local.clone()); - let fun: Handler = Arc::new(move |msg, _, tx| { + let fun: Handler = Arc::new(move |msg, _rx, tx| { let local = local.clone(); Box::pin(async move { match msg { @@ -146,7 +146,7 @@ impl StorageApi { Ok(()) }) }); - Ok(listen(endpoint, fun)) + Ok(AbortOnDropHandle::new(tokio::spawn(listen(endpoint, fun)))) } ServiceSender::Remote(_, _, _) => { Err(anyhow::anyhow!("cannot listen on a remote service")) diff --git a/src/lib.rs b/src/lib.rs index 1f35bb7..c1f0e14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::{fmt::Debug, marker::PhantomData, net::SocketAddr, ops::Deref}; +use std::{fmt::Debug, io, marker::PhantomData, ops::Deref}; use channel::none::NoReceiver; use serde::{de::DeserializeOwned, Serialize}; @@ -33,6 +33,20 @@ pub trait Channels { type Rx: Receiver; } +mod wasm_browser { + #![allow(dead_code)] + pub type BoxedFuture<'a, T> = std::pin::Pin + 'a>>; +} +mod multithreaded { + #![allow(dead_code)] + pub type BoxedFuture<'a, T> = + std::pin::Pin + Send + 'a>>; +} +#[cfg(not(feature = "wasm-browser"))] +pub use multithreaded::*; +#[cfg(feature = "wasm-browser")] +pub use wasm_browser::*; + /// Channels that abstract over local or remote sending pub mod channel { /// Oneshot channel, similar to tokio's oneshot channel @@ -48,7 +62,7 @@ pub mod channel { Tokio(tokio::sync::oneshot::Sender), Boxed( Box< - dyn FnOnce(T) -> n0_future::future::Boxed> + dyn FnOnce(T) -> crate::BoxedFuture<'static, io::Result<()>> + Send + Sync + 'static, @@ -71,12 +85,27 @@ pub mod channel { } } - #[derive(Debug, derive_more::From, derive_more::Display)] + #[derive(Debug)] pub enum SendError { ReceiverClosed, Io(std::io::Error), } + impl From for SendError { + fn from(e: std::io::Error) -> Self { + Self::Io(e) + } + } + + impl std::fmt::Display for SendError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SendError::ReceiverClosed => write!(f, "receiver closed"), + SendError::Io(e) => write!(f, "io error: {}", e), + } + } + } + impl std::error::Error for SendError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -99,7 +128,7 @@ pub mod channel { pub enum Receiver { Tokio(tokio::sync::oneshot::Receiver), - Boxed(n0_future::future::Boxed>), + Boxed(crate::BoxedFuture<'static, std::io::Result>), } impl Future for Receiver { @@ -159,12 +188,27 @@ pub mod channel { (tx.into(), rx.into()) } - #[derive(Debug, derive_more::From, derive_more::Display)] + #[derive(Debug)] pub enum SendError { ReceiverClosed, Io(std::io::Error), } + impl From for SendError { + fn from(e: std::io::Error) -> Self { + Self::Io(e) + } + } + + impl std::fmt::Display for SendError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SendError::ReceiverClosed => write!(f, "receiver closed"), + SendError::Io(e) => write!(f, "io error: {}", e), + } + } + } + impl std::error::Error for SendError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -329,7 +373,7 @@ impl, S: Service> Deref for WithChannels { pub enum ServiceSender { Local(LocalMpscChannel, PhantomData), #[cfg(feature = "rpc")] - Remote(quinn::Endpoint, SocketAddr, PhantomData<(R, S)>), + Remote(quinn::Endpoint, std::net::SocketAddr, PhantomData<(R, S)>), } impl From> for ServiceSender { @@ -339,12 +383,15 @@ impl From> for ServiceSender { } impl ServiceSender { - pub async fn request(&self) -> anyhow::Result> { + pub async fn request(&self) -> io::Result> { match self { Self::Local(tx, _) => Ok(ServiceRequest::from(tx.clone())), #[cfg(feature = "rpc")] Self::Remote(endpoint, addr, _) => { - let connection = endpoint.connect(*addr, "localhost")?.await?; + let connection = endpoint + .connect(*addr, "localhost") + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))? + .await?; let (send, recv) = connection.open_bi().await?; Ok(ServiceRequest::Remote(rpc::RemoteRequest::new(send, recv))) } @@ -371,7 +418,6 @@ impl Clone for LocalMpscChannel { pub mod rpc { use std::{fmt::Debug, future::Future, io, marker::PhantomData, pin::Pin, sync::Arc}; - use n0_future::task::AbortOnDropHandle; use serde::{de::DeserializeOwned, Serialize}; use smallvec::SmallVec; use tokio::task::JoinSet; @@ -399,9 +445,15 @@ pub mod rpc { } } - #[derive(Debug, derive_more::From)] + #[derive(Debug)] pub struct RemoteRead(quinn::RecvStream); + impl RemoteRead { + pub(crate) fn new(recv: quinn::RecvStream) -> Self { + Self(recv) + } + } + impl From for oneshot::Receiver { fn from(read: RemoteRead) -> Self { let fut = async move { @@ -432,9 +484,15 @@ pub mod rpc { } } - #[derive(Debug, derive_more::From)] + #[derive(Debug)] pub struct RemoteWrite(quinn::SendStream); + impl RemoteWrite { + pub(crate) fn new(send: quinn::SendStream) -> Self { + Self(send) + } + } + impl From for oneshot::Sender { fn from(write: RemoteWrite) -> Self { let mut writer = write.0; @@ -526,7 +584,7 @@ pub mod rpc { } impl RemoteRequest { - pub async fn write(self, msg: impl Into) -> anyhow::Result<(RemoteRead, RemoteWrite)> { + pub async fn write(self, msg: impl Into) -> io::Result<(RemoteRead, RemoteWrite)> { let RemoteRequest(mut send, recv, _) = self; let msg = msg.into(); let mut buf = SmallVec::<[u8; 128]>::new(); @@ -538,51 +596,51 @@ pub mod rpc { /// Type alias for a handler fn for remote requests pub type Handler = Arc< - dyn Fn(R, RemoteRead, RemoteWrite) -> n0_future::future::Boxed> + dyn Fn(R, RemoteRead, RemoteWrite) -> crate::BoxedFuture<'static, io::Result<()>> + Send + Sync + 'static, >; /// Utility function to listen for incoming connections and handle them with the provided handler - pub fn listen( + pub async fn listen( endpoint: quinn::Endpoint, handler: Handler, - ) -> AbortOnDropHandle<()> { - let task = tokio::spawn(async move { - let mut tasks = JoinSet::new(); - while let Some(incoming) = endpoint.accept().await { - let handler = handler.clone(); - tasks.spawn(async move { - let connection = match incoming.await { - Ok(connection) => connection, + ) { + let mut tasks = JoinSet::new(); + while let Some(incoming) = endpoint.accept().await { + let handler = handler.clone(); + tasks.spawn(async move { + let connection = match incoming.await { + Ok(connection) => connection, + Err(cause) => { + warn!("failed to accept connection {cause:?}"); + return io::Result::Ok(()); + } + }; + loop { + let (send, mut recv) = match connection.accept_bi().await { + Ok((s, r)) => (s, r), Err(cause) => { - warn!("failed to accept connection {cause:?}"); - return anyhow::Ok(()); + warn!("failed to accept bi stream {cause:?}"); + return Ok(()); } }; - loop { - let (send, mut recv) = match connection.accept_bi().await { - Ok((s, r)) => (s, r), - Err(cause) => { - warn!("failed to accept bi stream {cause:?}"); - return anyhow::Ok(()); - } - }; - let size = recv.read_varint_u64().await?.ok_or_else(|| { - io::Error::new(io::ErrorKind::UnexpectedEof, "failed to read size") - })?; - let mut buf = vec![0; size as usize]; - recv.read_exact(&mut buf).await?; - let msg: R = postcard::from_bytes(&buf)?; - let rx = RemoteRead::from(recv); - let tx = RemoteWrite::from(send); - handler(msg, rx, tx).await?; - } - }); - } - }); - AbortOnDropHandle::new(task) + let size = recv.read_varint_u64().await?.ok_or_else(|| { + io::Error::new(io::ErrorKind::UnexpectedEof, "failed to read size") + })?; + let mut buf = vec![0; size as usize]; + recv.read_exact(&mut buf) + .await + .map_err(|e| io::Error::new(io::ErrorKind::UnexpectedEof, e))?; + let msg: R = postcard::from_bytes(&buf) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let rx = RemoteRead::new(recv); + let tx = RemoteWrite::new(send); + handler(msg, rx, tx).await?; + } + }); + } } } @@ -600,12 +658,15 @@ impl From> for ServiceRequest { } impl LocalMpscChannel { - pub async fn send(&self, value: impl Into>) -> anyhow::Result<()> + pub async fn send(&self, value: impl Into>) -> io::Result<()> where T: Channels, M: From>, { - self.0.send(value.into().into()).await?; + self.0 + .send(value.into().into()) + .await + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; Ok(()) } } From 206daba08ef3f604ec9b1328b14e3ed863b4a047 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 19:43:58 +0200 Subject: [PATCH 14/43] enough for today --- Cargo.lock | 1 + Cargo.toml | 2 ++ examples/derive.rs | 31 ++++++++----------- examples/storage.rs | 27 +++++++---------- src/lib.rs | 73 ++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 91 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 414a891..45ff9ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -763,6 +763,7 @@ dependencies = [ "serde", "smallvec", "tokio", + "tokio-util", "tracing", "tracing-subscriber", ] diff --git a/Cargo.toml b/Cargo.toml index 0fffd53..2e96794 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ rust-version = "1.76" serde = { version = "1", default-features = false } # just for the oneshot and mpsc queues tokio = { version = "1", features = ["sync"], default-features = false } +# for PollSender (which for some reason is not available in the main tokio api) +tokio-util = { version = "0.7", default-features = false } # used in the endpoint handler code when using rpc tracing = { version = "0.1.41", optional = true } diff --git a/examples/derive.rs b/examples/derive.rs index d719f08..56da59a 100644 --- a/examples/derive.rs +++ b/examples/derive.rs @@ -5,7 +5,8 @@ use std::{ sync::Arc, }; -use n0_future::task::AbortOnDropHandle; +use anyhow::bail; +use n0_future::task::{self, AbortOnDropHandle}; use quic_rpc::{ channel::{mpsc, oneshot}, rpc::{listen, Handler}, @@ -62,7 +63,7 @@ impl StorageActor { recv: rx, state: BTreeMap::new(), }; - tokio::spawn(actor.run()); + n0_future::task::spawn(actor.run()); let local = LocalMpscChannel::::from(tx); StorageApi { inner: local.into(), @@ -116,28 +117,20 @@ impl StorageApi { match &self.inner { ServiceSender::Local(local, _) => { let local = LocalMpscChannel::from(local.clone()); - let fun: Handler = Arc::new(move |msg, _, tx| { + let handler: Handler = Arc::new(move |msg, _, tx| { let local = local.clone(); - Box::pin(async move { - match msg { - StorageProtocol::Get(msg) => { - local.send((msg, tx)).await?; - } - StorageProtocol::Set(msg) => { - local.send((msg, tx)).await?; - } - StorageProtocol::List(msg) => { - local.send((msg, tx)).await?; - } - }; - Ok(()) + Box::pin(match msg { + StorageProtocol::Get(msg) => local.send((msg, tx)), + StorageProtocol::Set(msg) => local.send((msg, tx)), + StorageProtocol::List(msg) => local.send((msg, tx)), }) }); - let x = AbortOnDropHandle::new(tokio::spawn(listen(endpoint, fun))); - Ok(x) + Ok(AbortOnDropHandle::new(task::spawn(listen( + endpoint, handler, + )))) } ServiceSender::Remote(_, _, _) => { - Err(anyhow::anyhow!("cannot listen on a remote service")) + bail!("cannot listen on a remote service"); } } } diff --git a/examples/storage.rs b/examples/storage.rs index fd8eb33..27fe787 100644 --- a/examples/storage.rs +++ b/examples/storage.rs @@ -5,7 +5,7 @@ use std::{ sync::Arc, }; -use n0_future::task::AbortOnDropHandle; +use n0_future::task::{self, AbortOnDropHandle}; use quic_rpc::{ channel::{mpsc, none::NoReceiver, oneshot}, rpc::{listen, Handler}, @@ -76,7 +76,7 @@ impl StorageActor { recv: rx, state: BTreeMap::new(), }; - tokio::spawn(actor.run()); + n0_future::task::spawn(actor.run()); let local = LocalMpscChannel::::from(tx); StorageApi { inner: local.into(), @@ -129,24 +129,17 @@ impl StorageApi { match &self.inner { ServiceSender::Local(local, _) => { let local = LocalMpscChannel::from(local.clone()); - let fun: Handler = Arc::new(move |msg, _rx, tx| { + let handler: Handler = Arc::new(move |msg, _rx, tx| { let local = local.clone(); - Box::pin(async move { - match msg { - StorageProtocol::Get(msg) => { - local.send((msg, tx)).await?; - } - StorageProtocol::Set(msg) => { - local.send((msg, tx)).await?; - } - StorageProtocol::List(msg) => { - local.send((msg, tx)).await?; - } - }; - Ok(()) + Box::pin(match msg { + StorageProtocol::Get(msg) => local.send((msg, tx)), + StorageProtocol::Set(msg) => local.send((msg, tx)), + StorageProtocol::List(msg) => local.send((msg, tx)), }) }); - Ok(AbortOnDropHandle::new(tokio::spawn(listen(endpoint, fun)))) + Ok(AbortOnDropHandle::new(task::spawn(listen( + endpoint, handler, + )))) } ServiceSender::Remote(_, _, _) => { Err(anyhow::anyhow!("cannot listen on a remote service")) diff --git a/src/lib.rs b/src/lib.rs index c1f0e14..033b363 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -657,16 +657,75 @@ impl From> for ServiceRequest { } } -impl LocalMpscChannel { - pub async fn send(&self, value: impl Into>) -> io::Result<()> +impl LocalMpscChannel { + pub fn send(&self, value: impl Into>) -> SendFut where T: Channels, M: From>, { - self.0 - .send(value.into().into()) - .await - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - Ok(()) + let value: M = value.into().into(); + SendFut::new(self.0.clone(), value) } } + +mod send_fut { + use std::{ + future::Future, + io, + pin::Pin, + task::{Context, Poll}, + }; + + use tokio::sync::mpsc::Sender; + use tokio_util::sync::PollSender; + + pub struct SendFut { + poll_sender: PollSender, + value: Option, + } + + impl SendFut { + pub fn new(sender: Sender, value: T) -> Self { + Self { + poll_sender: PollSender::new(sender), + value: Some(value), + } + } + } + + impl Future for SendFut { + type Output = io::Result<()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + // Safely extract the value + let value = match this.value.take() { + Some(v) => v, + None => return Poll::Ready(Ok(())), // Already completed + }; + + // Try to reserve capacity + match this.poll_sender.poll_reserve(cx) { + Poll::Ready(Ok(())) => { + // Send the item + this.poll_sender.send_item(value).ok(); + Poll::Ready(Ok(())) + } + Poll::Ready(Err(_)) => { + // Channel is closed + Poll::Ready(Err(io::Error::new( + io::ErrorKind::BrokenPipe, + "Channel closed", + ))) + } + Poll::Pending => { + // Restore the value and wait + this.value = Some(value); + Poll::Pending + } + } + } + } +} +use send_fut::SendFut; From 9b74bec0264a4f09f326c578827f4fc161fe24c0 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 20:18:49 +0200 Subject: [PATCH 15/43] Fix a dependency --- Cargo.toml | 2 +- src/lib.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e96794..bb89aad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ n0-future = "0.1.2" [features] # enable the remote transport -rpc = ["dep:quinn", "dep:postcard", "dep:smallvec", "dep:tracing"] +rpc = ["dep:quinn", "dep:postcard", "dep:smallvec", "dep:tracing", "tokio/io-util"] # add test utilities test = ["dep:rustls", "dep:rcgen", "dep:anyhow"] # switch on to avoid needing Send on the boxed futures diff --git a/src/lib.rs b/src/lib.rs index 033b363..8f22bc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,7 +220,7 @@ pub mod channel { pub enum Sender { Tokio(tokio::sync::mpsc::Sender), - Boxed(Box>), + Boxed(Box>), } impl From> for Sender { @@ -229,14 +229,14 @@ pub mod channel { } } - pub trait NetworkSender: Debug + Send + Sync + 'static { + pub trait BoxedSender: Debug + Send + Sync + 'static { fn send( &mut self, value: T, ) -> Pin> + Send + '_>>; } - pub trait NetworkReceiver: Debug + Send + Sync + 'static { + pub trait BoxedReceiver: Debug + Send + Sync + 'static { fn recv(&mut self) -> Pin>> + Send + '_>>; } @@ -264,7 +264,7 @@ pub mod channel { pub enum Receiver { Tokio(tokio::sync::mpsc::Receiver), - Boxed(Box>), + Boxed(Box>), } impl Receiver { @@ -317,7 +317,7 @@ pub mod channel { pub struct WithChannels, S: Service> { /// The inner message. pub inner: I, - /// The return channel to send the response to. Can be set to [`NoSender`] if not needed. + /// The return channel to send the response to. Can be set to [`crate::channel::none::NoSender`] if not needed. pub tx: >::Tx, /// The request channel to receive the request from. Can be set to [`NoReceiver`] if not needed. pub rx: >::Rx, @@ -425,7 +425,7 @@ pub mod rpc { use crate::{ channel::{ - mpsc::{self, NetworkReceiver, NetworkSender}, + mpsc::{self, BoxedReceiver, BoxedSender}, oneshot, }, util::{AsyncReadVarintExt, WriteVarintExt}, @@ -530,7 +530,7 @@ pub mod rpc { } } - impl NetworkReceiver for QuinnReceiver { + impl BoxedReceiver for QuinnReceiver { fn recv(&mut self) -> Pin>> + Send + '_>> { Box::pin(async { let read = &mut self.recv; @@ -564,7 +564,7 @@ pub mod rpc { } } - impl NetworkSender for QuinnSender { + impl BoxedSender for QuinnSender { fn send(&mut self, value: T) -> Pin> + Send + '_>> { Box::pin(async { let value = value; From 0006ac49f2cbd1e85a56875c1e344598a83f44bf Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 21:34:47 +0200 Subject: [PATCH 16/43] Add compute example --- Cargo.lock | 1 + Cargo.toml | 4 +- examples/compute.rs | 415 +++++++++++++++++++++++++++++++++++++ examples/derive.rs | 8 +- examples/storage.rs | 14 +- quic-rpc-derive/src/lib.rs | 2 +- src/lib.rs | 94 ++++++--- src/util.rs | 6 +- 8 files changed, 495 insertions(+), 49 deletions(-) create mode 100644 examples/compute.rs diff --git a/Cargo.lock b/Cargo.lock index 45ff9ec..d35248c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -754,6 +754,7 @@ version = "0.5.0" dependencies = [ "anyhow", "derive_more 2.0.1", + "futures-buffered", "iroh-quinn", "n0-future", "postcard", diff --git a/Cargo.toml b/Cargo.toml index bb89aad..fb82b31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ rustls = { version = "0.23.5", default-features = false, features = ["std"], opt rcgen = { version = "0.13.2", optional = true } # used in the test utils to generate quinn endpoints anyhow = { version = "1", optional = true } +# used in the benches +futures-buffered ={ version = "0.2.11", optional = true } [dev-dependencies] tracing-subscriber = { version = "0.3.19", features = ["fmt"] } @@ -50,7 +52,7 @@ n0-future = "0.1.2" # enable the remote transport rpc = ["dep:quinn", "dep:postcard", "dep:smallvec", "dep:tracing", "tokio/io-util"] # add test utilities -test = ["dep:rustls", "dep:rcgen", "dep:anyhow"] +test = ["dep:rustls", "dep:rcgen", "dep:anyhow", "dep:futures-buffered"] # switch on to avoid needing Send on the boxed futures wasm-browser = [] default = ["rpc", "test"] diff --git a/examples/compute.rs b/examples/compute.rs new file mode 100644 index 0000000..4929490 --- /dev/null +++ b/examples/compute.rs @@ -0,0 +1,415 @@ +use std::{ + io::{self, Write}, + marker::PhantomData, + net::{Ipv4Addr, SocketAddr, SocketAddrV4}, + sync::Arc, +}; + +use anyhow::bail; +use n0_future::task::{self, AbortOnDropHandle}; +use quic_rpc::{ + channel::{mpsc, oneshot}, + rpc::{listen, Handler, RemoteRead}, + util::{make_client_endpoint, make_server_endpoint}, + LocalMpscChannel, Msg, Service, ServiceRequest, ServiceSender, +}; +use quic_rpc_derive::rpc_requests; +use serde::{Deserialize, Serialize}; +use tracing::trace; + +// Define the ComputeService +#[derive(Debug, Clone, Copy)] +struct ComputeService; + +impl Service for ComputeService {} + +// Define ComputeRequest sub-messages +#[derive(Debug, Serialize, Deserialize)] +struct Sqr { + num: u64, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Sum; + +#[derive(Debug, Serialize, Deserialize)] +struct Fibonacci { + max: u64, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Multiply { + initial: u64, +} + +// Define ComputeRequest enum +#[derive(Debug, Serialize, Deserialize)] +enum ComputeRequest { + Sqr(Sqr), + Sum(Sum), + Fibonacci(Fibonacci), + Multiply(Multiply), +} + +// Define the protocol and message enums using the macro +#[rpc_requests(ComputeService, ComputeMessage)] +#[derive(derive_more::From, Serialize, Deserialize)] +enum ComputeProtocol { + #[rpc(tx=oneshot::Sender)] + Sqr(Sqr), + #[rpc(rx=mpsc::Receiver, tx=oneshot::Sender)] + Sum(Sum), + #[rpc(tx=mpsc::Sender)] + Fibonacci(Fibonacci), + #[rpc(rx=mpsc::Receiver, tx=mpsc::Sender)] + Multiply(Multiply), +} + +// The actor that processes requests +struct ComputeActor { + recv: tokio::sync::mpsc::Receiver, +} + +impl ComputeActor { + pub fn local() -> ComputeApi { + let (tx, rx) = tokio::sync::mpsc::channel(128); + let actor = Self { recv: rx }; + n0_future::task::spawn(actor.run()); + let local = LocalMpscChannel::::from(tx); + ComputeApi { + inner: local.into(), + } + } + + async fn run(mut self) { + while let Some(msg) = self.recv.recv().await { + if let Err(cause) = self.handle(msg).await { + eprintln!("Error: {}", cause); + } + } + } + + async fn handle(&mut self, msg: ComputeMessage) -> io::Result<()> { + match msg { + ComputeMessage::Sqr(sqr) => { + trace!("sqr {:?}", sqr); + let Msg { tx, inner, .. } = sqr; + let result = (inner.num as u128) * (inner.num as u128); + tx.send(result).await?; + } + ComputeMessage::Sum(sum) => { + trace!("sum {:?}", sum); + let Msg { rx, tx, .. } = sum; + let mut receiver = rx; + let mut total = 0; + while let Some(num) = receiver.recv().await? { + total += num; + } + tx.send(total).await?; + } + ComputeMessage::Fibonacci(fib) => { + trace!("fibonacci {:?}", fib); + let Msg { tx, inner, .. } = fib; + let mut sender = tx; + let mut a = 0u64; + let mut b = 1u64; + while a <= inner.max { + sender.send(a).await?; + let next = a + b; + a = b; + b = next; + } + } + ComputeMessage::Multiply(mult) => { + trace!("multiply {:?}", mult); + let Msg { rx, tx, inner } = mult; + let mut receiver = rx; + let mut sender = tx; + let multiplier = inner.initial; + while let Some(num) = receiver.recv().await? { + sender.send(multiplier * num).await?; + } + } + } + Ok(()) + } +} +// The API for interacting with the ComputeService +#[derive(Clone)] +struct ComputeApi { + inner: ServiceSender, +} + +impl ComputeApi { + pub fn connect(endpoint: quinn::Endpoint, addr: SocketAddr) -> anyhow::Result { + Ok(ComputeApi { + inner: ServiceSender::Remote(endpoint, addr, PhantomData), + }) + } + + pub fn listen(&self, endpoint: quinn::Endpoint) -> anyhow::Result> { + match &self.inner { + ServiceSender::Local(local, _) => { + let local = LocalMpscChannel::from(local.clone()); + let handler: Handler = Arc::new(move |msg, rx: RemoteRead, tx| { + let local = local.clone(); + Box::pin(match msg { + ComputeProtocol::Sqr(msg) => local.send((msg, tx)), + ComputeProtocol::Sum(msg) => local.send((msg, tx, rx)), + ComputeProtocol::Fibonacci(msg) => local.send((msg, tx)), + ComputeProtocol::Multiply(msg) => local.send((msg, tx, rx)), + }) + }); + Ok(AbortOnDropHandle::new(task::spawn(listen( + endpoint, handler, + )))) + } + ServiceSender::Remote(_, _, _) => { + bail!("cannot listen on a remote service"); + } + } + } + + pub async fn sqr(&self, num: u64) -> anyhow::Result> { + let msg = Sqr { num }; + match self.inner.request().await? { + ServiceRequest::Local(request, _) => { + let (tx, rx) = oneshot::channel(); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } + + pub async fn sum(&self) -> anyhow::Result<(mpsc::Sender, oneshot::Receiver)> { + let msg = Sum; + match self.inner.request().await? { + ServiceRequest::Local(request, _) => { + let (num_tx, num_rx) = mpsc::channel(10); + let (sum_tx, sum_rx) = oneshot::channel(); + request.send((msg, sum_tx, num_rx)).await?; + Ok((num_tx, sum_rx)) + } + ServiceRequest::Remote(request) => { + let (rx, tx) = request.write(msg).await?; + Ok((tx.into(), rx.into())) + } + } + } + + pub async fn fibonacci(&self, max: u64) -> anyhow::Result> { + let msg = Fibonacci { max }; + match self.inner.request().await? { + ServiceRequest::Local(request, _) => { + let (tx, rx) = mpsc::channel(10); + request.send((msg, tx)).await?; + Ok(rx) + } + ServiceRequest::Remote(request) => { + let (rx, _tx) = request.write(msg).await?; + Ok(rx.into()) + } + } + } + + pub async fn multiply( + &self, + initial: u64, + ) -> anyhow::Result<(mpsc::Sender, mpsc::Receiver)> { + let msg = Multiply { initial }; + match self.inner.request().await? { + ServiceRequest::Local(request, _) => { + let (in_tx, in_rx) = mpsc::channel(10); + let (out_tx, out_rx) = mpsc::channel(10); + request.send((msg, out_tx, in_rx)).await?; + Ok((in_tx, out_rx)) + } + ServiceRequest::Remote(request) => { + let (rx, tx) = request.write(msg).await?; + Ok((tx.into(), rx.into())) + } + } + } +} + +// Local usage example +async fn local() -> anyhow::Result<()> { + let api = ComputeActor::local(); + + // Test Sqr + let rx = api.sqr(5).await?; + println!("Local: 5^2 = {}", rx.await?); + + // Test Sum + let (mut tx, rx) = api.sum().await?; + tx.send(1).await?; + tx.send(2).await?; + tx.send(3).await?; + drop(tx); + println!("Local: sum of [1, 2, 3] = {}", rx.await?); + + // Test Fibonacci + let mut rx = api.fibonacci(10).await?; + print!("Local: Fibonacci up to 10 = "); + while let Some(num) = rx.recv().await? { + print!("{} ", num); + } + println!(); + + // Test Multiply + let (mut in_tx, mut out_rx) = api.multiply(3).await?; + in_tx.send(2).await?; + in_tx.send(4).await?; + in_tx.send(6).await?; + drop(in_tx); + print!("Local: 3 * [2, 4, 6] = "); + while let Some(num) = out_rx.recv().await? { + print!("{} ", num); + } + println!(); + + Ok(()) +} + +// Remote usage example +async fn remote() -> anyhow::Result<()> { + let port = 10114; + let (server, cert) = + make_server_endpoint(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port).into())?; + let client = + make_client_endpoint(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0).into(), &[&cert])?; + let compute = ComputeActor::local(); + let handle = compute.listen(server)?; + let api = ComputeApi::connect(client, SocketAddrV4::new(Ipv4Addr::LOCALHOST, port).into())?; + + // Test Sqr + let rx = api.sqr(4).await?; + println!("Remote: 4^2 = {}", rx.await?); + + // Test Sum + let (mut tx, rx) = api.sum().await?; + tx.send(4).await?; + tx.send(5).await?; + tx.send(6).await?; + drop(tx); + println!("Remote: sum of [4, 5, 6] = {}", rx.await?); + + // Test Fibonacci + let mut rx = api.fibonacci(20).await?; + print!("Remote: Fibonacci up to 20 = "); + while let Some(num) = rx.recv().await? { + print!("{} ", num); + } + println!(); + + // Test Multiply + let (mut in_tx, mut out_rx) = api.multiply(5).await?; + in_tx.send(1).await?; + in_tx.send(2).await?; + in_tx.send(3).await?; + drop(in_tx); + print!("Remote: 5 * [1, 2, 3] = "); + while let Some(num) = out_rx.recv().await? { + print!("{} ", num); + } + println!(); + + drop(handle); + Ok(()) +} + +// Benchmark function using the new ComputeApi +async fn bench(api: ComputeApi, n: u64) -> anyhow::Result<()> { + // Individual RPCs (sequential) + { + let mut sum = 0; + let t0 = std::time::Instant::now(); + for i in 0..n { + sum += api.sqr(i).await?.await?; + if i % 10000 == 0 { + print!("."); + io::stdout().flush()?; + } + } + let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round() as u64; + assert_eq!(sum, sum_of_squares(n)); + clear_line()?; + println!("RPC seq {} rps", rps); + } + + // Parallel RPCs + { + let t0 = std::time::Instant::now(); + let api = api.clone(); + let reqs = n0_future::stream::iter((0..n).map(move |i| { + let api = api.clone(); + async move { anyhow::Ok(api.sqr(i).await?.await?) } + })); + use futures_buffered::BufferedStreamExt; + use n0_future::stream::StreamExt; + let resp: Vec<_> = reqs.buffered_unordered(32).try_collect().await?; + let sum = resp.into_iter().sum::(); + let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round() as u64; + assert_eq!(sum, sum_of_squares(n)); + clear_line()?; + println!("RPC par {} rps", rps); + } + + // Sequential streaming (using Multiply instead of MultiplyUpdate) + { + let t0 = std::time::Instant::now(); + let (mut send, mut recv) = api.multiply(2).await?; + let handle = tokio::task::spawn(async move { + for i in 0..n { + send.send(i).await?; + } + Ok::<(), io::Error>(()) + }); + let mut sum = 0; + let mut i = 0; + while let Some(res) = recv.recv().await? { + sum += res; + if i % 10000 == 0 { + print!("."); + io::stdout().flush()?; + } + i += 1; + } + let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round() as u64; + assert_eq!(sum, (0..n).map(|x| x * 2).sum()); + clear_line()?; + println!("bidi seq {} rps", rps); + handle.await??; + } + + Ok(()) +} + +// Helper function to compute the sum of squares +fn sum_of_squares(n: u64) -> u128 { + (0..n).map(|x| (x * x) as u128).sum() +} + +// Helper function to clear the current line +fn clear_line() -> io::Result<()> { + io::stdout().write_all(b"\r\x1b[K")?; + io::stdout().flush()?; + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt().init(); + println!("Local use"); + local().await?; + println!("Remote use"); + remote().await?; + + let api = ComputeActor::local(); + bench(api, 1000000).await?; + Ok(()) +} diff --git a/examples/derive.rs b/examples/derive.rs index 56da59a..39bd879 100644 --- a/examples/derive.rs +++ b/examples/derive.rs @@ -11,7 +11,7 @@ use quic_rpc::{ channel::{mpsc, oneshot}, rpc::{listen, Handler}, util::{make_client_endpoint, make_server_endpoint}, - LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, + LocalMpscChannel, Msg, Service, ServiceRequest, ServiceSender, }; // Import the macro use quic_rpc_derive::rpc_requests; @@ -80,18 +80,18 @@ impl StorageActor { match msg { StorageMessage::Get(get) => { info!("get {:?}", get); - let WithChannels { tx, inner, .. } = get; + let Msg { tx, inner, .. } = get; tx.send(self.state.get(&inner.key).cloned()).await.ok(); } StorageMessage::Set(set) => { info!("set {:?}", set); - let WithChannels { tx, inner, .. } = set; + let Msg { tx, inner, .. } = set; self.state.insert(inner.key, inner.value); tx.send(()).await.ok(); } StorageMessage::List(list) => { info!("list {:?}", list); - let WithChannels { mut tx, .. } = list; + let Msg { mut tx, .. } = list; for (key, value) in &self.state { if tx.send(format!("{key}={value}")).await.is_err() { break; diff --git a/examples/storage.rs b/examples/storage.rs index 27fe787..dcda67a 100644 --- a/examples/storage.rs +++ b/examples/storage.rs @@ -10,7 +10,7 @@ use quic_rpc::{ channel::{mpsc, none::NoReceiver, oneshot}, rpc::{listen, Handler}, util::{make_client_endpoint, make_server_endpoint}, - Channels, LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, + Channels, LocalMpscChannel, Msg, Service, ServiceRequest, ServiceSender, }; use serde::{Deserialize, Serialize}; use tracing::info; @@ -59,9 +59,9 @@ enum StorageProtocol { #[derive(derive_more::From)] enum StorageMessage { - Get(WithChannels), - Set(WithChannels), - List(WithChannels), + Get(Msg), + Set(Msg), + List(Msg), } struct StorageActor { @@ -93,18 +93,18 @@ impl StorageActor { match msg { StorageMessage::Get(get) => { info!("get {:?}", get); - let WithChannels { tx, inner, .. } = get; + let Msg { tx, inner, .. } = get; tx.send(self.state.get(&inner.key).cloned()).await.ok(); } StorageMessage::Set(set) => { info!("set {:?}", set); - let WithChannels { tx, inner, .. } = set; + let Msg { tx, inner, .. } = set; self.state.insert(inner.key, inner.value); tx.send(()).await.ok(); } StorageMessage::List(list) => { info!("list {:?}", list); - let WithChannels { mut tx, .. } = list; + let Msg { mut tx, .. } = list; for (key, value) in &self.state { if tx.send(format!("{key}={value}")).await.is_err() { break; diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index bd86ecb..451e508 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -142,7 +142,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { let inner_type = &last_segment.ident; quote! { - #variant_name(::quic_rpc::WithChannels<#inner_type, #service_name>) + #variant_name(::quic_rpc::Msg<#inner_type, #service_name>) } }) .collect::>(); diff --git a/src/lib.rs b/src/lib.rs index 8f22bc3..b89f327 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,10 @@ use std::{fmt::Debug, io, marker::PhantomData, ops::Deref}; use channel::none::NoReceiver; use serde::{de::DeserializeOwned, Serialize}; +#[cfg(feature = "test")] pub mod util; +#[cfg(not(feature = "test"))] +mod util; /// Requirements for a RPC message /// @@ -35,39 +38,39 @@ pub trait Channels { mod wasm_browser { #![allow(dead_code)] - pub type BoxedFuture<'a, T> = std::pin::Pin + 'a>>; + pub(crate) type BoxedFuture<'a, T> = + std::pin::Pin + 'a>>; } mod multithreaded { #![allow(dead_code)] - pub type BoxedFuture<'a, T> = + pub(crate) type BoxedFuture<'a, T> = std::pin::Pin + Send + 'a>>; } #[cfg(not(feature = "wasm-browser"))] -pub use multithreaded::*; +use multithreaded::*; #[cfg(feature = "wasm-browser")] -pub use wasm_browser::*; +use wasm_browser::*; /// Channels that abstract over local or remote sending pub mod channel { /// Oneshot channel, similar to tokio's oneshot channel pub mod oneshot { - use std::{fmt::Debug, future::Future, io, pin::Pin}; + use std::{fmt::Debug, future::Future, io, pin::Pin, task}; pub fn channel() -> (Sender, Receiver) { let (tx, rx) = tokio::sync::oneshot::channel(); (tx.into(), rx.into()) } + pub type BoxedSender = Box< + dyn FnOnce(T) -> crate::BoxedFuture<'static, io::Result<()>> + Send + Sync + 'static, + >; + + pub type BoxedReceiver = crate::BoxedFuture<'static, io::Result>; + pub enum Sender { Tokio(tokio::sync::oneshot::Sender), - Boxed( - Box< - dyn FnOnce(T) -> crate::BoxedFuture<'static, io::Result<()>> - + Send - + Sync - + 'static, - >, - ), + Boxed(BoxedSender), } impl Debug for Sender { @@ -88,11 +91,20 @@ pub mod channel { #[derive(Debug)] pub enum SendError { ReceiverClosed, - Io(std::io::Error), + Io(io::Error), } - impl From for SendError { - fn from(e: std::io::Error) -> Self { + impl From for io::Error { + fn from(e: SendError) -> Self { + match e { + SendError::ReceiverClosed => io::Error::new(io::ErrorKind::BrokenPipe, e), + SendError::Io(e) => e, + } + } + } + + impl From for SendError { + fn from(e: io::Error) -> Self { Self::Io(e) } } @@ -128,20 +140,17 @@ pub mod channel { pub enum Receiver { Tokio(tokio::sync::oneshot::Receiver), - Boxed(crate::BoxedFuture<'static, std::io::Result>), + Boxed(BoxedReceiver), } impl Future for Receiver { - type Output = std::io::Result; + type Output = io::Result; - fn poll( - self: Pin<&mut Self>, - cx: &mut std::task::Context, - ) -> std::task::Poll { + fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> task::Poll { match self.get_mut() { Self::Tokio(rx) => Pin::new(rx) .poll(cx) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e)), + .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e)), Self::Boxed(rx) => Pin::new(rx).poll(cx), } } @@ -158,7 +167,7 @@ pub mod channel { impl From for Receiver where F: FnOnce() -> Fut, - Fut: Future> + Send + 'static, + Fut: Future> + Send + 'static, { fn from(f: F) -> Self { Self::Boxed(Box::pin(f())) @@ -191,15 +200,24 @@ pub mod channel { #[derive(Debug)] pub enum SendError { ReceiverClosed, - Io(std::io::Error), + Io(io::Error), } - impl From for SendError { - fn from(e: std::io::Error) -> Self { + impl From for SendError { + fn from(e: io::Error) -> Self { Self::Io(e) } } + impl From for io::Error { + fn from(e: SendError) -> Self { + match e { + SendError::ReceiverClosed => io::Error::new(io::ErrorKind::BrokenPipe, e), + SendError::Io(e) => e, + } + } + } + impl std::fmt::Display for SendError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -314,7 +332,7 @@ pub mod channel { /// /// rx and tx can be set to an appropriate channel kind. #[derive(Debug)] -pub struct WithChannels, S: Service> { +pub struct Msg, S: Service> { /// The inner message. pub inner: I, /// The return channel to send the response to. Can be set to [`crate::channel::none::NoSender`] if not needed. @@ -326,7 +344,7 @@ pub struct WithChannels, S: Service> { /// Tuple conversion from inner message and tx/rx channels to a WithChannels struct /// /// For the case where you want both tx and rx channels. -impl, S: Service, Tx, Rx> From<(I, Tx, Rx)> for WithChannels +impl, S: Service, Tx, Rx> From<(I, Tx, Rx)> for Msg where I: Channels, >::Tx: From, @@ -345,7 +363,7 @@ where /// Tuple conversion from inner message and tx channel to a WithChannels struct /// /// For the very common case where you just need a tx channel to send the response to. -impl From<(I, Tx)> for WithChannels +impl From<(I, Tx)> for Msg where I: Channels, S: Service, @@ -362,7 +380,7 @@ where } /// Deref so you can access the inner fields directly -impl, S: Service> Deref for WithChannels { +impl, S: Service> Deref for Msg { type Target = I; fn deref(&self) -> &Self::Target { @@ -376,6 +394,16 @@ pub enum ServiceSender { Remote(quinn::Endpoint, std::net::SocketAddr, PhantomData<(R, S)>), } +impl Clone for ServiceSender { + fn clone(&self) -> Self { + match self { + Self::Local(tx, _) => Self::Local(tx.clone(), PhantomData), + #[cfg(feature = "rpc")] + Self::Remote(endpoint, addr, _) => Self::Remote(endpoint.clone(), *addr, PhantomData), + } + } +} + impl From> for ServiceSender { fn from(tx: LocalMpscChannel) -> Self { Self::Local(tx, PhantomData) @@ -658,10 +686,10 @@ impl From> for ServiceRequest { } impl LocalMpscChannel { - pub fn send(&self, value: impl Into>) -> SendFut + pub fn send(&self, value: impl Into>) -> SendFut where T: Channels, - M: From>, + M: From>, { let value: M = value.into().into(); SendFut::new(self.0.clone(), value) diff --git a/src/util.rs b/src/util.rs index 4ff1054..22e157e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -169,8 +169,8 @@ mod varint_util { loop { // We can only shift up to 63 bits (for a u64) if shift >= 64 { - return Err(Error::new( - std::io::ErrorKind::InvalidData, + return Err(io::Error::new( + io::ErrorKind::InvalidData, "Varint is too large for u64", )); } @@ -179,7 +179,7 @@ mod varint_util { let res = reader.read_u8().await; if shift == 0 { if let Err(cause) = res { - if cause.kind() == std::io::ErrorKind::UnexpectedEof { + if cause.kind() == io::ErrorKind::UnexpectedEof { return Ok(None); } else { return Err(cause); From b6ddadeb17a6cef6817dae8166c58d952779f20f Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 21:53:48 +0200 Subject: [PATCH 17/43] WIP --- Cargo.lock | 7 ++++ Cargo.toml | 2 ++ examples/compute.rs | 86 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d35248c..70b6dcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -763,6 +763,7 @@ dependencies = [ "rustls", "serde", "smallvec", + "thousands", "tokio", "tokio-util", "tracing", @@ -1260,6 +1261,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + [[package]] name = "thread_local" version = "1.1.8" diff --git a/Cargo.toml b/Cargo.toml index fb82b31..c1e8adf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,8 @@ derive_more = { version = "2", features = ["debug", "display", "from"] } tokio = { version = "1", features = ["full"] } # for AbortOnDropHandle n0-future = "0.1.2" +# formatting +thousands = "0.2.0" [features] # enable the remote transport diff --git a/examples/compute.rs b/examples/compute.rs index 4929490..71d8abb 100644 --- a/examples/compute.rs +++ b/examples/compute.rs @@ -6,7 +6,11 @@ use std::{ }; use anyhow::bail; -use n0_future::task::{self, AbortOnDropHandle}; +use futures_buffered::BufferedStreamExt; +use n0_future::{ + stream::StreamExt, + task::{self, AbortOnDropHandle}, +}; use quic_rpc::{ channel::{mpsc, oneshot}, rpc::{listen, Handler, RemoteRead}, @@ -15,6 +19,7 @@ use quic_rpc::{ }; use quic_rpc_derive::rpc_requests; use serde::{Deserialize, Serialize}; +use thousands::Separable; use tracing::trace; // Define the ComputeService @@ -83,13 +88,15 @@ impl ComputeActor { async fn run(mut self) { while let Some(msg) = self.recv.recv().await { - if let Err(cause) = self.handle(msg).await { - eprintln!("Error: {}", cause); - } + n0_future::task::spawn(async move { + if let Err(cause) = Self::handle(msg).await { + eprintln!("Error: {}", cause); + } + }); } } - async fn handle(&mut self, msg: ComputeMessage) -> io::Result<()> { + async fn handle(msg: ComputeMessage) -> io::Result<()> { match msg { ComputeMessage::Sqr(sqr) => { trace!("sqr {:?}", sqr); @@ -338,7 +345,7 @@ async fn bench(api: ComputeApi, n: u64) -> anyhow::Result<()> { let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round() as u64; assert_eq!(sum, sum_of_squares(n)); clear_line()?; - println!("RPC seq {} rps", rps); + println!("RPC seq {} rps", rps.separate_with_underscores()); } // Parallel RPCs @@ -349,14 +356,12 @@ async fn bench(api: ComputeApi, n: u64) -> anyhow::Result<()> { let api = api.clone(); async move { anyhow::Ok(api.sqr(i).await?.await?) } })); - use futures_buffered::BufferedStreamExt; - use n0_future::stream::StreamExt; let resp: Vec<_> = reqs.buffered_unordered(32).try_collect().await?; let sum = resp.into_iter().sum::(); let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round() as u64; assert_eq!(sum, sum_of_squares(n)); clear_line()?; - println!("RPC par {} rps", rps); + println!("RPC par {} rps", rps.separate_with_underscores()); } // Sequential streaming (using Multiply instead of MultiplyUpdate) @@ -382,7 +387,7 @@ async fn bench(api: ComputeApi, n: u64) -> anyhow::Result<()> { let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round() as u64; assert_eq!(sum, (0..n).map(|x| x * 2).sum()); clear_line()?; - println!("bidi seq {} rps", rps); + println!("bidi seq {} rps", rps.separate_with_underscores()); handle.await??; } @@ -401,6 +406,65 @@ fn clear_line() -> io::Result<()> { Ok(()) } + +// Simple benchmark sending oneshot senders via an mpsc channel +pub async fn reference_bench(n: u64) -> anyhow::Result<()> { + // Create an mpsc channel to send oneshot senders + let (tx, mut rx) = tokio::sync::mpsc::channel::>(32); + + // Spawn a task to respond to all oneshot senders + tokio::spawn(async move { + while let Some(sender) = rx.recv().await { + // Immediately send a fixed response (42) back through the oneshot sender + sender.send(42).ok(); + } + Ok::<(), io::Error>(()) + }); + + // Sequential oneshot sends + { + let mut sum = 0; + let t0 = std::time::Instant::now(); + for i in 0..n { + let (send, recv) = tokio::sync::oneshot::channel(); + tx.send(send).await?; + sum += recv.await?; + if i % 10000 == 0 { + print!("."); + io::stdout().flush()?; + } + } + let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round() as u64; + assert_eq!(sum, 42 * n); // Each response is 42 + clear_line()?; + println!( + "Reference seq {} rps", + rps.separate_with_underscores() + ); + } + + // Parallel oneshot sends + { + let t0 = std::time::Instant::now(); + let reqs = n0_future::stream::iter((0..n).map(|_| async { + let (send, recv) = tokio::sync::oneshot::channel(); + tx.send(send).await?; + anyhow::Ok(recv.await?) + })); + let resp: Vec<_> = reqs.buffered_unordered(32).try_collect().await?; + let sum = resp.into_iter().sum::(); + let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round() as u64; + assert_eq!(sum, 42 * n); // Each response is 42 + clear_line()?; + println!( + "Reference par {} rps", + rps.separate_with_underscores() + ); + } + + Ok(()) +} + #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt().init(); @@ -411,5 +475,7 @@ async fn main() -> anyhow::Result<()> { let api = ComputeActor::local(); bench(api, 1000000).await?; + + reference_bench(1000000).await?; Ok(()) } From 6bde68350065074adb6b7e40fd7dc192ef6ffafd Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sun, 16 Mar 2025 22:00:58 +0200 Subject: [PATCH 18/43] adapt channel size to same value as in old quic-rpc bench --- examples/compute.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/compute.rs b/examples/compute.rs index 71d8abb..bdbacbe 100644 --- a/examples/compute.rs +++ b/examples/compute.rs @@ -212,7 +212,7 @@ impl ComputeApi { let msg = Fibonacci { max }; match self.inner.request().await? { ServiceRequest::Local(request, _) => { - let (tx, rx) = mpsc::channel(10); + let (tx, rx) = mpsc::channel(128); request.send((msg, tx)).await?; Ok(rx) } @@ -230,8 +230,8 @@ impl ComputeApi { let msg = Multiply { initial }; match self.inner.request().await? { ServiceRequest::Local(request, _) => { - let (in_tx, in_rx) = mpsc::channel(10); - let (out_tx, out_rx) = mpsc::channel(10); + let (in_tx, in_rx) = mpsc::channel(128); + let (out_tx, out_rx) = mpsc::channel(128); request.send((msg, out_tx, in_rx)).await?; Ok((in_tx, out_rx)) } From 02d7823c389631cb32cf73a445f59d53efdb6202 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 09:26:59 +0200 Subject: [PATCH 19/43] rename Msg to WithChannels again --- examples/compute.rs | 21 +++++++-------------- examples/derive.rs | 8 ++++---- examples/storage.rs | 14 +++++++------- quic-rpc-derive/src/lib.rs | 32 ++++++++++---------------------- src/lib.rs | 36 ++++++++++++++++++++++++------------ 5 files changed, 52 insertions(+), 59 deletions(-) diff --git a/examples/compute.rs b/examples/compute.rs index bdbacbe..3a1baa8 100644 --- a/examples/compute.rs +++ b/examples/compute.rs @@ -15,7 +15,7 @@ use quic_rpc::{ channel::{mpsc, oneshot}, rpc::{listen, Handler, RemoteRead}, util::{make_client_endpoint, make_server_endpoint}, - LocalMpscChannel, Msg, Service, ServiceRequest, ServiceSender, + LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, }; use quic_rpc_derive::rpc_requests; use serde::{Deserialize, Serialize}; @@ -100,13 +100,13 @@ impl ComputeActor { match msg { ComputeMessage::Sqr(sqr) => { trace!("sqr {:?}", sqr); - let Msg { tx, inner, .. } = sqr; + let WithChannels { tx, inner, .. } = sqr; let result = (inner.num as u128) * (inner.num as u128); tx.send(result).await?; } ComputeMessage::Sum(sum) => { trace!("sum {:?}", sum); - let Msg { rx, tx, .. } = sum; + let WithChannels { rx, tx, .. } = sum; let mut receiver = rx; let mut total = 0; while let Some(num) = receiver.recv().await? { @@ -116,7 +116,7 @@ impl ComputeActor { } ComputeMessage::Fibonacci(fib) => { trace!("fibonacci {:?}", fib); - let Msg { tx, inner, .. } = fib; + let WithChannels { tx, inner, .. } = fib; let mut sender = tx; let mut a = 0u64; let mut b = 1u64; @@ -129,7 +129,7 @@ impl ComputeActor { } ComputeMessage::Multiply(mult) => { trace!("multiply {:?}", mult); - let Msg { rx, tx, inner } = mult; + let WithChannels { rx, tx, inner } = mult; let mut receiver = rx; let mut sender = tx; let multiplier = inner.initial; @@ -406,7 +406,6 @@ fn clear_line() -> io::Result<()> { Ok(()) } - // Simple benchmark sending oneshot senders via an mpsc channel pub async fn reference_bench(n: u64) -> anyhow::Result<()> { // Create an mpsc channel to send oneshot senders @@ -437,10 +436,7 @@ pub async fn reference_bench(n: u64) -> anyhow::Result<()> { let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round() as u64; assert_eq!(sum, 42 * n); // Each response is 42 clear_line()?; - println!( - "Reference seq {} rps", - rps.separate_with_underscores() - ); + println!("Reference seq {} rps", rps.separate_with_underscores()); } // Parallel oneshot sends @@ -456,10 +452,7 @@ pub async fn reference_bench(n: u64) -> anyhow::Result<()> { let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round() as u64; assert_eq!(sum, 42 * n); // Each response is 42 clear_line()?; - println!( - "Reference par {} rps", - rps.separate_with_underscores() - ); + println!("Reference par {} rps", rps.separate_with_underscores()); } Ok(()) diff --git a/examples/derive.rs b/examples/derive.rs index 39bd879..56da59a 100644 --- a/examples/derive.rs +++ b/examples/derive.rs @@ -11,7 +11,7 @@ use quic_rpc::{ channel::{mpsc, oneshot}, rpc::{listen, Handler}, util::{make_client_endpoint, make_server_endpoint}, - LocalMpscChannel, Msg, Service, ServiceRequest, ServiceSender, + LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, }; // Import the macro use quic_rpc_derive::rpc_requests; @@ -80,18 +80,18 @@ impl StorageActor { match msg { StorageMessage::Get(get) => { info!("get {:?}", get); - let Msg { tx, inner, .. } = get; + let WithChannels { tx, inner, .. } = get; tx.send(self.state.get(&inner.key).cloned()).await.ok(); } StorageMessage::Set(set) => { info!("set {:?}", set); - let Msg { tx, inner, .. } = set; + let WithChannels { tx, inner, .. } = set; self.state.insert(inner.key, inner.value); tx.send(()).await.ok(); } StorageMessage::List(list) => { info!("list {:?}", list); - let Msg { mut tx, .. } = list; + let WithChannels { mut tx, .. } = list; for (key, value) in &self.state { if tx.send(format!("{key}={value}")).await.is_err() { break; diff --git a/examples/storage.rs b/examples/storage.rs index dcda67a..27fe787 100644 --- a/examples/storage.rs +++ b/examples/storage.rs @@ -10,7 +10,7 @@ use quic_rpc::{ channel::{mpsc, none::NoReceiver, oneshot}, rpc::{listen, Handler}, util::{make_client_endpoint, make_server_endpoint}, - Channels, LocalMpscChannel, Msg, Service, ServiceRequest, ServiceSender, + Channels, LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, }; use serde::{Deserialize, Serialize}; use tracing::info; @@ -59,9 +59,9 @@ enum StorageProtocol { #[derive(derive_more::From)] enum StorageMessage { - Get(Msg), - Set(Msg), - List(Msg), + Get(WithChannels), + Set(WithChannels), + List(WithChannels), } struct StorageActor { @@ -93,18 +93,18 @@ impl StorageActor { match msg { StorageMessage::Get(get) => { info!("get {:?}", get); - let Msg { tx, inner, .. } = get; + let WithChannels { tx, inner, .. } = get; tx.send(self.state.get(&inner.key).cloned()).await.ok(); } StorageMessage::Set(set) => { info!("set {:?}", set); - let Msg { tx, inner, .. } = set; + let WithChannels { tx, inner, .. } = set; self.state.insert(inner.key, inner.value); tx.send(()).await.ok(); } StorageMessage::List(list) => { info!("list {:?}", list); - let Msg { mut tx, .. } = list; + let WithChannels { mut tx, .. } = list; for (key, value) in &self.state { if tx.send(format!("{key}={value}")).await.is_err() { break; diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index 451e508..7f8c07b 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -63,8 +63,12 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { _ => return error_tokens(input.span(), "RpcRequests can only be applied to enums"), }; + // builder for the trait impls let mut additional_items = Vec::new(); + // types to check for uniqueness let mut types = HashSet::new(); + // variant names and types + let mut variants = Vec::new(); for variant in &mut data_enum.variants { // Check field structure for every variant @@ -77,6 +81,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { ) } }; + variants.push((variant.ident.clone(), request_type.clone())); if !types.insert(request_type.to_token_stream().to_string()) { return error_tokens(input_span, "Each variant must have a unique request type"); @@ -107,6 +112,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { ); } + // if there is no attr, the user has to impl Channels manually if let Some(attr) = rpc_attr { let args = match attr.parse_args::() { Ok(info) => info, @@ -120,29 +126,11 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { } } - let message_variants = data_enum - .variants - .iter() - .map(|variant| { - let variant_name = &variant.ident; - - // Extract the inner type using let-else patterns - let Fields::Unnamed(fields) = &variant.fields else { - unreachable!() - }; - let Some(field) = fields.unnamed.first() else { - unreachable!() - }; - let Type::Path(type_path) = &field.ty else { - unreachable!() - }; - let Some(last_segment) = type_path.path.segments.last() else { - unreachable!() - }; - let inner_type = &last_segment.ident; - + let message_variants = variants + .into_iter() + .map(|(variant_name, inner_type)| { quote! { - #variant_name(::quic_rpc::Msg<#inner_type, #service_name>) + #variant_name(::quic_rpc::WithChannels<#inner_type, #service_name>) } }) .collect::>(); diff --git a/src/lib.rs b/src/lib.rs index b89f327..6cb0dbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ use std::{fmt::Debug, io, marker::PhantomData, ops::Deref}; use channel::none::NoReceiver; +use sealed::Sealed; use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "test")] pub mod util; @@ -22,13 +23,17 @@ impl RpcMessage for T where } /// Marker trait for a service -pub trait Service: Debug + Clone {} +pub trait Service: Send + Sync + Debug + Clone + 'static {} + +mod sealed { + pub trait Sealed {} +} /// Marker trait for a sender -pub trait Sender: Debug {} +pub trait Sender: Debug + Sealed {} /// Marker trait for a receiver -pub trait Receiver: Debug {} +pub trait Receiver: Debug + Sealed {} /// Channels to be used for a message and service pub trait Channels { @@ -136,6 +141,7 @@ pub mod channel { } } + impl crate::sealed::Sealed for Sender {} impl crate::Sender for Sender {} pub enum Receiver { @@ -183,6 +189,7 @@ pub mod channel { } } + impl crate::sealed::Sealed for Receiver {} impl crate::Receiver for Receiver {} } @@ -278,6 +285,7 @@ pub mod channel { } } + impl crate::sealed::Sealed for Sender {} impl crate::Sender for Sender {} pub enum Receiver { @@ -309,30 +317,34 @@ pub mod channel { } } + impl crate::sealed::Sealed for Receiver {} impl crate::Receiver for Receiver {} } /// No channels, used when no communication is needed pub mod none { - use crate::{Receiver, Sender}; + use crate::{sealed::Sealed, Receiver, Sender}; #[derive(Debug)] pub struct NoSender; - + impl Sealed for NoSender {} impl Sender for NoSender {} #[derive(Debug)] pub struct NoReceiver; + impl Sealed for NoReceiver {} impl Receiver for NoReceiver {} } } -/// A wrapper for a message with channels to send and receive it +/// A wrapper for a message with channels to send and receive it. +/// This expands the protocol message to a full message that includes the +/// active and unserializable channels. /// /// rx and tx can be set to an appropriate channel kind. #[derive(Debug)] -pub struct Msg, S: Service> { +pub struct WithChannels, S: Service> { /// The inner message. pub inner: I, /// The return channel to send the response to. Can be set to [`crate::channel::none::NoSender`] if not needed. @@ -344,7 +356,7 @@ pub struct Msg, S: Service> { /// Tuple conversion from inner message and tx/rx channels to a WithChannels struct /// /// For the case where you want both tx and rx channels. -impl, S: Service, Tx, Rx> From<(I, Tx, Rx)> for Msg +impl, S: Service, Tx, Rx> From<(I, Tx, Rx)> for WithChannels where I: Channels, >::Tx: From, @@ -363,7 +375,7 @@ where /// Tuple conversion from inner message and tx channel to a WithChannels struct /// /// For the very common case where you just need a tx channel to send the response to. -impl From<(I, Tx)> for Msg +impl From<(I, Tx)> for WithChannels where I: Channels, S: Service, @@ -380,7 +392,7 @@ where } /// Deref so you can access the inner fields directly -impl, S: Service> Deref for Msg { +impl, S: Service> Deref for WithChannels { type Target = I; fn deref(&self) -> &Self::Target { @@ -686,10 +698,10 @@ impl From> for ServiceRequest { } impl LocalMpscChannel { - pub fn send(&self, value: impl Into>) -> SendFut + pub fn send(&self, value: impl Into>) -> SendFut where T: Channels, - M: From>, + M: From>, { let value: M = value.into().into(); SendFut::new(self.0.clone(), value) From fada3c4c6284492f06bc415b46ccf920b4076ea2 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 09:51:17 +0200 Subject: [PATCH 20/43] Add the weird stuff to get the feature flags in docs --- Cargo.toml | 7 ++++ DOCS.md | 32 +++++++++++++++++ README.md | 87 +++++++++++++++++++++++++++++++++++++++++++++ examples/compute.rs | 22 ++++++------ examples/derive.rs | 8 ++--- examples/storage.rs | 8 ++--- src/lib.rs | 45 ++++++++++++++++------- src/util.rs | 4 +++ 8 files changed, 182 insertions(+), 31 deletions(-) create mode 100644 DOCS.md create mode 100644 README.md diff --git a/Cargo.toml b/Cargo.toml index c1e8adf..b0317d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,3 +61,10 @@ default = ["rpc", "test"] [workspace] members = ["quic-rpc-derive"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "quicrpc_docsrs"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ["cfg(quicrpc_docsrs)"] } diff --git a/DOCS.md b/DOCS.md new file mode 100644 index 0000000..021b18b --- /dev/null +++ b/DOCS.md @@ -0,0 +1,32 @@ +Building docs for this crate is a bit complex. There are some feature flags, +so we want feature flag markers in the docs. + +There is an experimental cargo doc feature that adds feature flag markers. To +get those, run docs with this command line: + +```rust +RUSTDOCFLAGS="--cfg quicrpc_docsrs" cargo +nightly doc --all-features --no-deps --open +``` + +This sets the flag `quicrpc_docsrs` when creating docs, which triggers statements +like below that add feature flag markers. Note that you *need* nightly for this feature +as of now. + +``` +#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "flume-transport")))] +``` + +The feature is *enabled* using this statement in lib.rs: + +``` +#![cfg_attr(quicrpc_docsrs, feature(doc_cfg))] +``` + +We tell [docs.rs] to use the `quicrpc_docsrs` config using these statements +in Cargo.toml: + +``` +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "quicrpc_docsrs"] +``` \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2662bf --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# Quic-Rpc + +A streaming rpc system based on quic + +[][repo link] [![Latest Version]][crates.io] [![Docs Badge]][docs.rs] ![license badge] [![status badge]][status link] + +[Latest Version]: https://img.shields.io/crates/v/quic-rpc.svg +[crates.io]: https://crates.io/crates/quic-rpc +[Docs Badge]: https://img.shields.io/badge/docs-docs.rs-green +[docs.rs]: https://docs.rs/quic-rpc +[license badge]: https://img.shields.io/crates/l/quic-rpc +[status badge]: https://github.com/n0-computer/quic-rpc/actions/workflows/rust.yml/badge.svg +[status link]: https://github.com/n0-computer/quic-rpc/actions/workflows/rust.yml +[repo link]: https://github.com/n0-computer/quic-rpc + +## Goals + +### Interaction patterns + +Provide not just request/response RPC, but also streaming in both directions, similar to [grpc]. + +- 1 req -> 1 res +- 1 req, update stream -> 1 res +- 1 req -> res stream +- 1 req, update stream -> res stream + +It is still a RPC system in the sense that interactions get initiated by the client. + +### Transports + +- memory transport with very low overhead. In particular, no ser/deser, currently using [tokio channels] + when using tokio channels, there is zero overhead compared to just manually including backchannels in messages +- quic transport via the [iroh-quinn] crate + +### API + +- The API should be similar to the quinn api. Basically "quinn with types". + +## Non-Goals + +- Cross language interop. This is for talking from rust to rust +- Any kind of versioning. You have to do this yourself +- Making remote message passing look like local async function calls +- Being runtime agnostic. This is for tokio + +## Example + +[computation service](https://github.com/n0-computer/quic-rpc/blob/main/tests/math.rs) + +## Why? + +The purpose of quic-rpc is to serve as an *optional* rpc framework. One of the +main goals is to be able to use it as an *in process* way to have well specified +protocols and boundaries between subsystems, including an async boundary. + +It should not have noticeable overhead compared to what you would do anyway to +isolate subsystems in a complex single process app, but should have the *option* +to also send messages over a process boundary via one of the non mem transports. + +What do you usually do in rust to have isolation between subsystems, e.g. +between a database and a networking layer? You have some kind of +channel between the systems and define messages flowing back and forth over that +channel. For almost all interactions these messages itself will again contain +(oneshot or mpsc) channels for independent async communication between the +subsystems. + +Quic-rpc with the mem channel does exactly the same thing, except that it +distinguishes between the serializable protocol of a service and the full +messages of a service that include unserializable channels. + +Instead of having a message that explicitly contains some data and the send side +of a oneshot or mpsc channel for the response, it allows you to specify the +channel kind and message type of the channels in both directions. + +For the case where you have a process boundary, the overhead is very low for +transports that already have a concept of cheap substreams (http2, quic, ...). +Quic is the poster child of a network transport that has built in cheap +substreams including per substream backpressure. + +[iroh-quinn]: https://docs.rs/iroh-quinn/ +[tokio channels]: https://docs.rs/tokio/latest/tokio/sync/index.html +[grpc]: https://grpc.io/ + +# Docs + +Properly building docs for this crate is quite complex. For all the gory details, +see [DOCS.md]. \ No newline at end of file diff --git a/examples/compute.rs b/examples/compute.rs index 3a1baa8..584db72 100644 --- a/examples/compute.rs +++ b/examples/compute.rs @@ -12,7 +12,7 @@ use n0_future::{ task::{self, AbortOnDropHandle}, }; use quic_rpc::{ - channel::{mpsc, oneshot}, + channel::{oneshot, spsc}, rpc::{listen, Handler, RemoteRead}, util::{make_client_endpoint, make_server_endpoint}, LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, @@ -62,11 +62,11 @@ enum ComputeRequest { enum ComputeProtocol { #[rpc(tx=oneshot::Sender)] Sqr(Sqr), - #[rpc(rx=mpsc::Receiver, tx=oneshot::Sender)] + #[rpc(rx=spsc::Receiver, tx=oneshot::Sender)] Sum(Sum), - #[rpc(tx=mpsc::Sender)] + #[rpc(tx=spsc::Sender)] Fibonacci(Fibonacci), - #[rpc(rx=mpsc::Receiver, tx=mpsc::Sender)] + #[rpc(rx=spsc::Receiver, tx=spsc::Sender)] Multiply(Multiply), } @@ -192,11 +192,11 @@ impl ComputeApi { } } - pub async fn sum(&self) -> anyhow::Result<(mpsc::Sender, oneshot::Receiver)> { + pub async fn sum(&self) -> anyhow::Result<(spsc::Sender, oneshot::Receiver)> { let msg = Sum; match self.inner.request().await? { ServiceRequest::Local(request, _) => { - let (num_tx, num_rx) = mpsc::channel(10); + let (num_tx, num_rx) = spsc::channel(10); let (sum_tx, sum_rx) = oneshot::channel(); request.send((msg, sum_tx, num_rx)).await?; Ok((num_tx, sum_rx)) @@ -208,11 +208,11 @@ impl ComputeApi { } } - pub async fn fibonacci(&self, max: u64) -> anyhow::Result> { + pub async fn fibonacci(&self, max: u64) -> anyhow::Result> { let msg = Fibonacci { max }; match self.inner.request().await? { ServiceRequest::Local(request, _) => { - let (tx, rx) = mpsc::channel(128); + let (tx, rx) = spsc::channel(128); request.send((msg, tx)).await?; Ok(rx) } @@ -226,12 +226,12 @@ impl ComputeApi { pub async fn multiply( &self, initial: u64, - ) -> anyhow::Result<(mpsc::Sender, mpsc::Receiver)> { + ) -> anyhow::Result<(spsc::Sender, spsc::Receiver)> { let msg = Multiply { initial }; match self.inner.request().await? { ServiceRequest::Local(request, _) => { - let (in_tx, in_rx) = mpsc::channel(128); - let (out_tx, out_rx) = mpsc::channel(128); + let (in_tx, in_rx) = spsc::channel(128); + let (out_tx, out_rx) = spsc::channel(128); request.send((msg, out_tx, in_rx)).await?; Ok((in_tx, out_rx)) } diff --git a/examples/derive.rs b/examples/derive.rs index 56da59a..2e48de5 100644 --- a/examples/derive.rs +++ b/examples/derive.rs @@ -8,7 +8,7 @@ use std::{ use anyhow::bail; use n0_future::task::{self, AbortOnDropHandle}; use quic_rpc::{ - channel::{mpsc, oneshot}, + channel::{oneshot, spsc}, rpc::{listen, Handler}, util::{make_client_endpoint, make_server_endpoint}, LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, @@ -47,7 +47,7 @@ enum StorageProtocol { Get(Get), #[rpc(tx=oneshot::Sender<()>)] Set(Set), - #[rpc(tx=mpsc::Sender)] + #[rpc(tx=spsc::Sender)] List(List), } @@ -150,11 +150,11 @@ impl StorageApi { } } - pub async fn list(&self) -> anyhow::Result> { + pub async fn list(&self) -> anyhow::Result> { let msg = List; match self.inner.request().await? { ServiceRequest::Local(request, _) => { - let (tx, rx) = mpsc::channel(10); + let (tx, rx) = spsc::channel(10); request.send((msg, tx)).await?; Ok(rx) } diff --git a/examples/storage.rs b/examples/storage.rs index 27fe787..3dfb544 100644 --- a/examples/storage.rs +++ b/examples/storage.rs @@ -7,7 +7,7 @@ use std::{ use n0_future::task::{self, AbortOnDropHandle}; use quic_rpc::{ - channel::{mpsc, none::NoReceiver, oneshot}, + channel::{none::NoReceiver, oneshot, spsc}, rpc::{listen, Handler}, util::{make_client_endpoint, make_server_endpoint}, Channels, LocalMpscChannel, Service, ServiceRequest, ServiceSender, WithChannels, @@ -36,7 +36,7 @@ struct List; impl Channels for List { type Rx = NoReceiver; - type Tx = mpsc::Sender; + type Tx = spsc::Sender; } #[derive(Debug, Serialize, Deserialize)] @@ -162,11 +162,11 @@ impl StorageApi { } } - pub async fn list(&self) -> anyhow::Result> { + pub async fn list(&self) -> anyhow::Result> { let msg = List; match self.inner.request().await? { ServiceRequest::Local(request, _) => { - let (tx, rx) = mpsc::channel(10); + let (tx, rx) = spsc::channel(10); request.send((msg, tx)).await?; Ok(rx) } diff --git a/src/lib.rs b/src/lib.rs index 6cb0dbf..b12603b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(quicrpc_docsrs, feature(doc_cfg))] use std::{fmt::Debug, io, marker::PhantomData, ops::Deref}; use channel::none::NoReceiver; @@ -23,21 +24,28 @@ impl RpcMessage for T where } /// Marker trait for a service +/// +/// This is usually implemented by a zero-sized struct. +/// It has various bounds to make derives easier. pub trait Service: Send + Sync + Debug + Clone + 'static {} mod sealed { pub trait Sealed {} } -/// Marker trait for a sender +/// Sealed marker trait for a sender pub trait Sender: Debug + Sealed {} -/// Marker trait for a receiver +/// Sealed marker trait for a receiver pub trait Receiver: Debug + Sealed {} /// Channels to be used for a message and service pub trait Channels { + /// The sender type, can be either spsc, oneshot or none type Tx: Sender; + /// The receiver type, can be either spsc, oneshot or none + /// + /// For many services, the receiver is not needed, so it can be set to [`NoReceiver`]. type Rx: Receiver; } @@ -193,8 +201,10 @@ pub mod channel { impl crate::Receiver for Receiver {} } - /// MPSC channel, similar to tokio's mpsc channel - pub mod mpsc { + /// SPSC channel, similar to tokio's mpsc channel + /// + /// For the rpc case, the send side can not be cloned, hence spsc instead of mpsc. + pub mod spsc { use std::{fmt::Debug, future::Future, io, pin::Pin}; use crate::RpcMessage; @@ -294,6 +304,12 @@ pub mod channel { } impl Receiver { + /// Receive a message + /// + /// Returns Ok(None) if the sender has been dropped or the remote end has + /// cleanly closed the connection. + /// + /// Returns an an io error if there was an error receiving the message. pub async fn recv(&mut self) -> io::Result> { match self { Self::Tokio(rx) => Ok(rx.recv().await), @@ -323,18 +339,18 @@ pub mod channel { /// No channels, used when no communication is needed pub mod none { - use crate::{sealed::Sealed, Receiver, Sender}; + use crate::sealed::Sealed; #[derive(Debug)] pub struct NoSender; impl Sealed for NoSender {} - impl Sender for NoSender {} + impl crate::Sender for NoSender {} #[derive(Debug)] pub struct NoReceiver; impl Sealed for NoReceiver {} - impl Receiver for NoReceiver {} + impl crate::Receiver for NoReceiver {} } } @@ -403,6 +419,7 @@ impl, S: Service> Deref for WithChannels { pub enum ServiceSender { Local(LocalMpscChannel, PhantomData), #[cfg(feature = "rpc")] + #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] Remote(quinn::Endpoint, std::net::SocketAddr, PhantomData<(R, S)>), } @@ -411,6 +428,7 @@ impl Clone for ServiceSender { match self { Self::Local(tx, _) => Self::Local(tx.clone(), PhantomData), #[cfg(feature = "rpc")] + #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] Self::Remote(endpoint, addr, _) => Self::Remote(endpoint.clone(), *addr, PhantomData), } } @@ -427,6 +445,7 @@ impl ServiceSender { match self { Self::Local(tx, _) => Ok(ServiceRequest::from(tx.clone())), #[cfg(feature = "rpc")] + #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] Self::Remote(endpoint, addr, _) => { let connection = endpoint .connect(*addr, "localhost") @@ -455,6 +474,7 @@ impl Clone for LocalMpscChannel { } #[cfg(feature = "rpc")] +#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] pub mod rpc { use std::{fmt::Debug, future::Future, io, marker::PhantomData, pin::Pin, sync::Arc}; @@ -465,8 +485,8 @@ pub mod rpc { use crate::{ channel::{ - mpsc::{self, BoxedReceiver, BoxedSender}, oneshot, + spsc::{self, BoxedReceiver, BoxedSender}, }, util::{AsyncReadVarintExt, WriteVarintExt}, RpcMessage, @@ -514,10 +534,10 @@ pub mod rpc { } } - impl From for mpsc::Receiver { + impl From for spsc::Receiver { fn from(read: RemoteRead) -> Self { let read = read.0; - mpsc::Receiver::Boxed(Box::new(QuinnReceiver { + spsc::Receiver::Boxed(Box::new(QuinnReceiver { recv: read, _marker: PhantomData, })) @@ -548,10 +568,10 @@ pub mod rpc { } } - impl From for mpsc::Sender { + impl From for spsc::Sender { fn from(write: RemoteWrite) -> Self { let write = write.0; - mpsc::Sender::Boxed(Box::new(QuinnSender { + spsc::Sender::Boxed(Box::new(QuinnSender { send: write, buffer: SmallVec::new(), _marker: PhantomData, @@ -688,6 +708,7 @@ pub mod rpc { pub enum ServiceRequest { Local(LocalMpscChannel, PhantomData), #[cfg(feature = "rpc")] + #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] Remote(rpc::RemoteRequest), } diff --git a/src/util.rs b/src/util.rs index 22e157e..6b197ca 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,5 @@ #[cfg(all(feature = "rpc", feature = "test"))] +#[cfg_attr(quicrpc_docsrs, doc(cfg(all(feature = "rpc", feature = "test"))))] mod quinn_setup_utils { use std::{net::SocketAddr, sync::Arc}; @@ -139,9 +140,11 @@ mod quinn_setup_utils { } } #[cfg(all(feature = "rpc", feature = "test"))] +#[cfg_attr(quicrpc_docsrs, doc(cfg(all(feature = "rpc", feature = "test"))))] pub use quinn_setup_utils::*; #[cfg(feature = "rpc")] +#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] mod varint_util { use std::{ future::Future, @@ -342,6 +345,7 @@ mod varint_util { } } #[cfg(feature = "rpc")] +#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] pub use varint_util::{ read_varint_u64, write_varint_u64, AsyncReadVarintExt, AsyncWriteVarintExt, WriteVarintExt, }; From 57ec9a35584e93f109ffb7fb2742f9f47c401a0e Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 09:55:27 +0200 Subject: [PATCH 21/43] Remove old stuff --- old/.vscode/settings.json | 3 - old/CHANGELOG.md | 296 - old/Cargo.lock | 4997 ----------------- old/Cargo.toml | 106 - old/DOCS.md | 32 - old/README.md | 101 - old/cliff.toml | 64 - old/examples/errors.rs | 76 - old/examples/macro.rs | 158 - old/examples/modularize.rs | 507 -- old/examples/split/client/Cargo.toml | 16 - old/examples/split/client/src/main.rs | 66 - old/examples/split/server/Cargo.toml | 16 - old/examples/split/server/src/main.rs | 75 - old/examples/split/types/Cargo.toml | 11 - old/examples/split/types/src/lib.rs | 46 - old/examples/store.rs | 267 - old/quic-rpc-derive/Cargo.toml | 24 - old/quic-rpc-derive/src/lib.rs | 232 - .../tests/compile_fail/duplicate_type.rs | 9 - .../tests/compile_fail/duplicate_type.stderr | 5 - .../tests/compile_fail/extra_attr_types.rs | 9 - .../compile_fail/extra_attr_types.stderr | 5 - .../tests/compile_fail/multiple_fields.rs | 8 - .../tests/compile_fail/multiple_fields.stderr | 5 - .../tests/compile_fail/named_enum.rs | 8 - .../tests/compile_fail/named_enum.stderr | 5 - .../tests/compile_fail/non_enum.rs | 6 - .../tests/compile_fail/non_enum.stderr | 5 - .../tests/compile_fail/wrong_attr_types.rs | 9 - .../compile_fail/wrong_attr_types.stderr | 5 - old/quic-rpc-derive/tests/smoke.rs | 79 - old/src/client.rs | 195 - old/src/lib.rs | 219 - old/src/macros.rs | 508 -- old/src/message.rs | 31 - old/src/pattern/bidi_streaming.rs | 146 - old/src/pattern/client_streaming.rs | 146 - old/src/pattern/mod.rs | 11 - old/src/pattern/rpc.rs | 154 - old/src/pattern/server_streaming.rs | 139 - old/src/pattern/try_server_streaming.rs | 210 - old/src/server.rs | 496 -- old/src/transport/boxed.rs | 540 -- old/src/transport/combined.rs | 292 - old/src/transport/flume.rs | 345 -- old/src/transport/hyper.rs | 645 --- old/src/transport/iroh.rs | 732 --- old/src/transport/mapped.rs | 325 -- old/src/transport/misc/mod.rs | 52 - old/src/transport/mod.rs | 157 - old/src/transport/quinn.rs | 948 ---- old/src/transport/util.rs | 188 - old/tests/flume.rs | 103 - old/tests/hyper.rs | 297 - old/tests/iroh.rs | 168 - old/tests/math.rs | 390 -- old/tests/quinn.rs | 127 - old/tests/slow_math.rs | 131 - old/tests/try.rs | 103 - old/tests/util.rs | 20 - 61 files changed, 15069 deletions(-) delete mode 100644 old/.vscode/settings.json delete mode 100644 old/CHANGELOG.md delete mode 100644 old/Cargo.lock delete mode 100644 old/Cargo.toml delete mode 100644 old/DOCS.md delete mode 100644 old/README.md delete mode 100644 old/cliff.toml delete mode 100644 old/examples/errors.rs delete mode 100644 old/examples/macro.rs delete mode 100644 old/examples/modularize.rs delete mode 100644 old/examples/split/client/Cargo.toml delete mode 100644 old/examples/split/client/src/main.rs delete mode 100644 old/examples/split/server/Cargo.toml delete mode 100644 old/examples/split/server/src/main.rs delete mode 100644 old/examples/split/types/Cargo.toml delete mode 100644 old/examples/split/types/src/lib.rs delete mode 100644 old/examples/store.rs delete mode 100644 old/quic-rpc-derive/Cargo.toml delete mode 100644 old/quic-rpc-derive/src/lib.rs delete mode 100644 old/quic-rpc-derive/tests/compile_fail/duplicate_type.rs delete mode 100644 old/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr delete mode 100644 old/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs delete mode 100644 old/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr delete mode 100644 old/quic-rpc-derive/tests/compile_fail/multiple_fields.rs delete mode 100644 old/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr delete mode 100644 old/quic-rpc-derive/tests/compile_fail/named_enum.rs delete mode 100644 old/quic-rpc-derive/tests/compile_fail/named_enum.stderr delete mode 100644 old/quic-rpc-derive/tests/compile_fail/non_enum.rs delete mode 100644 old/quic-rpc-derive/tests/compile_fail/non_enum.stderr delete mode 100644 old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs delete mode 100644 old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr delete mode 100644 old/quic-rpc-derive/tests/smoke.rs delete mode 100644 old/src/client.rs delete mode 100644 old/src/lib.rs delete mode 100644 old/src/macros.rs delete mode 100644 old/src/message.rs delete mode 100644 old/src/pattern/bidi_streaming.rs delete mode 100644 old/src/pattern/client_streaming.rs delete mode 100644 old/src/pattern/mod.rs delete mode 100644 old/src/pattern/rpc.rs delete mode 100644 old/src/pattern/server_streaming.rs delete mode 100644 old/src/pattern/try_server_streaming.rs delete mode 100644 old/src/server.rs delete mode 100644 old/src/transport/boxed.rs delete mode 100644 old/src/transport/combined.rs delete mode 100644 old/src/transport/flume.rs delete mode 100644 old/src/transport/hyper.rs delete mode 100644 old/src/transport/iroh.rs delete mode 100644 old/src/transport/mapped.rs delete mode 100644 old/src/transport/misc/mod.rs delete mode 100644 old/src/transport/mod.rs delete mode 100644 old/src/transport/quinn.rs delete mode 100644 old/src/transport/util.rs delete mode 100644 old/tests/flume.rs delete mode 100644 old/tests/hyper.rs delete mode 100644 old/tests/iroh.rs delete mode 100644 old/tests/math.rs delete mode 100644 old/tests/quinn.rs delete mode 100644 old/tests/slow_math.rs delete mode 100644 old/tests/try.rs delete mode 100644 old/tests/util.rs diff --git a/old/.vscode/settings.json b/old/.vscode/settings.json deleted file mode 100644 index 3f22f4c..0000000 --- a/old/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.cargo.features": ["hyper-transport", "quinn-transport", "iroh-transport", "test-utils"] -} diff --git a/old/CHANGELOG.md b/old/CHANGELOG.md deleted file mode 100644 index 64f9d3b..0000000 --- a/old/CHANGELOG.md +++ /dev/null @@ -1,296 +0,0 @@ -# Changelog - -All notable changes to quic-rpc will be documented in this file. - -## [unreleased] - -### ⛰️ Features - -- Update to iroh@0.29.0 - ([02b2559](https://github.com/n0-computer/iroh/commit/02b25594bbfd210acb29b65c59d80c3063565193)) -- Update to iroh@0.29.0 ([#125](https://github.com/n0-computer/iroh/issues/125)) - ([07f1335](https://github.com/n0-computer/iroh/commit/07f1335f1616359b93ee60277a80a07df7552e18)) - -## [0.16.0](https://github.com/n0-computer/iroh/compare/v0.15.1..v0.16.0) - 2024-12-02 - -### ⛰️ Features - -- Use postcard encoding for all transports that require serialization ([#114](https://github.com/n0-computer/iroh/issues/114)) - ([badb606](https://github.com/n0-computer/iroh/commit/badb6068db23e6262c183ef8981228fd8ca1ef61)) - -### 🚜 Refactor - -- Rename iroh-net transport to iroh-transport - ([7ee875b](https://github.com/n0-computer/iroh/commit/7ee875b5a1cdf2bbfa377564d7a3c1792876f780)) -- Rename iroh-net transport to iroh-transport ([#123](https://github.com/n0-computer/iroh/issues/123)) - ([69e7c4a](https://github.com/n0-computer/iroh/commit/69e7c4a4f1c90533db61c32eb7145073f0bb1659)) - -### ⚙️ Miscellaneous Tasks - -- Update rcgen - ([27287e1](https://github.com/n0-computer/iroh/commit/27287e13fa125234898d9aabd7d9d640aba92a36)) -- Update rcgen ([#118](https://github.com/n0-computer/iroh/issues/118)) - ([2e1daa9](https://github.com/n0-computer/iroh/commit/2e1daa91552f99c448f6508fb55630f2933ee705)) -- Prune some deps ([#119](https://github.com/n0-computer/iroh/issues/119)) - ([dc75b95](https://github.com/n0-computer/iroh/commit/dc75b951bcd6b3b2239ab7a71e2fedcd12152853)) -- Remove `cc` version pinning - ([6da6783](https://github.com/n0-computer/iroh/commit/6da6783ca95f90e38f22091d0d5c8e6b13f6a3ec)) -- Remove `cc` version pinning ([#122](https://github.com/n0-computer/iroh/issues/122)) - ([a5606c2](https://github.com/n0-computer/iroh/commit/a5606c260275d433f00c3aed2fb57ed082900c38)) -- Release 0.16.0 ([#124](https://github.com/n0-computer/iroh/issues/124)) - ([a19ce1b](https://github.com/n0-computer/iroh/commit/a19ce1be542382a65ca7c56843de49a108bf21db)) - -## [0.15.1](https://github.com/n0-computer/iroh/compare/v0.15.0..v0.15.1) - 2024-11-14 - -### ⛰️ Features - -- Accept handler ([#116](https://github.com/n0-computer/iroh/issues/116)) - ([32d5bc1](https://github.com/n0-computer/iroh/commit/32d5bc1a08609f4f0b5650980088f07d81971a55)) - -### ⚙️ Miscellaneous Tasks - -- Consistently format imports... - ([33fb08b](https://github.com/n0-computer/iroh/commit/33fb08b417874bfdce79c8a5d3972aee1ca7ba8b)) -- Consistently format imports... ([#113](https://github.com/n0-computer/iroh/issues/113)) - ([08750c5](https://github.com/n0-computer/iroh/commit/08750c54cd82295e5819eeefd18d9f904fc51a02)) -- Introduce a .rustfmt.toml file with configs for automatic formatting ([#115](https://github.com/n0-computer/iroh/issues/115)) - ([a949899](https://github.com/n0-computer/iroh/commit/a949899deac2f626c03452028a86f9420bc93530)) - -## [0.15.0](https://github.com/n0-computer/iroh/compare/v0.14.0..v0.15.0) - 2024-11-06 - -### ⚙️ Miscellaneous Tasks - -- Release - ([be04be1](https://github.com/n0-computer/iroh/commit/be04be152a2be26cc6752e76e947b50f3b5da958)) - -## [0.14.0](https://github.com/n0-computer/iroh/compare/v0.12.0..v0.14.0) - 2024-11-04 - -### ⛰️ Features - -- Upgrade iroh-quinn to 0.12.0 ([#109](https://github.com/n0-computer/iroh/issues/109)) - ([a5fecdc](https://github.com/n0-computer/iroh/commit/a5fecdcd3b60d581328106dc79246a4b2b609709)) - -### 🐛 Bug Fixes - -- Transport:quinn only spawn when tokio is available ([#95](https://github.com/n0-computer/iroh/issues/95)) - ([baa4f83](https://github.com/n0-computer/iroh/commit/baa4f837f161ecd80c3a6d46fad1788aadf06049)) - -### ⚙️ Miscellaneous Tasks - -- Release - ([10a16f7](https://github.com/n0-computer/iroh/commit/10a16f72a3aca254259879bba611d099f9e9edf5)) -- Release ([#96](https://github.com/n0-computer/iroh/issues/96)) - ([277cde1](https://github.com/n0-computer/iroh/commit/277cde1fec1a341f35ed9b1ad5d9eda252c6ee9d)) -- New version for quic-rpc-derive as well ([#104](https://github.com/n0-computer/iroh/issues/104)) - ([39f5b20](https://github.com/n0-computer/iroh/commit/39f5b2014d48300f4308a6451cb378725c9926c0)) -- Release - ([64e0a7d](https://github.com/n0-computer/iroh/commit/64e0a7d1a9e9127fe8b6449964af10984097f186)) - -### Deps - -- Remove direct rustls dependency - ([f67c218](https://github.com/n0-computer/iroh/commit/f67c2189c1c8a8b8fa9f902877a74d64a38e994c)) -- Remove direct rustls dependency ([#94](https://github.com/n0-computer/iroh/issues/94)) - ([fe08b15](https://github.com/n0-computer/iroh/commit/fe08b157ae162ec71ca8ad77efea300132abce77)) - -## [0.12.0](https://github.com/n0-computer/iroh/compare/v0.10.1..v0.12.0) - 2024-08-15 - -### ⛰️ Features - -- WIP First somewhat working version of attribute macro to declare services - ([bab7fe0](https://github.com/n0-computer/iroh/commit/bab7fe083fdaf6ee81c4f17f2b96a5e1f9d18011)) - -### 🚜 Refactor - -- Use two stage accept - ([ac8f358](https://github.com/n0-computer/iroh/commit/ac8f358e0ea623a9906402639a0882794b0a06d0)) -- Use two stage everywhere - ([b3c37ff](https://github.com/n0-computer/iroh/commit/b3c37ff88de533c6edb9c457a8c5ddd1f713bbf9)) -- Use two stage accept ([#87](https://github.com/n0-computer/iroh/issues/87)) - ([c2520b8](https://github.com/n0-computer/iroh/commit/c2520b85f5fd37b78bd0fc4f87c2989605209bac)) -- Remove the interprocess transport - ([bd72cdc](https://github.com/n0-computer/iroh/commit/bd72cdcceba82fe9953413e050d86e2f528f93ea)) - -### ⚙️ Miscellaneous Tasks - -- Release - ([e0b50a1](https://github.com/n0-computer/iroh/commit/e0b50a1d1e1f0b5865b1aff01d43ac4920ae3b44)) -- Release ([#82](https://github.com/n0-computer/iroh/issues/82)) - ([3b01e85](https://github.com/n0-computer/iroh/commit/3b01e856822aca75126fbdd8ae640f2a440f80e8)) -- Upgrade to Quinn 0.11 and Rustls 0.23 - ([220aa35](https://github.com/n0-computer/iroh/commit/220aa35b69e178361c53567f37416c0852593cbd)) -- Upgrade to Quinn 0.11 and Rustls 0.23 - ([1c7e3c6](https://github.com/n0-computer/iroh/commit/1c7e3c6d98d38d92a13253486b565260b89686ba)) -- Upgrade to Quinn 0.11 and Rustls 0.23 - ([2221339](https://github.com/n0-computer/iroh/commit/2221339d5e98cb2f8952b303c5ead24c8030f1f8)) -- Release - ([50dde15](https://github.com/n0-computer/iroh/commit/50dde1542ad12b24154eadb5c8bf7d713d79495f)) -- Release ([#93](https://github.com/n0-computer/iroh/issues/93)) - ([9066a40](https://github.com/n0-computer/iroh/commit/9066a403feed8277503d6f2a512a834b7fcafef7)) - -### Deps - -- Upgrade to Quinn 0.11 and Rustls 0.23 ([#92](https://github.com/n0-computer/iroh/issues/92)) - ([93e64ab](https://github.com/n0-computer/iroh/commit/93e64ab904922a1879f6dcf58dca763b4d038070)) - -## [0.10.1](https://github.com/n0-computer/iroh/compare/v0.10.0..v0.10.1) - 2024-05-24 - -### ⛰️ Features - -- Update and cleanup deps ([#80](https://github.com/n0-computer/iroh/issues/80)) - ([eba3a06](https://github.com/n0-computer/iroh/commit/eba3a06a1c8a8ac2f74b1e6dde02220f935e9be7)) - -### ⚙️ Miscellaneous Tasks - -- Release - ([af9272b](https://github.com/n0-computer/iroh/commit/af9272b17a896056d5f02565e4090ecfdbe000b8)) - -## [0.10.0](https://github.com/n0-computer/iroh/compare/v0.8.0..v0.10.0) - 2024-05-21 - -### 🐛 Bug Fixes - -- Downgrade derive_more to non beta - ([9235fdf](https://github.com/n0-computer/iroh/commit/9235fdfe0efc4cbcd9694c248d1f112f32000666)) - -### 🚜 Refactor - -- Fix hyper and combined transports - ([a56099e](https://github.com/n0-computer/iroh/commit/a56099e8e557776d0dc41f6c45006f879fb88f05)) -- [**breaking**] Use `Service` generic in transport connections ([#76](https://github.com/n0-computer/iroh/issues/76)) - ([64ed5ef](https://github.com/n0-computer/iroh/commit/64ed5efea314a785ed9890fb78f857dadba3dc85)) - -### ⚙️ Miscellaneous Tasks - -- Clippy - ([38601de](https://github.com/n0-computer/iroh/commit/38601de6e19e52723b30a200d1c22621c1d772f2)) -- Update derive-more - ([78a3250](https://github.com/n0-computer/iroh/commit/78a32506214cfa564c06fac4f468952c22734c0c)) -- Fix merge issue - ([4e8deef](https://github.com/n0-computer/iroh/commit/4e8deef7c02d0e1b3a8dbfc1d5484636eadd9a21)) -- Update Cargo.lock - ([89f1b65](https://github.com/n0-computer/iroh/commit/89f1b65718b00f8a3880bc6f799d308693c88507)) -- Release - ([92f472b](https://github.com/n0-computer/iroh/commit/92f472bf34611cff39f238e7b0f45ca65287b9f8)) - -### Deps - -- Depend on iroh-quinn and bump version ([#75](https://github.com/n0-computer/iroh/issues/75)) - ([df54382](https://github.com/n0-computer/iroh/commit/df5438284ec30b5b5b527932b39a48659e4518e5)) - -### Wip - -- Easier generics - ([6d710b7](https://github.com/n0-computer/iroh/commit/6d710b74cf45d8a04cbbcd9d803718b9aceb8012)) - -## [0.8.0] - 2024-04-24 - -### ⛰️ Features - -- *(ci)* Add minimal crates version check - ([00b6d12](https://github.com/n0-computer/iroh/commit/00b6d1203e31a57c63f21fc59f8358fc3cdbfd42)) -- *(http2)* Shut down the hyper server on drop. - ([124591a](https://github.com/n0-computer/iroh/commit/124591a336df5399cb102da99cfb213159947317)) -- *(http2)* Shut down the hyper server on drop - ([cd86839](https://github.com/n0-computer/iroh/commit/cd868396ffd73bda310971ff59624e3d68ba8a3e)) -- *(http2)* Move serialization and deserialization closer to the user - ([c1742a5](https://github.com/n0-computer/iroh/commit/c1742a5cb6698e8067844a6944fb5d07bfd3585d)) -- *(http2)* Add config to http2 channel - ([8a98ba7](https://github.com/n0-computer/iroh/commit/8a98ba7cb4db3e258ae65f0c568e8524fc26fb4a)) -- Add optional macros to reduce boilerplate - ([0010a98](https://github.com/n0-computer/iroh/commit/0010a9861e956a31fe609e84d2da3569c535eb42)) -- Allow to split RPC types into a seperate crate. - ([1564ad8](https://github.com/n0-computer/iroh/commit/1564ad81ec2333732ce7268970ca76ced2ea59a5)) -- Generate code only if needed and add client - ([e4d6d91](https://github.com/n0-computer/iroh/commit/e4d6d91d3006666ef0e733b8cf97bc8c51d1bb79)) -- Better docs for macro-generated items - ([bbf8e97](https://github.com/n0-computer/iroh/commit/bbf8e97e9aafbe74eb1ee60b0fe3bfd997394bf3)) -- Add convenience function for handling errors in rpc calls - ([26283b1](https://github.com/n0-computer/iroh/commit/26283b1ebc2c68d0e4847856d1b07ae55fd7036e)) -- Lazy connections, take 2 - ([3ed491e](https://github.com/n0-computer/iroh/commit/3ed491ed3857e9ae495144df696b968d342ac91e)) -- More efficient channel replacement - ([6d5808d](https://github.com/n0-computer/iroh/commit/6d5808df40a7ec008d79ab7f8553cc0253a31c33)) -- Allow lazy connect in client of split example - ([97b7e1a](https://github.com/n0-computer/iroh/commit/97b7e1af7c517a70c270418a4d9c18bd58440461)) -- Implement std::error::Error for combined channel errors - ([d7828e3](https://github.com/n0-computer/iroh/commit/d7828e31395dd9a096c4afc128eae03357c4db2e)) -- Add minimal tracing support - ([850bde1](https://github.com/n0-computer/iroh/commit/850bde116a93d881294e7d80ac007137d075ba61)) -- Add minimal tracing support - ([2de50d5](https://github.com/n0-computer/iroh/commit/2de50d52dcefd3fb1e5d557f7ef8f170481038e8)) -- Make channels configurable - ([11d3071](https://github.com/n0-computer/iroh/commit/11d30715fc7912ba08e755a57138bc36cf675bfc)) -- Expose local address of ServerChannel - ([b67dcdb](https://github.com/n0-computer/iroh/commit/b67dcdb87f50a349a9977c1dc662d321f3ac68d2)) -- Add dummy endpoint - ([48cf5db](https://github.com/n0-computer/iroh/commit/48cf5db8af9d21418bc3b306d9b27dd27c6e8146)) -- Add ability to creaete a RpcClient from a single connection - ([7a4f40f](https://github.com/n0-computer/iroh/commit/7a4f40f2ddaea0e858c12a882a19302c9c3ca853)) -- Add AsRef and into_inner for RpcClient and RpcServer - ([ea8e119](https://github.com/n0-computer/iroh/commit/ea8e1195fda510b470feaf638f519f185151e8d6)) -- Update quinn and rustls - ([20679f9](https://github.com/n0-computer/iroh/commit/20679f938d1c257cb51d64f46ba39cc46c580a73)) -- Make wasm compatible ([#49](https://github.com/n0-computer/iroh/issues/49)) - ([6cbf62b](https://github.com/n0-computer/iroh/commit/6cbf62b2fdf150dca6a261dfdb16e338c7bd7cd0)) -- Add additional `Sync` bounds to allow for better reuse of streams - ([54c4ade](https://github.com/n0-computer/iroh/commit/54c4adeef2c851cbe2e6ac542d221b6f020a430c)) -- Add additional `Sync` bounds to allow for better reuse of streams ([#68](https://github.com/n0-computer/iroh/issues/68)) - ([bc589b7](https://github.com/n0-computer/iroh/commit/bc589b7a49e277b6847cb01675e92984152d033f)) -- Allow to compose RPC services ([#67](https://github.com/n0-computer/iroh/issues/67)) - ([77785a2](https://github.com/n0-computer/iroh/commit/77785a21babe4e56d28541d1b3ba401dcf366441)) - -### 🐛 Bug Fixes - -- *(ci)* Cancel stale repeat jobs ([#64](https://github.com/n0-computer/iroh/issues/64)) - ([d9b385c](https://github.com/n0-computer/iroh/commit/d9b385ce8ba66430c6a2744c0f595a74a9e3d578)) -- *(http2)* Don't log normal occurrences as errors - ([a4e76da](https://github.com/n0-computer/iroh/commit/a4e76da0fbc53381a9d97fbaf3df336b2bc04e0d)) -- Consistent naming - ([a23fc78](https://github.com/n0-computer/iroh/commit/a23fc784f30a9eae6314f4b2cc6151dcfae7b215)) -- Add docs to macro - ([c868bc8](https://github.com/n0-computer/iroh/commit/c868bc8c297ae9bb3cae5ba467e703a8bf1b135f)) -- Improve docs - ([1957794](https://github.com/n0-computer/iroh/commit/19577949db0becd2da2dc0d206bbb1a91daa5722)) -- Docs typos - ([a965fce](https://github.com/n0-computer/iroh/commit/a965fceba8558e144f15fb338d1d7586453fbfb5)) -- Derive Debug for generated client - ([9e72faf](https://github.com/n0-computer/iroh/commit/9e72faf76746f7e7cdc3e76d04a7c87c3bf03a8c)) -- Rename macro to rpc_service - ([bdb71c1](https://github.com/n0-computer/iroh/commit/bdb71c1659a58a41f11393e1c18f58e8f74e6206)) -- Hide the exports also behind feature flags - ([4cff83d](https://github.com/n0-computer/iroh/commit/4cff83dd9741e4fd3ae8982f96e203839dd17ec4)) -- Get rid of get rid of channel!!! - ([e0b504d](https://github.com/n0-computer/iroh/commit/e0b504de6c81054996aa6271f1bae319046d405f)) -- Add additional framing to messages - ([8a6038e](https://github.com/n0-computer/iroh/commit/8a6038e6f478f7782158c7a0e771672b9bf9722b)) -- Do buffering in the forwarder - ([4e3d9fd](https://github.com/n0-computer/iroh/commit/4e3d9fd841396aaf0cb4024017605cef19b2cacd)) -- Add flume dependency to quinn-transport - ([e64ba0b](https://github.com/n0-computer/iroh/commit/e64ba0b4e6725d4b1800fdd2c4b12bd1b39a97f8)) -- Call wait_idle on endpoint drop to allow for time for the close msg to be sent - ([7ba3bee](https://github.com/n0-computer/iroh/commit/7ba3bee8f6fcc92f761964582f684072fb8a1bd0)) -- Update MSRV to 1.65 - ([3cb7870](https://github.com/n0-computer/iroh/commit/3cb7870ffc1783290d536c74fe7d5481aa935a8b)) -- Explicitly close channel when client is dropped - ([2c81d23](https://github.com/n0-computer/iroh/commit/2c81d2307d994790ca67a7758953b748133c6655)) -- Do not use macros in tests - ([596a426](https://github.com/n0-computer/iroh/commit/596a426e20fd1e5ada2eebc2c2256d202700dc5d)) -- Do two forwarder tasks - ([2e334f3](https://github.com/n0-computer/iroh/commit/2e334f345d5d2a82bb425c993478772e63d11dfe)) -- Add explicit proc-macro dependency so the minimal-versions test works - ([cf2045c](https://github.com/n0-computer/iroh/commit/cf2045c6b7f25ddf84c1e99776cfcec60eae5778)) -- Make socket name os comaptible - ([ec3314c](https://github.com/n0-computer/iroh/commit/ec3314c8410a07aa66cf2b1792262902b94caa95)) -- Nightly failures ([#66](https://github.com/n0-computer/iroh/issues/66)) - ([865622e](https://github.com/n0-computer/iroh/commit/865622e99c0618dca16e530b5a44fe3f1f2bdced)) -- Rpc client concurrently waits for requests and new connections ([#62](https://github.com/n0-computer/iroh/issues/62)) - ([3323574](https://github.com/n0-computer/iroh/commit/3323574c972dbdf4dc4e9ae81ab8f32d27b7f3c2)) -- Try to make client streaming and bidi streaming work - ([2bb27d0](https://github.com/n0-computer/iroh/commit/2bb27d0b9ae912203f3292c527863e8203bbc619)) - -### 🚜 Refactor - -- *(http2)* Use a slice instead of a vec for the local addr - ([0d79990](https://github.com/n0-computer/iroh/commit/0d79990aacf48c1083aed30a3718ea35fb3225be)) -- *(mem)* Return &[LocalAddr::Mem] directly - ([2004c46](https://github.com/n0-computer/iroh/commit/2004c461cfbbc6c3c4b0caf8c953f810accb10b1)) -- Move more code out of the macro - ([796dccd](https://github.com/n0-computer/iroh/commit/796dccd183904659c8b2bfacf0ee33dae4967790)) -- One less &mut - ([53af0f9](https://github.com/n0-computer/iroh/commit/53af0f90cd9288a6a27d25558495b9f1091698d3)) -- Add some mut again - ([aab91dc](https://github.com/n0-computer/iroh/commit/aab91dc99efcbc472c405ad9e7862fc24723b8ee)) -- Error remodeling - ([6bad622](https://github.com/n0-computer/iroh/commit/6bad6228be19c111e052ea30f4334c469173414b)) -- Make lazy client a separate example - ([e92771e](https://github.com/n0-computer/iroh/commit/e92771ecbb7aad5c9442396bb351984f86ee94fa)) -- Make the lazy example simpler - ([86c2b94](https://github.com/n0-computer/iroh/commit/86c2b942c8328a0edd6154574f22936518aabed1)) -- Round and round we go - ([37a5703](https://github.com/n0-computer/iroh/commit/37a5703f09a36fa5587bb7a92e82133a588b41bf)) -- Remove dead code - ([7d7ac15](https://github.com/n0-computer/iroh/commit/7d7ac154e5c7149318628e41887cd07838a52438)) -- Add ClientChannelInner - ([8aafe34](https://github.com/n0-computer/iroh/commit/8aafe347c5390fb82f4e1033d389e3408a3d3ef9)) -- WIP make both server and client side take quinn endpoints - ([4d99d71](https://github.com/n0-computer/iroh/commit/4d99d712448ef17719255f2bd9c762c4a4e8b2f4)) -- WIP make benches work - ([639883c](https://github.com/n0-computer/iroh/commit/639883c7866d8de2149c932c62ab0e9638ed28e8)) -- Make the par bench work without the multithreaded executor - ([ee62f93](https://github.com/n0-computer/iroh/commit/ee62f9383c98f3705e50a34cd52b1053cc33d366)) -- WIP add substream source - ([6070939](https://github.com/n0-computer/iroh/commit/6070939fb36d9ed5a5dea9de5f301bc44f5e930d)) -- WIP channel source - ([45fb792](https://github.com/n0-computer/iroh/commit/45fb792fbd226ee3dc851d8202d0b6df371adb2b)) -- Combined has won - ([b743bf1](https://github.com/n0-computer/iroh/commit/b743bf138ad0ac600556fff692386ed4fce78610)) -- Move some things around - ([3ceade3](https://github.com/n0-computer/iroh/commit/3ceade3d4025b2d3f257a955f998e3892962f066)) -- Get rid of some boxes - ([ff786ab](https://github.com/n0-computer/iroh/commit/ff786ab152812a85b0907a5ea8504b6eda292ccb)) -- Rename transports to the crate they wrap - ([bd65fe6](https://github.com/n0-computer/iroh/commit/bd65fe6b3f23dc336150af817c821c9548f4c1e7)) -- Get rid of the lifetimes - ([6ea4862](https://github.com/n0-computer/iroh/commit/6ea486296647ae6d7eb13c784fc2afc931e99849)) -- Rename the macros to declare_... - ([ae24dd1](https://github.com/n0-computer/iroh/commit/ae24dd11e44ce8a09cc8617c47a563c52be08e6b)) -- Make macros optional, and also revert the trait name changes - ([884ceed](https://github.com/n0-computer/iroh/commit/884ceed646231a413c1f011068d3bcbb415b18fc)) -- Remove all transports from the defaults - ([3c02ee5](https://github.com/n0-computer/iroh/commit/3c02ee5f3058539365bd7d588c1610fb7d8ea050)) -- Use spans - ([15be738](https://github.com/n0-computer/iroh/commit/15be73800c511180834e8712c24121bd473e5edd)) -- Add mapped methods for client and server - ([58b029e](https://github.com/n0-computer/iroh/commit/58b029ed2022040e0eb924d80884f521ed76c195)) -- Better generics with helper trait - ([b41c76a](https://github.com/n0-computer/iroh/commit/b41c76ae2948341a37d97a2296fb4b9dc421a9a9)) -- Naming - ([ee5272a](https://github.com/n0-computer/iroh/commit/ee5272af1040223152e2750d8680a0d128b1afd6)) -- No more futures crate ([#73](https://github.com/n0-computer/iroh/issues/73)) - ([403fab0](https://github.com/n0-computer/iroh/commit/403fab014dea45b5d58978d9d4b8a9c80e145c1f)) - -### 📚 Documentation - -- *(http2)* Add comments for the new error cases - ([103b8f4](https://github.com/n0-computer/iroh/commit/103b8f400c39369710f110cbf9463813858708ff)) -- Better comments - ([c7de505](https://github.com/n0-computer/iroh/commit/c7de505a8730e8cd213d20f6d072d9d7fa61e0f7)) -- Yet another badge - ([c0c1ac3](https://github.com/n0-computer/iroh/commit/c0c1ac3a740ac2b5e28a8173329f8a7a2790f57f)) -- Fix github badge - ([60c511f](https://github.com/n0-computer/iroh/commit/60c511f8d8afefaa35c5fff1011a3030bc1db7c0)) -- Update todo comments and made some other comments nicer - ([311307c](https://github.com/n0-computer/iroh/commit/311307cb8acb9a7ca5a4c97adb1e7193a3c17c95)) -- Update docs to match implementation - ([7b6bf32](https://github.com/n0-computer/iroh/commit/7b6bf325884c7f6843fd7656b269aa9b2506b2b0)) -- Add some more text to the readme about why this thing exists in the first place - ([a512de5](https://github.com/n0-computer/iroh/commit/a512de5e80e2e7f19e14cfaf01873407247237aa)) -- Better docs for the declare macros - ([ffc934c](https://github.com/n0-computer/iroh/commit/ffc934c7f2b79cf76565f52e135d14e3c0d637ac)) - -### ⚡ Performance - -- Avoid a copy - ([b57564f](https://github.com/n0-computer/iroh/commit/b57564f3ee9cdbf9fcc1a70965484c40dfae2a40)) -- Preallocate small buffer - ([e306eba](https://github.com/n0-computer/iroh/commit/e306ebaf46e69e6b0f5ed086559900ff4b64dc4b)) - -### 🎨 Styling - -- Fmt - ([0152170](https://github.com/n0-computer/iroh/commit/01521701db70a14f11ba2e5937cd30a72d3e3b12)) - -### 🧪 Testing - -- *(http2)* Add some tests for the not so happy path - ([c04cf77](https://github.com/n0-computer/iroh/commit/c04cf7790ed92ca68129c95f26cae0c3136333a6)) -- Adapt examples - ([80f4921](https://github.com/n0-computer/iroh/commit/80f4921f959b9363204443c39aa81ba83a875c6e)) - -### ⚙️ Miscellaneous Tasks - -- *(docs)* Enable all features for docs.rs builds - ([d3f55ce](https://github.com/n0-computer/iroh/commit/d3f55ced941448de4e3b571ae6bdf6bc3942d4fb)) -- *(docs)* Enable all features for docs.rs builds ([#60](https://github.com/n0-computer/iroh/issues/60)) - ([e063747](https://github.com/n0-computer/iroh/commit/e063747f5eb47cde022845e1b2cecb5426b823c1)) -- Fmt - ([20bb7a0](https://github.com/n0-computer/iroh/commit/20bb7a01cfb7c019bd91106e51ffb6e5acc0ad88)) -- Rename main structs to include type - ([d61bf8d](https://github.com/n0-computer/iroh/commit/d61bf8d09f51d2df4728d4f76dbe69626ca9d0ac)) -- Configure rust version and check during CI - ([da6f282](https://github.com/n0-computer/iroh/commit/da6f2827229514946a4c800c085c956e195fec44)) -- Add more up to date n0 ci workflow - ([7adeaec](https://github.com/n0-computer/iroh/commit/7adeaec832ebf0e1da46f963d29f9cf16854c518)) -- Clippy ([#61](https://github.com/n0-computer/iroh/issues/61)) - ([b25d30d](https://github.com/n0-computer/iroh/commit/b25d30d6749c7508cf3e2e425703be53fd52c49e)) -- Fmt - ([63bc8d8](https://github.com/n0-computer/iroh/commit/63bc8d882453f45ee51187bca9e1399a928d417a)) -- Fix feature flags for tests - ([9c4a7e6](https://github.com/n0-computer/iroh/commit/9c4a7e69b186c84d16464de55b4d80baab73c41b)) -- Clippy - ([1652d5f](https://github.com/n0-computer/iroh/commit/1652d5fb39e464ad8929861614ad1a3153d3feea)) -- Fix feature flags for tests ([#69](https://github.com/n0-computer/iroh/issues/69)) - ([488bb8c](https://github.com/n0-computer/iroh/commit/488bb8c62850bd1cc74eac7303d820f24c0a9151)) - -### Fix - -- Typos - ([b39a1ac](https://github.com/n0-computer/iroh/commit/b39a1ac757add613787a6d66174733bc2f168251)) - -### Change - -- Improve split macro example - ([7d5dc82](https://github.com/n0-computer/iroh/commit/7d5dc82da29933fd922343e0722bab17e1011f5a)) - -### Cleanup - -- Move socket name generation into the lib - ([4f40732](https://github.com/n0-computer/iroh/commit/4f40732e33e812d40b97c3d031c3230096bd9ff9)) - -### Deps - -- Update flume - ([637f9f2](https://github.com/n0-computer/iroh/commit/637f9f28a917b01a6db1459042466a3bdb3dde66)) -- Update flume - ([c966283](https://github.com/n0-computer/iroh/commit/c96628305b6463f31db64ab6943317f2ca58c976)) - -### Pr - -- *(http2)* Log remote addr again - ([5388bb2](https://github.com/n0-computer/iroh/commit/5388bb2daf69aaeac4b1038c3f7a40937eec16dc)) -- Make ChannelConfigError a proper error - ([e4a548b](https://github.com/n0-computer/iroh/commit/e4a548b64fb97935a607d90f7da98d64ed89d8c5)) -- Rename extra constructors and add some comments - ([2c9a08b](https://github.com/n0-computer/iroh/commit/2c9a08bf5b0d2fcd62d93374862932a5c717af2f)) - -### Ref - -- Rename main structs not conflict with trait names - ([6fba32a](https://github.com/n0-computer/iroh/commit/6fba32a4ecf67cb4700f96005c4519f3a9d5bd5b)) - -### Wip - -- Modularize example - ([1782411](https://github.com/n0-computer/iroh/commit/17824114e5ddd50c3bfd590e884e977f401e6e0b)) -- Better approach - setup - ([92b9b60](https://github.com/n0-computer/iroh/commit/92b9b60beeb933bc85a8f977242336a504b735f6)) - - diff --git a/old/Cargo.lock b/old/Cargo.lock deleted file mode 100644 index c2c83e2..0000000 --- a/old/Cargo.lock +++ /dev/null @@ -1,4997 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "bytes", - "crypto-common", - "generic-array", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" - -[[package]] -name = "asn1-rs" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror 1.0.69", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "async-trait" -version = "0.1.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "attohttpc" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" -dependencies = [ - "http 0.2.12", - "log", - "url", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "backoff" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" -dependencies = [ - "futures-core", - "getrandom 0.2.15", - "instant", - "pin-project-lite", - "rand 0.8.5", -] - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bounded-integer" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102dbef1187b1893e6dfe05a774e79fd52265f49f214f6879c8ff49f52c8188b" - -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" - -[[package]] -name = "cc" -version = "1.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" -dependencies = [ - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chrono" -version = "0.4.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-targets 0.52.6", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", - "zeroize", -] - -[[package]] -name = "client" -version = "0.1.0" -dependencies = [ - "anyhow", - "futures", - "iroh-quinn", - "quic-rpc", - "rustls", - "tokio", - "tracing-subscriber", - "types", -] - -[[package]] -name = "cobs" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "cordyceps" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec10f0a762d93c4498d2e97a333805cb6250d60bead623f71d8034f9a4152ba3" -dependencies = [ - "loom 0.5.6", - "tracing", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crossbeam-channel" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "crypto_box" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" -dependencies = [ - "aead", - "chacha20", - "crypto_secretbox", - "curve25519-dalek", - "salsa20", - "serdect", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto_secretbox" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" -dependencies = [ - "aead", - "chacha20", - "cipher", - "generic-array", - "poly1305", - "salsa20", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest", - "fiat-crypto", - "rand_core 0.6.4", - "rustc_version", - "serde", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "data-encoding" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" - -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "der_derive", - "zeroize", -] - -[[package]] -name = "der-parser" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "der_derive" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "unicode-xid", -] - -[[package]] -name = "diatomic-waker" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "dlopen2" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" -dependencies = [ - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "document-features" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" -dependencies = [ - "litrs", -] - -[[package]] -name = "dtoa" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "serde", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core 0.6.4", - "serde", - "sha2", - "subtle", - "zeroize", -] - -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "enumflags2" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" -dependencies = [ - "enumflags2_derive", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" -dependencies = [ - "serde", -] - -[[package]] -name = "erased_set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a02a5d186d7bf1cb21f1f95e1a9cfa5c1f2dcd803a47aad454423ceec13525c5" - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-buffered" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34acda8ae8b63fbe0b2195c998b180cff89a8212fb2622a78b572a9f1c6f7684" -dependencies = [ - "cordyceps", - "diatomic-waker", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generator" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows 0.48.0", -] - -[[package]] -name = "generator" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" -dependencies = [ - "cfg-if", - "libc", - "log", - "rustversion", - "windows 0.58.0", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.2.0", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version", - "serde", - "spin", - "stable_deref_trait", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hickory-proto" -version = "0.25.0-alpha.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d00147af6310f4392a31680db52a3ed45a2e0f68eb18e8c3fe5537ecc96d9e2" -dependencies = [ - "async-recursion", - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna", - "ipnet", - "once_cell", - "rand 0.9.0", - "thiserror 2.0.11", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "hickory-resolver" -version = "0.25.0-alpha.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5762f69ebdbd4ddb2e975cd24690bf21fe6b2604039189c26acddbc427f12887" -dependencies = [ - "cfg-if", - "futures-util", - "hickory-proto", - "ipconfig", - "moka", - "once_cell", - "parking_lot", - "rand 0.9.0", - "resolv-conf", - "smallvec", - "thiserror 2.0.11", - "tokio", - "tracing", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "hmac-sha1" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b05da5b9e5d4720bfb691eebb2b9d42da3570745da71eac8a1f5bb7e59aab88" -dependencies = [ - "hmac", - "sha1", -] - -[[package]] -name = "hmac-sha256" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb" - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "hostname-validator" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.2.0", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2 0.4.7", - "http 1.2.0", - "http-body 1.0.1", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http 1.2.0", - "hyper 1.6.0", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "hyper 1.6.0", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core 0.52.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "igd-next" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b0d7d4541def58a37bf8efc559683f21edce7c82f0d866c93ac21f7e098f93" -dependencies = [ - "async-trait", - "attohttpc", - "bytes", - "futures", - "http 1.2.0", - "http-body-util", - "hyper 1.6.0", - "hyper-util", - "log", - "rand 0.8.5", - "tokio", - "url", - "xmltree", -] - -[[package]] -name = "indexmap" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" -dependencies = [ - "equivalent", - "hashbrown 0.15.2", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "ipconfig" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" -dependencies = [ - "socket2", - "widestring", - "windows-sys 0.48.0", - "winreg", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iroh" -version = "0.33.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=main#0c7a1227cf1b9f640145c059c7581f2c502e6691" -dependencies = [ - "aead", - "anyhow", - "atomic-waker", - "backoff", - "bytes", - "cfg_aliases", - "concurrent-queue", - "crypto_box", - "data-encoding", - "der", - "derive_more", - "ed25519-dalek", - "futures-util", - "hickory-resolver", - "http 1.2.0", - "igd-next", - "instant", - "iroh-base", - "iroh-metrics", - "iroh-net-report", - "iroh-quinn", - "iroh-quinn-proto", - "iroh-quinn-udp", - "iroh-relay", - "n0-future", - "netdev", - "netwatch", - "pin-project", - "pkarr", - "portmapper", - "rand 0.8.5", - "rcgen", - "reqwest", - "ring", - "rustls", - "rustls-webpki", - "serde", - "smallvec", - "strum", - "stun-rs", - "thiserror 2.0.11", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "url", - "wasm-bindgen-futures", - "webpki-roots", - "x509-parser", - "z32", -] - -[[package]] -name = "iroh-base" -version = "0.33.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=main#0c7a1227cf1b9f640145c059c7581f2c502e6691" -dependencies = [ - "curve25519-dalek", - "data-encoding", - "derive_more", - "ed25519-dalek", - "rand_core 0.6.4", - "serde", - "thiserror 2.0.11", - "url", -] - -[[package]] -name = "iroh-metrics" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571d177e20f0848a643a2c0f662be0e08968f8743b0776941f83a2152b87a180" -dependencies = [ - "erased_set", - "http-body-util", - "hyper 1.6.0", - "hyper-util", - "prometheus-client", - "reqwest", - "serde", - "struct_iterable", - "thiserror 2.0.11", - "tokio", - "tracing", -] - -[[package]] -name = "iroh-net-report" -version = "0.33.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=main#0c7a1227cf1b9f640145c059c7581f2c502e6691" -dependencies = [ - "anyhow", - "bytes", - "cfg_aliases", - "derive_more", - "hickory-resolver", - "iroh-base", - "iroh-metrics", - "iroh-quinn", - "iroh-relay", - "n0-future", - "netwatch", - "portmapper", - "rand 0.8.5", - "reqwest", - "rustls", - "surge-ping", - "thiserror 2.0.11", - "tokio", - "tokio-util", - "tracing", - "url", -] - -[[package]] -name = "iroh-quinn" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c6245c9ed906506ab9185e8d7f64857129aee4f935e899f398a3bd3b70338d" -dependencies = [ - "bytes", - "cfg_aliases", - "iroh-quinn-proto", - "iroh-quinn-udp", - "pin-project-lite", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.11", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "iroh-quinn-proto" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "929d5d8fa77d5c304d3ee7cae9aede31f13908bd049f9de8c7c0094ad6f7c535" -dependencies = [ - "bytes", - "getrandom 0.2.15", - "rand 0.8.5", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "slab", - "thiserror 2.0.11", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "iroh-quinn-udp" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53afaa1049f7c83ea1331f5ebb9e6ebc5fdd69c468b7a22dd598b02c9bcc973" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "iroh-relay" -version = "0.33.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=main#0c7a1227cf1b9f640145c059c7581f2c502e6691" -dependencies = [ - "anyhow", - "bytes", - "cfg_aliases", - "data-encoding", - "derive_more", - "hickory-resolver", - "http 1.2.0", - "http-body-util", - "hyper 1.6.0", - "hyper-util", - "iroh-base", - "iroh-metrics", - "iroh-quinn", - "iroh-quinn-proto", - "lru", - "n0-future", - "num_enum", - "pin-project", - "pkarr", - "postcard", - "rand 0.8.5", - "reqwest", - "rustls", - "rustls-webpki", - "serde", - "strum", - "stun-rs", - "thiserror 2.0.11", - "tokio", - "tokio-rustls", - "tokio-tungstenite-wasm", - "tokio-util", - "tracing", - "url", - "webpki-roots", - "z32", -] - -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.169" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "litrs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" - -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator 0.7.5", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator 0.8.4", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.2", -] - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "md5" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] - -[[package]] -name = "moka" -version = "0.12.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" -dependencies = [ - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "loom 0.7.2", - "parking_lot", - "portable-atomic", - "rustc_version", - "smallvec", - "tagptr", - "thiserror 1.0.69", - "uuid", -] - -[[package]] -name = "n0-future" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399e11dc3b0e8d9d65b27170d22f5d779d52d9bed888db70d7e0c2c7ce3dfc52" -dependencies = [ - "cfg_aliases", - "derive_more", - "futures-buffered", - "futures-lite", - "futures-util", - "js-sys", - "pin-project", - "send_wrapper", - "tokio", - "tokio-util", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-time", -] - -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "nested_enum_utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f256ef99e7ac37428ef98c89bef9d84b590172de4bbfbe81b68a4cd3abadb32" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "netdev" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f901362e84cd407be6f8cd9d3a46bccf09136b095792785401ea7d283c79b91d" -dependencies = [ - "dlopen2", - "ipnet", - "libc", - "netlink-packet-core", - "netlink-packet-route 0.17.1", - "netlink-sys", - "once_cell", - "system-configuration", - "windows-sys 0.52.0", -] - -[[package]] -name = "netlink-packet-core" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" -dependencies = [ - "anyhow", - "byteorder", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-route" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" -dependencies = [ - "anyhow", - "bitflags 1.3.2", - "byteorder", - "libc", - "netlink-packet-core", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-route" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c171cd77b4ee8c7708da746ce392440cb7bcf618d122ec9ecc607b12938bf4" -dependencies = [ - "anyhow", - "byteorder", - "libc", - "log", - "netlink-packet-core", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-utils" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" -dependencies = [ - "anyhow", - "byteorder", - "paste", - "thiserror 1.0.69", -] - -[[package]] -name = "netlink-proto" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60" -dependencies = [ - "bytes", - "futures", - "log", - "netlink-packet-core", - "netlink-sys", - "thiserror 2.0.11", -] - -[[package]] -name = "netlink-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" -dependencies = [ - "bytes", - "futures", - "libc", - "log", - "tokio", -] - -[[package]] -name = "netwatch" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64da82edf903649e6cb6a77b5a6f7fe01387d8865065d411d139018510880302" -dependencies = [ - "anyhow", - "atomic-waker", - "bytes", - "derive_more", - "futures-lite", - "futures-sink", - "futures-util", - "iroh-quinn-udp", - "libc", - "netdev", - "netlink-packet-core", - "netlink-packet-route 0.19.0", - "netlink-sys", - "once_cell", - "rtnetlink 0.13.1", - "rtnetlink 0.14.1", - "serde", - "socket2", - "thiserror 2.0.11", - "time", - "tokio", - "tokio-util", - "tracing", - "windows 0.58.0", - "wmi", -] - -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.8.0", - "cfg-if", - "libc", -] - -[[package]] -name = "no-std-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "oid-registry" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" -dependencies = [ - "asn1-rs", -] - -[[package]] -name = "once_cell" -version = "1.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pem" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" -dependencies = [ - "base64", - "serde", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pest" -version = "2.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" -dependencies = [ - "memchr", - "thiserror 2.0.11", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "pest_meta" -version = "2.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - -[[package]] -name = "pin-project" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkarr" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92eff194c72f00f3076855b413ad2d940e3a6e307fa697e5c7733e738341aed4" -dependencies = [ - "bytes", - "document-features", - "ed25519-dalek", - "flume", - "futures", - "js-sys", - "lru", - "self_cell", - "simple-dns", - "thiserror 2.0.11", - "tracing", - "ureq", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "z32", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pnet_base" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cf6fb3ab38b68d01ab2aea03ed3d1132b4868fa4e06285f29f16da01c5f4c" -dependencies = [ - "no-std-net", -] - -[[package]] -name = "pnet_macros" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688b17499eee04a0408aca0aa5cba5fc86401d7216de8a63fdf7a4c227871804" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn 2.0.98", -] - -[[package]] -name = "pnet_macros_support" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea925b72f4bd37f8eab0f221bbe4c78b63498350c983ffa9dd4bcde7e030f56" -dependencies = [ - "pnet_base", -] - -[[package]] -name = "pnet_packet" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a005825396b7fe7a38a8e288dbc342d5034dac80c15212436424fef8ea90ba" -dependencies = [ - "glob", - "pnet_base", - "pnet_macros", - "pnet_macros_support", -] - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "portable-atomic" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" - -[[package]] -name = "portmapper" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5469b29e6ce2a27bfc9382720b5f0768993afec9e53b133d8248c8b09406156a" -dependencies = [ - "anyhow", - "base64", - "bytes", - "derive_more", - "futures-lite", - "futures-util", - "igd-next", - "iroh-metrics", - "libc", - "netwatch", - "num_enum", - "rand 0.8.5", - "serde", - "smallvec", - "socket2", - "thiserror 2.0.11", - "time", - "tokio", - "tokio-util", - "tracing", - "url", -] - -[[package]] -name = "postcard" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "heapless", - "postcard-derive", - "serde", -] - -[[package]] -name = "postcard-derive" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0239fa9c1d225d4b7eb69925c25c5e082307a141e470573fbbe3a817ce6a7a37" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy 0.7.35", -] - -[[package]] -name = "precis-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a414cabc93f5f45d53463e73b3d89d3c5c0dc4a34dbf6901f0c6358f017203" -dependencies = [ - "precis-tools", - "ucd-parse", - "unicode-normalization", -] - -[[package]] -name = "precis-profiles" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58e2841ef58164e2626464d4fde67fa301d5e2c78a10300c1756312a03b169f" -dependencies = [ - "lazy_static", - "precis-core", - "precis-tools", - "unicode-normalization", -] - -[[package]] -name = "precis-tools" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016da884bc4c2c4670211641abef402d15fa2b06c6e9088ff270dac93675aee2" -dependencies = [ - "lazy_static", - "regex", - "ucd-parse", -] - -[[package]] -name = "proc-macro-crate" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prometheus-client" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" -dependencies = [ - "dtoa", - "itoa", - "parking_lot", - "prometheus-client-derive-encode", -] - -[[package]] -name = "prometheus-client-derive-encode" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "quic-rpc" -version = "0.18.3" -dependencies = [ - "anyhow", - "async-stream", - "bytes", - "derive_more", - "document-features", - "flume", - "futures", - "futures-buffered", - "futures-lite", - "futures-sink", - "futures-util", - "hyper 0.14.32", - "iroh", - "iroh-quinn", - "nested_enum_utils", - "pin-project", - "postcard", - "proc-macro2", - "rand 0.8.5", - "rcgen", - "rustls", - "serde", - "slab", - "smallvec", - "tempfile", - "testresult", - "thousands", - "time", - "tokio", - "tokio-serde", - "tokio-util", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "quic-rpc-derive" -version = "0.18.1" -dependencies = [ - "derive_more", - "proc-macro2", - "quic-rpc", - "quote", - "serde", - "syn 1.0.109", - "trybuild", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quinn" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" -dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.11", - "tokio", - "tracing", -] - -[[package]] -name = "quinn-proto" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" -dependencies = [ - "bytes", - "getrandom 0.2.15", - "rand 0.8.5", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.11", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "quote" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "quoted-string-parser" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc75379cdb451d001f1cb667a9f74e8b355e9df84cc5193513cbe62b96fc5e9" -dependencies = [ - "pest", - "pest_derive", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.0", - "zerocopy 0.8.18", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.0", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_core" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" -dependencies = [ - "getrandom 0.3.1", - "zerocopy 0.8.18", -] - -[[package]] -name = "rcgen" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" -dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time", - "yasna", -] - -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-lite" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "reqwest" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" -dependencies = [ - "base64", - "bytes", - "futures-core", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.6.0", - "hyper-rustls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", - "windows-registry", -] - -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] - -[[package]] -name = "ring" -version = "0.17.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.15", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rtnetlink" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" -dependencies = [ - "futures", - "log", - "netlink-packet-core", - "netlink-packet-route 0.17.1", - "netlink-packet-utils", - "netlink-proto", - "netlink-sys", - "nix 0.26.4", - "thiserror 1.0.69", - "tokio", -] - -[[package]] -name = "rtnetlink" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b684475344d8df1859ddb2d395dd3dac4f8f3422a1aa0725993cb375fc5caba5" -dependencies = [ - "futures", - "log", - "netlink-packet-core", - "netlink-packet-route 0.19.0", - "netlink-packet-utils", - "netlink-proto", - "netlink-sys", - "nix 0.27.1", - "thiserror 1.0.69", - "tokio", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.8.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.23.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" -dependencies = [ - "web-time", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e012c45844a1790332c9386ed4ca3a06def221092eda277e6f079728f8ea99da" -dependencies = [ - "core-foundation 0.10.0", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" - -[[package]] -name = "ryu" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" - -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" -dependencies = [ - "bitflags 2.8.0", - "core-foundation 0.10.0", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "self_cell" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" - -[[package]] -name = "semver" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" - -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - -[[package]] -name = "serde" -version = "1.0.217" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.217" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "serde_json" -version = "1.0.138" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serdect" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" -dependencies = [ - "base16ct", - "serde", -] - -[[package]] -name = "server" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-stream", - "futures", - "iroh-quinn", - "quic-rpc", - "tokio", - "tracing-subscriber", - "types", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "simple-dns" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "struct_iterable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "849a064c6470a650b72e41fa6c057879b68f804d113af92900f27574828e7712" -dependencies = [ - "struct_iterable_derive", - "struct_iterable_internal", -] - -[[package]] -name = "struct_iterable_derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb939ce88a43ea4e9d012f2f6b4cc789deb2db9d47bad697952a85d6978662c" -dependencies = [ - "erased-serde", - "proc-macro2", - "quote", - "struct_iterable_internal", - "syn 2.0.98", -] - -[[package]] -name = "struct_iterable_internal" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9426b2a0c03e6cc2ea8dbc0168dbbf943f88755e409fb91bcb8f6a268305f4a" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.98", -] - -[[package]] -name = "stun-rs" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79cc624c9a747353810310af44f1f03f71eb4561284a894acc0396e6d0de76e" -dependencies = [ - "base64", - "bounded-integer", - "byteorder", - "crc", - "enumflags2", - "fallible-iterator", - "hmac-sha1", - "hmac-sha256", - "hostname-validator", - "lazy_static", - "md5", - "paste", - "precis-core", - "precis-profiles", - "quoted-string-parser", - "rand 0.8.5", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "surge-ping" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbf95ce4c7c5b311d2ce3f088af2b93edef0f09727fa50fbe03c7a979afce77" -dependencies = [ - "hex", - "parking_lot", - "pnet_packet", - "rand 0.8.5", - "socket2", - "thiserror 1.0.69", - "tokio", - "tracing", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.8.0", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - -[[package]] -name = "target-triple" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" - -[[package]] -name = "tempfile" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" -dependencies = [ - "cfg-if", - "fastrand", - "getrandom 0.3.1", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "testresult" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614b328ff036a4ef882c61570f72918f7e9c5bee1da33f8e7f91e01daee7e56c" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" -dependencies = [ - "thiserror-impl 2.0.11", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "thousands" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "time" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" -dependencies = [ - "deranged", - "itoa", - "js-sys", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-serde" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf600e7036b17782571dd44fa0a5cea3c82f60db5137f774a325a76a0d6852b" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - -[[package]] -name = "tokio-tungstenite-wasm" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e21a5c399399c3db9f08d8297ac12b500e86bca82e930253fdc62eaf9c0de6ae" -dependencies = [ - "futures-channel", - "futures-util", - "http 1.2.0", - "httparse", - "js-sys", - "thiserror 1.0.69", - "tokio", - "tokio-tungstenite", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "tokio-util" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "futures-util", - "hashbrown 0.14.5", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "trybuild" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b812699e0c4f813b872b373a4471717d9eb550da14b311058a4d9cf4173cbca6" -dependencies = [ - "glob", - "serde", - "serde_derive", - "serde_json", - "target-triple", - "termcolor", - "toml", -] - -[[package]] -name = "tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.2.0", - "httparse", - "log", - "rand 0.8.5", - "sha1", - "thiserror 1.0.69", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "types" -version = "0.1.0" -dependencies = [ - "derive_more", - "quic-rpc", - "serde", -] - -[[package]] -name = "ucd-parse" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06ff81122fcbf4df4c1660b15f7e3336058e7aec14437c9f85c6b31a0f279b9" -dependencies = [ - "regex-lite", -] - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] -name = "unicode-ident" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "ureq" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" -dependencies = [ - "base64", - "log", - "once_cell", - "rustls", - "rustls-pki-types", - "url", - "webpki-roots", -] - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" -dependencies = [ - "getrandom 0.3.1", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.98", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-root-certs" -version = "0.26.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webpki-roots" -version = "0.26.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "widestring" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" -dependencies = [ - "windows-core 0.59.0", - "windows-targets 0.53.0", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" -dependencies = [ - "windows-implement 0.59.0", - "windows-interface 0.59.0", - "windows-result 0.3.0", - "windows-strings 0.3.0", - "windows-targets 0.53.0", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "windows-implement" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "windows-interface" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d08106ce80268c4067c0571ca55a9b4e9516518eaa1a1fe9b37ca403ae1d1a34" -dependencies = [ - "windows-targets 0.53.0", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b888f919960b42ea4e11c2f408fadb55f78a9f236d5eef084103c8ce52893491" -dependencies = [ - "windows-targets 0.53.0", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "winnow" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "wmi" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7787dacdd8e71cbc104658aade4009300777f9b5fda6a75f19145fedb8a18e71" -dependencies = [ - "chrono", - "futures", - "log", - "serde", - "thiserror 2.0.11", - "windows 0.59.0", - "windows-core 0.59.0", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "x509-parser" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror 1.0.69", - "time", -] - -[[package]] -name = "xml-rs" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" - -[[package]] -name = "xmltree" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" -dependencies = [ - "xml-rs", -] - -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" -dependencies = [ - "time", -] - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", -] - -[[package]] -name = "z32" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79386d31a42a4996e3336b0919ddb90f81112af416270cff95b5f5af22b839c2" -dependencies = [ - "zerocopy-derive 0.8.18", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76331675d372f91bf8d17e13afbd5fe639200b73d01f0fc748bb059f9cca2db7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] diff --git a/old/Cargo.toml b/old/Cargo.toml deleted file mode 100644 index 554d6cf..0000000 --- a/old/Cargo.toml +++ /dev/null @@ -1,106 +0,0 @@ -[package] -name = "quic-rpc" -version = "0.18.3" -edition = "2021" -authors = ["Rüdiger Klaehn ", "n0 team"] -keywords = ["api", "protocol", "network", "rpc"] -categories = ["network-programming"] -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.76" - -[dependencies] -bytes = { version = "1", optional = true } -flume = { version = "0.11", optional = true } -futures-lite = "2.3.0" -futures-sink = "0.3.30" -futures-util = { version = "0.3.30", features = ["sink"] } -hyper = { version = "0.14.16", features = ["full"], optional = true } -iroh = { version = "0.33", optional = true } -pin-project = "1" -quinn = { package = "iroh-quinn", version = "0.13", optional = true } -serde = { version = "1", features = ["derive"] } -tokio = { version = "1", default-features = false, features = ["macros", "sync"] } -tokio-serde = { version = "0.9", features = [], optional = true } -tokio-util = { version = "0.7", features = ["rt"] } -postcard = { version = "1", features = ["use-std"], optional = true } -tracing = "0.1" -futures = { version = "0.3.30", optional = true } -anyhow = "1" -document-features = "0.2" -# for test-utils -rcgen = { version = "0.13", optional = true } -# for test-utils -rustls = { version = "0.23", default-features = false, features = ["ring"], optional = true } - -# Indirect dependencies, is needed to make the minimal crates versions work -slab = "0.4.9" # iroh-quinn -smallvec = "1.13.2" -time = "0.3.36" # serde - -[dev-dependencies] -anyhow = "1" -async-stream = "0.3.3" -derive_more = { version = "1", features = ["from", "try_into", "display"] } -rand = "0.8" - -serde = { version = "1", features = ["derive"] } -tokio = { version = "1", features = ["full"] } -quinn = { package = "iroh-quinn", version = "0.13", features = ["ring"] } -rcgen = "0.13" -thousands = "0.2.0" -tracing-subscriber = "0.3.16" -tempfile = "3.5.0" -proc-macro2 = "1.0.66" -futures-buffered = "0.2.4" -testresult = "0.4.1" -nested_enum_utils = "0.1.0" -tokio-util = { version = "0.7", features = ["rt"] } - -[features] -## HTTP transport using the `hyper` crate -hyper-transport = ["dep:flume", "dep:hyper", "dep:postcard", "dep:bytes", "dep:tokio-serde", "tokio-util/codec"] -## QUIC transport using the `iroh-quinn` crate -quinn-transport = ["dep:flume", "dep:quinn", "dep:postcard", "dep:bytes", "dep:tokio-serde", "tokio-util/codec"] -## In memory transport using the `flume` crate -flume-transport = ["dep:flume"] -## p2p QUIC transport using the `iroh` crate -iroh-transport = ["dep:iroh", "dep:flume", "dep:postcard", "dep:tokio-serde", "tokio-util/codec"] -## Macros for creating request handlers -macros = [] -## Utilities for testing -test-utils = ["dep:rcgen", "dep:rustls"] -## Default, includes the memory transport -default = ["flume-transport"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "quicrpc_docsrs"] - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ["cfg(quicrpc_docsrs)"] } - -[[example]] -name = "errors" -required-features = ["flume-transport"] - -[[example]] -name = "macro" -required-features = ["flume-transport", "macros"] - -[[example]] -name = "store" -required-features = ["flume-transport", "macros"] - -[[example]] -name = "modularize" -required-features = ["flume-transport"] - -[workspace] -members = ["examples/split/types", "examples/split/server", "examples/split/client", "quic-rpc-derive"] - -[patch.crates-io] -iroh = { git = "https://github.com/n0-computer/iroh.git", branch = "main" } diff --git a/old/DOCS.md b/old/DOCS.md deleted file mode 100644 index ca1ec92..0000000 --- a/old/DOCS.md +++ /dev/null @@ -1,32 +0,0 @@ -Building docs for this crate is a bit complex. There are lots of feature flags, -so we want feature flag markers in the docs, especially for the transports. - -There is an experimental cargo doc feature that adds feature flag markers. To -get those, run docs with this command line: - -```rust -RUSTDOCFLAGS="--cfg quicrpc_docsrs" cargo +nightly doc --all-features --no-deps --open -``` - -This sets the flag `quicrpc_docsrs` when creating docs, which triggers statements -like below that add feature flag markers. Note that you *need* nightly for this feature -as of now. - -``` -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "flume-transport")))] -``` - -The feature is *enabled* using this statement in lib.rs: - -``` -#![cfg_attr(quicrpc_docsrs, feature(doc_cfg))] -``` - -We tell [docs.rs] to use the `quicrpc_docsrs` config using these statements -in Cargo.toml: - -``` -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "quicrpc_docsrs"] -``` \ No newline at end of file diff --git a/old/README.md b/old/README.md deleted file mode 100644 index 062c437..0000000 --- a/old/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# Quic-Rpc - -A streaming rpc system based on quic - -[][repo link] [![Latest Version]][crates.io] [![Docs Badge]][docs.rs] ![license badge] [![status badge]][status link] - -[Latest Version]: https://img.shields.io/crates/v/quic-rpc.svg -[crates.io]: https://crates.io/crates/quic-rpc -[Docs Badge]: https://img.shields.io/badge/docs-docs.rs-green -[docs.rs]: https://docs.rs/quic-rpc -[license badge]: https://img.shields.io/crates/l/quic-rpc -[status badge]: https://github.com/n0-computer/quic-rpc/actions/workflows/rust.yml/badge.svg -[status link]: https://github.com/n0-computer/quic-rpc/actions/workflows/rust.yml -[repo link]: https://github.com/n0-computer/quic-rpc - -## Goals - -### Interaction patterns - -Provide not just request/response RPC, but also streaming in both directions, similar to [grpc]. - -- 1 req -> 1 res -- 1 req, update stream -> 1 res -- 1 req -> res stream -- 1 req, update stream -> res stream - -It is still a RPC system in the sense that interactions get initiated by the client. - -### Transports - -- memory transport with very low overhead. In particular, no ser/deser, currently using [flume] -- quic transport via the [quinn] crate -- transparent combination of the above - -### API - -- The API should be similar to the quinn api. Basically "quinn with types". - -## Non-Goals - -- Cross language interop. This is for talking from rust to rust -- Any kind of versioning. You have to do this yourself -- Making remote message passing look like local async function calls -- Being runtime agnostic. This is for tokio - -## Example - -[computation service](https://github.com/n0-computer/quic-rpc/blob/main/tests/math.rs) - -## Why? - -The purpose of quic-rpc is to serve as an *optional* rpc framework. One of the -main goals is to be able to use it as an *in process* way to have well specified -protocols and boundaries between subsystems, including an async boundary. - -It should not have noticeable overhead compared to what you would do anyway to -isolate subsystems in a complex single process app, but should have the *option* -to also send messages over a process boundary via one of the non mem transports. - -What do you usually do in rust to have isolation between subsystems, e.g. -between a database and a networking layer? You have some kind of -channel between the systems and define messages flowing back and forth over that -channel. For almost all interactions these messages itself will again contain -(oneshot or mpsc) channels for independent async communication between the -subsystems. - -Quic-rpc with the mem channel does exactly the same thing, except that it hides -the details and allows you to specify a clean high level interaction protocol -in the rust type system. - -Instead of having a message that explicitly contains some data and the send side -of a oneshot or mpsc channel for the response, it creates a pair of flume -channels internally and sends one end of them to the server. This has some slight -overhead (2 flume channels vs. 1 oneshot channel) for a RPC interaction. But -for streaming interactions the overhead is negligible. - -For the case where you have a process boundary, the overhead is very low for -transports that already have a concept of cheap substreams (http2, quic, ...). -Quic is the poster child of a network transport that has built in cheap -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 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 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. - -This may change in the future as quic implementations get more optimized. - -[quinn]: https://docs.rs/quinn/ -[flume]: https://docs.rs/flume/ -[grpc]: https://grpc.io/ - -# Docs - -Properly building docs for this crate is quite complex. For all the gory details, -see [DOCS.md]. \ No newline at end of file diff --git a/old/cliff.toml b/old/cliff.toml deleted file mode 100644 index 32033c4..0000000 --- a/old/cliff.toml +++ /dev/null @@ -1,64 +0,0 @@ -[changelog] -# changelog header -header = """ -# Changelog\n -All notable changes to quic-rpc will be documented in this file.\n -""" - -body = """ -{% if version %}\ - {% if previous.version %}\ - ## [{{ version | trim_start_matches(pat="v") }}](/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} - {% else %}\ - ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} - {% endif %}\ -{% else %}\ - ## [unreleased] -{% endif %}\ - -{% macro commit(commit) -%} - - {% if commit.scope %}*({{ commit.scope }})* {% endif %}{% if commit.breaking %}[**breaking**] {% endif %}\ - {{ commit.message | upper_first }} - ([{{ commit.id | truncate(length=7, end="") }}](/commit/{{ commit.id }}))\ -{% endmacro -%} - -{% for group, commits in commits | group_by(attribute="group") %} - ### {{ group | striptags | trim | upper_first }} - {% for commit in commits - | filter(attribute="scope") - | sort(attribute="scope") %} - {{ self::commit(commit=commit) }} - {%- endfor -%} - {% raw %}\n{% endraw %}\ - {%- for commit in commits %} - {%- if not commit.scope -%} - {{ self::commit(commit=commit) }} - {% endif -%} - {% endfor -%} -{% endfor %}\n -""" - -footer = "" -postprocessors = [ - { pattern = '', replace = "https://github.com/n0-computer/iroh" }, - { pattern = "\\(#([0-9]+)\\)", replace = "([#${1}](https://github.com/n0-computer/iroh/issues/${1}))"} -] - - -[git] -# regex for parsing and grouping commits -commit_parsers = [ - { message = "^feat", group = "⛰️ Features" }, - { message = "^fix", group = "🐛 Bug Fixes" }, - { message = "^doc", group = "📚 Documentation" }, - { message = "^perf", group = "⚡ Performance" }, - { message = "^refactor", group = "🚜 Refactor" }, - { message = "^style", group = "🎨 Styling" }, - { message = "^test", group = "🧪 Testing" }, - { message = "^chore\\(release\\)", skip = true }, - { message = "^chore\\(deps\\)", skip = true }, - { message = "^chore\\(pr\\)", skip = true }, - { message = "^chore\\(pull\\)", skip = true }, - { message = "^chore|ci", group = "⚙️ Miscellaneous Tasks" }, - { body = ".*security", group = "🛡️ Security" }, - { message = "^revert", group = "◀️ Revert" }, -] \ No newline at end of file diff --git a/old/examples/errors.rs b/old/examples/errors.rs deleted file mode 100644 index a8e015e..0000000 --- a/old/examples/errors.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::result; - -use derive_more::{Display, From, TryInto}; -use quic_rpc::{message::RpcMsg, RpcClient, RpcServer, Service}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -struct WriteRequest(String, Vec); - -#[derive(Debug, Serialize, Deserialize, From, TryInto)] -enum IoRequest { - Write(WriteRequest), -} - -/// Serializable wire error type. There has to be a From instance from the convenience error type. -/// -/// The RPC client sees this type directly. -#[derive(Debug, Display, Serialize, Deserialize)] -struct WriteError(String); - -impl std::error::Error for WriteError {} - -impl From for WriteError { - fn from(e: anyhow::Error) -> Self { - WriteError(format!("{e:?}")) - } -} - -#[derive(Debug, Serialize, Deserialize, From, TryInto)] -enum IoResponse { - Write(result::Result<(), WriteError>), -} - -#[derive(Debug, Clone)] -struct IoService; - -impl Service for IoService { - type Req = IoRequest; - type Res = IoResponse; -} - -impl RpcMsg for WriteRequest { - type Response = result::Result<(), WriteError>; -} - -#[derive(Debug, Clone, Copy)] -struct Fs; - -impl Fs { - /// write a file, returning the convenient anyhow::Result - async fn write(self, _req: WriteRequest) -> anyhow::Result<()> { - Ok(()) - } -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let fs = Fs; - let (server, client) = quic_rpc::transport::flume::channel(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().await?.read_first().await?; - match req { - IoRequest::Write(req) => chan.rpc_map_err(req, fs, Fs::write).await, - }? - } - anyhow::Ok(()) - }); - client - .rpc(WriteRequest("hello".to_string(), vec![0u8; 32])) - .await??; - handle.await??; - Ok(()) -} diff --git a/old/examples/macro.rs b/old/examples/macro.rs deleted file mode 100644 index 1a5f2b6..0000000 --- a/old/examples/macro.rs +++ /dev/null @@ -1,158 +0,0 @@ -mod store_rpc { - use std::fmt::Debug; - - use quic_rpc::rpc_service; - use serde::{Deserialize, Serialize}; - - pub type Cid = [u8; 32]; - - #[derive(Debug, Serialize, Deserialize)] - pub struct Put(pub Vec); - #[derive(Debug, Serialize, Deserialize)] - pub struct PutResponse(pub Cid); - - #[derive(Debug, Serialize, Deserialize)] - pub struct Get(pub Cid); - #[derive(Debug, Serialize, Deserialize)] - pub struct GetResponse(pub Vec); - - #[derive(Debug, Serialize, Deserialize)] - pub struct PutFile; - #[derive(Debug, Serialize, Deserialize)] - pub struct PutFileUpdate(pub Vec); - #[derive(Debug, Serialize, Deserialize)] - pub struct PutFileResponse(pub Cid); - - #[derive(Debug, Serialize, Deserialize)] - pub struct GetFile(pub Cid); - #[derive(Debug, Serialize, Deserialize)] - pub struct GetFileResponse(pub Vec); - - #[derive(Debug, Serialize, Deserialize)] - pub struct ConvertFile; - #[derive(Debug, Serialize, Deserialize)] - pub struct ConvertFileUpdate(pub Vec); - #[derive(Debug, Serialize, Deserialize)] - pub struct ConvertFileResponse(pub Vec); - - rpc_service! { - Request = StoreRequest; - Response = StoreResponse; - Service = StoreService; - CreateDispatch = create_store_dispatch; - - Rpc put = Put, _ -> PutResponse; - Rpc get = Get, _ -> GetResponse; - ClientStreaming put_file = PutFile, PutFileUpdate -> PutFileResponse; - ServerStreaming get_file = GetFile, _ -> GetFileResponse; - BidiStreaming convert_file = ConvertFile, ConvertFileUpdate -> ConvertFileResponse; - } -} - -use async_stream::stream; -use futures_lite::{Stream, StreamExt}; -use futures_util::SinkExt; -use quic_rpc::{client::RpcClient, server::run_server_loop, transport::flume}; -use store_rpc::*; - -#[derive(Clone)] -pub struct Store; - -impl Store { - async fn put(self, _put: Put) -> PutResponse { - PutResponse([0; 32]) - } - - async fn get(self, _get: Get) -> GetResponse { - GetResponse(vec![]) - } - - async fn put_file( - self, - _put: PutFile, - updates: impl Stream, - ) -> PutFileResponse { - tokio::pin!(updates); - while let Some(_update) = updates.next().await {} - PutFileResponse([0; 32]) - } - - fn get_file(self, _get: GetFile) -> impl Stream + Send + 'static { - stream! { - for i in 0..3 { - yield GetFileResponse(vec![i]); - } - } - } - - fn convert_file( - self, - _convert: ConvertFile, - updates: impl Stream + Send + 'static, - ) -> impl Stream + Send + 'static { - stream! { - tokio::pin!(updates); - while let Some(msg) = updates.next().await { - yield ConvertFileResponse(msg.0); - } - } - } -} - -create_store_dispatch!(Store, dispatch_store_request); -// create_store_client!(StoreClient); - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let (server, client) = flume::channel(1); - let server_handle = tokio::task::spawn(async move { - let target = Store; - run_server_loop(StoreService, server, target, dispatch_store_request).await - }); - let client = RpcClient::::new(client); - - // a rpc call - for i in 0..3 { - println!("a rpc call [{i}]"); - let client = client.clone(); - tokio::task::spawn(async move { - 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.server_streaming(GetFile([0u8; 32])).await?; - while let Some(res) = s.next().await { - println!("streaming res: {res:?}"); - } - - // client streaming call - println!("a client streaming call"); - 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:?}"); - - // bidi streaming call - println!("a bidi streaming call"); - 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:?}"); - } - - // dropping the client will cause the server to terminate - drop(client); - server_handle.await??; - Ok(()) -} diff --git a/old/examples/modularize.rs b/old/examples/modularize.rs deleted file mode 100644 index 4143b3e..0000000 --- a/old/examples/modularize.rs +++ /dev/null @@ -1,507 +0,0 @@ -//! This example shows how an RPC service can be modularized, even between different crates. -//! -//! * `app` module is the top level. it composes `iroh` plus one handler of the app itself -//! * `iroh` module composes two other services, `calc` and `clock` -//! -//! The [`calc`] and [`clock`] modules both expose a [`quic_rpc::Service`] in a regular fashion. -//! They do not `use` anything from `super` or `app` so they could live in their own crates -//! unchanged. - -use anyhow::Result; -use app::AppService; -use futures_lite::StreamExt; -use futures_util::SinkExt; -use quic_rpc::{client::BoxedConnector, transport::flume, Listener, RpcClient, RpcServer}; - -#[tokio::main] -async fn main() -> Result<()> { - // Spawn an inmemory connection. - // Could use quic equally (all code in this example is generic over the transport) - let (server_conn, client_conn) = flume::channel(1); - - // spawn the server - let handler = app::Handler::default(); - tokio::task::spawn(run_server(server_conn, handler)); - - // run a client demo - client_demo(BoxedConnector::::new(client_conn)).await?; - - Ok(()) -} - -async fn run_server>(server_conn: C, handler: app::Handler) { - let server = RpcServer::::new(server_conn); - server - .accept_loop(move |req, chan| handler.clone().handle_rpc_request(req, chan)) - .await -} - -pub async fn client_demo(conn: BoxedConnector) -> Result<()> { - let rpc_client = RpcClient::::new(conn); - let client = app::Client::new(rpc_client.clone()); - - // call a method from the top-level app client - let res = client.app_version().await?; - println!("app_version: {res:?}"); - - // call a method from the wrapped iroh.calc client - let res = client.iroh.calc.add(40, 2).await?; - println!("iroh.calc.add: {res:?}"); - - // can also do "raw" calls without using the wrapped clients - let res = rpc_client - .clone() - .map::() - .map::() - .rpc(calc::AddRequest(19, 4)) - .await?; - println!("iroh.calc.add (raw): {res:?}"); - - let (mut sink, res) = rpc_client - .map::() - .map::() - .client_streaming(calc::SumRequest) - .await?; - sink.send(calc::SumUpdate(4)).await.unwrap(); - sink.send(calc::SumUpdate(8)).await.unwrap(); - sink.send(calc::SumUpdate(30)).await.unwrap(); - drop(sink); - let res = res.await?; - println!("iroh.calc.sum (raw): {res:?}"); - - // call a server-streaming method from the wrapped iroh.clock client - let mut stream = client.iroh.clock.tick().await?; - while let Some(tick) = stream.try_next().await? { - println!("iroh.clock.tick: {tick}"); - } - Ok(()) -} - -mod app { - //! This is the app-specific code. - //! - //! It composes all of `iroh` (which internally composes two other modules) and adds an - //! application specific RPC. - //! - //! It could also easily compose services from other crates or internal modules. - - use anyhow::Result; - use derive_more::{From, TryInto}; - use quic_rpc::{message::RpcMsg, server::RpcChannel, Listener, RpcClient, Service}; - use serde::{Deserialize, Serialize}; - - use super::iroh; - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - pub enum Request { - Iroh(iroh::Request), - AppVersion(AppVersionRequest), - } - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - pub enum Response { - Iroh(iroh::Response), - AppVersion(AppVersionResponse), - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct AppVersionRequest; - - impl RpcMsg for AppVersionRequest { - type Response = AppVersionResponse; - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct AppVersionResponse(pub String); - - #[derive(Copy, Clone, Debug)] - pub struct AppService; - impl Service for AppService { - type Req = Request; - type Res = Response; - } - - #[derive(Clone)] - pub struct Handler { - iroh: iroh::Handler, - app_version: String, - } - - impl Default for Handler { - fn default() -> Self { - Self { - iroh: iroh::Handler::default(), - app_version: "v0.1-alpha".to_string(), - } - } - } - - impl Handler { - pub async fn handle_rpc_request>( - self, - req: Request, - chan: RpcChannel, - ) -> Result<()> { - match req { - Request::Iroh(req) => { - self.iroh - .handle_rpc_request(req, chan.map().boxed()) - .await? - } - Request::AppVersion(req) => chan.rpc(req, self, Self::on_version).await?, - }; - Ok(()) - } - - pub async fn on_version(self, _req: AppVersionRequest) -> AppVersionResponse { - AppVersionResponse(self.app_version.clone()) - } - } - - #[derive(Debug, Clone)] - pub struct Client { - pub iroh: iroh::Client, - client: RpcClient, - } - - impl Client { - pub fn new(client: RpcClient) -> Self { - Self { - client: client.clone(), - iroh: iroh::Client::new(client.map().boxed()), - } - } - - pub async fn app_version(&self) -> Result { - let res = self.client.rpc(AppVersionRequest).await?; - Ok(res.0) - } - } -} - -mod iroh { - //! This module composes two sub-services. Think `iroh` crate which exposes services and - //! clients for iroh-bytes and iroh-gossip or so. - //! It uses only the `calc` and `clock` modules and nothing else. - - use anyhow::Result; - use derive_more::{From, TryInto}; - use quic_rpc::{server::RpcChannel, RpcClient, Service}; - use serde::{Deserialize, Serialize}; - - use super::{calc, clock}; - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - pub enum Request { - Calc(calc::Request), - Clock(clock::Request), - } - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - pub enum Response { - Calc(calc::Response), - Clock(clock::Response), - } - - #[derive(Copy, Clone, Debug)] - pub struct IrohService; - impl Service for IrohService { - type Req = Request; - type Res = Response; - } - - #[derive(Clone, Default)] - pub struct Handler { - calc: calc::Handler, - clock: clock::Handler, - } - - impl Handler { - pub async fn handle_rpc_request( - self, - req: Request, - chan: RpcChannel, - ) -> Result<()> { - match req { - Request::Calc(req) => { - self.calc - .handle_rpc_request(req, chan.map().boxed()) - .await? - } - Request::Clock(req) => { - self.clock - .handle_rpc_request(req, chan.map().boxed()) - .await? - } - } - Ok(()) - } - } - - #[derive(Debug, Clone)] - pub struct Client { - pub calc: calc::Client, - pub clock: clock::Client, - } - - impl Client { - pub fn new(client: RpcClient) -> Self { - Self { - calc: calc::Client::new(client.clone().map().boxed()), - clock: clock::Client::new(client.clone().map().boxed()), - } - } - } -} - -mod calc { - //! This is a library providing a service, and a client. E.g. iroh-bytes or iroh-hypermerge. - //! It does not use any `super` imports, it is completely decoupled. - - use std::fmt::Debug; - - use anyhow::{bail, Result}; - use derive_more::{From, TryInto}; - use futures_lite::{Stream, StreamExt}; - use quic_rpc::{ - message::{ClientStreaming, ClientStreamingMsg, Msg, RpcMsg}, - server::RpcChannel, - RpcClient, Service, - }; - use serde::{Deserialize, Serialize}; - - #[derive(Debug, Serialize, Deserialize)] - pub struct AddRequest(pub i64, pub i64); - - impl RpcMsg for AddRequest { - type Response = AddResponse; - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct AddResponse(pub i64); - - #[derive(Debug, Serialize, Deserialize)] - pub struct SumRequest; - - #[derive(Debug, Serialize, Deserialize)] - pub struct SumUpdate(pub i64); - - impl Msg for SumRequest { - type Pattern = ClientStreaming; - } - - impl ClientStreamingMsg for SumRequest { - type Update = SumUpdate; - type Response = SumResponse; - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct SumResponse(pub i64); - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - pub enum Request { - Add(AddRequest), - Sum(SumRequest), - SumUpdate(SumUpdate), - } - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - pub enum Response { - Add(AddResponse), - Sum(SumResponse), - } - - #[derive(Copy, Clone, Debug)] - pub struct CalcService; - impl Service for CalcService { - type Req = Request; - type Res = Response; - } - - #[derive(Clone, Default)] - pub struct Handler; - - impl Handler { - pub async fn handle_rpc_request( - self, - req: Request, - chan: RpcChannel, - ) -> Result<()> { - match req { - Request::Add(req) => chan.rpc(req, self, Self::on_add).await?, - Request::Sum(req) => chan.client_streaming(req, self, Self::on_sum).await?, - Request::SumUpdate(_) => bail!("Unexpected update message at start of request"), - } - Ok(()) - } - - pub async fn on_add(self, req: AddRequest) -> AddResponse { - AddResponse(req.0 + req.1) - } - - pub async fn on_sum( - self, - _req: SumRequest, - updates: impl Stream, - ) -> SumResponse { - let mut sum = 0i64; - tokio::pin!(updates); - while let Some(SumUpdate(n)) = updates.next().await { - sum += n; - } - SumResponse(sum) - } - } - - #[derive(Debug, Clone)] - pub struct Client { - client: RpcClient, - } - - impl Client { - pub fn new(client: RpcClient) -> Self { - Self { client } - } - pub async fn add(&self, a: i64, b: i64) -> anyhow::Result { - let res = self.client.rpc(AddRequest(a, b)).await?; - Ok(res.0) - } - } -} - -mod clock { - //! This is a library providing a service, and a client. E.g. iroh-bytes or iroh-hypermerge. - //! It does not use any `super` imports, it is completely decoupled. - - use std::{ - fmt::Debug, - sync::{Arc, RwLock}, - time::Duration, - }; - - use anyhow::Result; - use derive_more::{From, TryInto}; - use futures_lite::{stream::Boxed as BoxStream, Stream, StreamExt}; - use futures_util::TryStreamExt; - use quic_rpc::{ - message::{Msg, ServerStreaming, ServerStreamingMsg}, - server::RpcChannel, - RpcClient, Service, - }; - use serde::{Deserialize, Serialize}; - use tokio::sync::Notify; - - #[derive(Debug, Serialize, Deserialize)] - pub struct TickRequest; - - impl Msg for TickRequest { - type Pattern = ServerStreaming; - } - - impl ServerStreamingMsg for TickRequest { - type Response = TickResponse; - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct TickResponse { - tick: usize, - } - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - pub enum Request { - Tick(TickRequest), - } - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - pub enum Response { - Tick(TickResponse), - } - - #[derive(Copy, Clone, Debug)] - pub struct ClockService; - impl Service for ClockService { - type Req = Request; - type Res = Response; - } - - #[derive(Clone)] - pub struct Handler { - tick: Arc>, - ontick: Arc, - } - - impl Default for Handler { - fn default() -> Self { - Self::new(Duration::from_secs(1)) - } - } - - impl Handler { - pub fn new(tick_duration: Duration) -> Self { - let h = Handler { - tick: Default::default(), - ontick: Default::default(), - }; - let h2 = h.clone(); - tokio::task::spawn(async move { - loop { - tokio::time::sleep(tick_duration).await; - *h2.tick.write().unwrap() += 1; - h2.ontick.notify_waiters(); - } - }); - h - } - - pub async fn handle_rpc_request( - self, - req: Request, - chan: RpcChannel, - ) -> Result<()> { - match req { - Request::Tick(req) => chan.server_streaming(req, self, Self::on_tick).await?, - } - Ok(()) - } - - pub fn on_tick( - self, - req: TickRequest, - ) -> impl Stream + Send + 'static { - let (tx, rx) = flume::bounded(2); - tokio::task::spawn(async move { - if let Err(err) = self.on_tick0(req, tx).await { - tracing::warn!(?err, "on_tick RPC handler failed"); - } - }); - rx.into_stream() - } - - pub async fn on_tick0( - self, - _req: TickRequest, - tx: flume::Sender, - ) -> Result<()> { - loop { - let tick = *self.tick.read().unwrap(); - tx.send_async(TickResponse { tick }).await?; - self.ontick.notified().await; - } - } - } - - #[derive(Debug, Clone)] - pub struct Client { - client: RpcClient, - } - - impl Client { - pub fn new(client: RpcClient) -> Self { - Self { client } - } - pub async fn tick(&self) -> Result>> { - let res = self.client.server_streaming(TickRequest).await?; - Ok(res.map_ok(|r| r.tick).map_err(anyhow::Error::from).boxed()) - } - } -} diff --git a/old/examples/split/client/Cargo.toml b/old/examples/split/client/Cargo.toml deleted file mode 100644 index e0cbab5..0000000 --- a/old/examples/split/client/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "client" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1.0.14" -futures = "0.3.26" -quic-rpc = { path = "../../..", features = ["quinn-transport", "macros", "test-utils"] } -quinn = { package = "iroh-quinn", version = "0.13" } -rustls = { version = "0.23", default-features = false, features = ["ring"] } -tracing-subscriber = "0.3.16" -tokio = { version = "1", features = ["full"] } -types = { path = "../types" } diff --git a/old/examples/split/client/src/main.rs b/old/examples/split/client/src/main.rs deleted file mode 100644 index b7379ae..0000000 --- a/old/examples/split/client/src/main.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![allow(unknown_lints, non_local_definitions)] - -use std::net::SocketAddr; - -use futures::{sink::SinkExt, stream::StreamExt}; -use quic_rpc::{ - transport::quinn::{make_insecure_client_endpoint, QuinnConnector}, - RpcClient, -}; -use types::compute::*; - -// 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 = QuinnConnector::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 client = client.clone(); - tokio::task::spawn(async move { - println!("rpc call: square([{i}])"); - 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.client_streaming(Sum).await?; - tokio::task::spawn(async move { - for i in 2..4 { - println!("client streaming update: {i}"); - send.send(SumUpdate(i)).await.unwrap(); - } - }); - let res = recv.await?; - println!("client streaming res: {res:?}"); - - // server streaming call - println!("server streaming call: fibonacci(10)"); - 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.bidi(Multiply(2)).await?; - tokio::task::spawn(async move { - for i in 1..3 { - println!("bidi streaming update: {i}"); - send.send(MultiplyUpdate(i)).await.unwrap(); - } - }); - while let Some(res) = recv.next().await { - println!("bidi streaming res: {:?}", res?); - } - - Ok(()) -} diff --git a/old/examples/split/server/Cargo.toml b/old/examples/split/server/Cargo.toml deleted file mode 100644 index 5264bdb..0000000 --- a/old/examples/split/server/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "server" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1.0.14" -async-stream = "0.3.3" -futures = "0.3.26" -tracing-subscriber = "0.3.16" -quic-rpc = { path = "../../..", features = ["quinn-transport", "macros", "test-utils"] } -quinn = { package = "iroh-quinn", version = "0.13" } -tokio = { version = "1", features = ["full"] } -types = { path = "../types" } diff --git a/old/examples/split/server/src/main.rs b/old/examples/split/server/src/main.rs deleted file mode 100644 index 90bdfcd..0000000 --- a/old/examples/split/server/src/main.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::net::SocketAddr; - -use async_stream::stream; -use futures::stream::{Stream, StreamExt}; -use quic_rpc::{ - server::run_server_loop, - transport::quinn::{make_server_endpoint, QuinnListener}, -}; -use types::compute::*; - -#[derive(Clone)] -pub struct Compute; - -types::create_compute_dispatch!(Compute, dispatch_compute_request); - -impl Compute { - async fn square(self, req: Sqr) -> SqrResponse { - SqrResponse(req.0 as u128 * req.0 as u128) - } - - async fn sum(self, _req: Sum, updates: impl Stream) -> SumResponse { - let mut sum = 0u128; - tokio::pin!(updates); - while let Some(SumUpdate(n)) = updates.next().await { - sum += n as u128; - } - SumResponse(sum) - } - - fn fibonacci(self, req: Fibonacci) -> impl Stream { - let mut a = 0u128; - let mut b = 1u128; - let mut n = req.0; - stream! { - while n > 0 { - yield FibonacciResponse(a); - let c = a + b; - a = b; - b = c; - n -= 1; - } - } - } - - fn multiply( - self, - req: Multiply, - updates: impl Stream, - ) -> impl Stream { - let product = req.0 as u128; - stream! { - tokio::pin!(updates); - while let Some(MultiplyUpdate(n)) = updates.next().await { - yield MultiplyResponse(product * n as u128); - } - } - } -} - -#[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 channel = QuinnListener::new(server)?; - let target = Compute; - run_server_loop( - ComputeService, - channel.clone(), - target, - dispatch_compute_request, - ) - .await?; - Ok(()) -} diff --git a/old/examples/split/types/Cargo.toml b/old/examples/split/types/Cargo.toml deleted file mode 100644 index 81b8eb1..0000000 --- a/old/examples/split/types/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "types" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -quic-rpc = { path = "../../..", features = ["macros"] } -serde = { version = "1", features = ["derive"] } -derive_more = { version = "1", features = ["from", "try_into"] } diff --git a/old/examples/split/types/src/lib.rs b/old/examples/split/types/src/lib.rs deleted file mode 100644 index a25a6fd..0000000 --- a/old/examples/split/types/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub mod compute { - use std::fmt::Debug; - - use quic_rpc::rpc_service; - use serde::{Deserialize, Serialize}; - - /// compute the square of a number - #[derive(Debug, Serialize, Deserialize)] - pub struct Sqr(pub u64); - #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] - pub struct SqrResponse(pub u128); - - /// sum a stream of numbers - #[derive(Debug, Serialize, Deserialize)] - pub struct Sum; - #[derive(Debug, Serialize, Deserialize)] - pub struct SumUpdate(pub u64); - #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] - pub struct SumResponse(pub u128); - - /// compute the fibonacci sequence as a stream - #[derive(Debug, Serialize, Deserialize)] - pub struct Fibonacci(pub u64); - #[derive(Debug, Serialize, Deserialize)] - pub struct FibonacciResponse(pub u128); - - /// multiply a stream of numbers, returning a stream - #[derive(Debug, Serialize, Deserialize)] - pub struct Multiply(pub u64); - #[derive(Debug, Serialize, Deserialize)] - pub struct MultiplyUpdate(pub u64); - #[derive(Debug, Serialize, Deserialize)] - pub struct MultiplyResponse(pub u128); - - rpc_service! { - Request = ComputeRequest; - Response = ComputeResponse; - Service = ComputeService; - CreateDispatch = create_compute_dispatch; - - Rpc square = Sqr, _ -> SqrResponse; - ClientStreaming sum = Sum, SumUpdate -> SumResponse; - ServerStreaming fibonacci = Fibonacci, _ -> FibonacciResponse; - BidiStreaming multiply = Multiply, MultiplyUpdate -> MultiplyResponse; - } -} diff --git a/old/examples/store.rs b/old/examples/store.rs deleted file mode 100644 index b99edea..0000000 --- a/old/examples/store.rs +++ /dev/null @@ -1,267 +0,0 @@ -#![allow(clippy::enum_variant_names)] -use std::{fmt::Debug, result}; - -use async_stream::stream; -use derive_more::{From, TryInto}; -use futures_lite::{Stream, StreamExt}; -use futures_util::SinkExt; -use quic_rpc::{ - server::RpcServerError, - transport::{flume, Connector}, - *, -}; -use serde::{Deserialize, Serialize}; - -type Cid = [u8; 32]; -#[derive(Debug, Serialize, Deserialize)] -struct Put(Vec); -#[derive(Debug, Serialize, Deserialize)] -struct Get(Cid); -#[derive(Debug, Serialize, Deserialize)] -struct PutResponse(Cid); -#[derive(Debug, Serialize, Deserialize)] -struct GetResponse(Vec); - -#[derive(Debug, Serialize, Deserialize)] -struct PutFile; - -#[derive(Debug, Serialize, Deserialize)] -struct PutFileUpdate(Vec); - -#[derive(Debug, Serialize, Deserialize)] -struct PutFileResponse(Cid); - -#[derive(Debug, Serialize, Deserialize)] -struct GetFile(Cid); - -#[derive(Debug, Serialize, Deserialize)] -struct GetFileResponse(Vec); - -#[derive(Debug, Serialize, Deserialize)] -struct ConvertFile; - -#[derive(Debug, Serialize, Deserialize)] -struct ConvertFileUpdate(Vec); - -#[derive(Debug, Serialize, Deserialize)] -struct ConvertFileResponse(Vec); - -macro_rules! request_enum { - // User entry points. - ($enum_name:ident { $variant_name:ident $($tt:tt)* }) => { - request_enum!(@ {[$enum_name] [$variant_name]} $($tt)*); - }; - - // Internal rules to categorize each value - (@ {[$enum_name:ident] [$($agg:ident)*]} $(,)? $variant_name:ident $($tt:tt)*) => { - request_enum!(@ {[$enum_name] [$($agg)* $variant_name]} $($tt)*); - }; - - // Final internal rule that generates the enum from the categorized input - (@ {[$enum_name:ident] [$($n:ident)*]} $(,)?) => { - #[derive(::std::fmt::Debug, ::derive_more::From, ::derive_more::TryInto, ::serde::Serialize, ::serde::Deserialize)] - enum $enum_name { - $($n($n),)* - } - }; -} - -request_enum! { - StoreRequest2 { - Put, - Get, - PutFile, PutFileUpdate, - GetFile, - ConvertFile, ConvertFileUpdate, - } -} - -#[derive(Debug, From, TryInto, Serialize, Deserialize)] -enum StoreRequest { - Put(Put), - - Get(Get), - - PutFile(PutFile), - PutFileUpdate(PutFileUpdate), - - GetFile(GetFile), - - ConvertFile(ConvertFile), - ConvertFileUpdate(ConvertFileUpdate), -} - -#[derive(Debug, From, TryInto, Serialize, Deserialize)] -enum StoreResponse { - PutResponse(PutResponse), - GetResponse(GetResponse), - PutFileResponse(PutFileResponse), - GetFileResponse(GetFileResponse), - ConvertFileResponse(ConvertFileResponse), -} - -#[derive(Debug, Clone)] -struct StoreService; -impl Service for StoreService { - type Req = StoreRequest; - type Res = StoreResponse; -} - -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; -impl Store { - async fn put(self, _put: Put) -> PutResponse { - PutResponse([0; 32]) - } - - async fn get(self, _get: Get) -> GetResponse { - GetResponse(vec![]) - } - - async fn put_file( - self, - _put: PutFile, - updates: impl Stream, - ) -> PutFileResponse { - tokio::pin!(updates); - while let Some(_update) = updates.next().await {} - PutFileResponse([0; 32]) - } - - fn get_file(self, _get: GetFile) -> impl Stream + Send + 'static { - stream! { - for i in 0..3 { - yield GetFileResponse(vec![i]); - } - } - } - - fn convert_file( - self, - _convert: ConvertFile, - updates: impl Stream + Send + 'static, - ) -> impl Stream + Send + 'static { - stream! { - tokio::pin!(updates); - while let Some(msg) = updates.next().await { - yield ConvertFileResponse(msg.0); - } - } - } -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - async fn server_future>( - server: RpcServer, - ) -> result::Result<(), RpcServerError> { - let s = server; - let store = Store; - loop { - let (req, chan) = s.accept().await?.read_first().await?; - use StoreRequest::*; - let store = store.clone(); - #[rustfmt::skip] - match req { - 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) = flume::channel(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:?}"); - - // 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:?}"); - } - - // client streaming call - println!("a client streaming call"); - 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!("{res:?}"); - - // bidi streaming call - println!("a bidi streaming call"); - 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!("{res:?}"); - } - - // dropping the client will cause the server to terminate - drop(client); - server_handle.await??; - Ok(()) -} - -async fn _main_unsugared() -> anyhow::Result<()> { - use transport::Listener; - #[derive(Clone, Debug)] - struct Service; - impl crate::Service for Service { - type Req = u64; - type Res = String; - } - let (server, client) = flume::channel::(1); - let to_string_service = tokio::spawn(async move { - let (mut send, mut recv) = server.accept().await?; - while let Some(item) = recv.next().await { - let item = item?; - println!("server got: {item:?}"); - send.send(item.to_string()).await?; - } - anyhow::Ok(()) - }); - let (mut send, mut recv) = client.open().await?; - let print_result_service = tokio::spawn(async move { - while let Some(item) = recv.next().await { - let item = item?; - println!("got result: {item}"); - } - anyhow::Ok(()) - }); - for i in 0..100 { - send.send(i).await?; - } - drop(send); - to_string_service.await??; - print_result_service.await??; - Ok(()) -} diff --git a/old/quic-rpc-derive/Cargo.toml b/old/quic-rpc-derive/Cargo.toml deleted file mode 100644 index 5154ba5..0000000 --- a/old/quic-rpc-derive/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "quic-rpc-derive" -version = "0.18.1" -edition = "2021" -authors = ["Rüdiger Klaehn "] -keywords = ["api", "protocol", "network", "rpc", "macro"] -categories = ["network-programming"] -license = "Apache-2.0/MIT" -repository = "https://github.com/n0-computer/quic-rpc" -description = "Macros for quic-rpc" - -[lib] -proc-macro = true - -[dependencies] -syn = { version = "1", features = ["full"] } -quote = "1" -proc-macro2 = "1" -quic-rpc = { version = "0.18", path = ".." } - -[dev-dependencies] -derive_more = { version = "1", features = ["from", "try_into", "display"] } -serde = { version = "1", features = ["serde_derive"] } -trybuild = "1.0" diff --git a/old/quic-rpc-derive/src/lib.rs b/old/quic-rpc-derive/src/lib.rs deleted file mode 100644 index e31bb58..0000000 --- a/old/quic-rpc-derive/src/lib.rs +++ /dev/null @@ -1,232 +0,0 @@ -use std::collections::{BTreeMap, HashSet}; - -use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{quote, ToTokens}; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, - spanned::Spanned, - Data, DeriveInput, Fields, Ident, Token, Type, -}; - -const SERVER_STREAMING: &str = "server_streaming"; -const CLIENT_STREAMING: &str = "client_streaming"; -const BIDI_STREAMING: &str = "bidi_streaming"; -const RPC: &str = "rpc"; -const TRY_SERVER_STREAMING: &str = "try_server_streaming"; -const IDENTS: [&str; 5] = [ - SERVER_STREAMING, - CLIENT_STREAMING, - BIDI_STREAMING, - RPC, - TRY_SERVER_STREAMING, -]; - -fn generate_rpc_impls( - pat: &str, - mut args: RpcArgs, - service_name: &Ident, - request_type: &Type, - attr_span: Span, -) -> syn::Result { - let res = match pat { - RPC => { - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::pattern::rpc::RpcMsg<#service_name> for #request_type { - type Response = #response; - } - } - } - SERVER_STREAMING => { - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::server_streaming::ServerStreaming; - } - impl ::quic_rpc::pattern::server_streaming::ServerStreamingMsg<#service_name> for #request_type { - type Response = #response; - } - } - } - BIDI_STREAMING => { - let update = args.get("update", pat, attr_span)?; - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::bidi_streaming::BidiStreaming; - } - impl ::quic_rpc::pattern::bidi_streaming::BidiStreamingMsg<#service_name> for #request_type { - type Update = #update; - type Response = #response; - } - } - } - CLIENT_STREAMING => { - let update = args.get("update", pat, attr_span)?; - let response = args.get("response", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::client_streaming::ClientStreaming; - } - impl ::quic_rpc::pattern::client_streaming::ClientStreamingMsg<#service_name> for #request_type { - type Update = #update; - type Response = #response; - } - } - } - TRY_SERVER_STREAMING => { - let create_error = args.get("create_error", pat, attr_span)?; - let item_error = args.get("item_error", pat, attr_span)?; - let item = args.get("item", pat, attr_span)?; - quote! { - impl ::quic_rpc::message::Msg<#service_name> for #request_type { - type Pattern = ::quic_rpc::pattern::try_server_streaming::TryServerStreaming; - } - impl ::quic_rpc::pattern::try_server_streaming::TryServerStreamingMsg<#service_name> for #request_type { - type CreateError = #create_error; - type ItemError = #item_error; - type Item = #item; - } - } - } - _ => return Err(syn::Error::new(attr_span, "Unknown RPC pattern")), - }; - args.check_empty(attr_span)?; - - Ok(res) -} - -#[proc_macro_attribute] -pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { - let mut input = parse_macro_input!(item as DeriveInput); - let service_name = parse_macro_input!(attr as Ident); - - let input_span = input.span(); - let data_enum = match &mut input.data { - Data::Enum(data_enum) => data_enum, - _ => { - return syn::Error::new(input.span(), "RpcRequests can only be applied to enums") - .to_compile_error() - .into() - } - }; - - let mut additional_items = Vec::new(); - let mut types = HashSet::new(); - - for variant in &mut data_enum.variants { - // Check field structure for every variant - let request_type = match &variant.fields { - Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, - _ => { - return syn::Error::new( - variant.span(), - "Each variant must have exactly one unnamed field", - ) - .to_compile_error() - .into() - } - }; - - if !types.insert(request_type.to_token_stream().to_string()) { - return syn::Error::new(input_span, "Each variant must have a unique request type") - .to_compile_error() - .into(); - } - - // Extract and remove RPC attributes - let mut rpc_attr = Vec::new(); - variant.attrs.retain(|attr| { - for ident in IDENTS { - if attr.path.is_ident(ident) { - rpc_attr.push((ident, attr.clone())); - return false; - } - } - true - }); - - // Fail if there are multiple RPC patterns - if rpc_attr.len() > 1 { - return syn::Error::new(variant.span(), "Each variant can only have one RPC pattern") - .to_compile_error() - .into(); - } - - if let Some((ident, attr)) = rpc_attr.pop() { - let args = match attr.parse_args::() { - Ok(info) => info, - Err(e) => return e.to_compile_error().into(), - }; - - match generate_rpc_impls(ident, args, &service_name, request_type, attr.span()) { - Ok(impls) => additional_items.extend(impls), - Err(e) => return e.to_compile_error().into(), - } - } - } - - let output = quote! { - #input - - #(#additional_items)* - }; - - output.into() -} - -struct RpcArgs { - types: BTreeMap, -} - -impl RpcArgs { - /// Get and remove a type from the map, failing if it doesn't exist - fn get(&mut self, key: &str, kind: &str, span: Span) -> syn::Result { - self.types - .remove(key) - .ok_or_else(|| syn::Error::new(span, format!("{kind} requires a {key} type"))) - } - - /// Fail if there are any unknown arguments remaining - fn check_empty(&self, span: Span) -> syn::Result<()> { - if self.types.is_empty() { - Ok(()) - } else { - Err(syn::Error::new( - span, - format!( - "Unknown arguments provided: {:?}", - self.types.keys().collect::>() - ), - )) - } - } -} - -/// Parse the rpc args as a comma separated list of name=type pairs -impl Parse for RpcArgs { - fn parse(input: ParseStream) -> syn::Result { - let mut types = BTreeMap::new(); - - loop { - if input.is_empty() { - break; - } - - let key: Ident = input.parse()?; - let _: Token![=] = input.parse()?; - let value: Type = input.parse()?; - - types.insert(key.to_string(), value); - - if !input.peek(Token![,]) { - break; - } - let _: Token![,] = input.parse()?; - } - - Ok(RpcArgs { types }) - } -} \ No newline at end of file diff --git a/old/quic-rpc-derive/tests/compile_fail/duplicate_type.rs b/old/quic-rpc-derive/tests/compile_fail/duplicate_type.rs deleted file mode 100644 index 45db393..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/duplicate_type.rs +++ /dev/null @@ -1,9 +0,0 @@ -use quic_rpc_derive::rpc_requests; - -#[rpc_requests(Service)] -enum Enum { - A(u8), - B(u8), -} - -fn main() {} \ No newline at end of file diff --git a/old/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr b/old/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr deleted file mode 100644 index 71c5e95..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/duplicate_type.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Each variant must have a unique request type - --> tests/compile_fail/duplicate_type.rs:4:1 - | -4 | enum Enum { - | ^^^^ diff --git a/old/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs b/old/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs deleted file mode 100644 index 7ca34a9..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/extra_attr_types.rs +++ /dev/null @@ -1,9 +0,0 @@ -use quic_rpc_derive::rpc_requests; - -#[rpc_requests(Service)] -enum Enum { - #[rpc(response = Bla, fnord = Foo)] - A(u8), -} - -fn main() {} \ No newline at end of file diff --git a/old/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr b/old/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr deleted file mode 100644 index 2de36f7..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/extra_attr_types.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Unknown arguments provided: ["fnord"] - --> tests/compile_fail/extra_attr_types.rs:5:5 - | -5 | #[rpc(response = Bla, fnord = Foo)] - | ^ diff --git a/old/quic-rpc-derive/tests/compile_fail/multiple_fields.rs b/old/quic-rpc-derive/tests/compile_fail/multiple_fields.rs deleted file mode 100644 index b4ec1eb..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/multiple_fields.rs +++ /dev/null @@ -1,8 +0,0 @@ -use quic_rpc_derive::rpc_requests; - -#[rpc_requests(Service)] -enum Enum { - A(u8, u8), -} - -fn main() {} \ No newline at end of file diff --git a/old/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr b/old/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr deleted file mode 100644 index 80fc879..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/multiple_fields.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Each variant must have exactly one unnamed field - --> tests/compile_fail/multiple_fields.rs:5:5 - | -5 | A(u8, u8), - | ^ diff --git a/old/quic-rpc-derive/tests/compile_fail/named_enum.rs b/old/quic-rpc-derive/tests/compile_fail/named_enum.rs deleted file mode 100644 index 4bd442c..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/named_enum.rs +++ /dev/null @@ -1,8 +0,0 @@ -use quic_rpc_derive::rpc_requests; - -#[rpc_requests(Service)] -enum Enum { - A { name: u8 }, -} - -fn main() {} \ No newline at end of file diff --git a/old/quic-rpc-derive/tests/compile_fail/named_enum.stderr b/old/quic-rpc-derive/tests/compile_fail/named_enum.stderr deleted file mode 100644 index f13da1d..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/named_enum.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Each variant must have exactly one unnamed field - --> tests/compile_fail/named_enum.rs:5:5 - | -5 | A { name: u8 }, - | ^ diff --git a/old/quic-rpc-derive/tests/compile_fail/non_enum.rs b/old/quic-rpc-derive/tests/compile_fail/non_enum.rs deleted file mode 100644 index e80782d..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/non_enum.rs +++ /dev/null @@ -1,6 +0,0 @@ -use quic_rpc_derive::rpc_requests; - -#[rpc_requests(Service)] -struct Foo; - -fn main() {} \ No newline at end of file diff --git a/old/quic-rpc-derive/tests/compile_fail/non_enum.stderr b/old/quic-rpc-derive/tests/compile_fail/non_enum.stderr deleted file mode 100644 index c0286ef..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/non_enum.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: RpcRequests can only be applied to enums - --> tests/compile_fail/non_enum.rs:4:1 - | -4 | struct Foo; - | ^^^^^^ diff --git a/old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs b/old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs deleted file mode 100644 index 2daca8d..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.rs +++ /dev/null @@ -1,9 +0,0 @@ -use quic_rpc_derive::rpc_requests; - -#[rpc_requests(Service)] -enum Enum { - #[rpc(fnord = Bla)] - A(u8), -} - -fn main() {} \ No newline at end of file diff --git a/old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr b/old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr deleted file mode 100644 index 4c81995..0000000 --- a/old/quic-rpc-derive/tests/compile_fail/wrong_attr_types.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: rpc requires a response type - --> tests/compile_fail/wrong_attr_types.rs:5:5 - | -5 | #[rpc(fnord = Bla)] - | ^ diff --git a/old/quic-rpc-derive/tests/smoke.rs b/old/quic-rpc-derive/tests/smoke.rs deleted file mode 100644 index b6fe521..0000000 --- a/old/quic-rpc-derive/tests/smoke.rs +++ /dev/null @@ -1,79 +0,0 @@ -use quic_rpc_derive::rpc_requests; -use serde::{Deserialize, Serialize}; - -#[test] -fn simple() { - #[derive(Debug, Serialize, Deserialize)] - struct RpcRequest; - - #[derive(Debug, Serialize, Deserialize)] - struct ServerStreamingRequest; - - #[derive(Debug, Serialize, Deserialize)] - struct ClientStreamingRequest; - - #[derive(Debug, Serialize, Deserialize)] - struct BidiStreamingRequest; - - #[derive(Debug, Serialize, Deserialize)] - struct Update1; - - #[derive(Debug, Serialize, Deserialize)] - struct Update2; - - #[derive(Debug, Serialize, Deserialize)] - struct Response1; - - #[derive(Debug, Serialize, Deserialize)] - struct Response2; - - #[derive(Debug, Serialize, Deserialize)] - struct Response3; - - #[derive(Debug, Serialize, Deserialize)] - struct Response4; - - #[rpc_requests(Service)] - #[derive(Debug, Serialize, Deserialize, derive_more::From, derive_more::TryInto)] - enum Request { - #[rpc(response=Response1)] - Rpc(RpcRequest), - #[server_streaming(response=Response2)] - ServerStreaming(ServerStreamingRequest), - #[bidi_streaming(update= Update1, response = Response3)] - BidiStreaming(BidiStreamingRequest), - #[client_streaming(update = Update2, response = Response4)] - ClientStreaming(ClientStreamingRequest), - Update1(Update1), - Update2(Update2), - } - - #[derive(Debug, Serialize, Deserialize, derive_more::From, derive_more::TryInto)] - enum Response { - Response1(Response1), - Response2(Response2), - Response3(Response3), - Response4(Response4), - } - - #[derive(Debug, Clone)] - struct Service; - - impl quic_rpc::Service for Service { - type Req = Request; - type Res = Response; - } - - let _ = Service; -} - -/// Use -/// -/// TRYBUILD=overwrite cargo test --test smoke -/// -/// to update the snapshots -#[test] -fn compile_fail() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/compile_fail/*.rs"); -} diff --git a/old/src/client.rs b/old/src/client.rs deleted file mode 100644 index 203eb05..0000000 --- a/old/src/client.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! Client side api -//! -//! The main entry point is [RpcClient]. -use std::{ - fmt::Debug, - marker::PhantomData, - pin::Pin, - task::{Context, Poll}, -}; - -use futures_lite::Stream; -use futures_sink::Sink; -use pin_project::pin_project; - -use crate::{ - transport::{boxed::BoxableConnector, mapped::MappedConnector, StreamTypes}, - Connector, Service, -}; - -/// A boxed connector for the given [`Service`] -pub type BoxedConnector = - crate::transport::boxed::BoxedConnector<::Res, ::Req>; - -#[cfg(feature = "flume-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "flume-transport")))] -/// A flume connector for the given [`Service`] -pub type FlumeConnector = - crate::transport::flume::FlumeConnector<::Res, ::Req>; - -#[cfg(feature = "quinn-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "quinn-transport")))] -/// A quinn connector for the given [`Service`] -pub type QuinnConnector = - crate::transport::quinn::QuinnConnector<::Res, ::Req>; - -#[cfg(feature = "hyper-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "hyper-transport")))] -/// A hyper connector for the given [`Service`] -pub type HyperConnector = - crate::transport::hyper::HyperConnector<::Res, ::Req>; - -#[cfg(feature = "iroh-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "iroh-transport")))] -/// An iroh connector for the given [`Service`] -pub type IrohConnector = - crate::transport::iroh::IrohConnector<::Res, ::Req>; - -/// Sync version of `future::stream::BoxStream`. -pub type BoxStreamSync<'a, T> = Pin + Send + Sync + 'a>>; - -/// A client for a specific service -/// -/// This is a wrapper around a [`Connector`] that serves as the entry point -/// for the client DSL. -/// -/// Type parameters: -/// -/// `S` is the service type that determines what interactions this client supports. -/// `C` is the connector that determines the transport. -#[derive(Debug)] -pub struct RpcClient> { - pub(crate) source: C, - pub(crate) _p: PhantomData, -} - -impl Clone for RpcClient { - fn clone(&self) -> Self { - Self { - 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, [crate::message::ClientStreaming] and [crate::message::BidiStreaming]. -#[pin_project] -#[derive(Debug)] -pub struct UpdateSink(#[pin] pub C::SendSink, PhantomData) -where - C: StreamTypes; - -impl UpdateSink -where - C: StreamTypes, - T: Into, -{ - /// Create a new update sink - pub fn new(sink: C::SendSink) -> Self { - Self(sink, PhantomData) - } -} - -impl Sink for UpdateSink -where - C: StreamTypes, - T: Into, -{ - type Error = C::SendError; - - fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_ready(cx) - } - - fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - let req = item.into(); - self.project().0.start_send(req) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_flush(cx) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_close(cx) - } -} - -impl RpcClient -where - S: Service, - C: Connector, -{ - /// Create a new rpc client for a specific [Service] given a compatible - /// [Connector]. - /// - /// This is where a generic typed connection is converted into a client for a specific service. - /// - /// You can get a client for a nested service by calling [map](RpcClient::map). - pub fn new(source: C) -> Self { - Self { - source, - _p: PhantomData, - } - } -} - -impl RpcClient -where - S: Service, - C: Connector, -{ - /// Get the underlying connection - pub fn into_inner(self) -> C { - self.source - } - - /// Map this channel's service into an inner service. - /// - /// This method is available if the required bounds are upheld: - /// SNext::Req: Into + TryFrom, - /// SNext::Res: Into + TryFrom, - /// - /// Where SNext is the new service to map to and S is the current inner service. - /// - /// This method can be chained infintely. - pub fn map(self) -> RpcClient> - where - SNext: Service, - S::Req: From, - SNext::Res: TryFrom, - { - RpcClient::new(self.source.map::()) - } - - /// box - pub fn boxed(self) -> RpcClient> - where - C: BoxableConnector, - { - RpcClient::new(self.source.boxed()) - } -} - -impl AsRef for RpcClient -where - S: Service, - C: Connector, -{ - fn as_ref(&self) -> &C { - &self.source - } -} - -/// Wrap a stream with an additional item that is kept alive until the stream is dropped -#[pin_project] -pub(crate) struct DeferDrop(#[pin] pub S, pub X); - -impl Stream for DeferDrop { - type Item = S::Item; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_next(cx) - } -} diff --git a/old/src/lib.rs b/old/src/lib.rs deleted file mode 100644 index 074a573..0000000 --- a/old/src/lib.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! 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 -//! -//! See the [README](https://github.com/n0-computer/quic-rpc/blob/main/README.md) -//! -//! # Example -//! ``` -//! # async fn example() -> anyhow::Result<()> { -//! use derive_more::{From, TryInto}; -//! use quic_rpc::{message::RpcMsg, RpcClient, RpcServer, Service}; -//! use serde::{Deserialize, Serialize}; -//! -//! // Define your messages -//! #[derive(Debug, Serialize, Deserialize)] -//! struct Ping; -//! -//! #[derive(Debug, Serialize, Deserialize)] -//! struct Pong; -//! -//! // Define your RPC service and its request/response types -//! #[derive(Debug, Clone)] -//! struct PingService; -//! -//! #[derive(Debug, Serialize, Deserialize, From, TryInto)] -//! enum PingRequest { -//! Ping(Ping), -//! } -//! -//! #[derive(Debug, Serialize, Deserialize, From, TryInto)] -//! enum PingResponse { -//! Pong(Pong), -//! } -//! -//! impl Service for PingService { -//! type Req = PingRequest; -//! type Res = PingResponse; -//! } -//! -//! // Define interaction patterns for each request type -//! impl RpcMsg for Ping { -//! type Response = Pong; -//! } -//! -//! // create a transport channel, here a memory channel for testing -//! let (server, client) = quic_rpc::transport::flume::channel(1); -//! -//! // client side -//! // create the rpc client given the channel and the service type -//! 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?.read_first().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(()) -//! # } -//! ``` -//! -//! # Features -#![doc = document_features::document_features!()] -#![deny(missing_docs)] -#![deny(rustdoc::broken_intra_doc_links)] -#![cfg_attr(quicrpc_docsrs, feature(doc_cfg))] -use std::fmt::{Debug, Display}; - -use serde::{de::DeserializeOwned, Serialize}; -pub mod client; -pub mod message; -pub mod server; -pub mod transport; -pub use client::RpcClient; -pub use server::RpcServer; -#[cfg(feature = "macros")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "macros")))] -mod macros; - -pub mod pattern; - -/// Requirements for a RPC message -/// -/// Even when just using the mem transport, we require messages to be Serializable and Deserializable. -/// Likewise, even when using the quinn transport, we require messages to be Send. -/// -/// This does not seem like a big restriction. If you want a pure memory channel without the possibility -/// to also use the quinn transport, you might want to use a mpsc channel directly. -pub trait RpcMessage: Debug + Serialize + DeserializeOwned + Send + Sync + Unpin + 'static {} - -impl RpcMessage for T where - T: Debug + Serialize + DeserializeOwned + Send + Sync + Unpin + 'static -{ -} - -/// Requirements for an internal error -/// -/// 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. -/// -/// Instead we require them to implement `Into`, which is available -/// both for any type that implements [std::error::Error] and anyhow itself. -pub trait RpcError: Debug + Display + Into + Send + Sync + Unpin + 'static {} - -impl RpcError for T where T: Debug + Display + Into + 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; - /// Type of response messages - type Res: RpcMessage; -} - -/// A connector to a specific service -/// -/// This is just a trait alias for a [`transport::Connector`] with the right types. It is used -/// to make it easier to specify the bounds of a connector that matches a specific -/// service. -pub trait Connector: transport::Connector {} - -impl, S: Service> Connector for T {} - -/// A listener for a specific service -/// -/// This is just a trait alias for a [`transport::Listener`] with the right types. It is used -/// to make it easier to specify the bounds of a listener that matches a specific -/// service. -pub trait Listener: transport::Listener {} - -impl, S: Service> Listener for T {} - -#[cfg(feature = "flume-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "flume-transport")))] -/// Create a pair of [`RpcServer`] and [`RpcClient`] for the given [`Service`] type using a flume channel -pub fn flume_channel( - size: usize, -) -> ( - RpcServer>, - RpcClient>, -) { - let (listener, connector) = transport::flume::channel(size); - (RpcServer::new(listener), RpcClient::new(connector)) -} - -#[cfg(feature = "test-utils")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "test-utils")))] -/// Create a pair of [`RpcServer`] and [`RpcClient`] for the given [`Service`] type using a quinn channel -/// -/// This is using a network connection using the local network. It is useful for testing remote services -/// in a more realistic way than the memory transport. -#[allow(clippy::type_complexity)] -pub fn quinn_channel() -> anyhow::Result<( - RpcServer>, - RpcClient>, -)> { - let bind_addr: std::net::SocketAddr = ([0, 0, 0, 0], 0).into(); - let (server_endpoint, cert_der) = transport::quinn::make_server_endpoint(bind_addr)?; - let addr = server_endpoint.local_addr()?; - let server = server::QuinnListener::::new(server_endpoint)?; - let server = RpcServer::new(server); - let client_endpoint = transport::quinn::make_client_endpoint(bind_addr, &[&cert_der])?; - let client = client::QuinnConnector::::new(client_endpoint, addr, "localhost".into()); - let client = RpcClient::new(client); - Ok((server, client)) -} diff --git a/old/src/macros.rs b/old/src/macros.rs deleted file mode 100644 index 2f2ba6c..0000000 --- a/old/src/macros.rs +++ /dev/null @@ -1,508 +0,0 @@ -//! Macros to reduce boilerplate for RPC implementations. - -/// Derive a set of RPC types and message implementation from a declaration. -/// -/// The macros are completely optional. They generate the request and response -/// message enums and the service zerosized struct. -/// Optionally, a function can be created to dispatch RPC calls to methods -/// on a struct of your choice. -/// It can also create a type-safe RPC client for the service. -/// -/// Usage is as follows: -/// -/// ```no_run -/// # use serde::{Serialize,Deserialize}; -/// # use quic_rpc::*; -/// -/// // Define your message types -/// -/// #[derive(Debug, Serialize, Deserialize)] -/// struct Add(pub i32, pub i32); -/// #[derive(Debug, Serialize, Deserialize)] -/// pub struct Sum(pub i32); -/// #[derive(Debug, Serialize, Deserialize)] -/// pub struct Multiply(pub i32); -/// #[derive(Debug, Serialize, Deserialize)] -/// pub struct MultiplyUpdate(pub i32); -/// #[derive(Debug, Serialize, Deserialize)] -/// pub struct MultiplyOutput(pub i32); -/// -/// // Derive the RPC types. -/// -/// rpc_service! { -/// // Name of the created request enum. -/// Request = MyRequest; -/// // Name of the created response enum. -/// Response = MyResponse; -/// // Name of the created service struct enum. -/// Service = MyService; -/// // Name of the macro to create a dispatch function. -/// // Optional, if not needed pass _ (underscore) as name. -/// CreateDispatch = create_my_dispatch; -/// // Name of the macro to create an RPC client. -/// -/// Rpc add = Add, _ -> Sum; -/// BidiStreaming multiply = Multiply, MultiplyUpdate -> MultiplyOutput -/// } -/// ``` -/// -/// This will generate a request enum `MyRequest`, a response enum `MyRespone` -/// and a service declaration `MyService`. -/// -/// It will also generate two macros to create an RPC client and a dispatch function. -/// -/// To use the client, invoke the macro with a name. The macro will generate a struct that -/// takes a client channel and exposes typesafe methods for each RPC method. -/// -/// ```ignore -/// create_store_client!(MyClient); -/// let client = quic_rpc::quinn::Channel::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) -/// let (send, mut recv) = client.multiply(Multiply(2)); -/// send(Update(3)); -/// let res = recv.next().await?; -/// // Some(MultiplyOutput(6)) -/// ``` -/// -/// To use the dispatch function, invoke the macro with a struct that implements your RPC -/// methods and the name of the generated function. You can then use this dispatch function -/// to dispatch the RPC calls to the methods on your target struct. -/// -/// ```ignore -/// #[derive(Clone)] -/// pub struct Calculator; -/// impl Calculator { -/// async fn add(self, req: Add) -> Sum { -/// Sum(req.0 + req.1) -/// } -/// async fn multiply( -/// self, -/// req: Multiply, -/// updates: impl Stream -/// ) -> impl Stream { -/// stream! { -/// tokio::pin!(updates); -/// while let Some(MultiplyUpdate(n)) = updates.next().await { -/// yield MultiplyResponse(req.0 * n); -/// } -/// } -/// } -/// } -/// -/// create_my_dispatch!(Calculator, dispatch_calculator_request); -/// -/// #[tokio::main] -/// async fn main() -> anyhow::Result<()> { -/// let server_addr: std::net::SocketAddr = "127.0.0.1:12345".parse()?; -/// let (server, _server_certs) = make_server_endpoint(server_addr)?; -/// let accept = server.accept().await.context("accept failed")?.await?; -/// let connection = quic_rpc::quinn::Channel::new(accept); -/// let calculator = Calculator; -/// let server_handle = spawn_server( -/// StoreService, -/// quic_rpc::quinn::QuinnChannelTypes, -/// connection, -/// calculator, -/// dispatch_calculator_request, -/// ); -/// server_handle.await??; -/// Ok(()) -/// } -/// ``` -/// -/// The generation of the macros in `CreateDispatch` and `CreateClient` -/// is optional. If you don't need them, pass `_` instead: -/// -/// ```ignore -/// # use quic_rpc::*; -/// rpc_service! { -/// Request = MyRequest; -/// Response = MyResponse; -/// Service = MyService; -/// CreateDispatch = _; -/// CreateClient = _; -/// -/// Rpc add = Add, _ -> Sum; -/// ClientStreaming stream = Input, Update -> Output; -/// } -/// ``` -/// ` -#[macro_export] -macro_rules! rpc_service { - ( - Request = $request:ident; - Response = $response:ident; - Service = $service:ident; - CreateDispatch = $create_dispatch:tt; - - $($m_pattern:ident $m_name:ident = $m_input:ident, $m_update:tt -> $m_output:ident);+$(;)? - ) => { - - $crate::__request_enum! { - $service, - $request { - $($m_input,)* - $($m_update,)* - } - } - - #[doc=concat!("Response messages for ", stringify!($service))] - #[allow(clippy::enum_variant_names)] - #[derive(::std::fmt::Debug, ::derive_more::From, ::derive_more::TryInto, ::serde::Serialize, ::serde::Deserialize)] - pub enum $response { - $($m_output($m_output),)* - } - - $( - $crate::__rpc_message!($service, $m_pattern, $m_input, $m_update, $m_output); - )* - - #[doc=concat!("RPC service ", stringify!($service))] - #[derive(::std::clone::Clone, ::std::fmt::Debug)] - pub struct $service; - - impl $crate::Service for $service { - type Req = $request; - type Res = $response; - } - - $crate::__derive_create_dispatch!( - $service, - $request, - $create_dispatch, - [ $($m_pattern $m_name = $m_input, $m_update -> $m_output);+ ] - ); - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __derive_create_dispatch { - ( - $service:ident, - $request:ident, - _, - [ $($tt:tt)* ] - ) => {}; - ( - $service:ident, - $request:ident, - $create_dispatch:ident, - [ $($m_pattern:ident $m_name:ident = $m_input:ident, $m_update:tt -> $m_output:ident);+ ] - ) => { - #[doc = concat!("Create an RPC request dispatch function for ", stringify!($service), "\n\nSee the docs for [quic_rpc::rpc_service] for usage docs.")] - #[macro_export] - macro_rules! $create_dispatch { - ($target:ident, $handler:ident) => { - pub async fn $handler>( - mut chan: $crate::server::RpcChannel<$service, C>, - msg: <$service as $crate::Service>::Req, - target: $target, - ) -> Result<(), $crate::server::RpcServerError> { - let res = match msg { - $( - $request::$m_input(msg) => { $crate::__rpc_invoke!($m_pattern, $m_name, $target, msg, chan, target) }, - )* - _ => Err($crate::server::RpcServerError::::UnexpectedStartMessage), - }; - res?; - Ok(()) - } - } - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __request_enum { - // User entry points. - ($service:ident, $enum_name:ident { $variant_name:ident $($tt:tt)* }) => { - $crate::__request_enum!(@ {[$service $enum_name] [$variant_name]} $($tt)*); - }; - - // Internal rules to categorize each value - // This also filters out _ placeholders from non-streaming methods. - (@ {[$service:ident $enum_name:ident] [$($agg:ident)*]} $(,)? $(_$(,)?)* $variant_name:ident $($tt:tt)*) => { - $crate::__request_enum!(@ {[$service $enum_name] [$($agg)* $variant_name]} $($tt)*); - }; - - // Internal rules to categorize each value - (@ {[$service:ident $enum_name:ident] [$($agg:ident)*]} $(,)? $variant_name:ident $($tt:tt)*) => { - $crate::__request_enum!(@ {[$service $enum_name] [$($agg)* $variant_name]} $($tt)*); - }; - - // Final internal rule that generates the enum from the categorized input - (@ {[$service:ident $enum_name:ident] [$($n:ident)*]} $(,)? $(_$(,)?)*) => { - #[doc=concat!("Request messages for ", stringify!($service))] - #[derive(::std::fmt::Debug, ::derive_more::From, ::derive_more::TryInto, ::serde::Serialize, ::serde::Deserialize)] - pub enum $enum_name { - $($n($n),)* - } - }; -} - -/// 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 { - ($service:ident, Rpc, $m_input:ident, _, $m_output:ident) => { - impl $crate::message::RpcMsg<$service> for $m_input { - type Response = $m_output; - } - }; - ($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_update; - } - }; - ($service:ident, BidiStreaming, $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 Response = $m_output; - type Update = $m_update; - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __rpc_invoke { - (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, $msg:ident, $chan:ident, $target:ident) => { - $chan - .client_streaming($msg, $target, $target_ty::$m_name) - .await - }; - (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, $msg:ident, $chan:ident, $target:ident) => { - $chan - .bidi_streaming($msg, $target, $target_ty::$m_name) - .await - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __derive_create_client{ - ( - $service:ident, - _, - [ $($tt:tt)* ] - ) => {}; - ( - $service:ident, - $create_client:tt, - [ $($m_pattern:ident $m_name:ident = $m_input:ident, $m_update:tt -> $m_output:ident);+ ] - ) => { - #[doc = concat!("Create an RPC client for ", stringify!($service), "\n\nSee the docs for [quic_rpc::rpc_service] for usage docs.")] - #[macro_export] - macro_rules! $create_client { - ($struct:ident) => { - #[derive(::std::clone::Clone, ::std::fmt::Debug)] - pub struct $struct>(pub $crate::client::RpcClient<$service, C>); - - impl> $struct { - $( - $crate::__rpc_method!($m_pattern, $service, $m_name, $m_input, $m_output, $m_update); - )* - } - }; - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __rpc_method { - (Rpc, $service:ident, $m_name:ident, $m_input:ident, $m_output:ident, _) => { - pub async fn $m_name( - &mut self, - input: $m_input, - ) -> ::std::result::Result<$m_output, $crate::client::RpcClientError> { - self.0.rpc(input).await - } - }; - (ClientStreaming, $service:ident, $m_name:ident, $m_input:ident, $m_output:ident, $m_update:ident) => { - pub async fn $m_name( - &mut self, - input: $m_input, - ) -> ::std::result::Result< - ( - $crate::client::UpdateSink<$service, C, $m_input>, - ::futures::future::BoxFuture< - 'static, - ::std::result::Result<$m_output, $crate::client::ClientStreamingItemError>, - >, - ), - $crate::client::ClientStreamingError, - > { - self.0.client_streaming(input).await - } - }; - (ServerStreaming, $service:ident, $m_name:ident, $m_input:ident, $m_output:ident, _) => { - pub async fn $m_name( - &mut self, - input: $m_input, - ) -> ::std::result::Result< - ::futures::stream::BoxStream< - 'static, - ::std::result::Result<$m_output, $crate::client::StreamingResponseItemError>, - >, - $crate::client::StreamingResponseError, - > { - self.0.server_streaming(input).await - } - }; - (BidiStreaming, $service:ident, $m_name:ident, $m_input:ident, $m_output:ident, $m_update:ident) => { - pub async fn $m_name( - &mut self, - input: $m_input, - ) -> ::std::result::Result< - ( - $crate::client::UpdateSink<$service, C, $m_input>, - ::futures::stream::BoxStream< - 'static, - ::std::result::Result<$m_output, $crate::client::BidiItemError>, - >, - ), - $crate::client::BidiError, - > { - self.0.bidi(input).await - } - }; -} diff --git a/old/src/message.rs b/old/src/message.rs deleted file mode 100644 index 688303f..0000000 --- a/old/src/message.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Service definition -//! -//! Traits to define the behaviour of messages for services -use std::fmt::Debug; - -pub use crate::pattern::{ - bidi_streaming::{BidiStreaming, BidiStreamingMsg}, - client_streaming::{ClientStreaming, ClientStreamingMsg}, - rpc::{Rpc, RpcMsg}, - server_streaming::{ServerStreaming, ServerStreamingMsg}, -}; -use crate::Service; - -/// 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; -} - -/// 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 -/// -/// You could define your own interaction patterns such as OneWay. -pub trait InteractionPattern: Debug + Clone + Send + Sync + 'static {} diff --git a/old/src/pattern/bidi_streaming.rs b/old/src/pattern/bidi_streaming.rs deleted file mode 100644 index 6ea94d5..0000000 --- a/old/src/pattern/bidi_streaming.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! Bidirectional stream interaction pattern. - -use std::{ - error, - fmt::{self, Debug}, - result, -}; - -use futures_lite::{Stream, StreamExt}; -use futures_util::{FutureExt, SinkExt}; - -use crate::{ - client::{BoxStreamSync, UpdateSink}, - message::{InteractionPattern, Msg}, - server::{race2, RpcChannel, RpcServerError, UpdateStream}, - transport::{ConnectionErrors, Connector, StreamTypes}, - RpcClient, Service, -}; - -/// 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 {} - -/// 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; - - /// 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; -} - -/// Server error when accepting a bidi request -#[derive(Debug)] -pub enum Error { - /// 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 Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for Error {} - -/// Server error when receiving an item for a bidi request -#[derive(Debug)] -pub enum ItemError { - /// Unable to receive the response from the server - RecvError(C::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for ItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for ItemError {} - -impl RpcClient -where - S: Service, - C: Connector, -{ - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn bidi( - &self, - msg: M, - ) -> result::Result< - ( - UpdateSink, - BoxStreamSync<'static, result::Result>>, - ), - Error, - > - where - M: BidiStreamingMsg, - { - let msg = msg.into(); - let (mut send, recv) = self.source.open().await.map_err(Error::Open)?; - send.send(msg).await.map_err(Error::::Send)?; - let send = UpdateSink::new(send); - let recv = Box::pin(recv.map(move |x| match x { - Ok(msg) => M::Response::try_from(msg).map_err(|_| ItemError::DowncastError), - Err(e) => Err(ItemError::RecvError(e)), - })); - Ok((send, recv)) - } -} - -impl RpcChannel -where - C: StreamTypes, - S: Service, -{ - /// handle the message 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 bidi_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: BidiStreamingMsg, - F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, - Str: Stream + Send + 'static, - T: Send + 'static, - { - let Self { mut send, recv, .. } = self; - // downcast the updates - let (updates, read_error) = UpdateStream::new(recv); - // get the response - let responses = f(target, req, updates); - race2(read_error.map(Err), async move { - tokio::pin!(responses); - while let Some(response) = responses.next().await { - // turn into a S::Res so we can send it - let response = response.into(); - // send it and return the error if any - send.send(response) - .await - .map_err(RpcServerError::SendError)?; - } - Ok(()) - }) - .await - } -} diff --git a/old/src/pattern/client_streaming.rs b/old/src/pattern/client_streaming.rs deleted file mode 100644 index 055aaab..0000000 --- a/old/src/pattern/client_streaming.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! Client streaming interaction pattern. - -use std::{ - error, - fmt::{self, Debug}, - result, -}; - -use futures_lite::{future::Boxed, Future, StreamExt}; -use futures_util::{FutureExt, SinkExt, TryFutureExt}; - -use crate::{ - client::UpdateSink, - message::{InteractionPattern, Msg}, - server::{race2, RpcChannel, RpcServerError, UpdateStream}, - transport::{ConnectionErrors, StreamTypes}, - Connector, RpcClient, Service, -}; - -/// 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 {} - -/// 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 - /// the message type itself. Any update for such a request will result in an error. - 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; -} - -/// Server error when accepting a client streaming request -#[derive(Debug)] -pub enum Error { - /// 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 Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for Error {} - -/// Server error when receiving an item for a client streaming request -#[derive(Debug)] -pub enum ItemError { - /// Connection was closed before receiving the first message - EarlyClose, - /// Unable to receive the response from the server - RecvError(C::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for ItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for ItemError {} - -impl RpcClient -where - S: Service, - C: Connector, -{ - /// Call to the server that allows the client to stream, single response - pub async fn client_streaming( - &self, - msg: M, - ) -> result::Result< - ( - UpdateSink, - Boxed>>, - ), - Error, - > - where - M: ClientStreamingMsg, - { - let msg = msg.into(); - let (mut send, mut recv) = self.source.open().await.map_err(Error::Open)?; - send.send(msg).map_err(Error::Send).await?; - let send = UpdateSink::::new(send); - let recv = async move { - let item = recv.next().await.ok_or(ItemError::EarlyClose)?; - - match item { - Ok(msg) => M::Response::try_from(msg).map_err(|_| ItemError::DowncastError), - Err(e) => Err(ItemError::RecvError(e)), - } - } - .boxed(); - Ok((send, recv)) - } -} - -impl RpcChannel -where - S: Service, - C: StreamTypes, -{ - /// handle the message 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 client_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: ClientStreamingMsg, - F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, - Fut: Future + Send + 'static, - T: Send + 'static, - { - let Self { mut send, recv, .. } = self; - let (updates, read_error) = UpdateStream::new(recv); - race2(read_error.map(Err), async move { - // get the response - let res = f(target, req, updates).await; - // turn into a S::Res so we can send it - let res = res.into(); - // send it and return the error if any - send.send(res).await.map_err(RpcServerError::SendError) - }) - .await - } -} diff --git a/old/src/pattern/mod.rs b/old/src/pattern/mod.rs deleted file mode 100644 index da2b879..0000000 --- a/old/src/pattern/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Predefined interaction patterns. -//! -//! An interaction pattern can be as simple as an rpc call or something more -//! complex such as bidirectional streaming. -//! -//! Each pattern defines different associated message types for the interaction. -pub mod bidi_streaming; -pub mod client_streaming; -pub mod rpc; -pub mod server_streaming; -pub mod try_server_streaming; diff --git a/old/src/pattern/rpc.rs b/old/src/pattern/rpc.rs deleted file mode 100644 index 044069c..0000000 --- a/old/src/pattern/rpc.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! RPC interaction pattern. - -use std::{ - error, - fmt::{self, Debug}, - result, -}; - -use futures_lite::{Future, StreamExt}; -use futures_util::{FutureExt, SinkExt}; - -use crate::{ - message::{InteractionPattern, Msg}, - server::{race2, RpcChannel, RpcServerError}, - transport::{ConnectionErrors, StreamTypes}, - Connector, RpcClient, Service, -}; - -/// Rpc interaction pattern -/// -/// There is only one request and one response. -#[derive(Debug, Clone, Copy)] -pub struct Rpc; -impl InteractionPattern for Rpc {} - -/// 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; -} -/// Client error. All client DSL methods return a `Result` with this error type. -#[derive(Debug)] -pub enum Error { - /// 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 - EarlyClose, - /// Unable to receive the response from the server - RecvError(C::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for Error {} - -impl RpcClient -where - S: Service, - C: Connector, -{ - /// RPC call to the server, single request, single response - pub async fn rpc(&self, msg: M) -> result::Result> - where - M: RpcMsg, - { - let msg = msg.into(); - let (mut send, mut recv) = self.source.open().await.map_err(Error::Open)?; - send.send(msg).await.map_err(Error::::Send)?; - let res = recv - .next() - .await - .ok_or(Error::::EarlyClose)? - .map_err(Error::::RecvError)?; - // keep send alive until we have the answer - drop(send); - M::Response::try_from(res).map_err(|_| Error::DowncastError) - } -} - -impl RpcChannel -where - S: Service, - C: StreamTypes, -{ - /// 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, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: RpcMsg, - F: FnOnce(T, M) -> Fut, - Fut: Future, - T: Send + 'static, - { - let Self { - mut send, mut recv, .. - } = self; - // cancel if we get an update, no matter what it is - let cancel = recv - .next() - .map(|_| RpcServerError::UnexpectedUpdateMessage::); - // race the computation and the cancellation - race2(cancel.map(Err), async move { - // get the response - let res = f(target, req).await; - // turn into a S::Res so we can send it - let res = res.into(); - // send it and return the error if any - send.send(res).await.map_err(RpcServerError::SendError) - }) - .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 - } -} diff --git a/old/src/pattern/server_streaming.rs b/old/src/pattern/server_streaming.rs deleted file mode 100644 index 26d2846..0000000 --- a/old/src/pattern/server_streaming.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! Server streaming interaction pattern. - -use std::{ - error, - fmt::{self, Debug}, - result, -}; - -use futures_lite::{Stream, StreamExt}; -use futures_util::{FutureExt, SinkExt, TryFutureExt}; - -use crate::{ - client::{BoxStreamSync, DeferDrop}, - message::{InteractionPattern, Msg}, - server::{race2, RpcChannel, RpcServerError}, - transport::{ConnectionErrors, Connector, StreamTypes}, - RpcClient, Service, -}; - -/// 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 {} - -/// Defines response type for a server streaming message. -pub trait ServerStreamingMsg: 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; -} - -/// Server error when accepting a server streaming request -#[derive(Debug)] -pub enum Error { - /// 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 Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for Error {} - -/// Client error when handling responses from a server streaming request -#[derive(Debug)] -pub enum ItemError { - /// Unable to receive the response from the server - RecvError(S::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for ItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for ItemError {} - -impl RpcClient -where - C: crate::Connector, - S: Service, -{ - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn server_streaming( - &self, - msg: M, - ) -> result::Result>>, Error> - where - M: ServerStreamingMsg, - { - let msg = msg.into(); - let (mut send, recv) = self.source.open().await.map_err(Error::Open)?; - send.send(msg).map_err(Error::::Send).await?; - let recv = recv.map(move |x| match x { - Ok(msg) => M::Response::try_from(msg).map_err(|_| ItemError::DowncastError), - Err(e) => Err(ItemError::RecvError(e)), - }); - // keep send alive so the request on the server side does not get cancelled - let recv = Box::pin(DeferDrop(recv, send)); - Ok(recv) - } -} - -impl RpcChannel -where - S: Service, - C: StreamTypes, -{ - /// handle the message 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 server_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: ServerStreamingMsg, - F: FnOnce(T, M) -> Str + Send + 'static, - Str: Stream + Send + 'static, - T: Send + 'static, - { - let Self { - mut send, mut recv, .. - } = self; - // cancel if we get an update, no matter what it is - let cancel = recv - .next() - .map(|_| RpcServerError::UnexpectedUpdateMessage::); - // race the computation and the cancellation - race2(cancel.map(Err), async move { - // get the response - let responses = f(target, req); - tokio::pin!(responses); - while let Some(response) = responses.next().await { - // turn into a S::Res so we can send it - let response = response.into(); - // send it and return the error if any - send.send(response) - .await - .map_err(RpcServerError::SendError)?; - } - Ok(()) - }) - .await - } -} diff --git a/old/src/pattern/try_server_streaming.rs b/old/src/pattern/try_server_streaming.rs deleted file mode 100644 index e3ebb06..0000000 --- a/old/src/pattern/try_server_streaming.rs +++ /dev/null @@ -1,210 +0,0 @@ -//! Fallible server streaming interaction pattern. - -use std::{ - error, - fmt::{self, Debug}, - result, -}; - -use futures_lite::{Future, Stream, StreamExt}; -use futures_util::{FutureExt, SinkExt, TryFutureExt}; -use serde::{Deserialize, Serialize}; - -use crate::{ - client::{BoxStreamSync, DeferDrop}, - message::{InteractionPattern, Msg}, - server::{race2, RpcChannel, RpcServerError}, - transport::{self, ConnectionErrors, StreamTypes}, - Connector, RpcClient, Service, -}; - -/// A guard message to indicate that the stream has been created. -/// -/// This is so we can dinstinguish between an error creating the stream and -/// an error in the first item produced by the stream. -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct StreamCreated; - -/// Fallible server streaming interaction pattern. -#[derive(Debug, Clone, Copy)] -pub struct TryServerStreaming; - -impl InteractionPattern for TryServerStreaming {} - -/// Same as ServerStreamingMsg, but with lazy stream creation and the error type explicitly defined. -pub trait TryServerStreamingMsg: Msg -where - result::Result: Into + TryFrom, - result::Result: Into + TryFrom, -{ - /// Error when creating the stream - type CreateError: Debug + Send + 'static; - - /// Error for stream items - type ItemError: Debug + Send + 'static; - - /// Successful response item - type Item: Send + 'static; -} - -/// Server error when accepting a server streaming request -/// -/// This combines network errors with application errors. Usually you don't -/// care about the exact nature of the error, but if you want to handle -/// application errors differently, you can match on this enum. -#[derive(Debug)] -pub enum Error { - /// Unable to open a substream at all - Open(C::OpenError), - /// Unable to send the request to the server - Send(C::SendError), - /// Error received when creating the stream - Recv(C::RecvError), - /// Connection was closed before receiving the first message - EarlyClose, - /// Unexpected response from the server - Downcast, - /// Application error - Application(E), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for Error {} - -/// Client error when handling responses from a server streaming request. -/// -/// This combines network errors with application errors. -#[derive(Debug)] -pub enum ItemError { - /// Unable to receive the response from the server - Recv(S::RecvError), - /// Unexpected response from the server - Downcast, - /// Application error - Application(E), -} - -impl fmt::Display for ItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for ItemError {} - -impl RpcChannel -where - C: StreamTypes, - S: Service, -{ - /// handle the message 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. - /// - /// Compared to [RpcChannel::server_streaming], with this method the stream creation is via - /// a function that returns a future that resolves to a stream. - pub async fn try_server_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: TryServerStreamingMsg, - std::result::Result: Into + TryFrom, - std::result::Result: Into + TryFrom, - F: FnOnce(T, M) -> Fut + Send + 'static, - Fut: Future> + Send + 'static, - Str: Stream> + Send + 'static, - T: Send + 'static, - { - let Self { - mut send, mut recv, .. - } = self; - // cancel if we get an update, no matter what it is - let cancel = recv - .next() - .map(|_| RpcServerError::UnexpectedUpdateMessage::); - // race the computation and the cancellation - race2(cancel.map(Err), async move { - // get the response - let responses = match f(target, req).await { - Ok(responses) => { - // turn into a S::Res so we can send it - let response = Ok(StreamCreated).into(); - // send it and return the error if any - send.send(response) - .await - .map_err(RpcServerError::SendError)?; - responses - } - Err(cause) => { - // turn into a S::Res so we can send it - let response = Err(cause).into(); - // send it and return the error if any - send.send(response) - .await - .map_err(RpcServerError::SendError)?; - return Ok(()); - } - }; - tokio::pin!(responses); - while let Some(response) = responses.next().await { - // turn into a S::Res so we can send it - let response = response.into(); - // send it and return the error if any - send.send(response) - .await - .map_err(RpcServerError::SendError)?; - } - Ok(()) - }) - .await - } -} - -impl RpcClient -where - C: Connector, - S: Service, -{ - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn try_server_streaming( - &self, - msg: M, - ) -> result::Result< - BoxStreamSync<'static, Result>>, - Error, - > - where - M: TryServerStreamingMsg, - Result: Into + TryFrom, - Result: Into + TryFrom, - { - let msg = msg.into(); - let (mut send, mut recv) = self.source.open().await.map_err(Error::Open)?; - send.send(msg).map_err(Error::Send).await?; - let Some(initial) = recv.next().await else { - return Err(Error::EarlyClose); - }; - let initial = initial.map_err(Error::Recv)?; // initial response - let initial = >::try_from(initial) - .map_err(|_| Error::Downcast)?; - let _ = initial.map_err(Error::Application)?; - let recv = recv.map(move |x| { - let x = x.map_err(ItemError::Recv)?; - let x = >::try_from(x) - .map_err(|_| ItemError::Downcast)?; - let x = x.map_err(ItemError::Application)?; - Ok(x) - }); - // keep send alive so the request on the server side does not get cancelled - let recv = Box::pin(DeferDrop(recv, send)); - Ok(recv) - } -} diff --git a/old/src/server.rs b/old/src/server.rs deleted file mode 100644 index 387d41d..0000000 --- a/old/src/server.rs +++ /dev/null @@ -1,496 +0,0 @@ -//! Server side api -//! -//! The main entry point is [RpcServer] -use std::{ - error, - fmt::{self, Debug}, - marker::PhantomData, - pin::Pin, - result, - sync::Arc, - task::{self, Poll}, -}; - -use futures_lite::{Future, Stream, StreamExt}; -use futures_util::{SinkExt, TryStreamExt}; -use pin_project::pin_project; -use tokio::{sync::oneshot, task::JoinSet}; -use tokio_util::task::AbortOnDropHandle; -use tracing::{error, warn}; - -use crate::{ - transport::{ - self, - boxed::BoxableListener, - mapped::{ErrorOrMapError, MappedRecvStream, MappedSendSink, MappedStreamTypes}, - ConnectionErrors, StreamTypes, - }, - Listener, RpcMessage, Service, -}; - -/// Stream types on the server side -/// -/// On the server side, we receive requests and send responses. -/// On the client side, we send requests and receive responses. -pub trait ChannelTypes: transport::StreamTypes {} - -impl, S: Service> ChannelTypes for T {} - -/// Type alias for when you want to require a boxed channel -pub type BoxedChannelTypes = crate::transport::boxed::BoxedStreamTypes< - ::Req, - ::Res, ->; - -/// A boxed listener for the given [`Service`] -pub type BoxedListener = - crate::transport::boxed::BoxedListener<::Req, ::Res>; - -#[cfg(feature = "flume-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "flume-transport")))] -/// A flume listener for the given [`Service`] -pub type FlumeListener = - crate::transport::flume::FlumeListener<::Req, ::Res>; - -#[cfg(feature = "quinn-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "quinn-transport")))] -/// A quinn listener for the given [`Service`] -pub type QuinnListener = - crate::transport::quinn::QuinnListener<::Req, ::Res>; - -#[cfg(feature = "hyper-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "hyper-transport")))] -/// A hyper listener for the given [`Service`] -pub type HyperListener = - crate::transport::hyper::HyperListener<::Req, ::Res>; - -#[cfg(feature = "iroh-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "iroh-transport")))] -/// An iroh listener for the given [`Service`] -pub type IrohListener = - crate::transport::iroh::IrohListener<::Req, ::Res>; - -/// A server for a specific service. -/// -/// This is a wrapper around a [`Listener`] that serves as the entry point for the server DSL. -/// -/// Type parameters: -/// -/// `S` is the service type. -/// `C` is the channel type. -#[derive(Debug)] -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. - source: C, - _p: PhantomData, -} - -impl Clone for RpcServer { - fn clone(&self) -> Self { - Self { - source: self.source.clone(), - _p: PhantomData, - } - } -} - -impl> RpcServer { - /// Create a new rpc server for a specific service for a [Service] given a compatible - /// [Listener]. - /// - /// 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, - } - } - - /// Box the transport for the service. - /// - /// The boxed transport is the default for the `C` type parameter, so by boxing we can avoid - /// having to specify the type parameter. - pub fn boxed(self) -> RpcServer> - where - C: BoxableListener, - { - RpcServer::new(self.source.boxed()) - } -} - -/// 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. -/// -/// Type parameters: -/// -/// `S` is the service type. -/// `C` is the service endpoint from which the channel was created. -#[derive(Debug)] -pub struct RpcChannel = BoxedChannelTypes> { - /// Sink to send responses to the client. - pub send: C::SendSink, - /// Stream to receive requests from the client. - pub recv: C::RecvStream, - - pub(crate) _p: PhantomData, -} - -impl RpcChannel -where - S: Service, - C: StreamTypes, -{ - /// Create a new RPC channel. - pub fn new(send: C::SendSink, recv: C::RecvStream) -> Self { - Self { - send, - recv, - _p: PhantomData, - } - } - - /// Convert this channel into a boxed channel. - pub fn boxed(self) -> RpcChannel> - where - C::SendError: Into + Send + Sync + 'static, - C::RecvError: Into + Send + Sync + 'static, - { - let send = - transport::boxed::SendSink::boxed(Box::new(self.send.sink_map_err(|e| e.into()))); - let recv = transport::boxed::RecvStream::boxed(Box::new(self.recv.map_err(|e| e.into()))); - RpcChannel::new(send, recv) - } - - /// Map this channel's service into an inner service. - /// - /// This method is available if the required bounds are upheld: - /// SNext::Req: Into + TryFrom, - /// SNext::Res: Into + TryFrom, - /// - /// Where SNext is the new service to map to and S is the current inner service. - /// - /// This method can be chained infintely. - pub fn map(self) -> RpcChannel> - where - SNext: Service, - SNext::Req: TryFrom, - S::Res: From, - { - RpcChannel::new( - MappedSendSink::new(self.send), - MappedRecvStream::new(self.recv), - ) - } -} - -/// The result of accepting a new connection. -pub struct Accepting> { - send: C::SendSink, - recv: C::RecvStream, - _p: PhantomData, -} - -impl> Accepting { - /// Read the first message from the client. - /// - /// 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 read_first(self) -> result::Result<(S::Req, RpcChannel), RpcServerError> { - let Accepting { send, mut recv, .. } = self; - // 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))) - } -} - -impl> RpcServer { - /// Accepts a new channel from a client. The result is an [Accepting] object that - /// can be used to read the first request. - pub async fn accept(&self) -> result::Result, RpcServerError> { - let (send, recv) = self.source.accept().await.map_err(RpcServerError::Accept)?; - Ok(Accepting { - send, - recv, - _p: PhantomData, - }) - } - - /// Get the underlying service endpoint - pub fn into_inner(self) -> C { - self.source - } - - /// Run an accept loop for this server. - /// - /// Each request will be handled in a separate task. - /// - /// It is the caller's responsibility to poll the returned future to drive the server. - pub async fn accept_loop(self, handler: Fun) - where - S: Service, - C: Listener, - Fun: Fn(S::Req, RpcChannel) -> Fut + Send + Sync + 'static, - Fut: Future> + Send + 'static, - E: Into + 'static, - { - let handler = Arc::new(handler); - let mut tasks = JoinSet::new(); - loop { - tokio::select! { - Some(res) = tasks.join_next(), if !tasks.is_empty() => { - if let Err(e) = res { - if e.is_panic() { - error!("Panic handling RPC request: {e}"); - } - } - } - req = self.accept() => { - let req = match req { - Ok(req) => req, - Err(e) => { - warn!("Error accepting RPC request: {e}"); - continue; - } - }; - let handler = handler.clone(); - tasks.spawn(async move { - let (req, chan) = match req.read_first().await { - Ok((req, chan)) => (req, chan), - Err(e) => { - warn!("Error reading first message: {e}"); - return; - } - }; - if let Err(cause) = handler(req, chan).await { - warn!("Error handling RPC request: {}", cause.into()); - } - }); - } - } - } - } - - /// Spawn an accept loop and return a handle to the task. - pub fn spawn_accept_loop(self, handler: Fun) -> AbortOnDropHandle<()> - where - S: Service, - C: Listener, - Fun: Fn(S::Req, RpcChannel) -> Fut + Send + Sync + 'static, - Fut: Future> + Send + 'static, - E: Into + 'static, - { - AbortOnDropHandle::new(tokio::spawn(self.accept_loop(handler))) - } -} - -impl> AsRef for RpcServer { - fn as_ref(&self) -> &C { - &self.source - } -} - -/// A stream of updates -/// -/// 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] -#[derive(Debug)] -pub struct UpdateStream( - #[pin] C::RecvStream, - Option>>, - PhantomData, -) -where - C: StreamTypes; - -impl UpdateStream -where - C: StreamTypes, - T: TryFrom, -{ - pub(crate) fn new(recv: C::RecvStream) -> (Self, UnwrapToPending>) { - let (error_send, error_recv) = oneshot::channel(); - let error_recv = UnwrapToPending(futures_lite::future::fuse(error_recv)); - (Self(recv, Some(error_send), PhantomData), error_recv) - } -} - -impl Stream for UpdateStream -where - C: StreamTypes, - T: TryFrom, -{ - type Item = T; - - fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - let mut this = self.project(); - match Pin::new(&mut this.0).poll_next(cx) { - Poll::Ready(Some(msg)) => match msg { - Ok(msg) => { - let msg = T::try_from(msg).map_err(|_cause| ()); - match msg { - Ok(msg) => Poll::Ready(Some(msg)), - Err(_cause) => { - // we were unable to downcast, so we need to send an error - if let Some(tx) = this.1.take() { - let _ = tx.send(RpcServerError::UnexpectedUpdateMessage); - } - Poll::Pending - } - } - } - Err(cause) => { - // we got a recv error, so return pending and send the error - if let Some(tx) = this.1.take() { - let _ = tx.send(RpcServerError::RecvError(cause)); - } - Poll::Pending - } - }, - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } -} - -/// Server error. All server DSL methods return a `Result` with this error type. -pub enum RpcServerError { - /// Unable to open a new channel - Accept(C::AcceptError), - /// Recv side for a channel was closed before getting the first message - EarlyClose, - /// Got an unexpected first message, e.g. an update message - UnexpectedStartMessage, - /// Error receiving a message - RecvError(C::RecvError), - /// Error sending a response - SendError(C::SendError), - /// Got an unexpected update message, e.g. a request message or a non-matching update message - UnexpectedUpdateMessage, -} - -impl - RpcServerError> -{ - /// For a mapped connection, map the error back to the original error type - pub fn map_back(self) -> RpcServerError { - match self { - RpcServerError::EarlyClose => RpcServerError::EarlyClose, - RpcServerError::UnexpectedStartMessage => RpcServerError::UnexpectedStartMessage, - RpcServerError::UnexpectedUpdateMessage => RpcServerError::UnexpectedUpdateMessage, - RpcServerError::SendError(x) => RpcServerError::SendError(x), - RpcServerError::Accept(x) => RpcServerError::Accept(x), - RpcServerError::RecvError(ErrorOrMapError::Inner(x)) => RpcServerError::RecvError(x), - RpcServerError::RecvError(ErrorOrMapError::Conversion) => { - RpcServerError::UnexpectedUpdateMessage - } - } - } -} - -impl RpcServerError { - /// Convert into a different error type provided the send, recv and accept errors can be converted - pub fn errors_into(self) -> RpcServerError - where - T: ConnectionErrors, - C::SendError: Into, - C::RecvError: Into, - C::AcceptError: Into, - { - match self { - RpcServerError::EarlyClose => RpcServerError::EarlyClose, - RpcServerError::UnexpectedStartMessage => RpcServerError::UnexpectedStartMessage, - RpcServerError::UnexpectedUpdateMessage => RpcServerError::UnexpectedUpdateMessage, - RpcServerError::SendError(x) => RpcServerError::SendError(x.into()), - RpcServerError::Accept(x) => RpcServerError::Accept(x.into()), - RpcServerError::RecvError(x) => RpcServerError::RecvError(x.into()), - } - } -} - -impl fmt::Debug for RpcServerError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - 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(), - Self::UnexpectedStartMessage => f.debug_tuple("UnexpectedStartMessage").finish(), - Self::UnexpectedUpdateMessage => f.debug_tuple("UnexpectedStartMessage").finish(), - } - } -} - -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 {} - -/// Take an oneshot receiver and just return Pending the underlying future returns `Err(oneshot::Canceled)` -pub(crate) struct UnwrapToPending(futures_lite::future::Fuse>); - -impl Future for UnwrapToPending { - type Output = T; - - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { - // todo: use is_terminated from tokio 1.44 here to avoid the fused wrapper - match Pin::new(&mut self.0).poll(cx) { - Poll::Ready(Ok(x)) => Poll::Ready(x), - Poll::Ready(Err(_)) => Poll::Pending, - Poll::Pending => Poll::Pending, - } - } -} - -pub(crate) async fn race2, B: Future>(f1: A, f2: B) -> T { - tokio::select! { - x = f1 => x, - x = f2 => x, - } -} - -/// Run a server loop, invoking a handler callback for each request. -/// -/// Requests will be handled sequentially. -pub async fn run_server_loop( - _service_type: S, - conn: C, - target: T, - mut handler: F, -) -> Result<(), RpcServerError> -where - S: Service, - C: Listener, - T: Clone + Send + 'static, - F: FnMut(RpcChannel, S::Req, T) -> Fut + Send + 'static, - Fut: Future>> + Send + 'static, -{ - let server: RpcServer = RpcServer::::new(conn); - loop { - let (req, chan) = server.accept().await?.read_first().await?; - let target = target.clone(); - handler(chan, req, target).await?; - } -} diff --git a/old/src/transport/boxed.rs b/old/src/transport/boxed.rs deleted file mode 100644 index b440fd5..0000000 --- a/old/src/transport/boxed.rs +++ /dev/null @@ -1,540 +0,0 @@ -//! Boxed transport with concrete types - -use std::{ - fmt::Debug, - future::Future, - pin::Pin, - task::{Context, Poll}, -}; - -use futures_lite::FutureExt; -use futures_sink::Sink; -use futures_util::{future::BoxFuture, SinkExt, Stream, StreamExt, TryStreamExt}; -use pin_project::pin_project; - -use super::{ConnectionErrors, StreamTypes}; -use crate::RpcMessage; -type BoxedFuture<'a, T> = Pin + Send + Sync + 'a>>; - -enum SendSinkInner { - #[cfg(feature = "flume-transport")] - Direct(::flume::r#async::SendSink<'static, T>), - Boxed(Pin + Send + Sync + 'static>>), -} - -/// A sink that can be used to send messages to the remote end of a channel. -/// -/// For local channels, this is a thin wrapper around a flume send sink. -/// For network channels, this contains a boxed sink, since it is reasonable -/// to assume that in that case the additional overhead of boxing is negligible. -#[pin_project] -pub struct SendSink(SendSinkInner); - -impl SendSink { - /// Create a new send sink from a boxed sink - pub fn boxed(sink: impl Sink + Send + Sync + 'static) -> Self { - Self(SendSinkInner::Boxed(Box::pin(sink))) - } - - /// Create a new send sink from a direct flume send sink - #[cfg(feature = "flume-transport")] - pub(crate) fn direct(sink: ::flume::r#async::SendSink<'static, T>) -> Self { - Self(SendSinkInner::Direct(sink)) - } -} - -impl Sink for SendSink { - type Error = anyhow::Error; - - fn poll_ready( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - match self.project().0 { - #[cfg(feature = "flume-transport")] - SendSinkInner::Direct(sink) => sink.poll_ready_unpin(cx).map_err(anyhow::Error::from), - SendSinkInner::Boxed(sink) => sink.poll_ready_unpin(cx).map_err(anyhow::Error::from), - } - } - - fn start_send(self: std::pin::Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - match self.project().0 { - #[cfg(feature = "flume-transport")] - SendSinkInner::Direct(sink) => sink.start_send_unpin(item).map_err(anyhow::Error::from), - SendSinkInner::Boxed(sink) => sink.start_send_unpin(item), - } - } - - fn poll_flush( - self: std::pin::Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - match self.project().0 { - #[cfg(feature = "flume-transport")] - SendSinkInner::Direct(sink) => sink.poll_flush_unpin(cx).map_err(anyhow::Error::from), - SendSinkInner::Boxed(sink) => sink.poll_flush_unpin(cx).map_err(anyhow::Error::from), - } - } - - fn poll_close( - self: std::pin::Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - match self.project().0 { - #[cfg(feature = "flume-transport")] - SendSinkInner::Direct(sink) => sink.poll_close_unpin(cx).map_err(anyhow::Error::from), - SendSinkInner::Boxed(sink) => sink.poll_close_unpin(cx).map_err(anyhow::Error::from), - } - } -} - -enum RecvStreamInner { - #[cfg(feature = "flume-transport")] - Direct(::flume::r#async::RecvStream<'static, T>), - Boxed(Pin> + Send + Sync + 'static>>), -} - -/// A stream that can be used to receive messages from the remote end of a channel. -/// -/// For local channels, this is a thin wrapper around a flume receive stream. -/// For network channels, this contains a boxed stream, since it is reasonable -#[pin_project] -pub struct RecvStream(RecvStreamInner); - -impl RecvStream { - /// Create a new receive stream from a boxed stream - pub fn boxed( - stream: impl Stream> + Send + Sync + 'static, - ) -> Self { - Self(RecvStreamInner::Boxed(Box::pin(stream))) - } - - /// Create a new receive stream from a direct flume receive stream - #[cfg(feature = "flume-transport")] - pub(crate) fn direct(stream: ::flume::r#async::RecvStream<'static, T>) -> Self { - Self(RecvStreamInner::Direct(stream)) - } -} - -impl Stream for RecvStream { - type Item = Result; - - fn poll_next(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project().0 { - #[cfg(feature = "flume-transport")] - RecvStreamInner::Direct(stream) => match stream.poll_next_unpin(cx) { - Poll::Ready(Some(item)) => Poll::Ready(Some(Ok(item))), - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - }, - RecvStreamInner::Boxed(stream) => stream.poll_next_unpin(cx), - } - } -} - -enum OpenFutureInner<'a, In: RpcMessage, Out: RpcMessage> { - /// A direct future (todo) - #[cfg(feature = "flume-transport")] - Direct(super::flume::OpenFuture), - /// A boxed future - Boxed(BoxFuture<'a, anyhow::Result<(SendSink, RecvStream)>>), -} - -/// A concrete future for opening a channel -#[pin_project] -pub struct OpenFuture<'a, In: RpcMessage, Out: RpcMessage>(OpenFutureInner<'a, In, Out>); - -impl<'a, In: RpcMessage, Out: RpcMessage> OpenFuture<'a, In, Out> { - #[cfg(feature = "flume-transport")] - fn direct(f: super::flume::OpenFuture) -> Self { - Self(OpenFutureInner::Direct(f)) - } - - /// Create a new boxed future - pub fn boxed( - f: impl Future, RecvStream)>> + Send + 'a, - ) -> Self { - Self(OpenFutureInner::Boxed(Box::pin(f))) - } -} - -impl Future for OpenFuture<'_, In, Out> { - type Output = anyhow::Result<(SendSink, RecvStream)>; - - fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - match self.project().0 { - #[cfg(feature = "flume-transport")] - OpenFutureInner::Direct(f) => f - .poll(cx) - .map_ok(|(send, recv)| (SendSink::direct(send.0), RecvStream::direct(recv.0))) - .map_err(|e| e.into()), - OpenFutureInner::Boxed(f) => f.poll(cx), - } - } -} - -enum AcceptFutureInner<'a, In: RpcMessage, Out: RpcMessage> { - /// A direct future - #[cfg(feature = "flume-transport")] - Direct(super::flume::AcceptFuture), - /// A boxed future - Boxed(BoxedFuture<'a, anyhow::Result<(SendSink, RecvStream)>>), -} - -/// Concrete accept future -#[pin_project] -pub struct AcceptFuture<'a, In: RpcMessage, Out: RpcMessage>(AcceptFutureInner<'a, In, Out>); - -impl<'a, In: RpcMessage, Out: RpcMessage> AcceptFuture<'a, In, Out> { - #[cfg(feature = "flume-transport")] - fn direct(f: super::flume::AcceptFuture) -> Self { - Self(AcceptFutureInner::Direct(f)) - } - - /// Create a new boxed future - pub fn boxed( - f: impl Future, RecvStream)>> + Send + Sync + 'a, - ) -> Self { - Self(AcceptFutureInner::Boxed(Box::pin(f))) - } -} - -impl Future for AcceptFuture<'_, In, Out> { - type Output = anyhow::Result<(SendSink, RecvStream)>; - - fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - match self.project().0 { - #[cfg(feature = "flume-transport")] - AcceptFutureInner::Direct(f) => f - .poll(cx) - .map_ok(|(send, recv)| (SendSink::direct(send.0), RecvStream::direct(recv.0))) - .map_err(|e| e.into()), - AcceptFutureInner::Boxed(f) => f.poll(cx), - } - } -} - -/// A boxable connector -pub trait BoxableConnector: Debug + Send + Sync + 'static { - /// Clone the connection and box it - fn clone_box(&self) -> Box>; - - /// Open a channel to the remote che - fn open_boxed(&self) -> OpenFuture; -} - -/// A boxed connector -#[derive(Debug)] -pub struct BoxedConnector(Box>); - -impl BoxedConnector { - /// Wrap a boxable connector into a box, transforming all the types to concrete types - pub fn new(x: impl BoxableConnector) -> Self { - Self(Box::new(x)) - } -} - -impl Clone for BoxedConnector { - fn clone(&self) -> Self { - Self(self.0.clone_box()) - } -} - -impl StreamTypes for BoxedConnector { - type In = In; - type Out = Out; - type RecvStream = RecvStream; - type SendSink = SendSink; -} - -impl ConnectionErrors for BoxedConnector { - type SendError = anyhow::Error; - type RecvError = anyhow::Error; - type OpenError = anyhow::Error; - type AcceptError = anyhow::Error; -} - -impl super::Connector for BoxedConnector { - async fn open(&self) -> Result<(Self::SendSink, Self::RecvStream), Self::OpenError> { - self.0.open_boxed().await - } -} - -/// Stream types for boxed streams -#[derive(Debug)] -pub struct BoxedStreamTypes { - _p: std::marker::PhantomData<(In, Out)>, -} - -impl Clone for BoxedStreamTypes { - fn clone(&self) -> Self { - Self { - _p: std::marker::PhantomData, - } - } -} - -impl ConnectionErrors for BoxedStreamTypes { - type SendError = anyhow::Error; - type RecvError = anyhow::Error; - type OpenError = anyhow::Error; - type AcceptError = anyhow::Error; -} - -impl StreamTypes for BoxedStreamTypes { - type In = In; - type Out = Out; - type RecvStream = RecvStream; - type SendSink = SendSink; -} - -/// A boxable listener -pub trait BoxableListener: Debug + Send + Sync + 'static { - /// Clone the listener and box it - fn clone_box(&self) -> Box>; - - /// Accept a channel from a remote client - fn accept_bi_boxed(&self) -> AcceptFuture; - - /// Get the local address - fn local_addr(&self) -> &[super::LocalAddr]; -} - -/// A boxed listener -#[derive(Debug)] -pub struct BoxedListener(Box>); - -impl BoxedListener { - /// Wrap a boxable listener into a box, transforming all the types to concrete types - pub fn new(x: impl BoxableListener) -> Self { - Self(Box::new(x)) - } -} - -impl Clone for BoxedListener { - fn clone(&self) -> Self { - Self(self.0.clone_box()) - } -} - -impl StreamTypes for BoxedListener { - type In = In; - type Out = Out; - type RecvStream = RecvStream; - type SendSink = SendSink; -} - -impl ConnectionErrors for BoxedListener { - type SendError = anyhow::Error; - type RecvError = anyhow::Error; - type OpenError = anyhow::Error; - type AcceptError = anyhow::Error; -} - -impl super::Listener for BoxedListener { - fn accept( - &self, - ) -> impl Future> + Send - { - self.0.accept_bi_boxed() - } - - fn local_addr(&self) -> &[super::LocalAddr] { - self.0.local_addr() - } -} -impl BoxableConnector for BoxedConnector { - fn clone_box(&self) -> Box> { - Box::new(self.clone()) - } - - fn open_boxed(&self) -> OpenFuture { - OpenFuture::boxed(crate::transport::Connector::open(self)) - } -} - -#[cfg(feature = "quinn-transport")] -impl BoxableConnector - for super::quinn::QuinnConnector -{ - fn clone_box(&self) -> Box> { - Box::new(self.clone()) - } - - fn open_boxed(&self) -> OpenFuture { - let f = Box::pin(async move { - let (send, recv) = super::Connector::open(self).await?; - // map the error types to anyhow - let send = send.sink_map_err(anyhow::Error::from); - let recv = recv.map_err(anyhow::Error::from); - // return the boxed streams - anyhow::Ok((SendSink::boxed(send), RecvStream::boxed(recv))) - }); - OpenFuture::boxed(f) - } -} - -#[cfg(feature = "quinn-transport")] -impl BoxableListener - for super::quinn::QuinnListener -{ - fn clone_box(&self) -> Box> { - Box::new(self.clone()) - } - - fn accept_bi_boxed(&self) -> AcceptFuture { - let f = async move { - let (send, recv) = super::Listener::accept(self).await?; - let send = send.sink_map_err(anyhow::Error::from); - let recv = recv.map_err(anyhow::Error::from); - anyhow::Ok((SendSink::boxed(send), RecvStream::boxed(recv))) - }; - AcceptFuture::boxed(f) - } - - fn local_addr(&self) -> &[super::LocalAddr] { - super::Listener::local_addr(self) - } -} - -#[cfg(feature = "iroh-transport")] -impl BoxableConnector - for super::iroh::IrohConnector -{ - fn clone_box(&self) -> Box> { - Box::new(self.clone()) - } - - fn open_boxed(&self) -> OpenFuture { - let f = Box::pin(async move { - let (send, recv) = super::Connector::open(self).await?; - // map the error types to anyhow - let send = send.sink_map_err(anyhow::Error::from); - let recv = recv.map_err(anyhow::Error::from); - // return the boxed streams - anyhow::Ok((SendSink::boxed(send), RecvStream::boxed(recv))) - }); - OpenFuture::boxed(f) - } -} - -#[cfg(feature = "iroh-transport")] -impl BoxableListener - for super::iroh::IrohListener -{ - fn clone_box(&self) -> Box> { - Box::new(self.clone()) - } - - fn accept_bi_boxed(&self) -> AcceptFuture { - let f = async move { - let (send, recv) = super::Listener::accept(self).await?; - let send = send.sink_map_err(anyhow::Error::from); - let recv = recv.map_err(anyhow::Error::from); - anyhow::Ok((SendSink::boxed(send), RecvStream::boxed(recv))) - }; - AcceptFuture::boxed(f) - } - - fn local_addr(&self) -> &[super::LocalAddr] { - super::Listener::local_addr(self) - } -} - -#[cfg(feature = "flume-transport")] -impl BoxableConnector - for super::flume::FlumeConnector -{ - fn clone_box(&self) -> Box> { - Box::new(self.clone()) - } - - fn open_boxed(&self) -> OpenFuture { - OpenFuture::direct(super::Connector::open(self)) - } -} - -#[cfg(feature = "flume-transport")] -impl BoxableListener - for super::flume::FlumeListener -{ - fn clone_box(&self) -> Box> { - Box::new(self.clone()) - } - - fn accept_bi_boxed(&self) -> AcceptFuture { - AcceptFuture::direct(super::Listener::accept(self)) - } - - fn local_addr(&self) -> &[super::LocalAddr] { - super::Listener::local_addr(self) - } -} - -impl BoxableConnector for super::mapped::MappedConnector -where - In: RpcMessage, - Out: RpcMessage, - C: super::Connector, - C::Out: From, - In: TryFrom, - C::SendError: Into, - C::RecvError: Into, - C::OpenError: Into, -{ - fn clone_box(&self) -> Box> { - Box::new(self.clone()) - } - - fn open_boxed(&self) -> OpenFuture { - let f = Box::pin(async move { - let (send, recv) = super::Connector::open(self).await.map_err(|e| e.into())?; - // map the error types to anyhow - let send = send.sink_map_err(|e| e.into()); - let recv = recv.map_err(|e| e.into()); - // return the boxed streams - anyhow::Ok((SendSink::boxed(send), RecvStream::boxed(recv))) - }); - OpenFuture::boxed(f) - } -} - -#[cfg(test)] -mod tests { - use crate::Service; - - #[derive(Debug, Clone)] - struct FooService; - - impl Service for FooService { - type Req = u64; - type Res = u64; - } - - #[cfg(feature = "flume-transport")] - #[tokio::test] - async fn box_smoke() { - use futures_lite::StreamExt; - use futures_util::SinkExt; - - use crate::transport::{Connector, Listener}; - - let (server, client) = crate::transport::flume::channel(1); - let server = super::BoxedListener::new(server); - let client = super::BoxedConnector::new(client); - // spawn echo server - tokio::spawn(async move { - while let Ok((mut send, mut recv)) = server.accept().await { - if let Some(Ok(msg)) = recv.next().await { - send.send(msg).await.ok(); - } - } - anyhow::Ok(()) - }); - if let Ok((mut send, mut recv)) = client.open().await { - send.send(1).await.ok(); - let res = recv.next().await; - println!("{:?}", res); - } - } -} diff --git a/old/src/transport/combined.rs b/old/src/transport/combined.rs deleted file mode 100644 index 60e6843..0000000 --- a/old/src/transport/combined.rs +++ /dev/null @@ -1,292 +0,0 @@ -//! Transport that combines two other transports -use std::{ - error, fmt, - fmt::Debug, - pin::Pin, - task::{Context, Poll}, -}; - -use futures_lite::Stream; -use futures_sink::Sink; -use pin_project::pin_project; - -use super::{ConnectionErrors, Connector, Listener, LocalAddr, StreamTypes}; - -/// A connection that combines two other connections -#[derive(Debug, Clone)] -pub struct CombinedConnector { - /// First connection - pub a: Option, - /// Second connection - pub b: Option, -} - -impl> CombinedConnector { - /// Create a combined connection from two other connections - /// - /// It will always use the first connection that is not `None`. - pub fn new(a: Option, b: Option) -> Self { - Self { a, b } - } -} - -/// An endpoint that combines two other endpoints -#[derive(Debug, Clone)] -pub struct CombinedListener { - /// First endpoint - pub a: Option, - /// Second endpoint - pub b: Option, - /// Local addresses from all endpoints - local_addr: Vec, -} - -impl> CombinedListener { - /// Create a combined listener from two other listeners - /// - /// When listening for incoming connections with - /// [`Listener::accept`], all configured channels will be listened on, - /// and the first to receive a connection will be used. If no channels are configured, - /// accept will not throw an error but just 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 } - } - - /// Get back the inner endpoints - pub fn into_inner(self) -> (Option, Option) { - (self.a, self.b) - } -} - -/// Send sink for combined channels -#[pin_project(project = SendSinkProj)] -pub enum SendSink { - /// A variant - A(#[pin] A::SendSink), - /// B variant - B(#[pin] B::SendSink), -} - -impl> Sink for SendSink { - type Error = self::SendError; - - fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { - SendSinkProj::A(sink) => sink.poll_ready(cx).map_err(Self::Error::A), - SendSinkProj::B(sink) => sink.poll_ready(cx).map_err(Self::Error::B), - } - } - - fn start_send(self: Pin<&mut Self>, item: A::Out) -> Result<(), Self::Error> { - match self.project() { - SendSinkProj::A(sink) => sink.start_send(item).map_err(Self::Error::A), - SendSinkProj::B(sink) => sink.start_send(item).map_err(Self::Error::B), - } - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { - SendSinkProj::A(sink) => sink.poll_flush(cx).map_err(Self::Error::A), - SendSinkProj::B(sink) => sink.poll_flush(cx).map_err(Self::Error::B), - } - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { - SendSinkProj::A(sink) => sink.poll_close(cx).map_err(Self::Error::A), - SendSinkProj::B(sink) => sink.poll_close(cx).map_err(Self::Error::B), - } - } -} - -/// RecvStream for combined channels -#[pin_project(project = ResStreamProj)] -pub enum RecvStream { - /// A variant - A(#[pin] A::RecvStream), - /// B variant - B(#[pin] B::RecvStream), -} - -impl> Stream for RecvStream { - type Item = Result>; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { - ResStreamProj::A(stream) => stream.poll_next(cx).map_err(RecvError::::A), - ResStreamProj::B(stream) => stream.poll_next(cx).map_err(RecvError::::B), - } - } -} - -/// SendError for combined channels -#[derive(Debug)] -pub enum SendError { - /// A variant - A(A::SendError), - /// B variant - B(B::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 {} - -/// RecvError for combined channels -#[derive(Debug)] -pub enum RecvError { - /// A variant - A(A::RecvError), - /// B variant - B(B::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 {} - -/// OpenError for combined channels -#[derive(Debug)] -pub enum OpenError { - /// A variant - A(A::OpenError), - /// B variant - B(B::OpenError), - /// None of the two channels is configured - NoChannel, -} - -impl fmt::Display for OpenError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for OpenError {} - -/// AcceptError for combined channels -#[derive(Debug)] -pub enum AcceptError { - /// A variant - A(A::AcceptError), - /// B variant - B(B::AcceptError), -} - -impl fmt::Display for AcceptError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for AcceptError {} - -impl ConnectionErrors for CombinedConnector { - type SendError = self::SendError; - type RecvError = self::RecvError; - type OpenError = self::OpenError; - type AcceptError = self::AcceptError; -} - -impl> StreamTypes for CombinedConnector { - type In = A::In; - type Out = A::Out; - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; -} - -impl> Connector for CombinedConnector { - async fn open(&self) -> Result<(Self::SendSink, Self::RecvStream), Self::OpenError> { - let this = self.clone(); - // try a first, then b - if let Some(a) = this.a { - let (send, recv) = a.open().await.map_err(OpenError::A)?; - Ok((SendSink::A(send), RecvStream::A(recv))) - } else if let Some(b) = this.b { - let (send, recv) = b.open().await.map_err(OpenError::B)?; - Ok((SendSink::B(send), RecvStream::B(recv))) - } else { - Err(OpenError::NoChannel) - } - } -} - -impl ConnectionErrors for CombinedListener { - type SendError = self::SendError; - type RecvError = self::RecvError; - type OpenError = self::OpenError; - type AcceptError = self::AcceptError; -} - -impl> StreamTypes for CombinedListener { - type In = A::In; - type Out = A::Out; - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; -} - -impl> Listener for CombinedListener { - async fn accept(&self) -> Result<(Self::SendSink, Self::RecvStream), Self::AcceptError> { - let a_fut = async { - if let Some(a) = &self.a { - let (send, recv) = a.accept().await.map_err(AcceptError::A)?; - Ok((SendSink::A(send), RecvStream::A(recv))) - } else { - std::future::pending().await - } - }; - let b_fut = async { - if let Some(b) = &self.b { - let (send, recv) = b.accept().await.map_err(AcceptError::B)?; - Ok((SendSink::B(send), RecvStream::B(recv))) - } else { - std::future::pending().await - } - }; - async move { - tokio::select! { - res = a_fut => res, - res = b_fut => res, - } - } - .await - } - - fn local_addr(&self) -> &[LocalAddr] { - &self.local_addr - } -} - -#[cfg(test)] -#[cfg(feature = "flume-transport")] -mod tests { - use crate::transport::{ - combined::{self, OpenError}, - flume, Connector, - }; - - #[tokio::test] - async fn open_empty_channel() { - let channel = combined::CombinedConnector::< - flume::FlumeConnector<(), ()>, - flume::FlumeConnector<(), ()>, - >::new(None, None); - let res = channel.open().await; - assert!(matches!(res, Err(OpenError::NoChannel))); - } -} diff --git a/old/src/transport/flume.rs b/old/src/transport/flume.rs deleted file mode 100644 index 8db6d03..0000000 --- a/old/src/transport/flume.rs +++ /dev/null @@ -1,345 +0,0 @@ -//! Memory transport implementation using [flume] -//! -//! [flume]: https://docs.rs/flume/ -use core::fmt; -use std::{error, fmt::Display, marker::PhantomData, pin::Pin, result, task::Poll}; - -use futures_lite::{Future, Stream}; -use futures_sink::Sink; - -use super::StreamTypes; -use crate::{ - transport::{ConnectionErrors, Connector, Listener, LocalAddr}, - RpcMessage, -}; - -/// 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(pub(crate) 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> { - Pin::new(&mut self.0) - .poll_ready(cx) - .map_err(|_| SendError::ReceiverDropped) - } - - fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - Pin::new(&mut self.0) - .start_send(item) - .map_err(|_| SendError::ReceiverDropped) - } - - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - Pin::new(&mut self.0) - .poll_flush(cx) - .map_err(|_| SendError::ReceiverDropped) - } - - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - Pin::new(&mut self.0) - .poll_close(cx) - .map_err(|_| SendError::ReceiverDropped) - } -} - -/// Stream for memory channels -pub struct RecvStream(pub(crate) 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 Pin::new(&mut self.0).poll_next(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 listener. -/// -/// Created using [channel]. -pub struct FlumeListener { - #[allow(clippy::type_complexity)] - stream: flume::Receiver<(SendSink, RecvStream)>, -} - -impl Clone for FlumeListener { - fn clone(&self) -> Self { - Self { - stream: self.stream.clone(), - } - } -} - -impl fmt::Debug for FlumeListener { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FlumeListener") - .field("stream", &self.stream) - .finish() - } -} - -impl ConnectionErrors for FlumeListener { - type SendError = self::SendError; - type RecvError = self::RecvError; - type OpenError = self::OpenError; - type AcceptError = self::AcceptError; -} - -type Socket = (self::SendSink, self::RecvStream); - -/// Future returned by [FlumeConnector::open] -pub struct OpenFuture { - inner: flume::r#async::SendFut<'static, Socket>, - res: Option>, -} - -impl fmt::Debug for OpenFuture { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("OpenFuture").finish() - } -} - -impl OpenFuture { - fn new(inner: flume::r#async::SendFut<'static, Socket>, res: Socket) -> Self { - Self { - inner, - res: Some(res), - } - } -} - -impl Future for OpenFuture { - type Output = result::Result, self::OpenError>; - - fn poll( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - match Pin::new(&mut self.inner).poll(cx) { - Poll::Ready(Ok(())) => self - .res - .take() - .map(|x| Poll::Ready(Ok(x))) - .unwrap_or(Poll::Pending), - Poll::Ready(Err(_)) => Poll::Ready(Err(self::OpenError::RemoteDropped)), - Poll::Pending => Poll::Pending, - } - } -} - -/// Future returned by [FlumeListener::accept] -pub struct AcceptFuture { - wrapped: flume::r#async::RecvFut<'static, (SendSink, RecvStream)>, - _p: PhantomData<(In, Out)>, -} - -impl fmt::Debug for AcceptFuture { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AcceptFuture").finish() - } -} - -impl Future for AcceptFuture { - type Output = result::Result<(SendSink, RecvStream), AcceptError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - match Pin::new(&mut self.wrapped).poll(cx) { - Poll::Ready(Ok((send, recv))) => Poll::Ready(Ok((send, recv))), - Poll::Ready(Err(_)) => Poll::Ready(Err(AcceptError::RemoteDropped)), - Poll::Pending => Poll::Pending, - } - } -} - -impl StreamTypes for FlumeListener { - type In = In; - type Out = Out; - type SendSink = SendSink; - type RecvStream = RecvStream; -} - -impl Listener for FlumeListener { - #[allow(refining_impl_trait)] - fn accept(&self) -> AcceptFuture { - AcceptFuture { - wrapped: self.stream.clone().into_recv_async(), - _p: PhantomData, - } - } - - fn local_addr(&self) -> &[LocalAddr] { - &[LocalAddr::Mem] - } -} - -impl ConnectionErrors for FlumeConnector { - type SendError = self::SendError; - type RecvError = self::RecvError; - type OpenError = self::OpenError; - type AcceptError = self::AcceptError; -} - -impl StreamTypes for FlumeConnector { - type In = In; - type Out = Out; - type SendSink = SendSink; - type RecvStream = RecvStream; -} - -impl Connector for FlumeConnector { - #[allow(refining_impl_trait)] - fn open(&self) -> OpenFuture { - 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()), - ); - OpenFuture::new(self.sink.clone().into_send_async(remote_chan), local_chan) - } -} - -/// A flume based connector. -/// -/// Created using [channel]. -pub struct FlumeConnector { - #[allow(clippy::type_complexity)] - sink: flume::Sender<(SendSink, RecvStream)>, -} - -impl Clone for FlumeConnector { - fn clone(&self) -> Self { - Self { - sink: self.sink.clone(), - } - } -} - -impl fmt::Debug for FlumeConnector { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FlumeClientChannel") - .field("sink", &self.sink) - .finish() - } -} - -/// AcceptError for mem channels. -/// -/// There is not much that can go wrong with mem channels. -#[derive(Debug)] -pub enum AcceptError { - /// The remote side of the channel was dropped - RemoteDropped, -} - -impl fmt::Display for AcceptError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for AcceptError {} - -/// 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 {} - -/// OpenError for mem channels. -#[derive(Debug)] -pub enum OpenError { - /// The remote side of the channel was dropped - RemoteDropped, -} - -impl Display for OpenError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl std::error::Error for OpenError {} - -/// 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 listener and a connected flume connector. -/// -/// `buffer` the size of the buffer for each channel. Keep this at a low value to get backpressure -pub fn channel( - buffer: usize, -) -> (FlumeListener, FlumeConnector) { - let (sink, stream) = flume::bounded(buffer); - (FlumeListener { stream }, FlumeConnector { sink }) -} diff --git a/old/src/transport/hyper.rs b/old/src/transport/hyper.rs deleted file mode 100644 index 3ef4190..0000000 --- a/old/src/transport/hyper.rs +++ /dev/null @@ -1,645 +0,0 @@ -//! http2 transport using [hyper] -//! -//! [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 bytes::Bytes; -use flume::{Receiver, Sender}; -use futures_lite::{Stream, StreamExt}; -use futures_sink::Sink; -use hyper::{ - client::{connect::Connect, HttpConnector, ResponseFuture}, - server::conn::{AddrIncoming, AddrStream}, - service::{make_service_fn, service_fn}, - Body, Client, Request, Response, Server, StatusCode, Uri, -}; -use tokio::{sync::mpsc, task::JoinHandle}; -use tracing::{debug, event, trace, Level}; - -use crate::{ - transport::{ConnectionErrors, Connector, Listener, LocalAddr, StreamTypes}, - RpcMessage, -}; - -struct HyperConnectionInner { - client: Box, - config: Arc, - uri: Uri, -} - -/// Hyper based connection to a server -pub struct HyperConnector { - inner: Arc, - _p: PhantomData<(In, Out)>, -} - -impl Clone for HyperConnector { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - _p: PhantomData, - } - } -} - -/// Trait so we don't have to drag around the hyper internals -trait Requester: Send + Sync + 'static { - fn request(&self, req: Request) -> ResponseFuture; -} - -impl Requester for Client { - fn request(&self, req: Request) -> ResponseFuture { - self.request(req) - } -} - -impl HyperConnector { - /// create a client given an uri and the default configuration - pub fn new(uri: Uri) -> Self { - Self::with_config(uri, ChannelConfig::default()) - } - - /// create a client given an uri and a custom configuration - pub fn with_config(uri: Uri, config: ChannelConfig) -> Self { - let mut connector = HttpConnector::new(); - connector.set_nodelay(true); - Self::with_connector(connector, uri, Arc::new(config)) - } - - /// create a client given an uri and a custom configuration - pub fn with_connector( - connector: C, - uri: Uri, - config: Arc, - ) -> Self { - let client = Client::builder() - .http2_only(true) - .http2_initial_connection_window_size(Some(config.max_frame_size)) - .http2_initial_stream_window_size(Some(config.max_frame_size)) - .http2_max_frame_size(Some(config.max_frame_size)) - .http2_max_send_buf_size(config.max_frame_size.try_into().unwrap()) - .build(connector); - Self { - inner: Arc::new(HyperConnectionInner { - client: Box::new(client), - uri, - config, - }), - _p: PhantomData, - } - } -} - -impl fmt::Debug for HyperConnector { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ClientChannel") - .field("uri", &self.inner.uri) - .field("config", &self.inner.config) - .finish() - } -} - -/// A flume sender and receiver tuple. -type InternalChannel = ( - Receiver>, - Sender>, -); - -/// Error when setting a channel configuration -#[derive(Debug, Clone)] -pub enum ChannelConfigError { - /// The maximum frame size is invalid - InvalidMaxFrameSize(u32), - /// The maximum payload size is invalid - InvalidMaxPayloadSize(usize), -} - -impl fmt::Display for ChannelConfigError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self, f) - } -} - -impl error::Error for ChannelConfigError {} - -/// Channel configuration -/// -/// These settings apply to both client and server channels. -#[derive(Debug, Clone)] -pub struct ChannelConfig { - /// The maximum frame size to use. - max_frame_size: u32, - max_payload_size: usize, -} - -impl ChannelConfig { - /// Set the maximum frame size. - pub fn max_frame_size(mut self, value: u32) -> result::Result { - if !(0x4000..=0xFFFFFF).contains(&value) { - return Err(ChannelConfigError::InvalidMaxFrameSize(value)); - } - self.max_frame_size = value; - Ok(self) - } - - /// Set the maximum payload size. - pub fn max_payload_size(mut self, value: usize) -> result::Result { - if !(4096..1024 * 1024 * 16).contains(&value) { - return Err(ChannelConfigError::InvalidMaxPayloadSize(value)); - } - self.max_payload_size = value; - Ok(self) - } -} - -impl Default for ChannelConfig { - fn default() -> Self { - Self { - max_frame_size: 0xFFFFFF, - max_payload_size: 0xFFFFFF, - } - } -} - -/// A listener 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 -/// channels themselves to support streaming requests and responses. -/// -/// 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 HyperListener { - /// The channel. - channel: Receiver>, - /// The configuration. - config: Arc, - /// The sender to stop the server. - /// - /// We never send anything over this really, simply dropping it makes the receiver - /// complete and will shut down the hyper server. - stop_tx: mpsc::Sender<()>, - /// The local address this server is bound to. - /// - /// This is useful when the listen address uses a random port, `:0`, to find out which - /// port was bound by the kernel. - local_addr: [LocalAddr; 1], - /// Phantom data for service - _p: PhantomData<(In, Out)>, -} - -impl HyperListener { - /// 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()) - } - - /// Creates a server listening on the [`SocketAddr`] with a custom configuration. - pub fn serve_with_config(addr: &SocketAddr, config: ChannelConfig) -> hyper::Result { - let (accept_tx, accept_rx) = flume::bounded(32); - - // The hyper "MakeService" which is called for each connection that is made to the - // server. It creates another Service which handles a single request. - let service = make_service_fn(move |socket: &AddrStream| { - let remote_addr = socket.remote_addr(); - event!(Level::TRACE, "Connection from {:?}", remote_addr); - - // Need a new accept_tx to move to the future on every call of this FnMut. - let accept_tx = accept_tx.clone(); - async move { - let one_req_service = service_fn(move |req: Request| { - // This closure is an FnMut as well, so clone accept_tx once more. - Self::handle_one_http2_request(req, accept_tx.clone()) - }); - Ok::<_, Infallible>(one_req_service) - } - }); - - let mut incoming = AddrIncoming::bind(addr)?; - incoming.set_nodelay(true); - let server = Server::builder(incoming) - .http2_only(true) - .http2_initial_connection_window_size(Some(config.max_frame_size)) - .http2_initial_stream_window_size(Some(config.max_frame_size)) - .http2_max_frame_size(Some(config.max_frame_size)) - .http2_max_send_buf_size(config.max_frame_size.try_into().unwrap()) - .serve(service); - let local_addr = server.local_addr(); - - let (stop_tx, mut stop_rx) = mpsc::channel::<()>(1); - let server = server.with_graceful_shutdown(async move { - // If the sender is dropped this will also gracefully terminate the server. - stop_rx.recv().await; - }); - tokio::spawn(server); - - Ok(Self { - channel: accept_rx, - config: Arc::new(config), - stop_tx, - local_addr: [LocalAddr::Socket(local_addr)], - _p: PhantomData, - }) - } - - /// Handles a single HTTP2 request. - /// - /// This creates the channels to communicate the (optionally streaming) request and - /// response and sends them to the [`ServerChannel`]. - async fn handle_one_http2_request( - req: Request, - accept_tx: Sender>, - ) -> Result, String> { - let (req_tx, req_rx) = flume::bounded::>(32); - let (res_tx, res_rx) = flume::bounded::>(32); - accept_tx - .send_async((req_rx, res_tx)) - .await - .map_err(|_e| "unable to send")?; - - spawn_recv_forwarder(req.into_body(), req_tx); - // Create a response with the response body channel as the response body - let response = Response::builder() - .status(StatusCode::OK) - .body(Body::wrap_stream(res_rx.into_stream())) - .map_err(|_| "unable to set body")?; - Ok(response) - } -} - -fn try_get_length_prefixed(buf: &[u8]) -> Option<&[u8]> { - if buf.len() < 4 { - return None; - } - let len = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize; - if buf.len() < 4 + len { - return None; - } - Some(&buf[4..4 + len]) -} - -/// Try forward all frames as deserialized messages from the buffer to the sender. -/// -/// On success, returns the number of forwarded bytes. -/// On forward error, returns the unit error. -/// -/// Deserialization errors don't cause an error, they will be sent. -/// On error the number of consumed bytes is not returned. There is nothing to do but -/// to stop the forwarder since there is nowhere to forward to anymore. -async fn try_forward_all( - buffer: &[u8], - req_tx: &Sender>, -) -> result::Result { - let mut sent = 0; - while let Some(msg) = try_get_length_prefixed(&buffer[sent..]) { - sent += msg.len() + 4; - let item = postcard::from_bytes::(msg).map_err(RecvError::DeserializeError); - if let Err(_cause) = req_tx.send_async(item).await { - // The receiver is gone, so we can't send any more data. - // - // This is a normal way for an interaction to end, when the server side is done processing - // the request and drops the receiver. - // - // don't log the cause. It does not contain any useful information. - trace!("Flume receiver dropped"); - return Err(()); - } - } - Ok(sent) -} - -/// Spawns a task which forwards requests from the network to a flume channel. -/// -/// This task will read chunks from the network, split them into length prefixed -/// frames, deserialize those frames, and send the result to the flume channel. -/// -/// If there is a network error or the flume channel closes or the request -/// stream is simply ended this task will terminate. -/// -/// So it is fine to ignore the returned [`JoinHandle`]. -/// -/// The HTTP2 request comes from *req* and the data is sent to `req_tx`. -fn spawn_recv_forwarder( - req: Body, - req_tx: Sender>, -) -> JoinHandle> { - tokio::spawn(async move { - let mut stream = req; - let mut buf = Vec::new(); - - while let Some(chunk) = stream.next().await { - match chunk.as_ref() { - Ok(chunk) => { - event!(Level::TRACE, "Server got {} bytes", chunk.len()); - if buf.is_empty() { - // try to forward directly from buffer - let sent = try_forward_all(chunk, &req_tx).await?; - // add just the rest, if any - buf.extend_from_slice(&chunk[sent..]); - } else { - // no choice but to add it all - buf.extend_from_slice(chunk); - } - } - Err(cause) => { - // Indicates that the connection has been closed on the client side. - // This is a normal occurrence, e.g. when the client has raced the RPC - // call with something else and has droppped the future. - debug!("Network error: {}", cause); - break; - } - }; - let sent = try_forward_all(&buf, &req_tx).await?; - // remove the forwarded bytes. - // Frequently this will be the entire buffer, so no memcpy but just set the size to 0 - buf.drain(..sent); - } - Ok(()) - }) -} - -// 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 HyperListener { - fn clone(&self) -> Self { - Self { - channel: self.channel.clone(), - stop_tx: self.stop_tx.clone(), - local_addr: self.local_addr.clone(), - config: self.config.clone(), - _p: PhantomData, - } - } -} - -/// Receive stream for hyper channels. -/// -/// This is a newtype wrapper around a [`flume::async::RecvStream`] of deserialized -/// messages. -pub struct RecvStream { - recv: flume::r#async::RecvStream<'static, result::Result>, -} - -impl RecvStream { - /// Creates a new [`RecvStream`] from a [`flume::Receiver`]. - pub fn new(recv: flume::Receiver>) -> Self { - Self { - 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 { - fn clone(&self) -> Self { - Self { - recv: self.recv.clone(), - } - } -} - -impl Stream for RecvStream { - type Item = Result; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - Pin::new(&mut self.recv).poll_next(cx) - } -} - -/// Send sink for hyper channels -pub struct SendSink { - sink: flume::r#async::SendSink<'static, io::Result>, - config: Arc, - _p: PhantomData, -} - -impl SendSink { - fn new(sender: flume::Sender>, config: Arc) -> Self { - Self { - sink: sender.into_sink(), - config, - _p: PhantomData, - } - } - fn serialize(&self, item: Out) -> Result { - let mut data = Vec::with_capacity(1024); - data.extend_from_slice(&[0u8; 4]); - let mut data = postcard::to_extend(&item, data).map_err(SendError::SerializeError)?; - let len = data.len() - 4; - if len > self.config.max_payload_size { - return Err(SendError::SizeError(len)); - } - let len: u32 = len.try_into().expect("max_payload_size fits into u32"); - 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 { - type Error = SendError; - - fn poll_ready( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - Pin::new(&mut self.sink) - .poll_ready(cx) - .map_err(|_| SendError::ReceiverDropped) - } - - fn start_send(mut self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { - // figure out what to send and what to return - let (send, res) = match self.serialize(item) { - Ok(data) => (Ok(data), Ok(())), - Err(cause) => ( - Err(io::Error::new(io::ErrorKind::Other, cause.to_string())), - Err(cause), - ), - }; - // attempt sending - Pin::new(&mut self.sink) - .start_send(send) - .map_err(|_| SendError::ReceiverDropped)?; - res - } - - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - Pin::new(&mut self.sink) - .poll_flush(cx) - .map_err(|_| SendError::ReceiverDropped) - } - - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - Pin::new(&mut self.sink) - .poll_close(cx) - .map_err(|_| SendError::ReceiverDropped) - } -} - -/// Send error for hyper channels. -#[derive(Debug)] -pub enum SendError { - /// Error when postcard serializing the message. - SerializeError(postcard::Error), - /// The message is too large to be sent. - SizeError(usize), - /// The connection has been closed. - ReceiverDropped, -} - -impl fmt::Display for SendError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self, f) - } -} - -impl error::Error for SendError {} - -/// Receive error for hyper channels. -#[derive(Debug)] -pub enum RecvError { - /// Error when postcard deserializing the message. - DeserializeError(postcard::Error), - /// Hyper network error. - NetworkError(hyper::Error), -} - -impl fmt::Display for RecvError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self, f) - } -} - -impl error::Error for RecvError {} - -/// OpenError for hyper channels. -#[derive(Debug)] -pub enum OpenError { - /// Hyper http error - HyperHttp(hyper::http::Error), - /// Generic hyper error - Hyper(hyper::Error), - /// The remote side of the channel was dropped - RemoteDropped, -} - -impl fmt::Display for OpenError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl std::error::Error for OpenError {} - -/// AcceptError for hyper channels. -/// -/// There is not much that can go wrong with hyper channels. -#[derive(Debug)] -pub enum AcceptError { - /// Hyper error - Hyper(hyper::http::Error), - /// The remote side of the channel was dropped - RemoteDropped, -} - -impl fmt::Display for AcceptError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for AcceptError {} - -impl ConnectionErrors for HyperConnector { - type SendError = self::SendError; - - type RecvError = self::RecvError; - - type OpenError = OpenError; - - type AcceptError = AcceptError; -} - -impl StreamTypes for HyperConnector { - type In = In; - type Out = Out; - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; -} - -impl Connector for HyperConnector { - async fn open(&self) -> Result<(Self::SendSink, Self::RecvStream), Self::OpenError> { - let (out_tx, out_rx) = flume::bounded::>(32); - let req: Request = Request::post(&self.inner.uri) - .body(Body::wrap_stream(out_rx.into_stream())) - .map_err(OpenError::HyperHttp)?; - let res = self - .inner - .client - .request(req) - .await - .map_err(OpenError::Hyper)?; - let (in_tx, in_rx) = flume::bounded::>(32); - spawn_recv_forwarder(res.into_body(), in_tx); - - let out_tx = self::SendSink::new(out_tx, self.inner.config.clone()); - let in_rx = self::RecvStream::new(in_rx); - Ok((out_tx, in_rx)) - } -} - -impl ConnectionErrors for HyperListener { - type SendError = self::SendError; - type RecvError = self::RecvError; - type OpenError = AcceptError; - type AcceptError = AcceptError; -} - -impl StreamTypes for HyperListener { - type In = In; - type Out = Out; - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; -} - -impl Listener for HyperListener { - fn local_addr(&self) -> &[LocalAddr] { - &self.local_addr - } - - async fn accept(&self) -> Result<(Self::SendSink, Self::RecvStream), AcceptError> { - let (recv, send) = self - .channel - .recv_async() - .await - .map_err(|_| AcceptError::RemoteDropped)?; - Ok(( - SendSink::new(send, self.config.clone()), - RecvStream::new(recv), - )) - } -} diff --git a/old/src/transport/iroh.rs b/old/src/transport/iroh.rs deleted file mode 100644 index 04802d3..0000000 --- a/old/src/transport/iroh.rs +++ /dev/null @@ -1,732 +0,0 @@ -//! iroh transport implementation based on [iroh](https://crates.io/crates/iroh) - -use std::{ - collections::BTreeSet, - fmt, - future::Future, - io, - iter::once, - marker::PhantomData, - net::SocketAddr, - pin::{pin, Pin}, - sync::Arc, - task::{Context, Poll}, -}; - -use flume::TryRecvError; -use futures_lite::Stream; -use futures_sink::Sink; -use iroh::{endpoint::Connection, NodeAddr, NodeId}; -use pin_project::pin_project; -use serde::{de::DeserializeOwned, Serialize}; -use tokio::{sync::oneshot, task::yield_now}; -use tracing::{debug_span, Instrument}; - -use super::{ - util::{FramedPostcardRead, FramedPostcardWrite}, - StreamTypes, -}; -use crate::{ - transport::{ConnectionErrors, Connector, Listener, LocalAddr}, - RpcMessage, -}; - -const MAX_FRAME_LENGTH: usize = 1024 * 1024 * 16; - -#[derive(Debug)] -struct ListenerInner { - endpoint: Option, - task: Option>, - local_addr: Vec, - receiver: flume::Receiver, -} - -impl Drop for ListenerInner { - fn drop(&mut self) { - tracing::debug!("Dropping server endpoint"); - if let Some(endpoint) = self.endpoint.take() { - if let Ok(handle) = tokio::runtime::Handle::try_current() { - // spawn a task to wait for the endpoint to notify peers that it is closing - let span = debug_span!("closing listener"); - handle.spawn( - async move { - // iroh endpoint's close is async, and internally it waits the - // underlying quinn endpoint to be idle. - endpoint.close().await; - } - .instrument(span), - ); - } - } - if let Some(task) = self.task.take() { - task.abort() - } - } -} - -/// Access control for the server, either unrestricted or limited to a list of nodes that can -/// connect to the server endpoint -#[derive(Debug, Clone)] -pub enum AccessControl { - /// Unrestricted access, anyone can connect - Unrestricted, - /// Restricted access, only nodes in the list can connect, all other nodes will be rejected - Allowed(Vec), -} - -/// A server endpoint using a quinn connection -#[derive(Debug)] -pub struct IrohListener { - inner: Arc, - _p: PhantomData<(In, Out)>, -} - -impl IrohListener { - /// handles RPC requests from a connection - /// - /// to cleanly shut down the handler, drop the receiver side of the sender. - async fn connection_handler(connection: 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!(?e, "Peer closed the connection"); - break; - } - Err(e) => { - tracing::debug!(?e, "Error accepting stream"); - 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: iroh::Endpoint, - sender: flume::Sender, - allowed_node_ids: BTreeSet, - ) { - 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 connection = match connecting.await { - Ok(connection) => connection, - Err(e) => { - tracing::warn!(?e, "Error accepting connection"); - continue; - } - }; - - // When the `allowed_node_ids` is empty, it's empty forever, so the CPU's branch - // prediction should always optimize this block away from this loop. - // The same applies when it isn't empty, ignoring the check for emptiness and always - // extracting the node id and checking if it's in the set. - if !allowed_node_ids.is_empty() { - let Ok(client_node_id) = connection.remote_node_id().map_err(|e| { - tracing::error!( - ?e, - "Failed to extract iroh node id from incoming connection", - ) - }) else { - connection.close(0u32.into(), b"failed to extract iroh node id"); - continue; - }; - - if !allowed_node_ids.contains(&client_node_id) { - connection.close(0u32.into(), b"forbidden node id"); - continue; - } - } - - tracing::debug!( - "Connection established from {:?}", - connection.remote_node_id() - ); - - tracing::debug!("Spawning connection handler..."); - tokio::spawn(Self::connection_handler(connection, sender.clone())); - } - } - - /// Create a new server channel, given a quinn endpoint, with unrestricted access by node id - /// - /// The server channel will take care of listening on the endpoint and spawning - /// handlers for new connections. - pub fn new(endpoint: iroh::Endpoint) -> io::Result { - Self::new_with_access_control(endpoint, AccessControl::Unrestricted) - } - - /// Create a new server endpoint, with specified access control - /// - /// The server channel will take care of listening on the endpoint and spawning - /// handlers for new connections. - pub fn new_with_access_control( - endpoint: iroh::Endpoint, - access_control: AccessControl, - ) -> io::Result { - let allowed_node_ids = match access_control { - AccessControl::Unrestricted => BTreeSet::new(), - AccessControl::Allowed(list) if list.is_empty() => { - return Err(io::Error::other( - "Empty list of allowed nodes, \ - endpoint would reject all connections", - )); - } - AccessControl::Allowed(list) => BTreeSet::from_iter(list), - }; - - let (ipv4_socket_addr, maybe_ipv6_socket_addr) = endpoint.bound_sockets(); - let (sender, receiver) = flume::bounded(16); - let task = tokio::spawn(Self::endpoint_handler( - endpoint.clone(), - sender, - allowed_node_ids, - )); - - Ok(Self { - inner: Arc::new(ListenerInner { - endpoint: Some(endpoint), - task: Some(task), - local_addr: once(LocalAddr::Socket(ipv4_socket_addr)) - .chain(maybe_ipv6_socket_addr.map(LocalAddr::Socket)) - .collect(), - receiver, - }), - _p: 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 { - inner: Arc::new(ListenerInner { - endpoint: None, - task: Some(task), - local_addr: vec![LocalAddr::Socket(local_addr)], - receiver, - }), - _p: PhantomData, - } - } - - /// 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(ListenerInner { - endpoint: None, - task: None, - local_addr: vec![LocalAddr::Socket(local_addr)], - receiver, - }), - _p: PhantomData, - } - } -} - -impl Clone for IrohListener { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - _p: PhantomData, - } - } -} - -impl ConnectionErrors for IrohListener { - type SendError = io::Error; - type RecvError = io::Error; - type OpenError = quinn::ConnectionError; - type AcceptError = quinn::ConnectionError; -} - -impl StreamTypes for IrohListener { - type In = In; - type Out = Out; - type SendSink = SendSink; - type RecvStream = RecvStream; -} - -impl Listener for IrohListener { - async fn accept(&self) -> Result<(Self::SendSink, Self::RecvStream), AcceptError> { - let (send, recv) = self - .inner - .receiver - .recv_async() - .await - .map_err(|_| quinn::ConnectionError::LocallyClosed)?; - - Ok((SendSink::new(send), RecvStream::new(recv))) - } - - fn local_addr(&self) -> &[LocalAddr] { - &self.inner.local_addr - } -} - -type SocketInner = (quinn::SendStream, quinn::RecvStream); - -#[derive(Debug)] -struct ClientConnectionInner { - /// 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 send new received connections - requests_tx: flume::Sender>>, -} - -impl Drop for ClientConnectionInner { - fn drop(&mut self) { - tracing::debug!("Dropping client connection"); - if let Some(endpoint) = self.endpoint.take() { - if let Ok(handle) = tokio::runtime::Handle::try_current() { - // spawn a task to wait for the endpoint to notify peers that it is closing - let span = debug_span!("closing client endpoint"); - handle.spawn( - async move { - endpoint.close().await; - } - .instrument(span), - ); - } - } - // 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() { - tracing::debug!("Aborting task"); - task.abort(); - } - } -} - -/// A connection using an iroh connection -pub struct IrohConnector { - inner: Arc, - _p: PhantomData<(In, Out)>, -} - -impl IrohConnector { - async fn single_connection_handler( - connection: Connection, - requests_rx: flume::Receiver>>, - ) { - loop { - tracing::debug!("Awaiting request for new bidi substream..."); - let Ok(request_tx) = requests_rx.recv_async().await else { - tracing::info!("Single connection handler finished"); - return; - }; - - tracing::debug!("Got request for new bidi substream"); - match connection.open_bi().await { - Ok(pair) => { - tracing::debug!("Bidi substream opened"); - if request_tx.send(Ok(pair)).is_err() { - tracing::debug!("requester dropped"); - } - } - Err(e) => { - tracing::warn!(?e, "error opening bidi substream"); - if request_tx - .send(anyhow::Context::context( - Err(e), - "error opening bidi substream", - )) - .is_err() - { - tracing::debug!("requester dropped"); - } - } - } - } - } - - /// 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: iroh::Endpoint, - node_addr: NodeAddr, - alpn: Vec, - requests_rx: flume::Receiver>>, - ) { - let mut reconnect = pin!(ReconnectHandler { - endpoint, - state: ConnectionState::NotConnected, - node_addr, - alpn, - }); - - let mut pending_request: Option>> = None; - let mut connection: Option = None; - - loop { - // First we check if there is already a request ready in the channel - if pending_request.is_none() { - pending_request = match requests_rx.try_recv() { - Ok(req) => Some(req), - Err(TryRecvError::Empty) => None, - Err(TryRecvError::Disconnected) => { - tracing::debug!("client dropped"); - if let Some(connection) = connection { - connection.close(0u32.into(), b"requester dropped"); - } - break; - } - }; - } - - // If not connected, we attempt to establish a connection - if !reconnect.connected() { - tracing::trace!("tick: connection result"); - match reconnect.as_mut().await { - Ok(new_connection) => { - connection = Some(new_connection); - } - Err(e) => { - // If there was a pending request, we error it out as we're not connected - if let Some(request_ack_tx) = pending_request.take() { - if request_ack_tx.send(Err(e)).is_err() { - tracing::debug!("requester dropped"); - } - } - - // Yielding back to the runtime, otherwise this can run on a busy loop - // due to the always ready nature of things, messing up with single thread - // runtime flavor of tokio - yield_now().await; - } - } - // If we didn't have a ready request in the channel, we wait for one - } else if pending_request.is_none() { - let Ok(req) = requests_rx.recv_async().await else { - tracing::debug!("client dropped"); - if let Some(connection) = connection { - connection.close(0u32.into(), b"requester dropped"); - } - break; - }; - - tracing::trace!("tick: bidi request"); - pending_request = Some(req); - } - - // If we have a connection and a pending request, we good, just process it - if let Some(connection) = connection.as_mut() { - if let Some(request) = pending_request.take() { - 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!(?e, "error opening bidi substream"); - tracing::warn!("recreating connection"); - // NOTE: the connection might be stale, so we recreate the - // connection and set the request as pending instead of - // sending the error as a response - reconnect.set_not_connected(); - pending_request = Some(request); - } - } - } - } - } - } - - async fn reconnect_handler( - endpoint: iroh::Endpoint, - addr: NodeAddr, - alpn: Vec, - requests_rx: flume::Receiver>>, - ) { - Self::reconnect_handler_inner(endpoint, addr, alpn, requests_rx).await; - tracing::info!("Reconnect handler finished"); - } - - /// Create a new channel - pub fn from_connection(connection: Connection) -> Self { - let (requests_tx, requests_rx) = flume::bounded(16); - let task = tokio::spawn(Self::single_connection_handler(connection, requests_rx)); - Self { - inner: Arc::new(ClientConnectionInner { - endpoint: None, - task: Some(task), - requests_tx, - }), - _p: PhantomData, - } - } - - /// Create a new channel - pub fn new(endpoint: iroh::Endpoint, node_addr: impl Into, alpn: Vec) -> Self { - let (requests_tx, requests_rx) = flume::bounded(16); - let task = tokio::spawn(Self::reconnect_handler( - endpoint.clone(), - node_addr.into(), - alpn, - requests_rx, - )); - Self { - inner: Arc::new(ClientConnectionInner { - endpoint: Some(endpoint), - task: Some(task), - requests_tx, - }), - _p: PhantomData, - } - } -} - -struct ReconnectHandler { - endpoint: iroh::Endpoint, - state: ConnectionState, - node_addr: NodeAddr, - alpn: Vec, -} - -impl ReconnectHandler { - pub fn set_not_connected(&mut self) { - self.state.set_not_connected() - } - - pub fn connected(&self) -> bool { - matches!(self.state, ConnectionState::Connected(_)) - } -} - -enum ConnectionState { - /// There is no active connection. An attempt to connect will be made. - NotConnected, - /// Connecting to the remote. - Connecting(Pin> + Send>>), - /// A connection is already established. In this state, no more connection attempts are made. - Connected(Connection), - /// Intermediate state while processing. - Poisoned, -} - -impl ConnectionState { - pub fn poison(&mut self) -> Self { - std::mem::replace(self, Self::Poisoned) - } - - pub fn set_not_connected(&mut self) { - *self = Self::NotConnected - } -} - -impl Future for ReconnectHandler { - type Output = anyhow::Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.state.poison() { - ConnectionState::NotConnected => { - self.state = ConnectionState::Connecting(Box::pin({ - let endpoint = self.endpoint.clone(); - let node_addr = self.node_addr.clone(); - let alpn = self.alpn.clone(); - async move { endpoint.connect(node_addr, &alpn).await } - })); - self.poll(cx) - } - - ConnectionState::Connecting(mut connecting) => match connecting.as_mut().poll(cx) { - Poll::Ready(res) => match res { - Ok(connection) => { - self.state = ConnectionState::Connected(connection.clone()); - Poll::Ready(Ok(connection)) - } - Err(e) => { - self.state = ConnectionState::NotConnected; - Poll::Ready(Err(e)) - } - }, - Poll::Pending => { - self.state = ConnectionState::Connecting(connecting); - Poll::Pending - } - }, - - ConnectionState::Connected(connection) => { - self.state = ConnectionState::Connected(connection.clone()); - Poll::Ready(Ok(connection)) - } - - ConnectionState::Poisoned => unreachable!("poisoned connection state"), - } - } -} - -impl fmt::Debug for IrohConnector { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ClientChannel") - .field("inner", &self.inner) - .finish() - } -} - -impl Clone for IrohConnector { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - _p: PhantomData, - } - } -} - -impl ConnectionErrors for IrohConnector { - type SendError = io::Error; - type RecvError = io::Error; - type OpenError = anyhow::Error; - type AcceptError = anyhow::Error; -} - -impl StreamTypes for IrohConnector { - type In = In; - type Out = Out; - type SendSink = SendSink; - type RecvStream = RecvStream; -} - -impl Connector for IrohConnector { - async fn open(&self) -> Result<(Self::SendSink, Self::RecvStream), Self::OpenError> { - let (request_ack_tx, request_ack_rx) = oneshot::channel(); - - self.inner - .requests_tx - .send_async(request_ack_tx) - .await - .map_err(|_| quinn::ConnectionError::LocallyClosed)?; - - let (send, recv) = request_ack_rx - .await - .map_err(|_| quinn::ConnectionError::LocallyClosed)??; - - Ok((SendSink::new(send), RecvStream::new(recv))) - } -} - -/// A sink that wraps a quinn SendStream with length delimiting and postcard -/// -/// If you want to send bytes directly, use [SendSink::into_inner] to get the -/// underlying [quinn::SendStream]. -#[pin_project] -pub struct SendSink(#[pin] FramedPostcardWrite); - -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 = FramedPostcardWrite::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; - - fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.project().0).poll_ready(cx) - } - - fn start_send(self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { - Pin::new(&mut self.project().0).start_send(item) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.project().0).poll_flush(cx) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.project().0).poll_close(cx) - } -} - -/// A stream that wraps a quinn RecvStream with length delimiting and postcard -/// -/// If you want to receive bytes directly, use [RecvStream::into_inner] to get -/// the underlying [quinn::RecvStream]. -#[pin_project] -pub struct RecvStream(#[pin] FramedPostcardRead); - -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 = FramedPostcardRead::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; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.project().0).poll_next(cx) - } -} - -/// Error for open. Currently just an anyhow::Error -pub type OpenBiError = anyhow::Error; - -/// Error for accept. Currently just a ConnectionError -pub type AcceptError = quinn::ConnectionError; diff --git a/old/src/transport/mapped.rs b/old/src/transport/mapped.rs deleted file mode 100644 index 649ac1f..0000000 --- a/old/src/transport/mapped.rs +++ /dev/null @@ -1,325 +0,0 @@ -//! Transport with mapped input and output types. -use std::{ - fmt::{Debug, Display}, - marker::PhantomData, - task::{Context, Poll}, -}; - -use futures_lite::{Stream, StreamExt}; -use futures_util::SinkExt; -use pin_project::pin_project; - -use super::{ConnectionErrors, Connector, StreamTypes}; -use crate::{RpcError, RpcMessage}; - -/// A connection that maps input and output types -#[derive(Debug)] -pub struct MappedConnector { - inner: C, - _p: std::marker::PhantomData<(In, Out)>, -} - -impl MappedConnector -where - C: Connector, - In: TryFrom, - C::Out: From, -{ - /// Create a new mapped connection - pub fn new(inner: C) -> Self { - Self { - inner, - _p: std::marker::PhantomData, - } - } -} - -impl Clone for MappedConnector -where - C: Clone, -{ - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - _p: std::marker::PhantomData, - } - } -} - -impl ConnectionErrors for MappedConnector -where - In: RpcMessage, - Out: RpcMessage, - C: ConnectionErrors, -{ - type RecvError = ErrorOrMapError; - type SendError = C::SendError; - type OpenError = C::OpenError; - type AcceptError = C::AcceptError; -} - -impl StreamTypes for MappedConnector -where - C: StreamTypes, - In: RpcMessage, - Out: RpcMessage, - In: TryFrom, - C::Out: From, -{ - type In = In; - type Out = Out; - type RecvStream = MappedRecvStream; - type SendSink = MappedSendSink; -} - -impl Connector for MappedConnector -where - C: Connector, - In: RpcMessage, - Out: RpcMessage, - In: TryFrom, - C::Out: From, -{ - fn open( - &self, - ) -> impl std::future::Future> - + Send { - let inner = self.inner.open(); - async move { - let (send, recv) = inner.await?; - Ok((MappedSendSink::new(send), MappedRecvStream::new(recv))) - } - } -} - -/// A combinator that maps a stream of incoming messages to a different type -#[pin_project] -pub struct MappedRecvStream { - inner: S, - _p: std::marker::PhantomData, -} - -impl MappedRecvStream { - /// Create a new mapped receive stream - pub fn new(inner: S) -> Self { - Self { - inner, - _p: std::marker::PhantomData, - } - } -} - -/// Error mapping an incoming message to the inner type -#[derive(Debug)] -pub enum ErrorOrMapError { - /// Error from the inner stream - Inner(E), - /// Conversion error - Conversion, -} - -impl std::error::Error for ErrorOrMapError {} - -impl Display for ErrorOrMapError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ErrorOrMapError::Inner(e) => write!(f, "Inner error: {}", e), - ErrorOrMapError::Conversion => write!(f, "Conversion error"), - } - } -} - -impl Stream for MappedRecvStream -where - S: Stream> + Unpin, - In: TryFrom, - E: RpcError, -{ - type Item = Result>; - - fn poll_next(self: std::pin::Pin<&mut Self>, cx: &mut Context) -> Poll> { - match self.project().inner.poll_next(cx) { - Poll::Ready(Some(Ok(item))) => { - let item = item.try_into().map_err(|_| ErrorOrMapError::Conversion); - Poll::Ready(Some(item)) - } - Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ErrorOrMapError::Inner(e)))), - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } -} - -/// A sink that maps outgoing messages to a different type -/// -/// The conversion to the underlying message type always succeeds, so this -/// is relatively simple. -#[pin_project] -pub struct MappedSendSink { - inner: S, - _p: std::marker::PhantomData<(Out, OutS)>, -} - -impl MappedSendSink { - /// Create a new mapped send sink - pub fn new(inner: S) -> Self { - Self { - inner, - _p: std::marker::PhantomData, - } - } -} - -impl futures_sink::Sink for MappedSendSink -where - S: futures_sink::Sink + Unpin, - Out: Into, -{ - type Error = S::Error; - - fn poll_ready( - self: std::pin::Pin<&mut Self>, - cx: &mut Context, - ) -> Poll> { - self.project().inner.poll_ready_unpin(cx) - } - - fn start_send(self: std::pin::Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { - self.project().inner.start_send_unpin(item.into()) - } - - fn poll_flush( - self: std::pin::Pin<&mut Self>, - cx: &mut Context, - ) -> Poll> { - self.project().inner.poll_flush_unpin(cx) - } - - fn poll_close( - self: std::pin::Pin<&mut Self>, - cx: &mut Context, - ) -> Poll> { - self.project().inner.poll_close_unpin(cx) - } -} - -/// Connection types for a mapped connection -pub struct MappedStreamTypes(PhantomData<(In, Out, C)>); - -impl Debug for MappedStreamTypes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MappedConnectionTypes").finish() - } -} - -impl Clone for MappedStreamTypes { - fn clone(&self) -> Self { - Self(PhantomData) - } -} - -impl ConnectionErrors for MappedStreamTypes -where - In: RpcMessage, - Out: RpcMessage, - C: ConnectionErrors, -{ - type RecvError = ErrorOrMapError; - type SendError = C::SendError; - type OpenError = C::OpenError; - type AcceptError = C::AcceptError; -} - -impl StreamTypes for MappedStreamTypes -where - C: StreamTypes, - In: RpcMessage, - Out: RpcMessage, - In: TryFrom, - C::Out: From, -{ - type In = In; - type Out = Out; - type RecvStream = MappedRecvStream; - type SendSink = MappedSendSink; -} - -#[cfg(test)] -#[cfg(feature = "flume-transport")] -mod tests { - - use serde::{Deserialize, Serialize}; - use testresult::TestResult; - - use super::*; - use crate::{ - server::{BoxedChannelTypes, RpcChannel}, - transport::Listener, - RpcClient, RpcServer, - }; - - #[derive(Debug, Clone, Serialize, Deserialize, derive_more::From, derive_more::TryInto)] - enum Request { - A(u64), - B(String), - } - - #[derive(Debug, Clone, Serialize, Deserialize, derive_more::From, derive_more::TryInto)] - enum Response { - A(u64), - B(String), - } - - #[derive(Debug, Clone)] - struct FullService; - - impl crate::Service for FullService { - type Req = Request; - type Res = Response; - } - - #[derive(Debug, Clone)] - struct SubService; - - impl crate::Service for SubService { - type Req = String; - type Res = String; - } - - #[tokio::test] - #[ignore] - async fn smoke() -> TestResult<()> { - async fn handle_sub_request( - _req: String, - _chan: RpcChannel>, - ) -> anyhow::Result<()> { - Ok(()) - } - // create a listener / connector pair. Type will be inferred - let (s, c) = crate::transport::flume::channel(32); - // wrap the server in a RpcServer, this is where the service type is specified - let server = RpcServer::::new(s.clone()); - // when using a boxed transport, we can omit the transport type and use the default - let _server_boxed: RpcServer = RpcServer::::new(s.boxed()); - // create a client in a RpcClient, this is where the service type is specified - let client = RpcClient::::new(c); - // when using a boxed transport, we can omit the transport type and use the default - let _boxed_client = client.clone().boxed(); - // map the client to a sub-service - let _sub_client: RpcClient = client.clone().map::(); - // when using a boxed transport, we can omit the transport type and use the default - let _sub_client_boxed: RpcClient = client.clone().map::().boxed(); - // we can not map the service to a sub-service, since we need the first message to determine which sub-service to use - while let Ok(accepting) = server.accept().await { - let (msg, chan) = accepting.read_first().await?; - match msg { - Request::A(_x) => todo!(), - Request::B(x) => { - // but we can map the channel to the sub-service, once we know which one to use - handle_sub_request(x, chan.map::().boxed()).await? - } - } - } - Ok(()) - } -} diff --git a/old/src/transport/misc/mod.rs b/old/src/transport/misc/mod.rs deleted file mode 100644 index 59bd19c..0000000 --- a/old/src/transport/misc/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Miscellaneous transport utilities -use std::convert::Infallible; - -use futures_lite::stream; -use futures_sink::Sink; - -use super::StreamTypes; -use crate::{ - transport::{ConnectionErrors, Listener}, - RpcMessage, -}; - -/// A dummy listener that does nothing -/// -/// This can be useful as a default if you want to configure -/// an optional listener. -#[derive(Debug, Default)] -pub struct DummyListener { - _p: std::marker::PhantomData<(In, Out)>, -} - -impl Clone for DummyListener { - fn clone(&self) -> Self { - Self { - _p: std::marker::PhantomData, - } - } -} - -impl ConnectionErrors for DummyListener { - type RecvError = Infallible; - type SendError = Infallible; - type OpenError = Infallible; - type AcceptError = Infallible; -} - -impl StreamTypes for DummyListener { - type In = In; - type Out = Out; - type RecvStream = stream::Pending>; - type SendSink = Box + Unpin + Send + Sync>; -} - -impl Listener for DummyListener { - async fn accept(&self) -> Result<(Self::SendSink, Self::RecvStream), Self::AcceptError> { - futures_lite::future::pending().await - } - - fn local_addr(&self) -> &[super::LocalAddr] { - &[] - } -} diff --git a/old/src/transport/mod.rs b/old/src/transport/mod.rs deleted file mode 100644 index cb7a911..0000000 --- a/old/src/transport/mod.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! Built in transports for quic-rpc -//! -//! There are two sides to a transport, a server side where connections are -//! accepted and a client side where connections are initiated. -//! -//! Connections are bidirectional typed channels, with a distinct type for -//! the send and receive side. They are *unrelated* to services. -//! -//! In the transport module, the message types are referred to as `In` and `Out`. -//! -//! A [`Connector`] can be used to *open* bidirectional typed channels using -//! [`Connector::open`]. A [`Listener`] can be used to *accept* bidirectional -//! typed channels from any of the currently opened connections to clients, using -//! [`Listener::accept`]. -//! -//! In both cases, the result is a tuple of a send side and a receive side. These -//! types are defined by implementing the [`StreamTypes`] trait. -//! -//! Errors for both sides are defined by implementing the [`ConnectionErrors`] trait. -use std::{ - fmt::{self, Debug, Display}, - net::SocketAddr, -}; - -use boxed::{BoxableConnector, BoxableListener, BoxedConnector, BoxedListener}; -use futures_lite::{Future, Stream}; -use futures_sink::Sink; -use mapped::MappedConnector; - -use crate::{RpcError, RpcMessage}; - -pub mod boxed; -pub mod combined; -#[cfg(feature = "flume-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "flume-transport")))] -pub mod flume; -#[cfg(feature = "hyper-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "hyper-transport")))] -pub mod hyper; -#[cfg(feature = "iroh-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "iroh-transport")))] -pub mod iroh; -pub mod mapped; -pub mod misc; -#[cfg(feature = "quinn-transport")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "quinn-transport")))] -pub mod quinn; - -#[cfg(any(feature = "quinn-transport", feature = "iroh-transport"))] -#[cfg_attr( - quicrpc_docsrs, - doc(cfg(any(feature = "quinn-transport", feature = "iroh-transport"))) -)] -mod util; - -/// Errors that can happen when creating and using a [`Connector`] or [`Listener`]. -pub trait ConnectionErrors: Debug + Clone + Send + Sync + 'static { - /// Error when sending a message via a channel - type SendError: RpcError; - /// Error when receiving a message via a channel - type RecvError: RpcError; - /// Error when opening a channel - type OpenError: RpcError; - /// Error when accepting a channel - type AcceptError: RpcError; -} - -/// Types that are common to both [`Connector`] and [`Listener`]. -/// -/// Having this as a separate trait is useful when writing generic code that works with both. -pub trait StreamTypes: ConnectionErrors { - /// The type of messages that can be received on the channel - type In: RpcMessage; - /// The type of messages that can be sent on the channel - type Out: RpcMessage; - /// Receive side of a bidirectional typed channel - type RecvStream: Stream> - + Send - + Sync - + Unpin - + 'static; - /// Send side of a bidirectional typed channel - type SendSink: Sink + Send + Sync + Unpin + 'static; -} - -/// A connection to a specific remote machine -/// -/// A connection can be used to open bidirectional typed channels using [`Connector::open`]. -pub trait Connector: StreamTypes { - /// Open a channel to the remote che - fn open( - &self, - ) -> impl Future> + Send; - - /// Map the input and output types of this connection - fn map(self) -> MappedConnector - where - In1: TryFrom, - Self::Out: From, - { - MappedConnector::new(self) - } - - /// Box the connection - fn boxed(self) -> BoxedConnector - where - Self: BoxableConnector + Sized + 'static, - { - self::BoxedConnector::new(self) - } -} - -/// A listener that listens for connections -/// -/// A listener can be used to accept bidirectional typed channels from any of the -/// currently opened connections to clients, using [`Listener::accept`]. -pub trait Listener: StreamTypes { - /// Accept a new typed bidirectional channel on any of the connections we - /// have currently opened. - fn accept( - &self, - ) -> impl Future> + Send; - - /// The local addresses this endpoint is bound to. - fn local_addr(&self) -> &[LocalAddr]; - - /// Box the listener - fn boxed(self) -> BoxedListener - where - Self: BoxableListener + Sized + 'static, - { - BoxedListener::new(self) - } -} - -/// The kinds of local addresses a [Listener] can be bound to. -/// -/// Returned by [Listener::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/old/src/transport/quinn.rs b/old/src/transport/quinn.rs deleted file mode 100644 index 89a69e6..0000000 --- a/old/src/transport/quinn.rs +++ /dev/null @@ -1,948 +0,0 @@ -//! QUIC transport implementation based on [quinn](https://crates.io/crates/quinn) -use std::{ - fmt, io, - marker::PhantomData, - net::SocketAddr, - pin::Pin, - result, - sync::Arc, - task::{Context, Poll}, -}; - -use futures_lite::{Future, Stream, StreamExt}; -use futures_sink::Sink; -use futures_util::FutureExt; -use pin_project::pin_project; -use serde::{de::DeserializeOwned, Serialize}; -use tokio::sync::oneshot; -use tracing::{debug_span, Instrument}; - -use super::{ - util::{FramedPostcardRead, FramedPostcardWrite}, - StreamTypes, -}; -use crate::{ - transport::{ConnectionErrors, Connector, Listener, LocalAddr}, - RpcMessage, -}; - -const MAX_FRAME_LENGTH: usize = 1024 * 1024 * 16; - -#[derive(Debug)] -struct ListenerInner { - endpoint: Option, - task: Option>, - local_addr: [LocalAddr; 1], - receiver: flume::Receiver, -} - -impl Drop for ListenerInner { - fn drop(&mut self) { - tracing::debug!("Dropping listener"); - if let Some(endpoint) = self.endpoint.take() { - endpoint.close(0u32.into(), b"Listener dropped"); - - if let Ok(handle) = tokio::runtime::Handle::try_current() { - // spawn a task to wait for the endpoint to notify peers that it is closing - let span = debug_span!("closing listener"); - handle.spawn( - async move { - endpoint.wait_idle().await; - } - .instrument(span), - ); - } - } - if let Some(task) = self.task.take() { - task.abort() - } - } -} - -/// A listener using a quinn connection -#[derive(Debug)] -pub struct QuinnListener { - inner: Arc, - _p: PhantomData<(In, Out)>, -} - -impl QuinnListener { - /// 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 accepting 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(ListenerInner { - endpoint: Some(endpoint), - task: Some(task), - local_addr: [LocalAddr::Socket(local_addr)], - receiver, - }), - _p: 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 { - inner: Arc::new(ListenerInner { - endpoint: None, - task: Some(task), - local_addr: [LocalAddr::Socket(local_addr)], - receiver, - }), - _p: PhantomData, - } - } - - /// 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(ListenerInner { - endpoint: None, - task: None, - local_addr: [LocalAddr::Socket(local_addr)], - receiver, - }), - _p: PhantomData, - } - } -} - -impl Clone for QuinnListener { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - _p: PhantomData, - } - } -} - -impl ConnectionErrors for QuinnListener { - type SendError = io::Error; - type RecvError = io::Error; - type OpenError = quinn::ConnectionError; - type AcceptError = quinn::ConnectionError; -} - -impl StreamTypes for QuinnListener { - type In = In; - type Out = Out; - type SendSink = self::SendSink; - type RecvStream = self::RecvStream; -} - -impl Listener for QuinnListener { - async fn accept(&self) -> Result<(Self::SendSink, Self::RecvStream), AcceptError> { - let (send, recv) = self - .inner - .receiver - .recv_async() - .await - .map_err(|_| quinn::ConnectionError::LocallyClosed)?; - Ok((SendSink::new(send), RecvStream::new(recv))) - } - - fn local_addr(&self) -> &[LocalAddr] { - &self.inner.local_addr - } -} - -type SocketInner = (quinn::SendStream, quinn::RecvStream); - -#[derive(Debug)] -struct ClientConnectionInner { - /// 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 ClientConnectionInner { - fn drop(&mut self) { - tracing::debug!("Dropping client connection"); - if let Some(endpoint) = self.endpoint.take() { - endpoint.close(0u32.into(), b"client connection dropped"); - if let Ok(handle) = tokio::runtime::Handle::try_current() { - // spawn a task to wait for the endpoint to notify peers that it is closing - let span = debug_span!("closing client endpoint"); - handle.spawn( - async move { - endpoint.wait_idle().await; - } - .instrument(span), - ); - } - } - // 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() { - tracing::debug!("Aborting task"); - task.abort(); - } - } -} - -/// A connection using a quinn connection -pub struct QuinnConnector { - inner: Arc, - _p: PhantomData<(In, Out)>, -} - -impl QuinnConnector { - 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>>, - ) { - let reconnect = ReconnectHandler { - endpoint, - state: ConnectionState::NotConnected, - addr, - name, - }; - tokio::pin!(reconnect); - - let mut receiver = Receiver::new(&requests); - - let mut pending_request: Option< - oneshot::Sender>, - > = None; - let mut connection = None; - - enum Racer { - Reconnect(Result), - Channel(Option>>), - } - - loop { - let mut conn_result = None; - let mut chann_result = None; - if !reconnect.connected() && pending_request.is_none() { - match futures_lite::future::race( - reconnect.as_mut().map(Racer::Reconnect), - receiver.next().map(Racer::Channel), - ) - .await - { - Racer::Reconnect(connection_result) => conn_result = Some(connection_result), - Racer::Channel(channel_result) => { - chann_result = Some(channel_result); - } - } - } else if !reconnect.connected() { - // only need a new connection - conn_result = Some(reconnect.as_mut().await); - } else if pending_request.is_none() { - // there is a connection, just need a request - chann_result = Some(receiver.next().await); - } - - if let Some(conn_result) = conn_result { - tracing::trace!("tick: connection result"); - match conn_result { - Ok(new_connection) => { - connection = Some(new_connection); - } - Err(e) => { - let connection_err = match e { - ReconnectErr::Connect(e) => { - // TODO(@divma): the type for now accepts only a - // ConnectionError, not a ConnectError. I'm mapping this now to - // some ConnectionError since before it was not even reported. - // Maybe adjust the type? - tracing::warn!(%e, "error calling connect"); - quinn::ConnectionError::Reset - } - ReconnectErr::Connection(e) => { - tracing::warn!(%e, "failed to connect"); - e - } - }; - if let Some(request) = pending_request.take() { - if request.send(Err(connection_err)).is_err() { - tracing::debug!("requester dropped"); - } - } - } - } - } - - if let Some(req) = chann_result { - tracing::trace!("tick: bidi request"); - match req { - Some(request) => pending_request = Some(request), - None => { - tracing::debug!("client dropped"); - if let Some(connection) = connection { - connection.close(0u32.into(), b"requester dropped"); - } - break; - } - } - } - - if let Some(connection) = connection.as_mut() { - if let Some(request) = pending_request.take() { - 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"); - // NOTE: the connection might be stale, so we recreate the - // connection and set the request as pending instead of - // sending the error as a response - reconnect.set_not_connected(); - pending_request = Some(request); - } - } - } - } - } - } - - async fn reconnect_handler( - endpoint: quinn::Endpoint, - addr: SocketAddr, - name: String, - requests: flume::Receiver>>, - ) { - Self::reconnect_handler_inner(endpoint, addr, name, requests).await; - tracing::info!("Reconnect handler finished"); - } - - /// 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(ClientConnectionInner { - endpoint: None, - task: Some(task), - sender, - }), - _p: PhantomData, - } - } - - /// Create a new channel - 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(ClientConnectionInner { - endpoint: Some(endpoint), - task: Some(task), - sender, - }), - _p: PhantomData, - } - } -} - -struct ReconnectHandler { - endpoint: quinn::Endpoint, - state: ConnectionState, - addr: SocketAddr, - name: String, -} - -impl ReconnectHandler { - pub fn set_not_connected(&mut self) { - self.state.set_not_connected() - } - - pub fn connected(&self) -> bool { - matches!(self.state, ConnectionState::Connected(_)) - } -} - -enum ConnectionState { - /// There is no active connection. An attempt to connect will be made. - NotConnected, - /// Connecting to the remote. - Connecting(quinn::Connecting), - /// A connection is already established. In this state, no more connection attempts are made. - Connected(quinn::Connection), - /// Intermediate state while processing. - Poisoned, -} - -impl ConnectionState { - pub fn poison(&mut self) -> ConnectionState { - std::mem::replace(self, ConnectionState::Poisoned) - } - - pub fn set_not_connected(&mut self) { - *self = ConnectionState::NotConnected - } -} - -enum ReconnectErr { - Connect(quinn::ConnectError), - Connection(quinn::ConnectionError), -} - -impl Future for ReconnectHandler { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.state.poison() { - ConnectionState::NotConnected => match self.endpoint.connect(self.addr, &self.name) { - Ok(connecting) => { - self.state = ConnectionState::Connecting(connecting); - self.poll(cx) - } - Err(e) => { - self.state = ConnectionState::NotConnected; - Poll::Ready(Err(ReconnectErr::Connect(e))) - } - }, - ConnectionState::Connecting(mut connecting) => match Pin::new(&mut connecting).poll(cx) - { - Poll::Ready(res) => match res { - Ok(connection) => { - self.state = ConnectionState::Connected(connection.clone()); - Poll::Ready(Ok(connection)) - } - Err(e) => { - self.state = ConnectionState::NotConnected; - Poll::Ready(Err(ReconnectErr::Connection(e))) - } - }, - Poll::Pending => { - self.state = ConnectionState::Connecting(connecting); - Poll::Pending - } - }, - ConnectionState::Connected(connection) => { - self.state = ConnectionState::Connected(connection.clone()); - Poll::Ready(Ok(connection)) - } - ConnectionState::Poisoned => unreachable!("poisoned connection state"), - } - } -} - -/// Wrapper over [`flume::Receiver`] that can be used with [`tokio::select`]. -/// -/// NOTE: from https://github.com/zesterer/flume/issues/104: -/// > If RecvFut is dropped without being polled, the item is never received. -enum Receiver<'a, T> -where - Self: 'a, -{ - PreReceive(&'a flume::Receiver), - Receiving(&'a flume::Receiver, flume::r#async::RecvFut<'a, T>), - Poisoned, -} - -impl<'a, T> Receiver<'a, T> { - fn new(recv: &'a flume::Receiver) -> Self { - Receiver::PreReceive(recv) - } - - fn poison(&mut self) -> Self { - std::mem::replace(self, Self::Poisoned) - } -} - -impl Stream for Receiver<'_, T> { - type Item = T; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.poison() { - Receiver::PreReceive(recv) => { - let fut = recv.recv_async(); - *self = Receiver::Receiving(recv, fut); - self.poll_next(cx) - } - Receiver::Receiving(recv, mut fut) => match Pin::new(&mut fut).poll(cx) { - Poll::Ready(Ok(t)) => { - *self = Receiver::PreReceive(recv); - Poll::Ready(Some(t)) - } - Poll::Ready(Err(flume::RecvError::Disconnected)) => { - *self = Receiver::PreReceive(recv); - Poll::Ready(None) - } - Poll::Pending => { - *self = Receiver::Receiving(recv, fut); - Poll::Pending - } - }, - Receiver::Poisoned => unreachable!("poisoned receiver state"), - } - } -} - -impl fmt::Debug for QuinnConnector { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ClientChannel") - .field("inner", &self.inner) - .finish() - } -} - -impl Clone for QuinnConnector { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - _p: PhantomData, - } - } -} - -impl ConnectionErrors for QuinnConnector { - type SendError = io::Error; - type RecvError = io::Error; - type OpenError = quinn::ConnectionError; - type AcceptError = quinn::ConnectionError; -} - -impl StreamTypes for QuinnConnector { - type In = In; - type Out = Out; - type SendSink = self::SendSink; - type RecvStream = self::RecvStream; -} - -impl Connector for QuinnConnector { - async fn open(&self) -> Result<(Self::SendSink, Self::RecvStream), Self::OpenError> { - let (sender, receiver) = oneshot::channel(); - self.inner - .sender - .send_async(sender) - .await - .map_err(|_| quinn::ConnectionError::LocallyClosed)?; - let (send, recv) = receiver - .await - .map_err(|_| quinn::ConnectionError::LocallyClosed)??; - Ok((SendSink::new(send), RecvStream::new(recv))) - } -} - -/// A sink that wraps a quinn SendStream with length delimiting and postcard -/// -/// If you want to send bytes directly, use [SendSink::into_inner] to get the -/// underlying [quinn::SendStream]. -#[pin_project] -pub struct SendSink(#[pin] FramedPostcardWrite); - -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 = FramedPostcardWrite::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; - - fn poll_ready( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - Pin::new(&mut self.project().0).poll_ready(cx) - } - - fn start_send(self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { - Pin::new(&mut self.project().0).start_send(item) - } - - fn poll_flush( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - Pin::new(&mut self.project().0).poll_flush(cx) - } - - fn poll_close( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - Pin::new(&mut self.project().0).poll_close(cx) - } -} - -/// A stream that wraps a quinn RecvStream with length delimiting and postcard -/// -/// If you want to receive bytes directly, use [RecvStream::into_inner] to get -/// the underlying [quinn::RecvStream]. -#[pin_project] -pub struct RecvStream(#[pin] FramedPostcardRead); - -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 = FramedPostcardRead::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; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - Pin::new(&mut self.project().0).poll_next(cx) - } -} - -/// Error for open. Currently just a quinn::ConnectionError -pub type OpenError = quinn::ConnectionError; - -/// Error for accept. Currently just a quinn::ConnectionError -pub type AcceptError = quinn::ConnectionError; - -/// 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 From for CreateChannelError { - fn from(e: io::Error) -> Self { - CreateChannelError::Io(e.kind(), e.to_string()) - } -} - -impl From for CreateChannelError { - fn from(e: quinn::ConnectionError) -> Self { - CreateChannelError::Connection(e) - } -} - -impl From for CreateChannelError { - fn from(e: quinn::ConnectError) -> Self { - CreateChannelError::Connect(e) - } -} - -impl fmt::Display for CreateChannelError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -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(), - }) -} - -#[cfg(feature = "test-utils")] -mod quinn_setup_utils { - use std::{net::SocketAddr, sync::Arc}; - - use anyhow::Result; - use quinn::{crypto::rustls::QuicClientConfig, ClientConfig, Endpoint, ServerConfig}; - - /// Builds default quinn client config and trusts given certificates. - /// - /// ## Args - /// - /// - server_certs: a list of trusted certificates in DER format. - pub fn configure_client(server_certs: &[&[u8]]) -> Result { - let mut certs = rustls::RootCertStore::empty(); - for cert in server_certs { - let cert = rustls::pki_types::CertificateDer::from(cert.to_vec()); - certs.add(cert)?; - } - - let crypto_client_config = rustls::ClientConfig::builder_with_provider(Arc::new( - rustls::crypto::ring::default_provider(), - )) - .with_protocol_versions(&[&rustls::version::TLS13]) - .expect("valid versions") - .with_root_certificates(certs) - .with_no_client_auth(); - let quic_client_config = - quinn::crypto::rustls::QuicClientConfig::try_from(crypto_client_config)?; - - Ok(ClientConfig::new(Arc::new(quic_client_config))) - } - - /// Constructs a QUIC endpoint configured for use a client only. - /// - /// ## Args - /// - /// - server_certs: list of trusted certificates. - pub fn make_client_endpoint(bind_addr: SocketAddr, server_certs: &[&[u8]]) -> Result { - let client_cfg = configure_client(server_certs)?; - let mut endpoint = Endpoint::client(bind_addr)?; - endpoint.set_default_client_config(client_cfg); - Ok(endpoint) - } - - /// Create a server endpoint with a self-signed certificate - /// - /// Returns the server endpoint and the certificate in DER format - pub fn make_server_endpoint(bind_addr: SocketAddr) -> Result<(Endpoint, Vec)> { - let (server_config, server_cert) = configure_server()?; - let endpoint = Endpoint::server(server_config, bind_addr)?; - Ok((endpoint, server_cert)) - } - - /// Create a quinn server config with a self-signed certificate - /// - /// Returns the server config and the certificate in DER format - pub fn configure_server() -> anyhow::Result<(ServerConfig, Vec)> { - let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?; - let cert_der = cert.cert.der(); - let priv_key = rustls::pki_types::PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der()); - let cert_chain = vec![cert_der.clone()]; - - let mut server_config = ServerConfig::with_single_cert(cert_chain, priv_key.into())?; - Arc::get_mut(&mut server_config.transport) - .unwrap() - .max_concurrent_uni_streams(0_u8.into()); - - Ok((server_config, cert_der.to_vec())) - } - - /// Constructs a QUIC endpoint that trusts all certificates. - /// - /// This is useful for testing and local connections, but should be used with care. - pub fn make_insecure_client_endpoint(bind_addr: SocketAddr) -> Result { - let crypto = rustls::ClientConfig::builder() - .dangerous() - .with_custom_certificate_verifier(Arc::new(SkipServerVerification)) - .with_no_client_auth(); - - let client_cfg = QuicClientConfig::try_from(crypto)?; - let client_cfg = ClientConfig::new(Arc::new(client_cfg)); - let mut endpoint = Endpoint::client(bind_addr)?; - endpoint.set_default_client_config(client_cfg); - Ok(endpoint) - } - - #[derive(Debug)] - struct SkipServerVerification; - - impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( - &self, - _end_entity: &rustls::pki_types::CertificateDer<'_>, - _intermediates: &[rustls::pki_types::CertificateDer<'_>], - _server_name: &rustls::pki_types::ServerName<'_>, - _ocsp_response: &[u8], - _now: rustls::pki_types::UnixTime, - ) -> Result { - Ok(rustls::client::danger::ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - _message: &[u8], - _cert: &rustls::pki_types::CertificateDer<'_>, - _dss: &rustls::DigitallySignedStruct, - ) -> Result { - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) - } - - fn verify_tls13_signature( - &self, - _message: &[u8], - _cert: &rustls::pki_types::CertificateDer<'_>, - _dss: &rustls::DigitallySignedStruct, - ) -> Result { - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) - } - - fn supported_verify_schemes(&self) -> Vec { - use rustls::SignatureScheme::*; - // list them all, we don't care. - vec![ - RSA_PKCS1_SHA1, - ECDSA_SHA1_Legacy, - RSA_PKCS1_SHA256, - ECDSA_NISTP256_SHA256, - RSA_PKCS1_SHA384, - ECDSA_NISTP384_SHA384, - RSA_PKCS1_SHA512, - ECDSA_NISTP521_SHA512, - RSA_PSS_SHA256, - RSA_PSS_SHA384, - RSA_PSS_SHA512, - ED25519, - ED448, - ] - } - } -} -#[cfg(feature = "test-utils")] -pub use quinn_setup_utils::*; diff --git a/old/src/transport/util.rs b/old/src/transport/util.rs deleted file mode 100644 index 9157cd2..0000000 --- a/old/src/transport/util.rs +++ /dev/null @@ -1,188 +0,0 @@ -use std::{ - pin::Pin, - task::{self, Poll}, -}; - -use futures_lite::Stream; -use futures_sink::Sink; -use pin_project::pin_project; -use serde::{de::DeserializeOwned, Serialize}; -use tokio::io::{AsyncRead, AsyncWrite}; -use tokio_util::codec::LengthDelimitedCodec; - -#[pin_project] -pub struct FramedPostcardRead( - #[pin] - tokio_serde::SymmetricallyFramed< - tokio_util::codec::FramedRead, - In, - tokio_serde_postcard::SymmetricalPostcard, - >, -); - -impl FramedPostcardRead { - /// Wrap a socket in a length delimited codec and postcard 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); - let postcard = tokio_serde_postcard::Postcard::new(); - // 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, postcard); - Self(framed) - } -} - -impl FramedPostcardRead { - /// 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 FramedPostcardRead { - type Item = Result; - - fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - Pin::new(&mut self.project().0).poll_next(cx) - } -} - -/// Wrapper that wraps a bidirectional binary stream in a length delimited codec and postcard encoding -/// to get a bidirectional stream of rpc Messages -#[pin_project] -pub struct FramedPostcardWrite( - #[pin] - tokio_serde::SymmetricallyFramed< - tokio_util::codec::FramedWrite, - Out, - tokio_serde_postcard::SymmetricalPostcard, - >, -); - -impl FramedPostcardWrite { - /// Wrap a socket in a length delimited codec and postcard 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); - let postcard = tokio_serde_postcard::SymmetricalPostcard::new(); - // 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, postcard); - Self(framed) - } -} - -impl FramedPostcardWrite { - /// 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 FramedPostcardWrite { - type Error = std::io::Error; - - fn poll_ready( - self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - ) -> Poll> { - Pin::new(&mut self.project().0).poll_ready(cx) - } - - fn start_send(self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { - Pin::new(&mut self.project().0).start_send(item) - } - - fn poll_flush( - self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - ) -> Poll> { - Pin::new(&mut self.project().0).poll_flush(cx) - } - - fn poll_close( - self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - ) -> Poll> { - Pin::new(&mut self.project().0).poll_close(cx) - } -} - -mod tokio_serde_postcard { - use std::{io, marker::PhantomData, pin::Pin}; - - use bytes::{BufMut as _, Bytes, BytesMut}; - use pin_project::pin_project; - use serde::{Deserialize, Serialize}; - use tokio_serde::{Deserializer, Serializer}; - - #[pin_project] - pub struct Postcard { - #[pin] - buffer: Box>, - _marker: PhantomData<(Item, SinkItem)>, - } - - impl Default for Postcard { - fn default() -> Self { - Self::new() - } - } - - impl Postcard { - pub fn new() -> Self { - Self { - buffer: Box::new(None), - _marker: PhantomData, - } - } - } - - pub type SymmetricalPostcard = Postcard; - - impl Deserializer for Postcard - where - for<'a> Item: Deserialize<'a>, - { - type Error = io::Error; - - fn deserialize(self: Pin<&mut Self>, src: &BytesMut) -> Result { - postcard::from_bytes(src).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) - } - } - - impl Serializer for Postcard - where - SinkItem: Serialize, - { - type Error = io::Error; - - fn serialize(self: Pin<&mut Self>, data: &SinkItem) -> Result { - let mut this = self.project(); - let buffer = this.buffer.take().unwrap_or_default(); - let mut buffer = postcard::to_io(data, buffer.writer()) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))? - .into_inner(); - if buffer.len() <= 1024 { - let res = buffer.split().freeze(); - this.buffer.replace(buffer); - Ok(res) - } else { - Ok(buffer.freeze()) - } - } - } -} diff --git a/old/tests/flume.rs b/old/tests/flume.rs deleted file mode 100644 index 34fc900..0000000 --- a/old/tests/flume.rs +++ /dev/null @@ -1,103 +0,0 @@ -#![cfg(feature = "flume-transport")] -#![allow(non_local_definitions)] -mod math; -use math::*; -use quic_rpc::{ - server::{RpcChannel, RpcServerError}, - transport::flume, - RpcClient, RpcServer, Service, -}; -use tokio_util::task::AbortOnDropHandle; - -#[tokio::test] -async fn flume_channel_bench() -> anyhow::Result<()> { - tracing_subscriber::fmt::try_init().ok(); - let (server, client) = flume::channel(1); - - let server = RpcServer::::new(server); - let _server_handle = AbortOnDropHandle::new(tokio::spawn(ComputeService::server(server))); - let client = RpcClient::::new(client); - bench(client, 1000000).await?; - Ok(()) -} - -#[tokio::test] -async fn flume_channel_mapped_bench() -> anyhow::Result<()> { - use derive_more::{From, TryInto}; - use serde::{Deserialize, Serialize}; - - tracing_subscriber::fmt::try_init().ok(); - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - enum OuterRequest { - Inner(InnerRequest), - } - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - enum InnerRequest { - Compute(ComputeRequest), - } - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - enum OuterResponse { - Inner(InnerResponse), - } - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - enum InnerResponse { - Compute(ComputeResponse), - } - #[derive(Debug, Clone)] - struct OuterService; - impl Service for OuterService { - type Req = OuterRequest; - type Res = OuterResponse; - } - #[derive(Debug, Clone)] - struct InnerService; - impl Service for InnerService { - type Req = InnerRequest; - type Res = InnerResponse; - } - let (server, client) = flume::channel(1); - - let server = RpcServer::::new(server); - let server_handle: tokio::task::JoinHandle>> = - tokio::task::spawn(async move { - let service = ComputeService; - loop { - let (req, chan) = server.accept().await?.read_first().await?; - let service = service.clone(); - tokio::spawn(async move { - let req: OuterRequest = req; - match req { - OuterRequest::Inner(InnerRequest::Compute(req)) => { - let chan: RpcChannel = chan.map(); - let chan: RpcChannel = chan.map(); - ComputeService::handle_rpc_request(service, req, chan).await - } - } - }); - } - }); - - let client = RpcClient::::new(client); - let client: RpcClient = client.map(); - let client: RpcClient = client.map(); - 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::channel(1); - - let server = RpcServer::::new(server); - let _server_handle = AbortOnDropHandle::new(tokio::spawn(ComputeService::server(server))); - smoke_test(client).await?; - Ok(()) -} diff --git a/old/tests/hyper.rs b/old/tests/hyper.rs deleted file mode 100644 index fa67144..0000000 --- a/old/tests/hyper.rs +++ /dev/null @@ -1,297 +0,0 @@ -#![cfg(feature = "hyper-transport")] -#![cfg(feature = "macros")] -use std::{assert, net::SocketAddr, result}; - -use ::hyper::Uri; -use derive_more::{From, TryInto}; -use flume::Receiver; -use quic_rpc::{ - declare_rpc, - server::RpcServerError, - transport::hyper::{self, HyperConnector, HyperListener, RecvError}, - RpcClient, RpcServer, Service, -}; -use serde::{Deserialize, Serialize}; -use tokio::task::JoinHandle; - -mod math; -use math::*; -use tokio_util::task::AbortOnDropHandle; -mod util; - -fn run_server(addr: &SocketAddr) -> AbortOnDropHandle<()> { - let channel = HyperListener::serve(addr).unwrap(); - let server = RpcServer::new(channel); - ComputeService::server(server) -} - -#[derive(Debug, Serialize, Deserialize, From, TryInto)] -enum TestResponse { - Unit(()), - Big(Vec), - NoSer(NoSer), - NoDeser(NoDeser), -} - -type SC = HyperListener; - -/// request that can be too big -#[derive(Debug, Serialize, Deserialize)] -pub struct BigRequest(Vec); - -/// request that looks serializable but isn't -#[derive(Debug, Serialize, Deserialize)] -pub struct NoSerRequest(NoSer); - -/// request that looks deserializable but isn't -#[derive(Debug, Serialize, Deserialize)] -pub struct NoDeserRequest(NoDeser); - -/// request where the response is not serializable -#[derive(Debug, Serialize, Deserialize)] -pub struct NoSerResponseRequest; - -/// request where the response is not deserializable -#[derive(Debug, Serialize, Deserialize)] -pub struct NoDeserResponseRequest; - -/// request that can produce a response that is too big -#[derive(Debug, Serialize, Deserialize)] -pub struct BigResponseRequest(usize); - -/// helper struct that implements serde::Serialize but errors on serialization -#[derive(Debug, Deserialize)] -pub struct NoSer; - -impl serde::Serialize for NoSer { - fn serialize(&self, _serializer: S) -> Result - where - S: serde::Serializer, - { - Err(serde::ser::Error::custom("nope")) - } -} - -/// helper struct that implements serde::Deserialize but errors on deserialization -#[derive(Debug, Serialize)] -pub struct NoDeser; - -impl<'de> serde::Deserialize<'de> for NoDeser { - fn deserialize(_deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Err(serde::de::Error::custom("nope")) - } -} - -#[allow(clippy::enum_variant_names)] -#[derive(Debug, Serialize, Deserialize, From, TryInto)] -enum TestRequest { - BigRequest(BigRequest), - NoSerRequest(NoSerRequest), - NoDeserRequest(NoDeserRequest), - NoSerResponseRequest(NoSerResponseRequest), - NoDeserResponseRequest(NoDeserResponseRequest), - BigResponseRequest(BigResponseRequest), -} - -#[derive(Debug, Clone)] -struct TestService; - -impl Service for TestService { - type Req = TestRequest; - type Res = TestResponse; -} - -impl TestService { - async fn big(self, _req: BigRequest) {} - - async fn noser(self, _req: NoSerRequest) {} - - async fn nodeser(self, _req: NoDeserRequest) {} - - async fn noserresponse(self, _req: NoSerResponseRequest) -> NoSer { - NoSer - } - - async fn nodeserresponse(self, _req: NoDeserResponseRequest) -> NoDeser { - NoDeser - } - - async fn bigresponse(self, req: BigResponseRequest) -> Vec { - vec![0; req.0] - } -} - -#[tokio::test] -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 = HyperConnector::new(uri); - let client = RpcClient::new(client); - bench(client, 50000).await?; - println!("terminating server"); - Ok(()) -} - -#[tokio::test] -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 = HyperConnector::new(uri); - smoke_test(client).await?; - Ok(()) -} - -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); - -#[tokio::test] -async fn hyper_channel_errors() -> anyhow::Result<()> { - #[allow(clippy::type_complexity)] - fn run_test_server( - addr: &SocketAddr, - ) -> ( - JoinHandle>, - Receiver>>, - ) { - let channel = HyperListener::serve(addr).unwrap(); - let server = RpcServer::new(channel); - let (res_tx, res_rx) = flume::unbounded(); - let handle = tokio::spawn(async move { - loop { - let Ok(x) = server.accept().await else { - continue; - }; - let res = match x.read_first().await { - Ok((req, chan)) => match req { - TestRequest::BigRequest(req) => { - chan.rpc(req, TestService, TestService::big).await - } - TestRequest::NoSerRequest(req) => { - chan.rpc(req, TestService, TestService::noser).await - } - TestRequest::NoDeserRequest(req) => { - chan.rpc(req, TestService, TestService::nodeser).await - } - TestRequest::NoSerResponseRequest(req) => { - chan.rpc(req, TestService, TestService::noserresponse).await - } - TestRequest::NoDeserResponseRequest(req) => { - chan.rpc(req, TestService, TestService::nodeserresponse) - .await - } - TestRequest::BigResponseRequest(req) => { - chan.rpc(req, TestService, TestService::bigresponse).await - } - }, - Err(e) => Err(e), - }; - res_tx.send_async(res).await.unwrap(); - } - #[allow(unreachable_code)] - anyhow::Ok(()) - }); - (handle, res_rx) - } - - 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 = HyperConnector::new(uri); - let client = RpcClient::new(client); - - macro_rules! assert_matches { - ($e:expr, $p:pat) => { - assert!( - matches!($e, $p), - "expected {} to match {}", - stringify!($e), - stringify!($p) - ); - }; - } - macro_rules! assert_server_result { - ($p:pat) => { - let server_result = server_results.recv_async().await.unwrap(); - assert!( - matches!(server_result, $p), - "expected server result to match {}", - stringify!($p) - ); - assert!(server_results.is_empty()); - }; - } - - // small enough - should succeed - let res = client.rpc(BigRequest(vec![0; 10_000_000])).await; - assert_matches!(res, Ok(())); - assert_server_result!(Ok(())); - - // too big - should fail immediately after opening a connection - let res = client.rpc(BigRequest(vec![0; 20_000_000])).await; - assert_matches!( - res, - Err(quic_rpc::pattern::rpc::Error::Send( - hyper::SendError::SizeError(_) - )) - ); - assert_server_result!(Err(RpcServerError::EarlyClose)); - - // not serializable - should fail immediately after opening a connection - let res = client.rpc(NoSerRequest(NoSer)).await; - assert_matches!( - res, - Err(quic_rpc::pattern::rpc::Error::Send( - hyper::SendError::SerializeError(_) - )) - ); - assert_server_result!(Err(RpcServerError::EarlyClose)); - - // not deserializable - should fail on the server side - let res = client.rpc(NoDeserRequest(NoDeser)).await; - assert_matches!(res, Err(quic_rpc::pattern::rpc::Error::EarlyClose)); - assert_server_result!(Err(RpcServerError::RecvError( - hyper::RecvError::DeserializeError(_) - ))); - - // response not serializable - should fail on the server side - let res = client.rpc(NoSerResponseRequest).await; - assert_matches!(res, Err(quic_rpc::pattern::rpc::Error::EarlyClose)); - assert_server_result!(Err(RpcServerError::SendError( - hyper::SendError::SerializeError(_) - ))); - - // response not deserializable - should succeed on the server side fail on the client side - let res = client.rpc(NoDeserResponseRequest).await; - assert_matches!( - res, - Err(quic_rpc::pattern::rpc::Error::RecvError( - RecvError::DeserializeError(_) - )) - ); - assert_server_result!(Ok(())); - - // response small - should succeed - let res = client.rpc(BigResponseRequest(10_000_000)).await; - assert!(res.is_ok()); - assert_server_result!(Ok(())); - - // response big - should fail - let res = client.rpc(BigResponseRequest(20_000_000)).await; - assert_matches!(res, Err(quic_rpc::pattern::rpc::Error::EarlyClose)); - assert_server_result!(Err(RpcServerError::SendError(hyper::SendError::SizeError( - _ - )))); - - println!("terminating server"); - server_handle.abort(); - Ok(()) -} diff --git a/old/tests/iroh.rs b/old/tests/iroh.rs deleted file mode 100644 index 8acbe86..0000000 --- a/old/tests/iroh.rs +++ /dev/null @@ -1,168 +0,0 @@ -#![cfg(feature = "iroh-transport")] - -use iroh::{NodeAddr, SecretKey}; -use quic_rpc::{transport, RpcClient, RpcServer}; -use testresult::TestResult; - -use crate::transport::iroh::{IrohConnector, IrohListener}; - -mod math; -use math::*; -use tokio_util::task::AbortOnDropHandle; -mod util; - -const ALPN: &[u8] = b"quic-rpc/iroh/test"; - -/// Constructs an iroh endpoint -/// -/// ## Args -/// -/// - alpn: the ALPN protocol to use -pub async fn make_endpoint(secret_key: SecretKey, alpn: &[u8]) -> anyhow::Result { - iroh::Endpoint::builder() - .secret_key(secret_key) - .alpns(vec![alpn.to_vec()]) - .bind() - .await -} - -pub struct Endpoints { - client: iroh::Endpoint, - server: iroh::Endpoint, - server_node_addr: NodeAddr, -} - -impl Endpoints { - pub async fn new() -> anyhow::Result { - let server = make_endpoint(SecretKey::generate(rand::thread_rng()), ALPN).await?; - - Ok(Endpoints { - client: make_endpoint(SecretKey::generate(rand::thread_rng()), ALPN).await?, - server_node_addr: server.node_addr().await?, - server, - }) - } -} - -fn run_server(server: iroh::Endpoint) -> AbortOnDropHandle<()> { - let connection = IrohListener::new(server).unwrap(); - let server = RpcServer::new(connection); - ComputeService::server(server) -} - -// #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -#[tokio::test] -async fn iroh_channel_bench() -> anyhow::Result<()> { - tracing_subscriber::fmt::try_init().ok(); - - let Endpoints { - client, - server, - server_node_addr, - } = Endpoints::new().await?; - tracing::debug!("Starting server"); - let _server_handle = run_server(server); - tracing::debug!("Starting client"); - - let client = RpcClient::new(IrohConnector::new(client, server_node_addr, ALPN.into())); - tracing::debug!("Starting benchmark"); - bench(client, 50000).await?; - Ok(()) -} - -#[tokio::test] -async fn iroh_channel_smoke() -> anyhow::Result<()> { - tracing_subscriber::fmt::try_init().ok(); - let Endpoints { - client, - server, - server_node_addr, - } = Endpoints::new().await?; - let _server_handle = run_server(server); - let client_connection = IrohConnector::new(client, server_node_addr, ALPN.into()); - smoke_test(client_connection).await?; - Ok(()) -} - -/// Test that using the client after the server goes away and comes back behaves as if the server -/// had never gone away in the first place. -/// -/// This is a regression test. -#[tokio::test] -async fn server_away_and_back() -> TestResult<()> { - tracing_subscriber::fmt::try_init().ok(); - tracing::info!("Creating endpoints"); - - let client_endpoint = make_endpoint(SecretKey::generate(rand::thread_rng()), ALPN).await?; - - let server_secret_key = SecretKey::generate(rand::thread_rng()); - let server_node_id = server_secret_key.public(); - - // create the RPC client - let client_connection = transport::iroh::IrohConnector::::new( - client_endpoint.clone(), - server_node_id, - ALPN.into(), - ); - let client = RpcClient::< - ComputeService, - transport::iroh::IrohConnector, - >::new(client_connection); - - // send a request. No server available so it should fail - client.rpc(Sqr(4)).await.unwrap_err(); - - let server_endpoint = make_endpoint(server_secret_key.clone(), ALPN).await?; - - // create the RPC Server - let connection = transport::iroh::IrohListener::new(server_endpoint.clone())?; - let server = RpcServer::new(connection); - let server_handle = tokio::spawn(ComputeService::server_bounded(server, 1)); - - // wait a bit for connection due to Windows test failing on CI - tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; - - // Passing the server node address directly to client endpoint to not depend - // on a discovery service - let addr = server_endpoint.node_addr().await?; - println!("adding addr {:?}", addr); - client_endpoint.add_node_addr(addr)?; - - // send the first request and wait for the response to ensure everything works as expected - let SqrResponse(response) = client.rpc(Sqr(4)).await?; - assert_eq!(response, 16); - - println!("shutting down"); - let server = server_handle.await??; - drop(server); - server_endpoint.close().await; - - // wait for drop to free the socket - tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; - - // send a request. No server available so it should fail - client.rpc(Sqr(4)).await.unwrap_err(); - - println!("creating new endpoint"); - let server_endpoint = make_endpoint(server_secret_key.clone(), ALPN).await?; - - // make the server run again - let connection = transport::iroh::IrohListener::new(server_endpoint.clone())?; - let server = RpcServer::new(connection); - let server_handle = tokio::spawn(ComputeService::server_bounded(server, 5)); - - // wait a bit for connection due to Windows test failing on CI - tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; - - // Passing the server node address directly to client endpoint to not depend - // on a discovery service - let addr = server_endpoint.node_addr().await?; - println!("adding addr {:?}", addr); - client_endpoint.add_node_addr(addr)?; - - // server is running, this should work - let SqrResponse(response) = client.rpc(Sqr(3)).await?; - assert_eq!(response, 9); - server_handle.abort(); - Ok(()) -} diff --git a/old/tests/math.rs b/old/tests/math.rs deleted file mode 100644 index b628c52..0000000 --- a/old/tests/math.rs +++ /dev/null @@ -1,390 +0,0 @@ -#![cfg(any( - feature = "flume-transport", - feature = "hyper-transport", - feature = "quinn-transport", - feature = "iroh-transport", -))] -#![allow(dead_code)] -use std::{ - io::{self, Write}, - result, -}; - -use async_stream::stream; -use derive_more::{From, TryInto}; -use futures_buffered::BufferedStreamExt; -use futures_lite::{Stream, StreamExt}; -use futures_util::SinkExt; -use quic_rpc::{ - message::{ - BidiStreaming, BidiStreamingMsg, ClientStreaming, ClientStreamingMsg, Msg, RpcMsg, - ServerStreaming, ServerStreamingMsg, - }, - server::{RpcChannel, RpcServerError}, - transport::StreamTypes, - Connector, Listener, RpcClient, RpcServer, Service, -}; -use serde::{Deserialize, Serialize}; -use thousands::Separable; -use tokio_util::task::AbortOnDropHandle; - -/// compute the square of a number -#[derive(Debug, Serialize, Deserialize)] -pub struct Sqr(pub u64); - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct SqrResponse(pub u128); - -/// sum a stream of numbers -#[derive(Debug, Serialize, Deserialize)] -pub struct Sum; - -#[derive(Debug, Serialize, Deserialize)] -pub struct SumUpdate(pub u64); - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct SumResponse(pub u128); - -/// compute the fibonacci sequence as a stream -#[derive(Debug, Serialize, Deserialize)] -pub struct Fibonacci(pub u64); - -#[derive(Debug, Serialize, Deserialize)] -pub struct FibonacciResponse(pub u128); - -/// multiply a stream of numbers, returning a stream -#[derive(Debug, Serialize, Deserialize)] -pub struct Multiply(pub u64); - -#[derive(Debug, Serialize, Deserialize)] -pub struct MultiplyUpdate(pub u64); - -#[derive(Debug, Serialize, Deserialize)] -pub struct MultiplyResponse(pub u128); - -/// request enum -#[derive(Debug, Serialize, Deserialize, From, TryInto)] -pub enum ComputeRequest { - Sqr(Sqr), - Sum(Sum), - SumUpdate(SumUpdate), - Fibonacci(Fibonacci), - Multiply(Multiply), - MultiplyUpdate(MultiplyUpdate), -} - -/// response enum -#[allow(clippy::enum_variant_names)] -#[derive(Debug, Serialize, Deserialize, From, TryInto)] -pub enum ComputeResponse { - SqrResponse(SqrResponse), - SumResponse(SumResponse), - FibonacciResponse(FibonacciResponse), - MultiplyResponse(MultiplyResponse), -} - -#[derive(Debug, Clone)] -pub struct ComputeService; - -impl Service for ComputeService { - type Req = ComputeRequest; - type Res = ComputeResponse; -} - -impl RpcMsg for Sqr { - type Response = SqrResponse; -} - -impl Msg for Sum { - type Pattern = ClientStreaming; -} - -impl ClientStreamingMsg for Sum { - type Update = SumUpdate; - type Response = SumResponse; -} - -impl Msg for Fibonacci { - type Pattern = ServerStreaming; -} - -impl ServerStreamingMsg for Fibonacci { - type Response = FibonacciResponse; -} - -impl Msg for Multiply { - type Pattern = BidiStreaming; -} - -impl BidiStreamingMsg for Multiply { - type Update = MultiplyUpdate; - type Response = MultiplyResponse; -} - -impl ComputeService { - async fn sqr(self, req: Sqr) -> SqrResponse { - SqrResponse(req.0 as u128 * req.0 as u128) - } - - async fn sum(self, _req: Sum, updates: impl Stream) -> SumResponse { - let mut sum = 0u128; - tokio::pin!(updates); - while let Some(SumUpdate(n)) = updates.next().await { - sum += n as u128; - } - SumResponse(sum) - } - - fn fibonacci(self, req: Fibonacci) -> impl Stream { - let mut a = 0u128; - let mut b = 1u128; - let mut n = req.0; - stream! { - while n > 0 { - yield FibonacciResponse(a); - let c = a + b; - a = b; - b = c; - n -= 1; - } - } - } - - fn multiply( - self, - req: Multiply, - updates: impl Stream, - ) -> impl Stream { - let product = req.0 as u128; - stream! { - tokio::pin!(updates); - while let Some(MultiplyUpdate(n)) = updates.next().await { - yield MultiplyResponse(product * n as u128); - } - } - } - - pub fn server>( - server: RpcServer, - ) -> AbortOnDropHandle<()> { - server.spawn_accept_loop(|req, chan| Self::handle_rpc_request(ComputeService, req, chan)) - } - - pub async fn handle_rpc_request( - self, - req: ComputeRequest, - chan: RpcChannel, - ) -> Result<(), RpcServerError> - where - E: StreamTypes, - { - use ComputeRequest::*; - #[rustfmt::skip] - match req { - Sqr(msg) => chan.rpc(msg, self, Self::sqr).await, - Sum(msg) => chan.client_streaming(msg, self, Self::sum).await, - Fibonacci(msg) => chan.server_streaming(msg, self, Self::fibonacci).await, - Multiply(msg) => chan.bidi_streaming(msg, self, Self::multiply).await, - MultiplyUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, - SumUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, - }?; - Ok(()) - } - - /// Runs the service until `count` requests have been received. - pub async fn server_bounded>( - server: RpcServer, - count: usize, - ) -> result::Result, RpcServerError> { - tracing::info!(%count, "server running"); - let s = server; - let mut received = 0; - let service = ComputeService; - while received < count { - received += 1; - let (req, chan) = s.accept().await?.read_first().await?; - let service = service.clone(); - tokio::spawn(async move { - use ComputeRequest::*; - tracing::info!(?req, "got request"); - #[rustfmt::skip] - match req { - 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)?, - }?; - Ok::<_, RpcServerError>(()) - }); - } - tracing::info!(%count, "server finished"); - Ok(s) - } - - pub async fn server_par>( - server: RpcServer, - parallelism: usize, - ) -> result::Result<(), RpcServerError> { - let s = server.clone(); - let s2 = s.clone(); - let service = ComputeService; - let request_stream = stream! { - loop { - yield s2.accept().await?.read_first().await; - } - }; - let process_stream = request_stream.map(move |r| { - let service = service.clone(); - async move { - let (req, chan) = r?; - use ComputeRequest::*; - #[rustfmt::skip] - match req { - 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)?, - }?; - Ok::<_, RpcServerError>(()) - } - }); - process_stream - .buffered_unordered(parallelism) - .for_each(|x| { - if let Err(e) = x { - eprintln!("error: {e:?}"); - } - }) - .await; - Ok(()) - } -} - -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 { - send.send(SumUpdate(i)).await?; - } - 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: Vec<_> = s.map(|x| x.map(|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 { - send.send(MultiplyUpdate(i)).await?; - } - Ok::<_, C::SendError>(()) - }); - let res: Vec<_> = recv.map(|x| x.map(|x| x.0)).try_collect().await?; - tracing::debug!("got response {:?}", res); - assert_eq!(res, vec![2, 4, 6]); - - tracing::debug!("dropping client!"); - Ok(()) -} - -fn clear_line() { - print!("\r{}\r", " ".repeat(80)); -} - -pub async fn bench(client: RpcClient, n: u64) -> anyhow::Result<()> -where - C::SendError: std::error::Error, - C: Connector, -{ - // individual RPCs - { - let mut sum = 0; - let t0 = std::time::Instant::now(); - for i in 0..n { - sum += client.rpc(Sqr(i)).await?.0; - if i % 10000 == 0 { - print!("."); - io::stdout().flush()?; - } - } - let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round(); - assert_eq!(sum, sum_of_squares(n)); - clear_line(); - println!("RPC seq {} rps", rps.separate_with_underscores(),); - } - // parallel RPCs - { - let t0 = std::time::Instant::now(); - let reqs = futures_lite::stream::iter((0..n).map(Sqr)); - let resp: Vec<_> = reqs - .map(|x| { - let client = client.clone(); - async move { - let res = client.rpc(x).await?.0; - anyhow::Ok(res) - } - }) - .buffered_unordered(32) - .try_collect() - .await?; - let sum = resp.into_iter().sum::(); - let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round(); - assert_eq!(sum, sum_of_squares(n)); - clear_line(); - println!("RPC par {} rps", rps.separate_with_underscores(),); - } - // sequential streaming - { - let t0 = std::time::Instant::now(); - let (send, recv) = client.bidi(Multiply(2)).await?; - let handle = tokio::task::spawn(async move { - let requests = futures_lite::stream::iter((0..n).map(MultiplyUpdate)); - futures_util::StreamExt::forward(requests.map(Ok), send).await?; - anyhow::Result::<()>::Ok(()) - }); - let mut sum = 0; - tokio::pin!(recv); - let mut i = 0; - while let Some(res) = recv.next().await { - sum += res?.0; - if i % 10000 == 0 { - print!("."); - io::stdout().flush()?; - } - i += 1; - } - assert_eq!(sum, (0..n as u128).map(|x| x * 2).sum()); - let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round(); - clear_line(); - println!("bidi seq {} rps", rps.separate_with_underscores(),); - - handle.await??; - } - Ok(()) -} - -fn sum_of_squares(n: u64) -> u128 { - (0..n).map(|x| (x * x) as u128).sum() -} diff --git a/old/tests/quinn.rs b/old/tests/quinn.rs deleted file mode 100644 index 54f42cf..0000000 --- a/old/tests/quinn.rs +++ /dev/null @@ -1,127 +0,0 @@ -#![cfg(feature = "quinn-transport")] -#![cfg(feature = "test-utils")] -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; - -use quic_rpc::{ - transport::{ - self, - quinn::{ - configure_server, make_client_endpoint, make_server_endpoint, QuinnConnector, - QuinnListener, - }, - }, - RpcClient, RpcServer, -}; -use quinn::Endpoint; - -mod math; -use math::*; -use testresult::TestResult; -use tokio_util::task::AbortOnDropHandle; -mod util; - -pub struct Endpoints { - client: Endpoint, - server: Endpoint, - server_addr: SocketAddr, -} - -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 { - client, - server, - server_addr, - }) -} - -fn run_server(server: quinn::Endpoint) -> AbortOnDropHandle<()> { - let listener = QuinnListener::new(server).unwrap(); - let listener = RpcServer::new(listener); - ComputeService::server(listener) -} - -// #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -#[tokio::test] -async fn quinn_channel_bench() -> anyhow::Result<()> { - tracing_subscriber::fmt::try_init().ok(); - let Endpoints { - client, - server, - server_addr, - } = make_endpoints(12345)?; - tracing::debug!("Starting server"); - let _server_handle = run_server(server); - tracing::debug!("Starting client"); - let client = QuinnConnector::new(client, server_addr, "localhost".into()); - let client = RpcClient::new(client); - tracing::debug!("Starting benchmark"); - bench(client, 50000).await?; - Ok(()) -} - -#[tokio::test] -async fn quinn_channel_smoke() -> anyhow::Result<()> { - tracing_subscriber::fmt::try_init().ok(); - let Endpoints { - client, - server, - server_addr, - } = make_endpoints(12346)?; - let _server_handle = run_server(server); - let client_connection = - transport::quinn::QuinnConnector::new(client, server_addr, "localhost".into()); - smoke_test(client_connection).await?; - Ok(()) -} - -/// Test that using the client after the server goes away and comes back behaves as if the server -/// had never gone away in the first place. -/// -/// This is a regression test. -#[tokio::test] -async fn server_away_and_back() -> TestResult<()> { - tracing_subscriber::fmt::try_init().ok(); - tracing::info!("Creating endpoints"); - - let server_addr: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 12347)); - let (server_config, server_cert) = configure_server()?; - - // create the RPC client - let client = make_client_endpoint("0.0.0.0:0".parse()?, &[&server_cert])?; - let client_connection = - transport::quinn::QuinnConnector::new(client, server_addr, "localhost".into()); - let client = RpcClient::new(client_connection); - - // send a request. No server available so it should fail - client.rpc(Sqr(4)).await.unwrap_err(); - - // create the RPC Server - let server = Endpoint::server(server_config.clone(), server_addr)?; - let connection = transport::quinn::QuinnListener::new(server)?; - let server = RpcServer::new(connection); - let server_handle = tokio::task::spawn(ComputeService::server_bounded(server, 1)); - - // send the first request and wait for the response to ensure everything works as expected - let SqrResponse(response) = client.rpc(Sqr(4)).await?; - assert_eq!(response, 16); - - let server = server_handle.await??; - drop(server); - // wait for drop to free the socket - tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; - - // make the server run again - let server = Endpoint::server(server_config, server_addr)?; - let connection = transport::quinn::QuinnListener::new(server)?; - let server = RpcServer::new(connection); - let server_handle = tokio::task::spawn(ComputeService::server_bounded(server, 5)); - - // server is running, this should work - let SqrResponse(response) = client.rpc(Sqr(3)).await?; - assert_eq!(response, 9); - server_handle.abort(); - Ok(()) -} diff --git a/old/tests/slow_math.rs b/old/tests/slow_math.rs deleted file mode 100644 index 2060a9a..0000000 --- a/old/tests/slow_math.rs +++ /dev/null @@ -1,131 +0,0 @@ -#![cfg(any( - feature = "flume-transport", - feature = "hyper-transport", - feature = "quinn-transport", - feature = "iroh-transport", -))] -mod math; -use std::result; - -use async_stream::stream; -use futures_lite::{Stream, StreamExt}; -use math::*; -use quic_rpc::{ - message::{ - BidiStreaming, BidiStreamingMsg, ClientStreaming, ClientStreamingMsg, Msg, RpcMsg, - ServerStreaming, ServerStreamingMsg, - }, - server::RpcServerError, - Listener, RpcServer, Service, -}; - -#[derive(Debug, Clone)] -pub struct ComputeService; - -impl Service for ComputeService { - type Req = ComputeRequest; - type Res = ComputeResponse; -} - -impl RpcMsg for Sqr { - type Response = SqrResponse; -} - -impl Msg for Sum { - type Pattern = ClientStreaming; -} - -impl ClientStreamingMsg for Sum { - type Update = SumUpdate; - type Response = SumResponse; -} - -impl Msg for Fibonacci { - type Pattern = ServerStreaming; -} - -impl ServerStreamingMsg for Fibonacci { - type Response = FibonacciResponse; -} - -impl Msg for Multiply { - type Pattern = BidiStreaming; -} - -impl BidiStreamingMsg for Multiply { - type Update = MultiplyUpdate; - type Response = MultiplyResponse; -} - -async fn sleep_ms(ms: u64) { - tokio::time::sleep(std::time::Duration::from_millis(ms)).await; -} - -impl ComputeService { - async fn sqr(self, req: Sqr) -> SqrResponse { - sleep_ms(10000).await; - SqrResponse(req.0 as u128 * req.0 as u128) - } - - async fn sum(self, _req: Sum, updates: impl Stream) -> SumResponse { - let mut sum = 0u128; - tokio::pin!(updates); - while let Some(SumUpdate(n)) = updates.next().await { - sleep_ms(100).await; - sum += n as u128; - } - SumResponse(sum) - } - - fn fibonacci(self, req: Fibonacci) -> impl Stream { - let mut a = 0u128; - let mut b = 1u128; - let mut n = req.0; - stream! { - sleep_ms(100).await; - while n > 0 { - yield FibonacciResponse(a); - let c = a + b; - a = b; - b = c; - n -= 1; - } - } - } - - fn multiply( - self, - req: Multiply, - updates: impl Stream, - ) -> impl Stream { - let product = req.0 as u128; - stream! { - tokio::pin!(updates); - while let Some(MultiplyUpdate(n)) = updates.next().await { - sleep_ms(100).await; - yield MultiplyResponse(product * n as u128); - } - } - } - - pub async fn server>( - server: RpcServer, - ) -> result::Result<(), RpcServerError> { - let s = server; - let service = ComputeService; - loop { - let (req, chan) = s.accept().await?.read_first().await?; - use ComputeRequest::*; - let service = service.clone(); - #[rustfmt::skip] - match req { - 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/old/tests/try.rs b/old/tests/try.rs deleted file mode 100644 index b11f633..0000000 --- a/old/tests/try.rs +++ /dev/null @@ -1,103 +0,0 @@ -#![cfg(feature = "flume-transport")] -use derive_more::{From, TryInto}; -use futures_lite::{Stream, StreamExt}; -use quic_rpc::{ - message::Msg, - pattern::try_server_streaming::{StreamCreated, TryServerStreaming, TryServerStreamingMsg}, - server::RpcServerError, - transport::flume, - RpcClient, RpcServer, Service, -}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone)] -struct TryService; - -impl Service for TryService { - type Req = TryRequest; - type Res = TryResponse; -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct StreamN { - n: u64, -} - -impl Msg for StreamN { - type Pattern = TryServerStreaming; -} - -impl TryServerStreamingMsg for StreamN { - type Item = u64; - type ItemError = String; - type CreateError = String; -} - -/// request enum -#[derive(Debug, Serialize, Deserialize, From, TryInto)] -pub enum TryRequest { - StreamN(StreamN), -} - -#[derive(Debug, Serialize, Deserialize, From, TryInto, Clone)] -pub enum TryResponse { - StreamN(std::result::Result), - StreamNError(std::result::Result), -} - -#[derive(Clone)] -struct Handler; - -impl Handler { - async fn try_stream_n( - self, - req: StreamN, - ) -> std::result::Result>, String> { - if req.n % 2 != 0 { - return Err("odd n not allowed".to_string()); - } - let stream = async_stream::stream! { - for i in 0..req.n { - if i > 5 { - yield Err("n too large".to_string()); - return; - } - yield Ok(i); - } - }; - Ok(stream) - } -} - -#[tokio::test] -async fn try_server_streaming() -> anyhow::Result<()> { - tracing_subscriber::fmt::try_init().ok(); - let (server, client) = flume::channel(1); - - let server = RpcServer::::new(server); - let server_handle = tokio::task::spawn(async move { - loop { - let (req, chan) = server.accept().await?.read_first().await?; - let handler = Handler; - match req { - TryRequest::StreamN(req) => { - chan.try_server_streaming(req, handler, Handler::try_stream_n) - .await?; - } - } - } - #[allow(unreachable_code)] - Ok(()) - }); - let client = RpcClient::::new(client); - let stream_n = client.try_server_streaming(StreamN { n: 10 }).await?; - let items: Vec<_> = stream_n.collect().await; - println!("{:?}", items); - drop(client); - // 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/old/tests/util.rs b/old/tests/util.rs deleted file mode 100644 index cd946e4..0000000 --- a/old/tests/util.rs +++ /dev/null @@ -1,20 +0,0 @@ -use anyhow::Context; -use quic_rpc::{server::RpcServerError, transport::Connector}; - -#[allow(unused)] -pub async fn check_termination_anyhow( - server_handle: tokio::task::JoinHandle>, -) -> anyhow::Result<()> { - // dropping the client will cause the server to terminate - match server_handle.await? { - Err(e) => { - let err: RpcServerError = e.downcast().context("unexpected termination result")?; - match err { - RpcServerError::Accept(_) => {} - e => panic!("unexpected termination error {e:?}"), - } - } - e => panic!("server should have terminated with an error {e:?}"), - } - Ok(()) -} From 6b02fc8f7310d05749c2a0221c256ec15c4ec1d2 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 10:15:07 +0200 Subject: [PATCH 22/43] Remove some stuff and do the wasm part in a different way --- Cargo.toml | 6 ++--- src/lib.rs | 5 ++-- src/util.rs | 67 ++--------------------------------------------------- 3 files changed, 7 insertions(+), 71 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b0317d2..973e230 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,18 +45,16 @@ quic-rpc-derive = { path = "quic-rpc-derive" } derive_more = { version = "2", features = ["debug", "display", "from"] } # we need full for example main etc. tokio = { version = "1", features = ["full"] } -# for AbortOnDropHandle -n0-future = "0.1.2" # formatting thousands = "0.2.0" +# for AbortOnDropHandle +n0-future = { version = "0.1.2" } [features] # enable the remote transport rpc = ["dep:quinn", "dep:postcard", "dep:smallvec", "dep:tracing", "tokio/io-util"] # add test utilities test = ["dep:rustls", "dep:rcgen", "dep:anyhow", "dep:futures-buffered"] -# switch on to avoid needing Send on the boxed futures -wasm-browser = [] default = ["rpc", "test"] [workspace] diff --git a/src/lib.rs b/src/lib.rs index b12603b..f44d648 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ use channel::none::NoReceiver; use sealed::Sealed; use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "test")] +#[cfg_attr(quicrpc_docsrs, doc(cfg(all(feature = "rpc", feature = "test"))))] pub mod util; #[cfg(not(feature = "test"))] mod util; @@ -59,9 +60,9 @@ mod multithreaded { pub(crate) type BoxedFuture<'a, T> = std::pin::Pin + Send + 'a>>; } -#[cfg(not(feature = "wasm-browser"))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] use multithreaded::*; -#[cfg(feature = "wasm-browser")] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_browser::*; /// Channels that abstract over local or remote sending diff --git a/src/util.rs b/src/util.rs index 6b197ca..07100d2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,3 @@ -#[cfg(all(feature = "rpc", feature = "test"))] -#[cfg_attr(quicrpc_docsrs, doc(cfg(all(feature = "rpc", feature = "test"))))] mod quinn_setup_utils { use std::{net::SocketAddr, sync::Arc}; @@ -139,12 +137,8 @@ mod quinn_setup_utils { } } } -#[cfg(all(feature = "rpc", feature = "test"))] -#[cfg_attr(quicrpc_docsrs, doc(cfg(all(feature = "rpc", feature = "test"))))] pub use quinn_setup_utils::*; -#[cfg(feature = "rpc")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] mod varint_util { use std::{ future::Future, @@ -152,7 +146,7 @@ mod varint_util { }; use serde::Serialize; - use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; + use tokio::io::{AsyncRead, AsyncReadExt}; /// Reads a u64 varint from an AsyncRead source, using the Postcard/LEB128 format. /// @@ -210,49 +204,6 @@ mod varint_util { Ok(Some(result)) } - /// Writes a u64 varint to an AsyncWrite destination, using the same LEB128 format. - /// - /// The function stages the entire varint in a 9-byte buffer (maximum size needed for u64) - /// and performs just a single write operation. - /// - /// Returns the number of bytes written. - pub async fn write_varint_u64(writer: &mut W, value: u64) -> io::Result - where - W: AsyncWrite + Unpin, - { - // Buffer to stage the varint (max 9 bytes for u64) - let mut buffer = [0u8; 9]; - let mut pos = 0; - - // Handle zero as a special case - if value == 0 { - buffer[0] = 0; - writer.write_all(&buffer[0..1]).await?; - return Ok(1); - } - - // Encode the value using LEB128 - let mut remaining = value; - while remaining > 0 { - // Extract the 7 least significant bits - let mut byte = (remaining & 0x7F) as u8; - remaining >>= 7; - - // Set the continuation bit if there's more data - if remaining > 0 { - byte |= 0x80; - } - - buffer[pos] = byte; - pos += 1; - } - - // Write the entire buffer in one go - writer.write_all(&buffer[0..pos]).await?; - - Ok(pos) - } - /// Writes a u64 varint to any object that implements the `std::io::Write` trait. /// /// This encodes the value using LEB128 encoding. @@ -319,16 +270,6 @@ mod varint_util { } } - pub trait AsyncWriteVarintExt: AsyncWrite + Unpin { - fn write_varint_u64(&mut self, value: u64) -> impl Future>; - } - - impl AsyncWriteVarintExt for T { - fn write_varint_u64(&mut self, value: u64) -> impl Future> { - write_varint_u64(self, value) - } - } - pub trait WriteVarintExt: std::io::Write { fn write_varint_u64(&mut self, value: u64) -> io::Result; fn write_length_prefixed(&mut self, value: T) -> io::Result<()>; @@ -344,8 +285,4 @@ mod varint_util { } } } -#[cfg(feature = "rpc")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] -pub use varint_util::{ - read_varint_u64, write_varint_u64, AsyncReadVarintExt, AsyncWriteVarintExt, WriteVarintExt, -}; +pub use varint_util::{AsyncReadVarintExt, WriteVarintExt}; From bc25ba8fc34f5cad4a66cd5bc51110c9c851bbb4 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 10:21:09 +0200 Subject: [PATCH 23/43] clippy --- examples/compute.rs | 2 +- examples/derive.rs | 2 +- examples/storage.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/compute.rs b/examples/compute.rs index 584db72..83af27e 100644 --- a/examples/compute.rs +++ b/examples/compute.rs @@ -157,7 +157,7 @@ impl ComputeApi { pub fn listen(&self, endpoint: quinn::Endpoint) -> anyhow::Result> { match &self.inner { ServiceSender::Local(local, _) => { - let local = LocalMpscChannel::from(local.clone()); + let local = local.clone(); let handler: Handler = Arc::new(move |msg, rx: RemoteRead, tx| { let local = local.clone(); Box::pin(match msg { diff --git a/examples/derive.rs b/examples/derive.rs index 2e48de5..6842238 100644 --- a/examples/derive.rs +++ b/examples/derive.rs @@ -116,7 +116,7 @@ impl StorageApi { pub fn listen(&self, endpoint: quinn::Endpoint) -> anyhow::Result> { match &self.inner { ServiceSender::Local(local, _) => { - let local = LocalMpscChannel::from(local.clone()); + let local = local.clone(); let handler: Handler = Arc::new(move |msg, _, tx| { let local = local.clone(); Box::pin(match msg { diff --git a/examples/storage.rs b/examples/storage.rs index 3dfb544..93aa9a8 100644 --- a/examples/storage.rs +++ b/examples/storage.rs @@ -128,7 +128,7 @@ impl StorageApi { pub fn listen(&self, endpoint: quinn::Endpoint) -> anyhow::Result> { match &self.inner { ServiceSender::Local(local, _) => { - let local = LocalMpscChannel::from(local.clone()); + let local = local.clone(); let handler: Handler = Arc::new(move |msg, _rx, tx| { let local = local.clone(); Box::pin(match msg { From aacb6dbe671ed56d0dd68fc231b78a04219d2f94 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 10:23:37 +0200 Subject: [PATCH 24/43] disable compile fail tests for now - they have tiny stupid diffs depending on rustc version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E.g. EXPECTED: ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ error: RpcRequests can only be applied to enums --> tests/compile_fail/non_enum.rs:4:1 | 4 | struct Foo; | ^^^^^^^^^^^ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ ACTUAL OUTPUT: ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ error: RpcRequests can only be applied to enums --> tests/compile_fail/non_enum.rs:4:1 | 4 | struct Foo; | ^^^^^^ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ --- quic-rpc-derive/tests/smoke.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/quic-rpc-derive/tests/smoke.rs b/quic-rpc-derive/tests/smoke.rs index f1adeef..5559569 100644 --- a/quic-rpc-derive/tests/smoke.rs +++ b/quic-rpc-derive/tests/smoke.rs @@ -59,6 +59,7 @@ fn simple() { /// /// to update the snapshots #[test] +#[ignore = "stupid diffs depending on rustc version"] fn compile_fail() { let t = trybuild::TestCases::new(); t.compile_fail("tests/compile_fail/*.rs"); From ce1abd232ab5255a0e3751a352132996fa8159b1 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 10:30:50 +0200 Subject: [PATCH 25/43] try to fix minimal crates --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 973e230..1cf1176 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ rustls = { version = "0.23.5", default-features = false, features = ["std"], opt # used in the test utils to generate quinn endpoints rcgen = { version = "0.13.2", optional = true } # used in the test utils to generate quinn endpoints -anyhow = { version = "1", optional = true } +anyhow = { version = "1.0.66", optional = true } # used in the benches futures-buffered ={ version = "0.2.11", optional = true } From 342c25343f41669169deb7cea8f449bd75522167 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 11:04:36 +0200 Subject: [PATCH 26/43] another minimal crates fix --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1cf1176..6cad962 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ rust-version = "1.76" # we require serde even in non-rpc mode serde = { version = "1", default-features = false } # just for the oneshot and mpsc queues -tokio = { version = "1", features = ["sync"], default-features = false } +tokio = { version = "1.38", features = ["sync"], default-features = false } # for PollSender (which for some reason is not available in the main tokio api) tokio-util = { version = "0.7", default-features = false } From 902277e3dae63e148c048dc1ff2287b19af69b11 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 11:08:20 +0200 Subject: [PATCH 27/43] increase version number by a lot and fix postcard minimal version --- Cargo.lock | 4 ++-- Cargo.toml | 6 +++--- quic-rpc-derive/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70b6dcd..8095417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -750,7 +750,7 @@ dependencies = [ [[package]] name = "quic-rpc" -version = "0.5.0" +version = "0.50.0" dependencies = [ "anyhow", "derive_more 2.0.1", @@ -772,7 +772,7 @@ dependencies = [ [[package]] name = "quic-rpc-derive" -version = "0.18.1" +version = "0.50.0" dependencies = [ "derive_more 1.0.0", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 6cad962..69d282b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic-rpc" -version = "0.5.0" +version = "0.50.0" edition = "2021" authors = ["Rüdiger Klaehn ", "n0 team"] keywords = ["api", "protocol", "network", "rpc"] @@ -22,8 +22,8 @@ tokio-util = { version = "0.7", default-features = false } # used in the endpoint handler code when using rpc tracing = { version = "0.1.41", optional = true } -# used to ser/de messages when using rpc -postcard = { version = "1", features = ["alloc", "use-std"], optional = true } +# used to ser/de messages when using rpc +postcard = { version = "1.1.1", features = ["alloc", "use-std"], optional = true } # currently only transport when using rpc quinn = { version = "0.13.0", package = "iroh-quinn", optional = true } # used as a buffer for serialization when using rpc diff --git a/quic-rpc-derive/Cargo.toml b/quic-rpc-derive/Cargo.toml index 0a59a72..8a67e10 100644 --- a/quic-rpc-derive/Cargo.toml +++ b/quic-rpc-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic-rpc-derive" -version = "0.18.1" +version = "0.50.0" edition = "2021" authors = ["Rüdiger Klaehn "] keywords = ["api", "protocol", "network", "rpc", "macro"] From e282b198601e43931203a85bf7ae2de5eb6120d2 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 11:14:17 +0200 Subject: [PATCH 28/43] fix tokio-util as well --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 69d282b..586b399 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ serde = { version = "1", default-features = false } # just for the oneshot and mpsc queues tokio = { version = "1.38", features = ["sync"], default-features = false } # for PollSender (which for some reason is not available in the main tokio api) -tokio-util = { version = "0.7", default-features = false } +tokio-util = { version = "0.7.14", default-features = false } # used in the endpoint handler code when using rpc tracing = { version = "0.1.41", optional = true } From 6965776a146dde6a1ef1173ff10711a9475e90aa Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 11:53:26 +0200 Subject: [PATCH 29/43] Don't require derive_more in the macro we gen the from impls ourselves now. --- Cargo.lock | 3 +-- Cargo.toml | 2 +- examples/compute.rs | 2 +- examples/derive.rs | 2 +- quic-rpc-derive/Cargo.toml | 2 +- quic-rpc-derive/src/lib.rs | 41 ++++++++++++++++++++++++++++++++-- quic-rpc-derive/tests/smoke.rs | 2 +- src/lib.rs | 2 +- 8 files changed, 46 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8095417..daad1ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,7 +215,6 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.100", - "unicode-xid", ] [[package]] @@ -774,7 +773,7 @@ dependencies = [ name = "quic-rpc-derive" version = "0.50.0" dependencies = [ - "derive_more 1.0.0", + "derive_more 2.0.1", "proc-macro2", "quic-rpc", "quote", diff --git a/Cargo.toml b/Cargo.toml index 586b399..f0c29ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ tracing-subscriber = { version = "0.3.19", features = ["fmt"] } # used in the derive example. This must not be a main crate dep or else it will be circular! quic-rpc-derive = { path = "quic-rpc-derive" } # just convenient for the enum definitions -derive_more = { version = "2", features = ["debug", "display", "from"] } +derive_more = { version = "2", features = ["from"] } # we need full for example main etc. tokio = { version = "1", features = ["full"] } # formatting diff --git a/examples/compute.rs b/examples/compute.rs index 83af27e..83dfb27 100644 --- a/examples/compute.rs +++ b/examples/compute.rs @@ -58,7 +58,7 @@ enum ComputeRequest { // Define the protocol and message enums using the macro #[rpc_requests(ComputeService, ComputeMessage)] -#[derive(derive_more::From, Serialize, Deserialize)] +#[derive(Serialize, Deserialize)] enum ComputeProtocol { #[rpc(tx=oneshot::Sender)] Sqr(Sqr), diff --git a/examples/derive.rs b/examples/derive.rs index 6842238..f7d171e 100644 --- a/examples/derive.rs +++ b/examples/derive.rs @@ -41,7 +41,7 @@ struct Set { // Use the macro to generate both the StorageProtocol and StorageMessage enums // plus implement Channels for each type #[rpc_requests(StorageService, StorageMessage)] -#[derive(derive_more::From, Serialize, Deserialize)] +#[derive(Serialize, Deserialize)] enum StorageProtocol { #[rpc(tx=oneshot::Sender>)] Get(Get), diff --git a/quic-rpc-derive/Cargo.toml b/quic-rpc-derive/Cargo.toml index 8a67e10..a3f5ff8 100644 --- a/quic-rpc-derive/Cargo.toml +++ b/quic-rpc-derive/Cargo.toml @@ -18,7 +18,7 @@ quote = "1" proc-macro2 = "1" [dev-dependencies] -derive_more = { version = "1", features = ["from", "try_into", "display"] } +derive_more = { version = "2", features = ["from"] } serde = { version = "1", features = ["serde_derive"] } trybuild = "1.0" quic-rpc = { path = ".." } diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index 7f8c07b..38ce6b2 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -48,6 +48,35 @@ fn generate_channels_impl( args.check_empty(attr_span)?; Ok(res) } +fn generate_from_impls( + message_enum_name: &Ident, + variants: &[(Ident, Type)], + service_name: &Ident, + original_enum_name: &Ident, + additional_items: &mut Vec, +) { + // Generate and add From impls for the message enum + for (variant_name, inner_type) in variants { + let message_impl = quote! { + impl From<::quic_rpc::WithChannels<#inner_type, #service_name>> for #message_enum_name { + fn from(value: ::quic_rpc::WithChannels<#inner_type, #service_name>) -> Self { + #message_enum_name::#variant_name(value) + } + } + }; + additional_items.extend(message_impl); + + // Generate and add From impls for the original enum + let original_impl = quote! { + impl From<#inner_type> for #original_enum_name { + fn from(value: #inner_type) -> Self { + #original_enum_name::#variant_name(value) + } + } + }; + additional_items.extend(original_impl); + } +} #[proc_macro_attribute] pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -127,7 +156,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { } let message_variants = variants - .into_iter() + .iter() .map(|(variant_name, inner_type)| { quote! { #variant_name(::quic_rpc::WithChannels<#inner_type, #service_name>) @@ -136,12 +165,20 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { .collect::>(); let message_enum = quote! { - #[derive(derive_more::From)] enum #message_enum_name { #(#message_variants),* } }; + // Generate the From implementations + generate_from_impls( + &message_enum_name, + &variants, + &service_name, + &input.ident, + &mut additional_items, + ); + let output = quote! { #input diff --git a/quic-rpc-derive/tests/smoke.rs b/quic-rpc-derive/tests/smoke.rs index 5559569..9302814 100644 --- a/quic-rpc-derive/tests/smoke.rs +++ b/quic-rpc-derive/tests/smoke.rs @@ -35,7 +35,7 @@ fn simple() { struct Response4; #[rpc_requests(Service, RequestWithChannels)] - #[derive(Debug, Serialize, Deserialize, derive_more::From, derive_more::TryInto)] + #[derive(Debug, Serialize, Deserialize)] enum Request { #[rpc(tx=oneshot::Sender<()>)] Rpc(RpcRequest), diff --git a/src/lib.rs b/src/lib.rs index f44d648..03b8500 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -450,7 +450,7 @@ impl ServiceSender { Self::Remote(endpoint, addr, _) => { let connection = endpoint .connect(*addr, "localhost") - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))? + .map_err(io::Error::other)? .await?; let (send, recv) = connection.open_bi().await?; Ok(ServiceRequest::Remote(rpc::RemoteRequest::new(send, recv))) From d6ecf837155ad56368e344761b34c0111c35aa42 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 12:07:47 +0200 Subject: [PATCH 30/43] fix features --- src/lib.rs | 6 ++---- src/util.rs | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 03b8500..b0c588e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,11 +4,9 @@ use std::{fmt::Debug, io, marker::PhantomData, ops::Deref}; use channel::none::NoReceiver; use sealed::Sealed; use serde::{de::DeserializeOwned, Serialize}; -#[cfg(feature = "test")] -#[cfg_attr(quicrpc_docsrs, doc(cfg(all(feature = "rpc", feature = "test"))))] +#[cfg(feature = "rpc")] +#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] pub mod util; -#[cfg(not(feature = "test"))] -mod util; /// Requirements for a RPC message /// diff --git a/src/util.rs b/src/util.rs index 07100d2..91d5706 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "test")] +#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "test")))] mod quinn_setup_utils { use std::{net::SocketAddr, sync::Arc}; @@ -137,6 +139,8 @@ mod quinn_setup_utils { } } } +#[cfg(feature = "test")] +#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "test")))] pub use quinn_setup_utils::*; mod varint_util { From 2f73a8200d766adf4069a43749558beb615dc318 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Mar 2025 18:04:26 +0200 Subject: [PATCH 31/43] Fuse the damn oneshot receiver todo: hide the enums after all --- src/lib.rs | 6 ++++-- src/util.rs | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b0c588e..0db17dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,8 @@ pub mod channel { pub mod oneshot { use std::{fmt::Debug, future::Future, io, pin::Pin, task}; + use crate::util::FusedOneshotReceiver; + pub fn channel() -> (Sender, Receiver) { let (tx, rx) = tokio::sync::oneshot::channel(); (tx.into(), rx.into()) @@ -152,7 +154,7 @@ pub mod channel { impl crate::Sender for Sender {} pub enum Receiver { - Tokio(tokio::sync::oneshot::Receiver), + Tokio(FusedOneshotReceiver), Boxed(BoxedReceiver), } @@ -172,7 +174,7 @@ pub mod channel { /// Convert a tokio oneshot receiver to a receiver for this crate impl From> for Receiver { fn from(rx: tokio::sync::oneshot::Receiver) -> Self { - Self::Tokio(rx) + Self::Tokio(FusedOneshotReceiver(rx)) } } diff --git a/src/util.rs b/src/util.rs index 91d5706..9a7653f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -290,3 +290,23 @@ mod varint_util { } } pub use varint_util::{AsyncReadVarintExt, WriteVarintExt}; + +mod fuse_wrapper { + use std::{future::Future, pin::Pin, task::{Context, Poll}}; + + pub struct FusedOneshotReceiver(pub tokio::sync::oneshot::Receiver); + + impl Future for FusedOneshotReceiver { + type Output = std::result::Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.0.is_terminated() { + // don't panic when polling a terminated receiver + Poll::Pending + } else { + Future::poll(Pin::new(&mut self.0), cx) + } + } + } +} +pub use fuse_wrapper::FusedOneshotReceiver; From 2250980b0834e5384f6a680dee258e83e91c4b4b Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 18 Mar 2025 10:14:47 +0200 Subject: [PATCH 32/43] Add ability to send if not busy --- src/lib.rs | 25 ++++++++++++++++++++++++- src/util.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0db17dd..d45ae4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -270,6 +270,11 @@ pub mod channel { &mut self, value: T, ) -> Pin> + Send + '_>>; + + fn try_send( + &mut self, + value: T, + ) -> Pin> + Send + '_>>; } pub trait BoxedReceiver: Debug + Send + Sync + 'static { @@ -489,7 +494,7 @@ pub mod rpc { oneshot, spsc::{self, BoxedReceiver, BoxedSender}, }, - util::{AsyncReadVarintExt, WriteVarintExt}, + util::{now_or_never, AsyncReadVarintExt, WriteVarintExt}, RpcMessage, }; @@ -636,6 +641,24 @@ pub mod rpc { Ok(()) }) } + + fn try_send( + &mut self, + value: T, + ) -> Pin> + Send + '_>> { + Box::pin(async { + let value = value; + self.buffer.clear(); + self.buffer.write_length_prefixed(value)?; + let Some(n) = now_or_never(self.send.write(&mut self.buffer)) else { + return Ok(false); + }; + let n = n?; + self.send.write_all(&self.buffer[n..]).await?; + self.buffer.clear(); + Ok(true) + }) + } } impl Drop for QuinnSender { diff --git a/src/util.rs b/src/util.rs index 9a7653f..6fc8187 100644 --- a/src/util.rs +++ b/src/util.rs @@ -292,7 +292,11 @@ mod varint_util { pub use varint_util::{AsyncReadVarintExt, WriteVarintExt}; mod fuse_wrapper { - use std::{future::Future, pin::Pin, task::{Context, Poll}}; + use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + }; pub struct FusedOneshotReceiver(pub tokio::sync::oneshot::Receiver); @@ -310,3 +314,47 @@ mod fuse_wrapper { } } pub use fuse_wrapper::FusedOneshotReceiver; + +mod now_or_never { + use std::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + // Simple pin_mut! macro implementation + macro_rules! pin_mut { + ($($x:ident),* $(,)?) => { + $( + let mut $x = $x; + #[allow(unused_mut)] + let mut $x = unsafe { Pin::new_unchecked(&mut $x) }; + )* + } +} + + // Minimal implementation of a no-op waker + fn noop_waker() -> Waker { + fn noop(_: *const ()) {} + fn clone(_: *const ()) -> RawWaker { + let vtable = &RawWakerVTable::new(clone, noop, noop, noop); + RawWaker::new(std::ptr::null(), vtable) + } + + unsafe { Waker::from_raw(clone(std::ptr::null())) } + } + + /// Attempts to complete a future immediately, returning None if it would block + pub fn now_or_never(future: F) -> Option { + let waker = noop_waker(); + let mut cx = Context::from_waker(&waker); + + pin_mut!(future); + + match future.poll(&mut cx) { + Poll::Ready(x) => Some(x), + Poll::Pending => None, + } + } +} +pub use now_or_never::now_or_never; From 90195f70c7b3208b013f109acc512b29316df82c Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 18 Mar 2025 10:31:38 +0200 Subject: [PATCH 33/43] comment --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index d45ae4e..78eb90d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -647,6 +647,7 @@ pub mod rpc { value: T, ) -> Pin> + Send + '_>> { Box::pin(async { + // todo: move the non-async part out of the box. Will require a new return type. let value = value; self.buffer.clear(); self.buffer.write_length_prefixed(value)?; From 1f6561dc120f475b16f11c9c167f6af2acc2fbbb Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 18 Mar 2025 15:30:35 +0200 Subject: [PATCH 34/43] fixes --- quic-rpc-derive/src/lib.rs | 2 +- src/lib.rs | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index 38ce6b2..c649471 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -165,7 +165,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { .collect::>(); let message_enum = quote! { - enum #message_enum_name { + pub enum #message_enum_name { #(#message_variants),* } }; diff --git a/src/lib.rs b/src/lib.rs index 78eb90d..fd5d11d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,17 @@ pub mod channel { } } + impl TryFrom> for tokio::sync::oneshot::Sender { + type Error = Sender; + + fn try_from(value: Sender) -> Result { + match value { + Sender::Tokio(tx) => Ok(tx), + Sender::Boxed(_) => Err(value), + } + } + } + #[derive(Debug)] pub enum SendError { ReceiverClosed, @@ -178,6 +189,17 @@ pub mod channel { } } + impl TryFrom> for tokio::sync::oneshot::Receiver { + type Error = Receiver; + + fn try_from(value: Receiver) -> Result { + match value { + Receiver::Tokio(tx) => Ok(tx.0), + Receiver::Boxed(_) => Err(value), + } + } + } + /// Convert a function that produces a future to a receiver for this crate impl From for Receiver where @@ -265,6 +287,17 @@ pub mod channel { } } + impl TryFrom> for tokio::sync::mpsc::Sender { + type Error = Sender; + + fn try_from(value: Sender) -> Result { + match value { + Sender::Tokio(tx) => Ok(tx), + Sender::Boxed(_) => Err(value), + } + } + } + pub trait BoxedSender: Debug + Send + Sync + 'static { fn send( &mut self, @@ -299,6 +332,16 @@ pub mod channel { Sender::Boxed(sink) => sink.send(value).await.map_err(SendError::from), } } + + pub async fn try_send(&mut self, value: T) -> std::result::Result<(), SendError> { + match self { + Sender::Tokio(tx) => tx.try_send(value).map_err(|_| SendError::ReceiverClosed), + Sender::Boxed(sink) => { + sink.try_send(value).await.map_err(SendError::from)?; + Ok(()) + } + } + } } impl crate::sealed::Sealed for Sender {} @@ -330,6 +373,17 @@ pub mod channel { } } + impl TryFrom> for tokio::sync::mpsc::Receiver { + type Error = Receiver; + + fn try_from(value: Receiver) -> Result { + match value { + Receiver::Tokio(tx) => Ok(tx), + Receiver::Boxed(_) => Err(value), + } + } + } + impl Debug for Receiver { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { From 39b7172e820ca532c468af9a65ebae3fd16ffd23 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 18 Mar 2025 19:19:35 +0200 Subject: [PATCH 35/43] Add generic enum for local/remote --- examples/compute.rs | 8 ++--- examples/derive.rs | 6 ++-- examples/storage.rs | 6 ++-- quic-rpc-derive/src/lib.rs | 1 + src/lib.rs | 65 ++++++++++++++++++++++++-------------- 5 files changed, 52 insertions(+), 34 deletions(-) diff --git a/examples/compute.rs b/examples/compute.rs index 83dfb27..2d05bc2 100644 --- a/examples/compute.rs +++ b/examples/compute.rs @@ -180,7 +180,7 @@ impl ComputeApi { pub async fn sqr(&self, num: u64) -> anyhow::Result> { let msg = Sqr { num }; match self.inner.request().await? { - ServiceRequest::Local(request, _) => { + ServiceRequest::Local(request) => { let (tx, rx) = oneshot::channel(); request.send((msg, tx)).await?; Ok(rx) @@ -195,7 +195,7 @@ impl ComputeApi { pub async fn sum(&self) -> anyhow::Result<(spsc::Sender, oneshot::Receiver)> { let msg = Sum; match self.inner.request().await? { - ServiceRequest::Local(request, _) => { + ServiceRequest::Local(request) => { let (num_tx, num_rx) = spsc::channel(10); let (sum_tx, sum_rx) = oneshot::channel(); request.send((msg, sum_tx, num_rx)).await?; @@ -211,7 +211,7 @@ impl ComputeApi { pub async fn fibonacci(&self, max: u64) -> anyhow::Result> { let msg = Fibonacci { max }; match self.inner.request().await? { - ServiceRequest::Local(request, _) => { + ServiceRequest::Local(request) => { let (tx, rx) = spsc::channel(128); request.send((msg, tx)).await?; Ok(rx) @@ -229,7 +229,7 @@ impl ComputeApi { ) -> anyhow::Result<(spsc::Sender, spsc::Receiver)> { let msg = Multiply { initial }; match self.inner.request().await? { - ServiceRequest::Local(request, _) => { + ServiceRequest::Local(request) => { let (in_tx, in_rx) = spsc::channel(128); let (out_tx, out_rx) = spsc::channel(128); request.send((msg, out_tx, in_rx)).await?; diff --git a/examples/derive.rs b/examples/derive.rs index f7d171e..5dc7811 100644 --- a/examples/derive.rs +++ b/examples/derive.rs @@ -138,7 +138,7 @@ impl StorageApi { pub async fn get(&self, key: String) -> anyhow::Result>> { let msg = Get { key }; match self.inner.request().await? { - ServiceRequest::Local(request, _) => { + ServiceRequest::Local(request) => { let (tx, rx) = oneshot::channel(); request.send((msg, tx)).await?; Ok(rx) @@ -153,7 +153,7 @@ impl StorageApi { pub async fn list(&self) -> anyhow::Result> { let msg = List; match self.inner.request().await? { - ServiceRequest::Local(request, _) => { + ServiceRequest::Local(request) => { let (tx, rx) = spsc::channel(10); request.send((msg, tx)).await?; Ok(rx) @@ -168,7 +168,7 @@ impl StorageApi { pub async fn set(&self, key: String, value: String) -> anyhow::Result> { let msg = Set { key, value }; match self.inner.request().await? { - ServiceRequest::Local(request, _) => { + ServiceRequest::Local(request) => { let (tx, rx) = oneshot::channel(); request.send((msg, tx)).await?; Ok(rx) diff --git a/examples/storage.rs b/examples/storage.rs index 93aa9a8..7562f23 100644 --- a/examples/storage.rs +++ b/examples/storage.rs @@ -150,7 +150,7 @@ impl StorageApi { pub async fn get(&self, key: String) -> anyhow::Result>> { let msg = Get { key }; match self.inner.request().await? { - ServiceRequest::Local(request, _) => { + ServiceRequest::Local(request) => { let (tx, rx) = oneshot::channel(); request.send((msg, tx)).await?; Ok(rx) @@ -165,7 +165,7 @@ impl StorageApi { pub async fn list(&self) -> anyhow::Result> { let msg = List; match self.inner.request().await? { - ServiceRequest::Local(request, _) => { + ServiceRequest::Local(request) => { let (tx, rx) = spsc::channel(10); request.send((msg, tx)).await?; Ok(rx) @@ -180,7 +180,7 @@ impl StorageApi { pub async fn set(&self, key: String, value: String) -> anyhow::Result> { let msg = Set { key, value }; match self.inner.request().await? { - ServiceRequest::Local(request, _) => { + ServiceRequest::Local(request) => { let (tx, rx) = oneshot::channel(); request.send((msg, tx)).await?; Ok(rx) diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index c649471..03ef8df 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -165,6 +165,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { .collect::>(); let message_enum = quote! { + #[derive(Debug)] pub enum #message_enum_name { #(#message_variants),* } diff --git a/src/lib.rs b/src/lib.rs index fd5d11d..14c5a8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -476,6 +476,7 @@ impl, S: Service> Deref for WithChannels { } } +#[derive(Debug)] pub enum ServiceSender { Local(LocalMpscChannel, PhantomData), #[cfg(feature = "rpc")] @@ -483,6 +484,12 @@ pub enum ServiceSender { Remote(quinn::Endpoint, std::net::SocketAddr, PhantomData<(R, S)>), } +impl From> for ServiceSender { + fn from(tx: tokio::sync::mpsc::Sender) -> Self { + Self::Local(tx.into(), PhantomData) + } +} + impl Clone for ServiceSender { fn clone(&self) -> Self { match self { @@ -501,9 +508,20 @@ impl From> for ServiceSender { } impl ServiceSender { - pub async fn request(&self) -> io::Result> { + pub fn local(&self) -> Option<&LocalMpscChannel> { + match self { + Self::Local(tx, _) => Some(tx), + #[cfg(feature = "rpc")] + #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] + Self::Remote(_, _, _) => None, + } + } + + pub async fn request( + &self, + ) -> io::Result, rpc::RemoteRequest>> { match self { - Self::Local(tx, _) => Ok(ServiceRequest::from(tx.clone())), + Self::Local(tx, _) => Ok(ServiceRequest::Local(tx)), #[cfg(feature = "rpc")] #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] Self::Remote(endpoint, addr, _) => { @@ -563,6 +581,18 @@ pub mod rpc { pub fn new(send: quinn::SendStream, recv: quinn::RecvStream) -> Self { Self(send, recv, PhantomData) } + + pub async fn write(self, msg: impl Into) -> io::Result<(RemoteRead, RemoteWrite)> + where + R: Serialize, + { + let RemoteRequest(mut send, recv, _) = self; + let msg = msg.into(); + let mut buf = SmallVec::<[u8; 128]>::new(); + buf.write_length_prefixed(msg)?; + send.write_all(&buf).await?; + Ok((RemoteRead(recv), RemoteWrite(send))) + } } #[derive(Debug)] @@ -722,17 +752,6 @@ pub mod rpc { } } - impl RemoteRequest { - pub async fn write(self, msg: impl Into) -> io::Result<(RemoteRead, RemoteWrite)> { - let RemoteRequest(mut send, recv, _) = self; - let msg = msg.into(); - let mut buf = SmallVec::<[u8; 128]>::new(); - buf.write_length_prefixed(msg)?; - send.write_all(&buf).await?; - Ok((RemoteRead(recv), RemoteWrite(send))) - } - } - /// Type alias for a handler fn for remote requests pub type Handler = Arc< dyn Fn(R, RemoteRead, RemoteWrite) -> crate::BoxedFuture<'static, io::Result<()>> @@ -784,20 +803,13 @@ pub mod rpc { } #[derive(Debug)] -pub enum ServiceRequest { - Local(LocalMpscChannel, PhantomData), - #[cfg(feature = "rpc")] - #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] - Remote(rpc::RemoteRequest), -} - -impl From> for ServiceRequest { - fn from(tx: LocalMpscChannel) -> Self { - Self::Local(tx, PhantomData) - } +pub enum ServiceRequest { + Local(L), + Remote(R), } impl LocalMpscChannel { + /// Send a message to the service pub fn send(&self, value: impl Into>) -> SendFut where T: Channels, @@ -806,6 +818,11 @@ impl LocalMpscChannel { let value: M = value.into().into(); SendFut::new(self.0.clone(), value) } + + /// Send a message to the service without the type conversion magic + pub fn send_raw(&self, value: M) -> SendFut { + SendFut::new(self.0.clone(), value) + } } mod send_fut { From 9fd3f350c942115c2e4fbc2e0bbb6d834cccc07b Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 18 Mar 2025 21:21:57 +0200 Subject: [PATCH 36/43] fix mismatch with blobs --- Cargo.toml | 2 +- src/lib.rs | 34 ++++++++++++++++++++++------------ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f0c29ec..1ed7222 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ rcgen = { version = "0.13.2", optional = true } # used in the test utils to generate quinn endpoints anyhow = { version = "1.0.66", optional = true } # used in the benches -futures-buffered ={ version = "0.2.11", optional = true } +futures-buffered ={ version = "0.2.9", optional = true } [dev-dependencies] tracing-subscriber = { version = "0.3.19", features = ["fmt"] } diff --git a/src/lib.rs b/src/lib.rs index 14c5a8f..1b2c9fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![cfg_attr(quicrpc_docsrs, feature(doc_cfg))] -use std::{fmt::Debug, io, marker::PhantomData, ops::Deref}; +use std::{fmt::Debug, future::Future, io, marker::PhantomData, ops::Deref}; use channel::none::NoReceiver; use sealed::Sealed; @@ -517,20 +517,30 @@ impl ServiceSender { } } - pub async fn request( + pub fn request( &self, - ) -> io::Result, rpc::RemoteRequest>> { - match self { - Self::Local(tx, _) => Ok(ServiceRequest::Local(tx)), + ) -> impl Future< + Output = io::Result, rpc::RemoteRequest>>, + > + 'static { + let cloned = match self { + Self::Local(tx, _) => ServiceRequest::Local(tx.clone()), #[cfg(feature = "rpc")] #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] - Self::Remote(endpoint, addr, _) => { - let connection = endpoint - .connect(*addr, "localhost") - .map_err(io::Error::other)? - .await?; - let (send, recv) = connection.open_bi().await?; - Ok(ServiceRequest::Remote(rpc::RemoteRequest::new(send, recv))) + Self::Remote(endpoint, addr, _) => ServiceRequest::Remote((endpoint.clone(), *addr)), + }; + async move { + match cloned { + ServiceRequest::Local(tx) => Ok(ServiceRequest::Local(tx.clone())), + #[cfg(feature = "rpc")] + #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] + ServiceRequest::Remote((endpoint, addr)) => { + let connection = endpoint + .connect(addr, "localhost") + .map_err(io::Error::other)? + .await?; + let (send, recv) = connection.open_bi().await?; + Ok(ServiceRequest::Remote(rpc::RemoteRequest::new(send, recv))) + } } } } From 30e28b8746abdf6360b20b20bcdbca56e6e61b5c Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 20 Mar 2025 09:55:41 +0200 Subject: [PATCH 37/43] add spans to compute example --- Cargo.toml | 4 +++- examples/compute.rs | 22 ++++++++++++++++++---- src/lib.rs | 20 +++++++++++++++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ed7222..9858063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,9 @@ n0-future = { version = "0.1.2" } rpc = ["dep:quinn", "dep:postcard", "dep:smallvec", "dep:tracing", "tokio/io-util"] # add test utilities test = ["dep:rustls", "dep:rcgen", "dep:anyhow", "dep:futures-buffered"] -default = ["rpc", "test"] +# pick up parent span when creating channel messages +message_spans = [] +default = ["rpc", "test", "message_spans"] [workspace] members = ["quic-rpc-derive"] diff --git a/examples/compute.rs b/examples/compute.rs index 2d05bc2..d6a6242 100644 --- a/examples/compute.rs +++ b/examples/compute.rs @@ -100,13 +100,17 @@ impl ComputeActor { match msg { ComputeMessage::Sqr(sqr) => { trace!("sqr {:?}", sqr); - let WithChannels { tx, inner, .. } = sqr; + let WithChannels { + tx, inner, span, .. + } = sqr; + let _entered = span.enter(); let result = (inner.num as u128) * (inner.num as u128); tx.send(result).await?; } ComputeMessage::Sum(sum) => { trace!("sum {:?}", sum); - let WithChannels { rx, tx, .. } = sum; + let WithChannels { rx, tx, span, .. } = sum; + let _entered = span.enter(); let mut receiver = rx; let mut total = 0; while let Some(num) = receiver.recv().await? { @@ -116,7 +120,10 @@ impl ComputeActor { } ComputeMessage::Fibonacci(fib) => { trace!("fibonacci {:?}", fib); - let WithChannels { tx, inner, .. } = fib; + let WithChannels { + tx, inner, span, .. + } = fib; + let _entered = span.enter(); let mut sender = tx; let mut a = 0u64; let mut b = 1u64; @@ -129,7 +136,14 @@ impl ComputeActor { } ComputeMessage::Multiply(mult) => { trace!("multiply {:?}", mult); - let WithChannels { rx, tx, inner } = mult; + let WithChannels { + rx, + tx, + inner, + span, + .. + } = mult; + let _entered = span.enter(); let mut receiver = rx; let mut sender = tx; let multiplier = inner.initial; diff --git a/src/lib.rs b/src/lib.rs index 1b2c9fa..b4f41e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -427,6 +427,10 @@ pub struct WithChannels, S: Service> { pub tx: >::Tx, /// The request channel to receive the request from. Can be set to [`NoReceiver`] if not needed. pub rx: >::Rx, + /// The current span where the full message was created. + #[cfg(feature = "message_spans")] + #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "message_spans")))] + pub span: tracing::Span, } /// Tuple conversion from inner message and tx/rx channels to a WithChannels struct @@ -444,6 +448,9 @@ where inner, tx: tx.into(), rx: rx.into(), + #[cfg(feature = "message_spans")] + #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "message_spans")))] + span: tracing::Span::current(), } } } @@ -463,6 +470,9 @@ where inner, tx: tx.into(), rx: NoReceiver, + #[cfg(feature = "message_spans")] + #[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "message_spans")))] + span: tracing::Span::current(), } } } @@ -569,7 +579,7 @@ pub mod rpc { use serde::{de::DeserializeOwned, Serialize}; use smallvec::SmallVec; use tokio::task::JoinSet; - use tracing::warn; + use tracing::{trace_span, warn, Instrument}; use crate::{ channel::{ @@ -775,10 +785,11 @@ pub mod rpc { endpoint: quinn::Endpoint, handler: Handler, ) { + let mut request_id = 0u64; let mut tasks = JoinSet::new(); while let Some(incoming) = endpoint.accept().await { let handler = handler.clone(); - tasks.spawn(async move { + let fut = async move { let connection = match incoming.await { Ok(connection) => connection, Err(cause) => { @@ -807,7 +818,10 @@ pub mod rpc { let tx = RemoteWrite::new(send); handler(msg, rx, tx).await?; } - }); + }; + let span = trace_span!("rpc", id = request_id); + tasks.spawn(fut.instrument(span)); + request_id += 1; } } } From f6d5069ef81423c2d3155d09c7e3a75cba6b72f0 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 20 Mar 2025 10:23:12 +0200 Subject: [PATCH 38/43] Pass though get_parent_span from the WithChannels impl --- Cargo.lock | 1 + quic-rpc-derive/Cargo.toml | 2 ++ quic-rpc-derive/src/lib.rs | 13 +++++++++++++ src/lib.rs | 13 +++++++++++++ 4 files changed, 29 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index daad1ed..ece961b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -779,6 +779,7 @@ dependencies = [ "quote", "serde", "syn 1.0.109", + "tracing", "trybuild", ] diff --git a/quic-rpc-derive/Cargo.toml b/quic-rpc-derive/Cargo.toml index a3f5ff8..02aa100 100644 --- a/quic-rpc-derive/Cargo.toml +++ b/quic-rpc-derive/Cargo.toml @@ -22,3 +22,5 @@ derive_more = { version = "2", features = ["from"] } serde = { version = "1", features = ["serde_derive"] } trybuild = "1.0" quic-rpc = { path = ".." } +tracing = "0.1.41" + diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index 03ef8df..ac47d73 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -164,11 +164,24 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { }) .collect::>(); + // Extract variant names for the match pattern + let variant_names = variants.iter().map(|(name, _)| name).collect::>(); + let message_enum = quote! { #[derive(Debug)] pub enum #message_enum_name { #(#message_variants),* } + + impl #message_enum_name { + /// Get the parent span of the message + fn parent_span(&self) -> tracing::Span { + let span = match self { + #(#message_enum_name::#variant_names(inner) => inner.parent_span_opt()),* + }; + span.cloned().unwrap_or_else(|| ::tracing::Span::current()) + } + } }; // Generate the From implementations diff --git a/src/lib.rs b/src/lib.rs index b4f41e9..5cedc8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -433,6 +433,19 @@ pub struct WithChannels, S: Service> { pub span: tracing::Span, } +impl, S: Service> WithChannels { + pub fn parent_span_opt(&self) -> Option<&tracing::Span> { + #[cfg(feature = "message_spans")] + { + Some(&self.span) + } + #[cfg(not(feature = "message_spans"))] + { + None + } + } +} + /// Tuple conversion from inner message and tx/rx channels to a WithChannels struct /// /// For the case where you want both tx and rx channels. From ec252c04fd12e0b4a51bf3f03cba8f8d86adfb18 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 20 Mar 2025 10:27:05 +0200 Subject: [PATCH 39/43] pub duh! --- quic-rpc-derive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quic-rpc-derive/src/lib.rs b/quic-rpc-derive/src/lib.rs index ac47d73..32fe2ec 100644 --- a/quic-rpc-derive/src/lib.rs +++ b/quic-rpc-derive/src/lib.rs @@ -175,7 +175,7 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { impl #message_enum_name { /// Get the parent span of the message - fn parent_span(&self) -> tracing::Span { + pub fn parent_span(&self) -> tracing::Span { let span = match self { #(#message_enum_name::#variant_names(inner) => inner.parent_span_opt()),* }; From 15249c2263beb52b1eafa852b950616dd8aa27cb Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 20 Mar 2025 10:52:04 +0200 Subject: [PATCH 40/43] skip the span when debugging --- src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5cedc8e..369ae11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -419,7 +419,6 @@ pub mod channel { /// active and unserializable channels. /// /// rx and tx can be set to an appropriate channel kind. -#[derive(Debug)] pub struct WithChannels, S: Service> { /// The inner message. pub inner: I, @@ -433,6 +432,16 @@ pub struct WithChannels, S: Service> { pub span: tracing::Span, } +impl + Debug, S: Service> Debug for WithChannels { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WithChannels") + .field("inner", &self.inner) + .field("tx", &self.tx) + .field("rx", &self.rx) + .finish_non_exhaustive() + } +} + impl, S: Service> WithChannels { pub fn parent_span_opt(&self) -> Option<&tracing::Span> { #[cfg(feature = "message_spans")] From 34c4192a183c5ed147da932268f7d7ad0cd444de Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 20 Mar 2025 12:43:00 +0200 Subject: [PATCH 41/43] Add a way to find out if a sender is rpc --- src/lib.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 369ae11..d53ae46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,6 +161,18 @@ pub mod channel { } } + impl Sender { + pub fn is_rpc(&self) -> bool + where + T: 'static, + { + match self { + Sender::Tokio(_) => false, + Sender::Boxed(_) => true, + } + } + } + impl crate::sealed::Sealed for Sender {} impl crate::Sender for Sender {} @@ -281,6 +293,18 @@ pub mod channel { Boxed(Box>), } + impl Sender { + pub fn is_rpc(&self) -> bool + where + T: 'static, + { + match self { + Sender::Tokio(_) => false, + Sender::Boxed(x) => x.is_rpc(), + } + } + } + impl From> for Sender { fn from(tx: tokio::sync::mpsc::Sender) -> Self { Self::Tokio(tx) @@ -308,6 +332,8 @@ pub mod channel { &mut self, value: T, ) -> Pin> + Send + '_>>; + + fn is_rpc(&self) -> bool; } pub trait BoxedReceiver: Debug + Send + Sync + 'static { @@ -777,7 +803,7 @@ pub mod rpc { let value = value; self.buffer.clear(); self.buffer.write_length_prefixed(value)?; - let Some(n) = now_or_never(self.send.write(&mut self.buffer)) else { + let Some(n) = now_or_never(self.send.write(&self.buffer)) else { return Ok(false); }; let n = n?; @@ -786,6 +812,10 @@ pub mod rpc { Ok(true) }) } + + fn is_rpc(&self) -> bool { + true + } } impl Drop for QuinnSender { From 170f57b2e463d9a28d62a24e64d2684c176e0c1a Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 20 Mar 2025 14:37:28 +0200 Subject: [PATCH 42/43] Nicer debug, and don't log at warn level for a normal application close --- src/lib.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d53ae46..e9313de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -460,10 +460,10 @@ pub struct WithChannels, S: Service> { impl + Debug, S: Service> Debug for WithChannels { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("WithChannels") - .field("inner", &self.inner) - .field("tx", &self.tx) - .field("rx", &self.rx) + f.debug_tuple("") + .field(&self.inner) + .field(&self.tx) + .field(&self.rx) .finish_non_exhaustive() } } @@ -624,10 +624,11 @@ impl Clone for LocalMpscChannel { pub mod rpc { use std::{fmt::Debug, future::Future, io, marker::PhantomData, pin::Pin, sync::Arc}; + use quinn::ConnectionError; use serde::{de::DeserializeOwned, Serialize}; use smallvec::SmallVec; use tokio::task::JoinSet; - use tracing::{trace_span, warn, Instrument}; + use tracing::{trace, trace_span, warn, Instrument}; use crate::{ channel::{ @@ -852,9 +853,13 @@ pub mod rpc { loop { let (send, mut recv) = match connection.accept_bi().await { Ok((s, r)) => (s, r), + Err(ConnectionError::ApplicationClosed(cause)) if cause.error_code.into_inner() == 0 => { + trace!("remote side closed connection {cause:?}"); + return Ok(()); + } Err(cause) => { warn!("failed to accept bi stream {cause:?}"); - return Ok(()); + return Err(cause.into()); } }; let size = recv.read_varint_u64().await?.ok_or_else(|| { From cf8f4fda3a0f217d320de06322e2d0f3ca61f9a1 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 20 Mar 2025 16:34:07 +0200 Subject: [PATCH 43/43] fix bug in try_send try_send not working should not be considered an error --- src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e9313de..222d338 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -361,7 +361,13 @@ pub mod channel { pub async fn try_send(&mut self, value: T) -> std::result::Result<(), SendError> { match self { - Sender::Tokio(tx) => tx.try_send(value).map_err(|_| SendError::ReceiverClosed), + Sender::Tokio(tx) => match tx.try_send(value) { + Ok(()) => Ok(()), + Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => { + Err(SendError::ReceiverClosed) + } + Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => Ok(()), + }, Sender::Boxed(sink) => { sink.try_send(value).await.map_err(SendError::from)?; Ok(()) @@ -853,7 +859,9 @@ pub mod rpc { loop { let (send, mut recv) = match connection.accept_bi().await { Ok((s, r)) => (s, r), - Err(ConnectionError::ApplicationClosed(cause)) if cause.error_code.into_inner() == 0 => { + Err(ConnectionError::ApplicationClosed(cause)) + if cause.error_code.into_inner() == 0 => + { trace!("remote side closed connection {cause:?}"); return Ok(()); }