From a5864127d18c4b056d3407a98bf48756f08de8d5 Mon Sep 17 00:00:00 2001 From: Christian Nennemann Date: Wed, 4 Mar 2026 12:02:07 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20v2=20Phase=201=20=E2=80=94=20foundation?= =?UTF-8?q?,=20proto=20schemas,=20RPC=20framework,=20SDK=20skeleton?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New workspace structure with 9 crates. Adds: - proto/qpq/v1/*.proto: 11 protobuf schemas covering all 33 RPC methods - quicproquo-proto: dual codegen (capnp legacy + prost v2) - quicproquo-rpc: QUIC RPC framework (framing, server, client, middleware) - quicproquo-sdk: client SDK (QpqClient, events, conversation store) - quicproquo-server/domain/: protocol-agnostic domain types and services - justfile: build commands Wire format: [method_id:u16][req_id:u32][len:u32][protobuf] per QUIC stream. All 151 existing tests pass. Backward compatible with v1 capnp code. --- Cargo.lock | 2918 ++--------------- Cargo.toml | 24 +- crates/quicproquo-proto/Cargo.toml | 26 +- crates/quicproquo-proto/build.rs | 73 +- crates/quicproquo-proto/src/lib.rs | 130 +- crates/quicproquo-rpc/Cargo.toml | 25 + crates/quicproquo-rpc/src/client.rs | 175 + crates/quicproquo-rpc/src/error.rs | 68 + crates/quicproquo-rpc/src/framing.rs | 280 ++ crates/quicproquo-rpc/src/lib.rs | 13 + crates/quicproquo-rpc/src/method.rs | 102 + crates/quicproquo-rpc/src/middleware.rs | 96 + crates/quicproquo-rpc/src/server.rs | 198 ++ crates/quicproquo-sdk/Cargo.toml | 32 + crates/quicproquo-sdk/src/client.rs | 193 ++ crates/quicproquo-sdk/src/config.rs | 44 + crates/quicproquo-sdk/src/conversation.rs | 481 +++ crates/quicproquo-sdk/src/error.rs | 29 + crates/quicproquo-sdk/src/events.rs | 56 + crates/quicproquo-sdk/src/lib.rs | 10 + crates/quicproquo-server/src/domain/auth.rs | 72 + .../quicproquo-server/src/domain/delivery.rs | 110 + crates/quicproquo-server/src/domain/mod.rs | 10 + crates/quicproquo-server/src/domain/types.rs | 260 ++ crates/quicproquo-server/src/main.rs | 1 + justfile | 49 + proto/qpq/v1/auth.proto | 43 + proto/qpq/v1/blob.proto | 29 + proto/qpq/v1/channel.proto | 14 + proto/qpq/v1/common.proto | 19 + proto/qpq/v1/delivery.proto | 72 + proto/qpq/v1/device.proto | 34 + proto/qpq/v1/federation.proto | 65 + proto/qpq/v1/keys.proto | 45 + proto/qpq/v1/p2p.proto | 26 + proto/qpq/v1/push.proto | 49 + proto/qpq/v1/user.proto | 22 + 37 files changed, 3115 insertions(+), 2778 deletions(-) create mode 100644 crates/quicproquo-rpc/Cargo.toml create mode 100644 crates/quicproquo-rpc/src/client.rs create mode 100644 crates/quicproquo-rpc/src/error.rs create mode 100644 crates/quicproquo-rpc/src/framing.rs create mode 100644 crates/quicproquo-rpc/src/lib.rs create mode 100644 crates/quicproquo-rpc/src/method.rs create mode 100644 crates/quicproquo-rpc/src/middleware.rs create mode 100644 crates/quicproquo-rpc/src/server.rs create mode 100644 crates/quicproquo-sdk/Cargo.toml create mode 100644 crates/quicproquo-sdk/src/client.rs create mode 100644 crates/quicproquo-sdk/src/config.rs create mode 100644 crates/quicproquo-sdk/src/conversation.rs create mode 100644 crates/quicproquo-sdk/src/error.rs create mode 100644 crates/quicproquo-sdk/src/events.rs create mode 100644 crates/quicproquo-sdk/src/lib.rs create mode 100644 crates/quicproquo-server/src/domain/auth.rs create mode 100644 crates/quicproquo-server/src/domain/delivery.rs create mode 100644 crates/quicproquo-server/src/domain/mod.rs create mode 100644 crates/quicproquo-server/src/domain/types.rs create mode 100644 justfile create mode 100644 proto/qpq/v1/auth.proto create mode 100644 proto/qpq/v1/blob.proto create mode 100644 proto/qpq/v1/channel.proto create mode 100644 proto/qpq/v1/common.proto create mode 100644 proto/qpq/v1/delivery.proto create mode 100644 proto/qpq/v1/device.proto create mode 100644 proto/qpq/v1/federation.proto create mode 100644 proto/qpq/v1/keys.proto create mode 100644 proto/qpq/v1/p2p.proto create mode 100644 proto/qpq/v1/push.proto create mode 100644 proto/qpq/v1/user.proto diff --git a/Cargo.lock b/Cargo.lock index 0538fb8..cc65d85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,21 +108,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -248,7 +233,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -260,7 +245,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -299,7 +284,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -313,29 +298,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -357,7 +319,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" dependencies = [ - "base64 0.22.1", + "base64", "http", "log", "url", @@ -414,7 +376,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -429,12 +391,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -467,9 +423,6 @@ name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" -dependencies = [ - "serde_core", -] [[package]] name = "blake2" @@ -530,27 +483,6 @@ dependencies = [ "objc2", ] -[[package]] -name = "brotli" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bstr" version = "1.12.1" @@ -568,12 +500,6 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - [[package]] name = "byteorder" version = "1.5.0" @@ -585,43 +511,6 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.11.0", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "camino" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" -dependencies = [ - "serde_core", -] [[package]] name = "capnp" @@ -662,39 +551,6 @@ dependencies = [ "capnp", ] -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.12+spec-1.1.0", -] - [[package]] name = "cassowary" version = "0.3.0" @@ -734,27 +590,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -825,7 +660,7 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -903,10 +738,10 @@ version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -990,12 +825,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.10.0" @@ -1005,16 +834,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - [[package]] name = "cordyceps" version = "0.3.4" @@ -1041,30 +860,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.11.0", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.11.0", - "core-foundation", - "libc", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -1074,15 +869,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - [[package]] name = "criterion" version = "0.5.1" @@ -1222,43 +1008,6 @@ dependencies = [ "hybrid-array 0.4.7", ] -[[package]] -name = "cssparser" -version = "0.29.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.117", -] - -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.117", -] - [[package]] name = "ctr" version = "0.7.0" @@ -1334,7 +1083,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1360,16 +1109,6 @@ dependencies = [ "darling_macro 0.20.11", ] -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - [[package]] name = "darling" version = "0.23.0" @@ -1391,21 +1130,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", + "syn", ] [[package]] @@ -1418,7 +1143,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn", ] [[package]] @@ -1429,18 +1154,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1451,7 +1165,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1516,7 +1230,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde_core", ] [[package]] @@ -1527,7 +1240,7 @@ checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1548,7 +1261,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1558,20 +1271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.117", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", + "syn", ] [[package]] @@ -1589,11 +1289,11 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "convert_case 0.10.0", + "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn", "unicode-xid", ] @@ -1641,33 +1341,6 @@ dependencies = [ "crypto-common 0.2.1", ] -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - [[package]] name = "dispatch2" version = "0.3.1" @@ -1688,7 +1361,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1702,29 +1375,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "document-features" version = "0.2.12" @@ -1734,30 +1384,6 @@ dependencies = [ "litrs", ] -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] - -[[package]] -name = "dtoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - [[package]] name = "dunce" version = "1.0.5" @@ -1890,26 +1516,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "embed-resource" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 0.9.12+spec-1.1.0", - "vswhom", - "winreg 0.55.0", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - [[package]] name = "embedded-io" version = "0.4.0" @@ -1928,10 +1534,10 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1942,7 +1548,7 @@ checksum = "3ed8956bd5c1f0415200516e78ff07ec9e16415ade83c056c230d7b7ea0d55b7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1951,17 +1557,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erased-serde" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - [[package]] name = "errno" version = "0.3.14" @@ -1993,7 +1588,7 @@ dependencies = [ "getrandom 0.3.4", "libm", "rand 0.9.2", - "siphasher 1.0.2", + "siphasher", ] [[package]] @@ -2002,15 +1597,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - [[package]] name = "ff" version = "0.13.1" @@ -2033,16 +1619,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -2050,14 +1626,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "flate2" -version = "1.1.9" +name = "fixedbitset" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flume" @@ -2088,33 +1660,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -2130,16 +1675,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures" version = "0.3.32" @@ -2222,7 +1757,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2254,114 +1789,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - [[package]] name = "generator" version = "0.8.8" @@ -2373,8 +1800,8 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.2.1", - "windows-result 0.4.1", + "windows-link", + "windows-result", ] [[package]] @@ -2468,91 +1895,6 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.11.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.0", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "gloo-timers" version = "0.3.0" @@ -2565,17 +1907,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - [[package]] name = "group" version = "0.13.0" @@ -2587,58 +1918,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "h2" version = "0.4.13" @@ -2651,7 +1930,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -2678,12 +1957,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -2738,12 +2011,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -2879,18 +2146,6 @@ dependencies = [ "x25519-dalek-ng", ] -[[package]] -name = "html5ever" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" -dependencies = [ - "log", - "mac", - "markup5ever", - "match_token", -] - [[package]] name = "http" version = "1.4.0" @@ -2993,7 +2248,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.6", + "webpki-roots", ] [[package]] @@ -3002,7 +2257,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", @@ -3031,7 +2286,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -3043,16 +2298,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ico" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" -dependencies = [ - "byteorder", - "png", -] - [[package]] name = "icu_collections" version = "2.1.1" @@ -3204,17 +2449,6 @@ dependencies = [ "xmltree", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.13.0" @@ -3236,15 +2470,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", -] - [[package]] name = "inout" version = "0.1.4" @@ -3264,7 +2489,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3276,7 +2501,7 @@ dependencies = [ "socket2 0.5.10", "widestring", "windows-sys 0.48.0", - "winreg 0.50.0", + "winreg", ] [[package]] @@ -3305,7 +2530,7 @@ dependencies = [ "bytes", "cfg_aliases", "data-encoding", - "derive_more 2.1.1", + "derive_more", "ed25519-dalek 3.0.0-pre.1", "futures-util", "getrandom 0.3.4", @@ -3345,7 +2570,7 @@ dependencies = [ "tracing", "url", "wasm-bindgen-futures", - "webpki-roots 1.0.6", + "webpki-roots", ] [[package]] @@ -3356,7 +2581,7 @@ checksum = "20c99d836a1c99e037e98d1bf3ef209c3a4df97555a00ce9510eb78eccdf5567" dependencies = [ "curve25519-dalek 5.0.0-pre.1", "data-encoding", - "derive_more 2.1.1", + "derive_more", "digest 0.11.0-rc.10", "ed25519-dalek 3.0.0-pre.1", "n0-error", @@ -3389,10 +2614,10 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab063c2bfd6c3d5a33a913d4fdb5252f140db29ec67c704f20f3da7e8f92dbf" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3423,7 +2648,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de99ad8adc878ee0e68509ad256152ce23b8bbe45f5539d04e179630aca40a9" dependencies = [ "bytes", - "derive_more 2.1.1", + "derive_more", "enum-assoc", "fastbloom", "getrandom 0.3.4", @@ -3465,7 +2690,7 @@ dependencies = [ "bytes", "cfg_aliases", "data-encoding", - "derive_more 2.1.1", + "derive_more", "getrandom 0.3.4", "hickory-resolver", "http", @@ -3497,7 +2722,7 @@ dependencies = [ "tracing", "url", "vergen-gitcl", - "webpki-roots 1.0.6", + "webpki-roots", "ws_stream_wasm", "z32", ] @@ -3552,29 +2777,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "jni" version = "0.21.1" @@ -3617,28 +2819,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "keccak" version = "0.1.6" @@ -3658,29 +2838,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.11.0", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser", - "html5ever", - "indexmap 2.13.0", - "selectors", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -3693,46 +2850,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading 0.7.4", - "once_cell", -] - [[package]] name = "libc" version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libloading" version = "0.8.9" @@ -3740,7 +2863,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3749,15 +2872,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" -dependencies = [ - "libc", -] - [[package]] name = "libsqlite3-sys" version = "0.28.0" @@ -3845,43 +2959,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - [[package]] name = "mac-addr" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" -[[package]] -name = "markup5ever" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" -dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "matchers" version = "0.2.0" @@ -3891,12 +2974,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "mdns-sd" version = "0.12.0" @@ -3917,15 +2994,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "metrics" version = "0.22.4" @@ -3952,12 +3020,12 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4f0c8427b39666bf970460908b213ec09b3b350f20c0c2eabcbba51704a08e6" dependencies = [ - "base64 0.22.1", + "base64", "http-body-util", "hyper", "hyper-rustls", "hyper-util", - "indexmap 2.13.0", + "indexmap", "ipnet", "metrics 0.23.1", "metrics-util", @@ -3982,12 +3050,6 @@ dependencies = [ "sketches-ddsketch", ] -[[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" @@ -4001,7 +3063,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", - "simd-adler32", ] [[package]] @@ -4046,25 +3107,10 @@ dependencies = [ ] [[package]] -name = "muda" -version = "0.17.1" +name = "multimap" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png", - "serde", - "thiserror 2.0.18", - "windows-sys 0.60.2", -] +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "n0-error" @@ -4084,7 +3130,7 @@ checksum = "03755949235714b2b307e5ae89dd8c1c2531fb127d9b8b7b4adf9c876cd3ed18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4094,7 +3140,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe" dependencies = [ "cfg_aliases", - "derive_more 2.1.1", + "derive_more", "futures-buffered", "futures-lite", "futures-util", @@ -4114,41 +3160,11 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38795f7932e6e9d1c6e989270ef5b3ff24ebb910e2c9d4bed2d28d8bae3007dc" dependencies = [ - "derive_more 2.1.1", + "derive_more", "n0-error", "n0-future", ] -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.11.0", - "jni-sys", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - [[package]] name = "netdev" version = "0.40.0" @@ -4157,7 +3173,7 @@ checksum = "dc9815643a243856e7bd84524e1ff739e901e846cfb06ad9627cd2b6d59bd737" dependencies = [ "block2", "dispatch2", - "dlopen2 0.5.0", + "dlopen2", "ipnet", "libc", "mac-addr", @@ -4240,7 +3256,7 @@ dependencies = [ "atomic-waker", "bytes", "cfg_aliases", - "derive_more 2.1.1", + "derive_more", "iroh-quinn-udp", "js-sys", "libc", @@ -4262,23 +3278,11 @@ dependencies = [ "tokio-util", "tracing", "web-sys", - "windows 0.62.2", - "windows-result 0.4.1", + "windows", + "windows-result", "wmi", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nom" version = "7.1.3" @@ -4373,10 +3377,10 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4395,50 +3399,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", - "objc2-exception-helper", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.11.0", - "block2", - "libc", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-text", - "objc2-core-video", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-foundation", ] [[package]] @@ -4454,115 +3414,12 @@ dependencies = [ "objc2", ] -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.11.0", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-core-video" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-io-surface", -] - [[package]] name = "objc2-encode" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.11.0", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-javascript-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" -dependencies = [ - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - [[package]] name = "objc2-security" version = "0.3.2" @@ -4588,34 +3445,6 @@ dependencies = [ "objc2-security", ] -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.11.0", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-javascript-core", - "objc2-security", -] - [[package]] name = "object" version = "0.37.3" @@ -4753,12 +3582,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "p256" version = "0.13.2" @@ -4783,31 +3606,6 @@ dependencies = [ "sha2 0.10.9", ] -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "papaya" version = "0.2.3" @@ -4844,7 +3642,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4870,7 +3668,7 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64 0.22.1", + "base64", "serde_core", ] @@ -4898,6 +3696,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pharos" version = "0.5.3" @@ -4908,140 +3716,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.2", -] - [[package]] name = "pin-project" version = "1.1.11" @@ -5059,7 +3733,7 @@ checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -5137,8 +3811,8 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ - "base64 0.22.1", - "indexmap 2.13.0", + "base64", + "indexmap", "quick-xml", "serde", "time", @@ -5172,19 +3846,6 @@ dependencies = [ "plotters-backend", ] -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - [[package]] name = "polling" version = "2.8.0" @@ -5259,9 +3920,9 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d2a8825353ace3285138da3378b1e21860d60351942f7aa3b99b13b41f80318" dependencies = [ - "base64 0.22.1", + "base64", "bytes", - "derive_more 2.1.1", + "derive_more", "futures-lite", "futures-util", "hyper-util", @@ -5314,7 +3975,7 @@ checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -5341,12 +4002,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "predicates" version = "3.1.4" @@ -5381,7 +4036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn", ] [[package]] @@ -5393,25 +4048,6 @@ dependencies = [ "elliptic-curve", ] -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" -dependencies = [ - "toml_edit 0.20.7", -] - [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -5421,36 +4057,6 @@ dependencies = [ "toml_edit 0.23.10+spec-1.0.0", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.106" @@ -5470,6 +4076,26 @@ dependencies = [ "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + [[package]] name = "prost-derive" version = "0.13.5" @@ -5480,7 +4106,25 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "protobuf-src" +version = "2.1.1+27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6217c3504da19b85a3a4b2e9a5183d635822d83507ba0986624b5c05b83bfc40" +dependencies = [ + "cmake", ] [[package]] @@ -5507,22 +4151,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quicproquo-bot" -version = "0.1.0" -dependencies = [ - "anyhow", - "hex", - "openmls_rust_crypto", - "quicproquo-client", - "quicproquo-core", - "quicproquo-proto", - "serde", - "serde_json", - "tokio", - "tracing", -] - [[package]] name = "quicproquo-client" version = "0.1.0" @@ -5600,39 +4228,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "quicproquo-ffi" -version = "0.1.0" -dependencies = [ - "anyhow", - "capnp", - "hex", - "quicproquo-client", - "serde_json", - "tokio", -] - -[[package]] -name = "quicproquo-gen" -version = "0.1.0" -dependencies = [ - "clap", -] - -[[package]] -name = "quicproquo-gui" -version = "0.1.0" -dependencies = [ - "quicproquo-client", - "quicproquo-core", - "quicproquo-proto", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tokio", -] - [[package]] name = "quicproquo-kt" version = "0.1.0" @@ -5643,18 +4238,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "quicproquo-mobile" -version = "0.1.0" -dependencies = [ - "anyhow", - "quinn", - "rcgen", - "rustls", - "tokio", - "webpki-roots 0.26.11", -] - [[package]] name = "quicproquo-p2p" version = "0.1.0" @@ -5680,10 +4263,58 @@ version = "0.1.0" [[package]] name = "quicproquo-proto" -version = "0.1.0" +version = "0.2.0" dependencies = [ + "bytes", "capnp", "capnpc", + "prost", + "prost-build", + "prost-types", + "protobuf-src", +] + +[[package]] +name = "quicproquo-rpc" +version = "0.1.0" +dependencies = [ + "bytes", + "dashmap", + "futures", + "prost", + "quicproquo-proto", + "quinn", + "rcgen", + "rustls", + "thiserror 1.0.69", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "quicproquo-sdk" +version = "0.1.0" +dependencies = [ + "anyhow", + "argon2", + "bincode", + "futures", + "hex", + "quicproquo-core", + "quicproquo-proto", + "quicproquo-rpc", + "quinn", + "rand 0.8.5", + "rusqlite", + "rustls", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror 1.0.69", + "tokio", + "tracing", + "zeroize", ] [[package]] @@ -5691,7 +4322,7 @@ name = "quicproquo-server" version = "0.1.0" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "bincode", "capnp", "capnp-rpc", @@ -5699,7 +4330,7 @@ dependencies = [ "dashmap", "futures", "hex", - "libloading 0.8.9", + "libloading", "mdns-sd", "metrics 0.22.4", "metrics-exporter-prometheus", @@ -5723,7 +4354,7 @@ dependencies = [ "tokio", "tokio-tungstenite", "tokio-util", - "toml 0.8.23", + "toml", "tracing", "tracing-subscriber", "x509-parser", @@ -5814,7 +4445,6 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", - "rand_pcg", ] [[package]] @@ -5904,15 +4534,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "ratatui" version = "0.29.0" @@ -5943,12 +4564,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - [[package]] name = "rayon" version = "1.11.0" @@ -5991,37 +4606,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.18", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "regex" version = "1.12.3" @@ -6057,7 +4641,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-core", "futures-util", @@ -6087,9 +4671,9 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.4.2", + "wasm-streams", "web-sys", - "webpki-roots 1.0.6", + "webpki-roots", ] [[package]] @@ -6098,10 +4682,9 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-core", - "futures-util", "http", "http-body", "http-body-util", @@ -6116,19 +4699,15 @@ dependencies = [ "rustls", "rustls-pki-types", "rustls-platform-verifier", - "serde", - "serde_json", "sync_wrapper", "tokio", "tokio-rustls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.5.0", "web-sys", ] @@ -6349,57 +4928,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.117", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -6460,24 +4988,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more 0.99.20", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", -] - [[package]] name = "self_cell" version = "1.2.2" @@ -6489,10 +4999,6 @@ name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] [[package]] name = "send_wrapper" @@ -6510,18 +5016,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - [[package]] name = "serde_bytes" version = "0.11.19" @@ -6549,18 +5043,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -6576,17 +5059,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "serde_spanned" version = "0.6.9" @@ -6596,15 +5068,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" -dependencies = [ - "serde_core", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6617,44 +5080,13 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.13.0", - "schemars 0.9.0", - "schemars 1.2.1", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" -dependencies = [ - "darling 0.21.3", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.13.0", + "indexmap", "itoa", "ryu", "serde", @@ -6671,38 +5103,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - [[package]] name = "sha1" version = "0.10.6" @@ -6833,12 +5233,6 @@ version = "3.0.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3" -[[package]] -name = "simd-adler32" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" - [[package]] name = "simdutf8" version = "0.1.5" @@ -6854,12 +5248,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "siphasher" version = "1.0.2" @@ -6904,60 +5292,12 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" -dependencies = [ - "bytemuck", - "js-sys", - "ndk", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall", - "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", -] - [[package]] name = "sorted-index-buffer" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "spez" version = "0.1.2" @@ -6966,7 +5306,7 @@ checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -7016,31 +5356,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - [[package]] name = "strsim" version = "0.11.1" @@ -7071,11 +5386,11 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.117", + "syn", ] [[package]] @@ -7084,10 +5399,10 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -7102,28 +5417,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[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.117" @@ -7152,20 +5445,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.23", - "version-compare", + "syn", ] [[package]] @@ -7174,278 +5454,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" -[[package]] -name = "tao" -version = "0.34.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" -dependencies = [ - "bitflags 2.11.0", - "block2", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dispatch", - "dlopen2 0.8.2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "parking_lot", - "raw-window-handle", - "scopeguard", - "tao-macros", - "unicode-segmentation", - "url", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463ae8677aa6d0f063a900b9c41ecd4ac2b7ca82f0b058cc4491540e55b20129" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest 0.13.2", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.18", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows 0.61.3", -] - -[[package]] -name = "tauri-build" -version = "2.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca7bd893329425df750813e95bd2b643d5369d929438da96d5bbb7cc2c918f74" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "toml 0.9.12+spec-1.1.0", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac423e5859d9f9ccdd32e3cf6a5866a15bedbf25aa6630bcb2acde9468f6ae3" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2 0.10.9", - "syn 2.0.117", - "tauri-utils", - "thiserror 2.0.18", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6a1bd2861ff0c8766b1d38b32a6a410f6dc6532d4ef534c47cfb2236092f59" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-runtime" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni", - "objc2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dunce", - "glob", - "html5ever", - "http", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.3", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" -dependencies = [ - "dunce", - "embed-resource", - "toml 0.9.12+spec-1.1.0", -] - [[package]] name = "tempfile" version = "3.26.0" @@ -7459,17 +5467,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - [[package]] name = "termtree" version = "0.5.1" @@ -7502,7 +5499,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -7513,7 +5510,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -7624,7 +5621,7 @@ checksum = "3226440488120aabe7e7cc80292634a68e541c407d97b66eceaae787454dae25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -7635,7 +5632,7 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -7662,7 +5659,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -7720,7 +5717,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1b6348ebfaaecd771cecb69e832961d277f59845d4220a584701f72728152b7" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-core", "futures-sink", @@ -7743,26 +5740,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned 0.6.9", + "serde_spanned", "toml_datetime 0.6.11", "toml_edit 0.22.27", ] -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap 2.13.0", - "serde_core", - "serde_spanned 1.0.4", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.14", -] - [[package]] name = "toml_datetime" version = "0.6.11" @@ -7781,40 +5763,18 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.13.0", - "toml_datetime 0.6.11", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" -dependencies = [ - "indexmap 2.13.0", - "toml_datetime 0.6.11", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.13.0", + "indexmap", "serde", - "serde_spanned 0.6.9", + "serde_spanned", "toml_datetime 0.6.11", "toml_write", - "winnow 0.7.14", + "winnow", ] [[package]] @@ -7823,10 +5783,10 @@ version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.13.0", + "indexmap", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow 0.7.14", + "winnow", ] [[package]] @@ -7835,7 +5795,7 @@ version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ - "winnow 0.7.14", + "winnow", ] [[package]] @@ -7844,12 +5804,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" -[[package]] -name = "toml_writer" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" - [[package]] name = "tower" version = "0.5.3" @@ -7861,8 +5815,10 @@ dependencies = [ "pin-project-lite", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -7915,7 +5871,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -7957,28 +5913,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "tray-icon" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "once_cell", - "png", - "serde", - "thiserror 2.0.18", - "windows-sys 0.60.2", -] - [[package]] name = "try-lock" version = "0.2.5" @@ -8002,59 +5936,12 @@ dependencies = [ "utf-8", ] -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicode-ident" version = "1.0.24" @@ -8141,18 +6028,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - [[package]] name = "utf-8" version = "0.7.6" @@ -8179,7 +6054,6 @@ checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ "getrandom 0.4.1", "js-sys", - "serde_core", "wasm-bindgen", ] @@ -8243,12 +6117,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - [[package]] name = "version_check" version = "0.9.5" @@ -8274,26 +6142,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "wait-timeout" version = "0.2.1" @@ -8398,7 +6246,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -8428,7 +6276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap", "wasm-encoder", "wasmparser", ] @@ -8446,19 +6294,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmparser" version = "0.244.0" @@ -8467,7 +6302,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap", "semver", ] @@ -8491,50 +6326,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webkit2gtk" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - [[package]] name = "webpki-root-certs" version = "1.0.6" @@ -8544,15 +6335,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.6", -] - [[package]] name = "webpki-roots" version = "1.0.6" @@ -8562,42 +6344,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webview2-com" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" -dependencies = [ - "thiserror 2.0.18", - "windows 0.61.3", - "windows-core 0.61.2", -] - [[package]] name = "widestring" version = "1.2.1" @@ -8635,53 +6381,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.2", - "windows-future 0.2.1", - "windows-link 0.1.3", - "windows-numerics 0.2.0", -] - [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections 0.3.2", - "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", ] [[package]] @@ -8690,20 +6399,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -8714,20 +6410,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -8736,9 +6421,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading 0.2.1", + "windows-core", + "windows-link", + "windows-threading", ] [[package]] @@ -8749,7 +6434,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -8760,48 +6445,23 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - [[package]] name = "windows-numerics" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", + "windows-core", + "windows-link", ] [[package]] @@ -8810,16 +6470,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -8828,7 +6479,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -8882,7 +6533,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -8937,7 +6588,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -8948,31 +6599,13 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -9155,15 +6788,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.7.14" @@ -9183,16 +6807,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -9209,7 +6823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", - "heck 0.5.0", + "heck", "wit-parser", ] @@ -9220,10 +6834,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", - "heck 0.5.0", - "indexmap 2.13.0", + "heck", + "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -9239,7 +6853,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -9252,7 +6866,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap 2.13.0", + "indexmap", "log", "serde", "serde_derive", @@ -9271,7 +6885,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap", "log", "semver", "serde", @@ -9292,8 +6906,8 @@ dependencies = [ "log", "serde", "thiserror 2.0.18", - "windows 0.62.2", - "windows-core 0.62.2", + "windows", + "windows-core", ] [[package]] @@ -9302,51 +6916,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" -[[package]] -name = "wry" -version = "0.54.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dirs", - "dpi", - "dunce", - "gdkx11", - "gtk", - "html5ever", - "http", - "javascriptcore-rs", - "jni", - "kuchikiki", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2 0.10.9", - "soup3", - "tao-macros", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - [[package]] name = "ws_stream_wasm" version = "0.7.5" @@ -9366,27 +6935,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - [[package]] name = "x25519-dalek" version = "2.0.1" @@ -9471,7 +7019,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -9498,7 +7046,7 @@ checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -9518,7 +7066,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -9540,7 +7088,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -9573,7 +7121,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9075403..ef64687 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,16 +5,12 @@ members = [ "crates/quicproquo-proto", "crates/quicproquo-plugin-api", "crates/quicproquo-kt", + "crates/quicproquo-rpc", + "crates/quicproquo-sdk", "crates/quicproquo-server", "crates/quicproquo-client", - "crates/quicproquo-bot", - "crates/quicproquo-gen", - "crates/quicproquo-gui", - "crates/quicproquo-mobile", - "crates/quicproquo-ffi", - # P2P crate uses iroh (~90 extra deps). Kept in the workspace so it can be - # referenced as an optional dependency; only compiled when the `mesh` feature - # is enabled on quicproquo-client. + # P2P crate uses iroh (~90 extra deps). Only compiled when the `mesh` + # feature is enabled on quicproquo-client. "crates/quicproquo-p2p", ] @@ -29,7 +25,6 @@ openmls_traits = { version = "0.2" } # duplicate Serialize trait versions in the dependency graph. tls_codec = { version = "0.3", features = ["derive"] } # ml-kem 0.2 is the current stable release (FIPS 203, ML-KEM-768). -# All three parameter sets (512/768/1024) are compiled in by default — no feature flag needed. ml-kem = { version = "0.2" } x25519-dalek = { version = "2", features = ["static_secrets"] } ed25519-dalek = { version = "2", features = ["rand_core"] } @@ -47,7 +42,12 @@ serde = { version = "1", features = ["derive"] } serde_json = { version = "1" } bincode = { version = "1" } -# ── Serialisation + RPC ─────────────────────────────────────────────────────── +# ── Serialisation (v2: protobuf) ───────────────────────────────────────────── +prost = { version = "0.13" } +prost-types = { version = "0.13" } +prost-build = { version = "0.13" } + +# ── Serialisation (v1 legacy — capnp, used by existing server/client) ──────── capnp = { version = "0.19" } capnp-rpc = { version = "0.19" } @@ -60,11 +60,15 @@ quinn-proto = { version = "0.11" } rustls = { version = "0.23", default-features = false, features = ["std", "ring"] } rcgen = { version = "0.13" } +# ── Middleware ──────────────────────────────────────────────────────────────── +tower = { version = "0.5", features = ["util", "limit", "timeout"] } + # ── Database ───────────────────────────────────────────────────────────── rusqlite = { version = "0.31", features = ["bundled-sqlcipher"] } # ── Encoding ───────────────────────────────────────────────────────────────── hex = { version = "0.4" } +bytes = { version = "1" } # ── Server utilities ────────────────────────────────────────────────────────── dashmap = { version = "5" } diff --git a/crates/quicproquo-proto/Cargo.toml b/crates/quicproquo-proto/Cargo.toml index fe264fd..34d6f5d 100644 --- a/crates/quicproquo-proto/Cargo.toml +++ b/crates/quicproquo-proto/Cargo.toml @@ -1,22 +1,28 @@ [package] -name = "quicproquo-proto" -version = "0.1.0" -edition = "2021" -description = "Cap'n Proto schemas, generated types, and serialisation helpers for quicproquo. No crypto, no I/O." -license = "MIT" +name = "quicproquo-proto" +version = "0.2.0" +edition = "2021" +description = "Protocol types for quicproquo — v1 Cap'n Proto (legacy) + v2 Protobuf (prost)" -# build.rs invokes capnpc to generate Rust source from .capnp schemas. -build = "build.rs" +build = "build.rs" [dependencies] +# v1 legacy (Cap'n Proto) — used by existing server/client until rewrite capnp = { workspace = true } +# v2 (Protobuf via prost) — new RPC types +prost = { workspace = true } +prost-types = { workspace = true } +bytes = { workspace = true } + +[build-dependencies] +capnpc = { workspace = true } +prost-build = { workspace = true } +protobuf-src = "2" + [lints.rust] unsafe_code = "warn" [lints.clippy] # Generated Cap'n Proto code uses patterns that trigger clippy lints. unwrap_used = "allow" - -[build-dependencies] -capnpc = { workspace = true } diff --git a/crates/quicproquo-proto/build.rs b/crates/quicproquo-proto/build.rs index 51efa81..5dfb16d 100644 --- a/crates/quicproquo-proto/build.rs +++ b/crates/quicproquo-proto/build.rs @@ -1,51 +1,30 @@ //! Build script for quicproquo-proto. //! -//! Invokes the `capnp` compiler to generate Rust types from `.capnp` schemas -//! located in the workspace-root `schemas/` directory. -//! -//! # Prerequisites -//! -//! The `capnp` CLI must be installed and on `PATH`. -//! -//! Debian/Ubuntu: apt-get install capnproto -//! macOS: brew install capnp -//! Docker: see docker/Dockerfile +//! Runs two code generators: +//! 1. Cap'n Proto (v1 legacy) — from `schemas/*.capnp` +//! 2. Protobuf/prost (v2) — from `proto/qpq/v1/*.proto` use std::{env, path::PathBuf}; fn main() { - let manifest_dir = - PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set by Cargo")); + // Vendor protoc from protobuf-src so the build doesn't require system protoc. + std::env::set_var("PROTOC", protobuf_src::protoc()); - // Workspace root is two levels above this crate (quicproquo/crates/quicproquo-proto). + let manifest_dir = + PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); let workspace_root = manifest_dir .join("../..") .canonicalize() - .expect("could not canonicalize workspace root path"); + .expect("canonicalize workspace root"); + // ── v1: Cap'n Proto codegen ────────────────────────────────────────────── let schemas_dir = workspace_root.join("schemas"); - // Re-run this build script whenever any schema file changes. - println!( - "cargo:rerun-if-changed={}", - schemas_dir.join("auth.capnp").display() - ); - println!( - "cargo:rerun-if-changed={}", - schemas_dir.join("delivery.capnp").display() - ); - println!( - "cargo:rerun-if-changed={}", - schemas_dir.join("node.capnp").display() - ); - println!( - "cargo:rerun-if-changed={}", - schemas_dir.join("federation.capnp").display() - ); + for schema in &["auth.capnp", "delivery.capnp", "node.capnp", "federation.capnp"] { + println!("cargo:rerun-if-changed={}", schemas_dir.join(schema).display()); + } capnpc::CompilerCommand::new() - // Treat `schemas/` as the include root so that inter-schema imports - // resolve correctly. .src_prefix(&schemas_dir) .file(schemas_dir.join("auth.capnp")) .file(schemas_dir.join("delivery.capnp")) @@ -56,4 +35,32 @@ fn main() { "Cap'n Proto schema compilation failed. \ Is `capnp` installed? (apt-get install capnproto / brew install capnp)", ); + + // ── v2: Protobuf/prost codegen ─────────────────────────────────────────── + let proto_dir = workspace_root.join("proto"); + + let proto_files = [ + "qpq/v1/common.proto", + "qpq/v1/auth.proto", + "qpq/v1/delivery.proto", + "qpq/v1/keys.proto", + "qpq/v1/channel.proto", + "qpq/v1/user.proto", + "qpq/v1/blob.proto", + "qpq/v1/device.proto", + "qpq/v1/p2p.proto", + "qpq/v1/federation.proto", + "qpq/v1/push.proto", + ]; + + let full_paths: Vec = proto_files.iter().map(|f| proto_dir.join(f)).collect(); + + for path in &full_paths { + println!("cargo:rerun-if-changed={}", path.display()); + } + + prost_build::Config::new() + .out_dir(PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"))) + .compile_protos(&full_paths, &[&proto_dir]) + .expect("prost compile_protos failed"); } diff --git a/crates/quicproquo-proto/src/lib.rs b/crates/quicproquo-proto/src/lib.rs index 9b827df..4f429f0 100644 --- a/crates/quicproquo-proto/src/lib.rs +++ b/crates/quicproquo-proto/src/lib.rs @@ -1,56 +1,38 @@ -//! Cap'n Proto schemas, generated types, and serialisation helpers for quicproquo. +//! Protocol types for quicproquo. +//! +//! This crate contains both: +//! - **v1 (legacy)**: Cap'n Proto generated types from `schemas/*.capnp` +//! - **v2**: Protobuf generated types from `proto/qpq/v1/*.proto` //! -//! Generated Cap'n Proto code emits unnecessary parentheses; allow per coding standards. -#![allow(unused_parens)] - //! # Design constraints //! -//! This crate is intentionally restricted: //! - **No crypto** — key material never enters this crate. -//! - **No I/O** — callers own transport; this crate only converts bytes ↔ types. +//! - **No I/O** — callers own transport; this crate only converts bytes <-> types. //! - **No async** — pure synchronous data-layer code. -//! -//! # Generated code -//! -//! `build.rs` invokes `capnpc` at compile time and writes generated Rust source -//! into `$OUT_DIR`. The `include!` macros below splice that code in as a module. -// ── Generated types ─────────────────────────────────────────────────────────── +// ════════════════════════════════════════════════════════════════════════════ +// v1 (legacy): Cap'n Proto generated types +// ════════════════════════════════════════════════════════════════════════════ + +#![allow(unused_parens)] -/// Cap'n Proto generated types for `schemas/auth.capnp`. -/// -/// Do not edit this module by hand — it is entirely machine-generated. pub mod auth_capnp { include!(concat!(env!("OUT_DIR"), "/auth_capnp.rs")); } -/// Cap'n Proto generated types for `schemas/delivery.capnp`. -/// -/// Do not edit this module by hand — it is entirely machine-generated. pub mod delivery_capnp { include!(concat!(env!("OUT_DIR"), "/delivery_capnp.rs")); } -/// Cap'n Proto generated types for `schemas/node.capnp`. -/// -/// Do not edit this module by hand — it is entirely machine-generated. pub mod node_capnp { include!(concat!(env!("OUT_DIR"), "/node_capnp.rs")); } -/// Cap'n Proto generated types for `schemas/federation.capnp`. -/// -/// Do not edit this module by hand — it is entirely machine-generated. pub mod federation_capnp { include!(concat!(env!("OUT_DIR"), "/federation_capnp.rs")); } -// ── Low-level byte ↔ message conversions ────────────────────────────────────── - /// Serialise a Cap'n Proto message builder to unpacked wire bytes. -/// -/// The output includes the segment table header. For transport, the -/// `quicproquo-core` frame codec prepends a 4-byte little-endian length field. pub fn to_bytes( msg: &capnp::message::Builder, ) -> Result, capnp::Error> { @@ -59,25 +41,17 @@ pub fn to_bytes( Ok(buf) } -/// Deserialise unpacked wire bytes into a message with owned segments. -/// -/// Uses a stricter default traversal limit of 1 Mi words (~8 MiB) instead -/// of the Cap'n Proto default of 64 MiB, reducing DoS amplification from -/// untrusted input. Use [`from_bytes_with_options`] if you need a custom limit. +/// Deserialise unpacked wire bytes into a Cap'n Proto message. pub fn from_bytes( bytes: &[u8], ) -> Result, capnp::Error> { let mut options = capnp::message::ReaderOptions::new(); - options.traversal_limit_in_words(Some(1_048_576)); // 1 Mi words = ~8 MiB + options.traversal_limit_in_words(Some(1_048_576)); let mut cursor = std::io::Cursor::new(bytes); capnp::serialize::read_message(&mut cursor, options) } -/// Deserialise unpacked wire bytes with caller-specified [`ReaderOptions`]. -/// -/// Prefer [`from_bytes`] for typical use. Use this variant when you need to -/// raise the traversal limit for large messages (e.g. blob transfers) or -/// lower it further for tighter validation. +/// Deserialise with custom [`ReaderOptions`]. pub fn from_bytes_with_options( bytes: &[u8], options: capnp::message::ReaderOptions, @@ -85,3 +59,79 @@ pub fn from_bytes_with_options( let mut cursor = std::io::Cursor::new(bytes); capnp::serialize::read_message(&mut cursor, options) } + +// ════════════════════════════════════════════════════════════════════════════ +// v2: Protobuf (prost) generated types +// ════════════════════════════════════════════════════════════════════════════ + +/// Protobuf types for the v2 RPC protocol. +pub mod qpq { + pub mod v1 { + include!(concat!(env!("OUT_DIR"), "/qpq.v1.rs")); + } +} + +/// Method ID constants for the v2 RPC dispatch table. +pub mod method_ids { + // Auth (100-103) + pub const OPAQUE_REGISTER_START: u16 = 100; + pub const OPAQUE_REGISTER_FINISH: u16 = 101; + pub const OPAQUE_LOGIN_START: u16 = 102; + pub const OPAQUE_LOGIN_FINISH: u16 = 103; + + // Delivery (200-205) + pub const ENQUEUE: u16 = 200; + pub const FETCH: u16 = 201; + pub const FETCH_WAIT: u16 = 202; + pub const PEEK: u16 = 203; + pub const ACK: u16 = 204; + pub const BATCH_ENQUEUE: u16 = 205; + + // Keys (300-304) + pub const UPLOAD_KEY_PACKAGE: u16 = 300; + pub const FETCH_KEY_PACKAGE: u16 = 301; + pub const UPLOAD_HYBRID_KEY: u16 = 302; + pub const FETCH_HYBRID_KEY: u16 = 303; + pub const FETCH_HYBRID_KEYS: u16 = 304; + + // Channel (400) + pub const CREATE_CHANNEL: u16 = 400; + + // User (500-501) + pub const RESOLVE_USER: u16 = 500; + pub const RESOLVE_IDENTITY: u16 = 501; + + // Blob (600-601) + pub const UPLOAD_BLOB: u16 = 600; + pub const DOWNLOAD_BLOB: u16 = 601; + + // Device (700-702) + pub const REGISTER_DEVICE: u16 = 700; + pub const LIST_DEVICES: u16 = 701; + pub const REVOKE_DEVICE: u16 = 702; + + // P2P (800-802) + pub const PUBLISH_ENDPOINT: u16 = 800; + pub const RESOLVE_ENDPOINT: u16 = 801; + pub const HEALTH: u16 = 802; + + // Federation (900-905) + pub const RELAY_ENQUEUE: u16 = 900; + pub const RELAY_BATCH_ENQUEUE: u16 = 901; + pub const PROXY_FETCH_KEY_PACKAGE: u16 = 902; + pub const PROXY_FETCH_HYBRID_KEY: u16 = 903; + pub const PROXY_RESOLVE_USER: u16 = 904; + pub const FEDERATION_HEALTH: u16 = 905; + + // Account (950) + pub const DELETE_ACCOUNT: u16 = 950; + + // Push event types (1000+) + pub const PUSH_NEW_MESSAGE: u16 = 1000; + pub const PUSH_TYPING: u16 = 1001; + pub const PUSH_PRESENCE: u16 = 1002; + pub const PUSH_MEMBERSHIP: u16 = 1003; +} + +pub use prost; +pub use bytes; diff --git a/crates/quicproquo-rpc/Cargo.toml b/crates/quicproquo-rpc/Cargo.toml new file mode 100644 index 0000000..eccc986 --- /dev/null +++ b/crates/quicproquo-rpc/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "quicproquo-rpc" +version = "0.1.0" +edition = "2021" +description = "QUIC RPC framework for quicproquo v2 — framing, dispatch, tower middleware" + +[dependencies] +quicproquo-proto = { path = "../quicproquo-proto" } +prost = { workspace = true } +bytes = { workspace = true } +quinn = { workspace = true } +rustls = { workspace = true } +rcgen = { workspace = true } +tokio = { workspace = true } +futures = { workspace = true } +tower = { workspace = true } +tracing = { workspace = true } +thiserror = { workspace = true } +dashmap = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true, features = ["test-util"] } + +[lints] +workspace = true diff --git a/crates/quicproquo-rpc/src/client.rs b/crates/quicproquo-rpc/src/client.rs new file mode 100644 index 0000000..268f3c2 --- /dev/null +++ b/crates/quicproquo-rpc/src/client.rs @@ -0,0 +1,175 @@ +//! QUIC RPC client — connect to server, send requests, receive push events. + +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; + +use bytes::{Bytes, BytesMut}; +use quinn::{Connection, Endpoint}; +use tokio::sync::mpsc; +use tracing::{debug, warn}; + +use crate::error::{RpcError, RpcStatus}; +use crate::framing::{PushFrame, RequestFrame, ResponseFrame}; + +/// Configuration for the RPC client. +pub struct RpcClientConfig { + /// Server address to connect to. + pub server_addr: std::net::SocketAddr, + /// Server name for TLS verification. + pub server_name: String, + /// TLS client config (rustls). + pub tls_config: Arc, + /// ALPN protocol. + pub alpn: Vec, +} + +/// A QUIC RPC client connection. +pub struct RpcClient { + connection: Connection, + next_request_id: AtomicU32, +} + +impl RpcClient { + /// Connect to the RPC server. + pub async fn connect(config: RpcClientConfig) -> Result { + let mut tls = (*config.tls_config).clone(); + tls.alpn_protocols = vec![config.alpn]; + let quic_tls = quinn::crypto::rustls::QuicClientConfig::try_from(tls) + .map_err(|e| RpcError::Connection(format!("TLS config: {e}")))?; + + let mut endpoint = Endpoint::client("0.0.0.0:0".parse().expect("valid addr")) + .map_err(|e| RpcError::Connection(e.to_string()))?; + endpoint.set_default_client_config(quinn::ClientConfig::new(Arc::new(quic_tls))); + + let connection = endpoint + .connect(config.server_addr, &config.server_name) + .map_err(|e| RpcError::Connection(e.to_string()))? + .await + .map_err(|e| RpcError::Connection(e.to_string()))?; + + debug!(remote = %connection.remote_address(), "connected to RPC server"); + + Ok(Self { + connection, + next_request_id: AtomicU32::new(1), + }) + } + + /// Send an RPC request and wait for the response. + pub async fn call( + &self, + method_id: u16, + payload: Bytes, + ) -> Result { + let request_id = self.next_request_id.fetch_add(1, Ordering::Relaxed); + + let (mut send, mut recv) = self + .connection + .open_bi() + .await + .map_err(|e| RpcError::Connection(e.to_string()))?; + + // Send request. + let frame = RequestFrame { + method_id, + request_id, + payload, + }; + let encoded = frame.encode(); + send.write_all(&encoded) + .await + .map_err(|e| RpcError::Connection(e.to_string()))?; + send.finish().map_err(|e| RpcError::Connection(e.to_string()))?; + + // Read response. + let mut buf = BytesMut::new(); + while let Some(chunk) = recv + .read_chunk(65536, true) + .await + .map_err(|e| RpcError::Connection(e.to_string()))? + { + buf.extend_from_slice(&chunk.bytes); + if buf.len() > crate::framing::MAX_PAYLOAD_SIZE + crate::framing::RESPONSE_HEADER_SIZE { + return Err(RpcError::PayloadTooLarge { + size: buf.len(), + max: crate::framing::MAX_PAYLOAD_SIZE, + }); + } + } + + let response = ResponseFrame::decode(&mut buf)? + .ok_or_else(|| RpcError::Decode("incomplete response frame".into()))?; + + if response.request_id != request_id { + return Err(RpcError::Decode(format!( + "request_id mismatch: sent {request_id}, got {}", + response.request_id + ))); + } + + match RpcStatus::from_u8(response.status) { + Some(RpcStatus::Ok) => Ok(response.payload), + Some(status) => Err(RpcError::Server { + status, + message: String::from_utf8_lossy(&response.payload).into_owned(), + }), + None => Err(RpcError::Decode(format!( + "unknown status byte: {}", + response.status + ))), + } + } + + /// Subscribe to server-push events. Returns a receiver channel. + /// Spawns a background task that reads uni-streams. + pub fn subscribe_push(&self) -> mpsc::UnboundedReceiver { + let (tx, rx) = mpsc::unbounded_channel(); + let conn = self.connection.clone(); + + tokio::spawn(async move { + loop { + match conn.accept_uni().await { + Ok(mut recv) => { + let mut buf = BytesMut::new(); + loop { + match recv.read_chunk(65536, true).await { + Ok(Some(chunk)) => buf.extend_from_slice(&chunk.bytes), + Ok(None) => break, + Err(e) => { + debug!("push stream read error: {e}"); + break; + } + } + } + match PushFrame::decode(&mut buf) { + Ok(Some(frame)) => { + if tx.send(frame).is_err() { + return; // receiver dropped + } + } + Ok(None) => debug!("incomplete push frame"), + Err(e) => debug!("push decode error: {e}"), + } + } + Err(quinn::ConnectionError::ApplicationClosed(_)) => break, + Err(e) => { + warn!("accept_uni error: {e}"); + break; + } + } + } + }); + + rx + } + + /// Close the connection gracefully. + pub fn close(&self) { + self.connection.close(0u32.into(), b"bye"); + } + + /// Get the underlying QUIC connection (for advanced use). + pub fn connection(&self) -> &Connection { + &self.connection + } +} diff --git a/crates/quicproquo-rpc/src/error.rs b/crates/quicproquo-rpc/src/error.rs new file mode 100644 index 0000000..ac8cfb0 --- /dev/null +++ b/crates/quicproquo-rpc/src/error.rs @@ -0,0 +1,68 @@ +//! RPC error types. + +/// Status codes for RPC responses. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum RpcStatus { + /// Request succeeded. + Ok = 0, + /// Client sent a malformed request. + BadRequest = 1, + /// Authentication required or token invalid. + Unauthorized = 2, + /// Caller lacks permission for this operation. + Forbidden = 3, + /// Requested resource not found. + NotFound = 4, + /// Rate limit exceeded. + RateLimited = 5, + /// Internal server error. + Internal = 10, + /// Method not recognized. + UnknownMethod = 11, +} + +impl RpcStatus { + /// Decode a status byte. Returns `None` for unknown values. + pub fn from_u8(v: u8) -> Option { + match v { + 0 => Some(Self::Ok), + 1 => Some(Self::BadRequest), + 2 => Some(Self::Unauthorized), + 3 => Some(Self::Forbidden), + 4 => Some(Self::NotFound), + 5 => Some(Self::RateLimited), + 10 => Some(Self::Internal), + 11 => Some(Self::UnknownMethod), + _ => None, + } + } +} + +/// Errors that can occur in the RPC layer. +#[derive(Debug, thiserror::Error)] +pub enum RpcError { + #[error("connection error: {0}")] + Connection(String), + + #[error("encoding error: {0}")] + Encode(String), + + #[error("decoding error: {0}")] + Decode(String), + + #[error("server returned error status {status:?}: {message}")] + Server { + status: RpcStatus, + message: String, + }, + + #[error("request timed out")] + Timeout, + + #[error("stream closed unexpectedly")] + StreamClosed, + + #[error("payload too large: {size} bytes (max {max})")] + PayloadTooLarge { size: usize, max: usize }, +} diff --git a/crates/quicproquo-rpc/src/framing.rs b/crates/quicproquo-rpc/src/framing.rs new file mode 100644 index 0000000..c82e355 --- /dev/null +++ b/crates/quicproquo-rpc/src/framing.rs @@ -0,0 +1,280 @@ +//! Wire format encoding and decoding for the quicproquo v2 RPC protocol. +//! +//! ## Request frame +//! ```text +//! [method_id: u16 BE][request_id: u32 BE][payload_len: u32 BE][protobuf bytes] +//! ``` +//! +//! ## Response frame +//! ```text +//! [status: u8][request_id: u32 BE][payload_len: u32 BE][protobuf bytes] +//! ``` +//! +//! ## Push frame (server → client, uni-stream) +//! ```text +//! [event_type: u16 BE][payload_len: u32 BE][protobuf bytes] +//! ``` + +use bytes::{Buf, BufMut, Bytes, BytesMut}; + +use crate::error::{RpcError, RpcStatus}; + +/// Maximum payload size: 4 MiB. +pub const MAX_PAYLOAD_SIZE: usize = 4 * 1024 * 1024; + +/// Request header size: 2 (method) + 4 (req_id) + 4 (len) = 10 bytes. +pub const REQUEST_HEADER_SIZE: usize = 10; + +/// Response header size: 1 (status) + 4 (req_id) + 4 (len) = 9 bytes. +pub const RESPONSE_HEADER_SIZE: usize = 9; + +/// Push header size: 2 (event_type) + 4 (len) = 6 bytes. +pub const PUSH_HEADER_SIZE: usize = 6; + +// ── Request ────────────────────────────────────────────────────────────────── + +/// A decoded RPC request frame. +#[derive(Debug, Clone)] +pub struct RequestFrame { + pub method_id: u16, + pub request_id: u32, + pub payload: Bytes, +} + +impl RequestFrame { + /// Encode this request into a byte buffer. + pub fn encode(&self) -> Bytes { + let mut buf = BytesMut::with_capacity(REQUEST_HEADER_SIZE + self.payload.len()); + buf.put_u16(self.method_id); + buf.put_u32(self.request_id); + buf.put_u32(self.payload.len() as u32); + buf.put(self.payload.clone()); + buf.freeze() + } + + /// Decode a request frame from a byte buffer. + /// Returns `None` if the buffer does not contain a complete frame. + pub fn decode(buf: &mut BytesMut) -> Result, RpcError> { + if buf.len() < REQUEST_HEADER_SIZE { + return Ok(None); + } + + // Peek at payload_len without consuming. + let payload_len = + u32::from_be_bytes([buf[6], buf[7], buf[8], buf[9]]) as usize; + + if payload_len > MAX_PAYLOAD_SIZE { + return Err(RpcError::PayloadTooLarge { + size: payload_len, + max: MAX_PAYLOAD_SIZE, + }); + } + + let total = REQUEST_HEADER_SIZE + payload_len; + if buf.len() < total { + return Ok(None); + } + + let method_id = buf.get_u16(); + let request_id = buf.get_u32(); + let _len = buf.get_u32(); + let payload = buf.split_to(payload_len).freeze(); + + Ok(Some(Self { + method_id, + request_id, + payload, + })) + } +} + +// ── Response ───────────────────────────────────────────────────────────────── + +/// A decoded RPC response frame. +#[derive(Debug, Clone)] +pub struct ResponseFrame { + pub status: u8, + pub request_id: u32, + pub payload: Bytes, +} + +impl ResponseFrame { + /// Encode this response into a byte buffer. + pub fn encode(&self) -> Bytes { + let mut buf = BytesMut::with_capacity(RESPONSE_HEADER_SIZE + self.payload.len()); + buf.put_u8(self.status); + buf.put_u32(self.request_id); + buf.put_u32(self.payload.len() as u32); + buf.put(self.payload.clone()); + buf.freeze() + } + + /// Decode a response frame from a byte buffer. + pub fn decode(buf: &mut BytesMut) -> Result, RpcError> { + if buf.len() < RESPONSE_HEADER_SIZE { + return Ok(None); + } + + let payload_len = + u32::from_be_bytes([buf[5], buf[6], buf[7], buf[8]]) as usize; + + if payload_len > MAX_PAYLOAD_SIZE { + return Err(RpcError::PayloadTooLarge { + size: payload_len, + max: MAX_PAYLOAD_SIZE, + }); + } + + let total = RESPONSE_HEADER_SIZE + payload_len; + if buf.len() < total { + return Ok(None); + } + + let status = buf.get_u8(); + let request_id = buf.get_u32(); + let _len = buf.get_u32(); + let payload = buf.split_to(payload_len).freeze(); + + Ok(Some(Self { + status, + request_id, + payload, + })) + } + + /// Convert the status byte to an `RpcStatus`. + pub fn rpc_status(&self) -> Option { + RpcStatus::from_u8(self.status) + } +} + +// ── Push ───────────────────────────────────────────────────────────────────── + +/// A decoded server-push event frame (sent on QUIC uni-streams). +#[derive(Debug, Clone)] +pub struct PushFrame { + pub event_type: u16, + pub payload: Bytes, +} + +impl PushFrame { + /// Encode this push frame into a byte buffer. + pub fn encode(&self) -> Bytes { + let mut buf = BytesMut::with_capacity(PUSH_HEADER_SIZE + self.payload.len()); + buf.put_u16(self.event_type); + buf.put_u32(self.payload.len() as u32); + buf.put(self.payload.clone()); + buf.freeze() + } + + /// Decode a push frame from a byte buffer. + pub fn decode(buf: &mut BytesMut) -> Result, RpcError> { + if buf.len() < PUSH_HEADER_SIZE { + return Ok(None); + } + + let payload_len = + u32::from_be_bytes([buf[2], buf[3], buf[4], buf[5]]) as usize; + + if payload_len > MAX_PAYLOAD_SIZE { + return Err(RpcError::PayloadTooLarge { + size: payload_len, + max: MAX_PAYLOAD_SIZE, + }); + } + + let total = PUSH_HEADER_SIZE + payload_len; + if buf.len() < total { + return Ok(None); + } + + let event_type = buf.get_u16(); + let _len = buf.get_u32(); + let payload = buf.split_to(payload_len).freeze(); + + Ok(Some(Self { + event_type, + payload, + })) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn request_roundtrip() { + let frame = RequestFrame { + method_id: 42, + request_id: 1001, + payload: Bytes::from_static(b"hello"), + }; + let encoded = frame.encode(); + let mut buf = BytesMut::from(encoded.as_ref()); + let decoded = RequestFrame::decode(&mut buf).expect("decode").expect("complete"); + assert_eq!(decoded.method_id, 42); + assert_eq!(decoded.request_id, 1001); + assert_eq!(decoded.payload, Bytes::from_static(b"hello")); + assert!(buf.is_empty()); + } + + #[test] + fn response_roundtrip() { + let frame = ResponseFrame { + status: RpcStatus::Ok as u8, + request_id: 2002, + payload: Bytes::from_static(b"world"), + }; + let encoded = frame.encode(); + let mut buf = BytesMut::from(encoded.as_ref()); + let decoded = ResponseFrame::decode(&mut buf).expect("decode").expect("complete"); + assert_eq!(decoded.status, 0); + assert_eq!(decoded.request_id, 2002); + assert_eq!(decoded.payload, Bytes::from_static(b"world")); + } + + #[test] + fn push_roundtrip() { + let frame = PushFrame { + event_type: 7, + payload: Bytes::from_static(b"event-data"), + }; + let encoded = frame.encode(); + let mut buf = BytesMut::from(encoded.as_ref()); + let decoded = PushFrame::decode(&mut buf).expect("decode").expect("complete"); + assert_eq!(decoded.event_type, 7); + assert_eq!(decoded.payload, Bytes::from_static(b"event-data")); + } + + #[test] + fn incomplete_request_returns_none() { + let mut buf = BytesMut::from(&[0u8; 5][..]); + assert!(RequestFrame::decode(&mut buf).expect("no error").is_none()); + } + + #[test] + fn payload_too_large_rejected() { + // Craft a request header with payload_len = MAX + 1. + let mut buf = BytesMut::new(); + buf.put_u16(1); + buf.put_u32(1); + buf.put_u32((MAX_PAYLOAD_SIZE + 1) as u32); + let result = RequestFrame::decode(&mut buf); + assert!(matches!(result, Err(RpcError::PayloadTooLarge { .. }))); + } + + #[test] + fn empty_payload_request() { + let frame = RequestFrame { + method_id: 0, + request_id: 0, + payload: Bytes::new(), + }; + let encoded = frame.encode(); + assert_eq!(encoded.len(), REQUEST_HEADER_SIZE); + let mut buf = BytesMut::from(encoded.as_ref()); + let decoded = RequestFrame::decode(&mut buf).expect("decode").expect("complete"); + assert!(decoded.payload.is_empty()); + } +} diff --git a/crates/quicproquo-rpc/src/lib.rs b/crates/quicproquo-rpc/src/lib.rs new file mode 100644 index 0000000..4789425 --- /dev/null +++ b/crates/quicproquo-rpc/src/lib.rs @@ -0,0 +1,13 @@ +//! QUIC RPC framework for quicproquo v2. +//! +//! Wire format per QUIC stream: +//! - Request: `[method_id: u16][request_id: u32][payload_len: u32][protobuf bytes]` +//! - Response: `[status: u8][request_id: u32][payload_len: u32][protobuf bytes]` +//! - Push: `[event_type: u16][payload_len: u32][protobuf bytes]` (uni-stream) + +pub mod framing; +pub mod method; +pub mod server; +pub mod client; +pub mod middleware; +pub mod error; diff --git a/crates/quicproquo-rpc/src/method.rs b/crates/quicproquo-rpc/src/method.rs new file mode 100644 index 0000000..ac18cf0 --- /dev/null +++ b/crates/quicproquo-rpc/src/method.rs @@ -0,0 +1,102 @@ +//! Method registry — maps method IDs to handler functions. + +use std::collections::HashMap; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; + +use bytes::Bytes; + +use crate::error::RpcStatus; + +/// The result of handling an RPC request. +pub struct HandlerResult { + pub status: RpcStatus, + pub payload: Bytes, +} + +impl HandlerResult { + /// Shorthand for a successful response. + pub fn ok(payload: Bytes) -> Self { + Self { + status: RpcStatus::Ok, + payload, + } + } + + /// Shorthand for an error response. + pub fn err(status: RpcStatus, message: &str) -> Self { + Self { + status, + payload: Bytes::copy_from_slice(message.as_bytes()), + } + } +} + +/// Context passed to every RPC handler. +pub struct RequestContext { + /// The authenticated identity key of the caller, if any. + pub identity_key: Option>, + /// The session token, if provided. + pub session_token: Option>, + /// The raw request payload (protobuf-encoded). + pub payload: Bytes, +} + +/// Type-erased async handler function. +pub type HandlerFn = Arc< + dyn Fn(Arc, RequestContext) -> Pin + Send>> + + Send + + Sync, +>; + +/// Registry mapping method IDs to handler functions. +pub struct MethodRegistry { + handlers: HashMap, &'static str)>, +} + +impl MethodRegistry { + pub fn new() -> Self { + Self { + handlers: HashMap::new(), + } + } + + /// Register a handler for a method ID. + pub fn register(&mut self, method_id: u16, name: &'static str, handler: F) + where + F: Fn(Arc, RequestContext) -> Fut + Send + Sync + 'static, + Fut: Future + Send + 'static, + { + let handler = Arc::new(move |state: Arc, ctx: RequestContext| { + Box::pin(handler(state, ctx)) as Pin + Send>> + }); + self.handlers.insert(method_id, (handler, name)); + } + + /// Look up a handler by method ID. + pub fn get(&self, method_id: u16) -> Option<&(HandlerFn, &'static str)> { + self.handlers.get(&method_id) + } + + /// Return the number of registered methods. + pub fn len(&self) -> usize { + self.handlers.len() + } + + /// Whether the registry is empty. + pub fn is_empty(&self) -> bool { + self.handlers.is_empty() + } + + /// Iterate over all registered (method_id, name) pairs. + pub fn methods(&self) -> impl Iterator + '_ { + self.handlers.iter().map(|(&id, (_, name))| (id, *name)) + } +} + +impl Default for MethodRegistry { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/quicproquo-rpc/src/middleware.rs b/crates/quicproquo-rpc/src/middleware.rs new file mode 100644 index 0000000..b0d62e2 --- /dev/null +++ b/crates/quicproquo-rpc/src/middleware.rs @@ -0,0 +1,96 @@ +//! Tower-based middleware layers for the RPC server. +//! +//! - `AuthLayer`: validates session tokens and attaches identity to context. +//! - `RateLimitLayer`: per-IP request rate limiting. + +use std::time::{Duration, Instant}; + +use dashmap::DashMap; + +// ── Auth middleware ────────────────────────────────────────────────────────── + +/// Validates bearer tokens and resolves identity keys. +pub trait SessionValidator: Send + Sync + 'static { + /// Validate a session token, returning the identity key if valid. + fn validate(&self, token: &[u8]) -> Option>; +} + +/// Auth context extracted from a validated session. +#[derive(Debug, Clone)] +pub struct AuthContext { + /// The Ed25519 identity key of the authenticated caller. + pub identity_key: Vec, +} + +// ── Rate limiter ───────────────────────────────────────────────────────────── + +/// Simple per-key sliding-window rate limiter. +pub struct RateLimiter { + /// Max requests per window. + max_requests: u32, + /// Window duration. + window: Duration, + /// Map from key → (count, window_start). + state: DashMap, (u32, Instant)>, +} + +impl RateLimiter { + /// Create a new rate limiter. + pub fn new(max_requests: u32, window: Duration) -> Self { + Self { + max_requests, + window, + state: DashMap::new(), + } + } + + /// Check if a request from `key` is allowed. Returns `true` if allowed. + pub fn check(&self, key: &[u8]) -> bool { + let now = Instant::now(); + let mut entry = self.state.entry(key.to_vec()).or_insert((0, now)); + let (count, window_start) = entry.value_mut(); + + if now.duration_since(*window_start) >= self.window { + // Reset window. + *count = 1; + *window_start = now; + true + } else if *count < self.max_requests { + *count += 1; + true + } else { + false + } + } + + /// Remove expired entries (call periodically for memory hygiene). + pub fn gc(&self) { + let now = Instant::now(); + self.state.retain(|_, (_, start)| now.duration_since(*start) < self.window * 2); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rate_limiter_allows_within_limit() { + let rl = RateLimiter::new(3, Duration::from_secs(60)); + let key = b"test-key"; + assert!(rl.check(key)); + assert!(rl.check(key)); + assert!(rl.check(key)); + assert!(!rl.check(key)); // 4th request denied + } + + #[test] + fn rate_limiter_resets_after_window() { + let rl = RateLimiter::new(1, Duration::from_millis(1)); + let key = b"test-key"; + assert!(rl.check(key)); + assert!(!rl.check(key)); + std::thread::sleep(Duration::from_millis(5)); + assert!(rl.check(key)); // window expired + } +} diff --git a/crates/quicproquo-rpc/src/server.rs b/crates/quicproquo-rpc/src/server.rs new file mode 100644 index 0000000..a6bff6b --- /dev/null +++ b/crates/quicproquo-rpc/src/server.rs @@ -0,0 +1,198 @@ +//! QUIC RPC server — accepts connections, dispatches requests to handlers. + +use std::sync::Arc; + +use bytes::BytesMut; +use quinn::{Endpoint, Incoming, RecvStream, SendStream}; +use tracing::{debug, info, warn}; + +use crate::error::{RpcError, RpcStatus}; +use crate::framing::{RequestFrame, ResponseFrame, PushFrame}; +use crate::method::{HandlerResult, MethodRegistry, RequestContext}; + +/// Configuration for the RPC server. +pub struct RpcServerConfig { + /// QUIC listen address. + pub listen_addr: std::net::SocketAddr, + /// TLS server config (rustls). + pub tls_config: Arc, + /// ALPN protocol for the RPC service. + pub alpn: Vec, +} + +/// The QUIC RPC server. +pub struct RpcServer { + endpoint: Endpoint, + state: Arc, + registry: Arc>, +} + +impl RpcServer { + /// Create and bind the QUIC endpoint. Does not start accepting yet. + pub fn bind( + config: RpcServerConfig, + state: Arc, + registry: MethodRegistry, + ) -> Result { + let mut tls = (*config.tls_config).clone(); + tls.alpn_protocols = vec![config.alpn]; + let quic_tls = quinn::crypto::rustls::QuicServerConfig::try_from(tls) + .map_err(|e| RpcError::Connection(format!("TLS config: {e}")))?; + let server_config = quinn::ServerConfig::with_crypto(Arc::new(quic_tls)); + + let endpoint = Endpoint::server(server_config, config.listen_addr) + .map_err(|e| RpcError::Connection(format!("bind {}: {e}", config.listen_addr)))?; + + info!(addr = %config.listen_addr, "RPC server bound"); + + Ok(Self { + endpoint, + state, + registry: Arc::new(registry), + }) + } + + /// Accept connections in a loop. Spawns a task per connection. + pub async fn serve(self) -> Result<(), RpcError> { + info!("RPC server accepting connections"); + while let Some(incoming) = self.endpoint.accept().await { + let state = Arc::clone(&self.state); + let registry = Arc::clone(&self.registry); + tokio::spawn(async move { + if let Err(e) = handle_connection(incoming, state, registry).await { + warn!("connection error: {e}"); + } + }); + } + Ok(()) + } + + /// Get the local address the server is listening on. + pub fn local_addr(&self) -> Result { + self.endpoint + .local_addr() + .map_err(|e| RpcError::Connection(e.to_string())) + } +} + +/// Handle a single QUIC connection: accept bi-directional streams for RPCs. +async fn handle_connection( + incoming: Incoming, + state: Arc, + registry: Arc>, +) -> Result<(), RpcError> { + let connection = incoming + .await + .map_err(|e| RpcError::Connection(e.to_string()))?; + + let remote = connection.remote_address(); + debug!(remote = %remote, "new connection"); + + loop { + let stream = connection.accept_bi().await; + match stream { + Ok((send, recv)) => { + let state = Arc::clone(&state); + let registry = Arc::clone(®istry); + tokio::spawn(async move { + if let Err(e) = handle_stream(send, recv, state, registry).await { + debug!("stream error: {e}"); + } + }); + } + Err(quinn::ConnectionError::ApplicationClosed(_)) => { + debug!(remote = %remote, "connection closed by peer"); + break; + } + Err(e) => { + debug!(remote = %remote, "accept_bi error: {e}"); + break; + } + } + } + + Ok(()) +} + +/// Handle a single bi-directional stream: read request, dispatch, write response. +async fn handle_stream( + mut send: SendStream, + mut recv: RecvStream, + state: Arc, + registry: Arc>, +) -> Result<(), RpcError> { + // Read the complete request from the stream. + let mut buf = BytesMut::new(); + while let Some(chunk) = recv + .read_chunk(65536, true) + .await + .map_err(|e| RpcError::Connection(e.to_string()))? + { + buf.extend_from_slice(&chunk.bytes); + if buf.len() > crate::framing::MAX_PAYLOAD_SIZE + crate::framing::REQUEST_HEADER_SIZE { + return Err(RpcError::PayloadTooLarge { + size: buf.len(), + max: crate::framing::MAX_PAYLOAD_SIZE, + }); + } + } + + let frame = match RequestFrame::decode(&mut buf)? { + Some(f) => f, + None => return Err(RpcError::Decode("incomplete request frame".into())), + }; + + let result = match registry.get(frame.method_id) { + Some((handler, name)) => { + debug!(method_id = frame.method_id, method = name, req_id = frame.request_id, "dispatching"); + let ctx = RequestContext { + identity_key: None, // populated by auth middleware + session_token: None, + payload: frame.payload, + }; + handler(Arc::clone(&state), ctx).await + } + None => { + warn!(method_id = frame.method_id, "unknown method"); + HandlerResult::err(RpcStatus::UnknownMethod, "unknown method") + } + }; + + let response = ResponseFrame { + status: result.status as u8, + request_id: frame.request_id, + payload: result.payload, + }; + + let encoded = response.encode(); + send.write_all(&encoded) + .await + .map_err(|e| RpcError::Connection(e.to_string()))?; + send.finish().map_err(|e| RpcError::Connection(e.to_string()))?; + + Ok(()) +} + +/// Send a push event to a client via a QUIC uni-stream. +pub async fn send_push( + connection: &quinn::Connection, + event_type: u16, + payload: bytes::Bytes, +) -> Result<(), RpcError> { + let mut send = connection + .open_uni() + .await + .map_err(|e| RpcError::Connection(e.to_string()))?; + + let frame = PushFrame { + event_type, + payload, + }; + let encoded = frame.encode(); + send.write_all(&encoded) + .await + .map_err(|e| RpcError::Connection(e.to_string()))?; + send.finish().map_err(|e| RpcError::Connection(e.to_string()))?; + + Ok(()) +} diff --git a/crates/quicproquo-sdk/Cargo.toml b/crates/quicproquo-sdk/Cargo.toml new file mode 100644 index 0000000..fec4348 --- /dev/null +++ b/crates/quicproquo-sdk/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "quicproquo-sdk" +version = "0.1.0" +edition = "2021" +description = "Client SDK for quicproquo v2 — connect, auth, send, receive, subscribe" + +[dependencies] +quicproquo-core = { path = "../quicproquo-core" } +quicproquo-proto = { path = "../quicproquo-proto" } +quicproquo-rpc = { path = "../quicproquo-rpc" } +tokio = { workspace = true } +futures = { workspace = true } +tracing = { workspace = true } +thiserror = { workspace = true } +anyhow = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +bincode = { workspace = true } +hex = { workspace = true } +zeroize = { workspace = true } +rusqlite = { workspace = true } +argon2 = { workspace = true } +rand = { workspace = true } +sha2 = { workspace = true } +rustls = { workspace = true } +quinn = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true, features = ["test-util"] } + +[lints] +workspace = true diff --git a/crates/quicproquo-sdk/src/client.rs b/crates/quicproquo-sdk/src/client.rs new file mode 100644 index 0000000..59eae9b --- /dev/null +++ b/crates/quicproquo-sdk/src/client.rs @@ -0,0 +1,193 @@ +//! `QpqClient` — the main entry point for the quicproquo SDK. + +use std::sync::Arc; + +use tokio::sync::broadcast; +use tracing::info; + +use crate::config::ClientConfig; +use crate::conversation::ConversationStore; +use crate::error::SdkError; +use crate::events::ClientEvent; + +/// The main SDK client. All state is contained within this struct — no globals. +pub struct QpqClient { + config: ClientConfig, + rpc: Option, + event_tx: broadcast::Sender, + /// The authenticated username, if logged in. + username: Option, + /// The local identity key (Ed25519 public key, 32 bytes). + identity_key: Option>, + /// Session token from OPAQUE login. + session_token: Option>, + /// Local conversation store (SQLCipher). + conv_store: Option, +} + +impl QpqClient { + /// Create a new client with the given configuration. + pub fn new(config: ClientConfig) -> Self { + let (event_tx, _) = broadcast::channel(256); + Self { + config, + rpc: None, + event_tx, + username: None, + identity_key: None, + session_token: None, + conv_store: None, + } + } + + /// Connect to the server. + pub async fn connect(&mut self) -> Result<(), SdkError> { + let tls_config = build_tls_config(self.config.accept_invalid_certs)?; + + let rpc_config = quicproquo_rpc::client::RpcClientConfig { + server_addr: self.config.server_addr, + server_name: self.config.server_name.clone(), + tls_config: Arc::new(tls_config), + alpn: self.config.alpn.clone(), + }; + + let client = quicproquo_rpc::client::RpcClient::connect(rpc_config).await?; + self.rpc = Some(client); + + // Open local conversation store. + let store = ConversationStore::open( + &self.config.db_path, + self.config.db_password.as_deref(), + )?; + self.conv_store = Some(store); + + self.emit(ClientEvent::Connected); + info!(server = %self.config.server_addr, "connected"); + Ok(()) + } + + /// Subscribe to client events. Returns a broadcast receiver. + pub fn subscribe(&self) -> broadcast::Receiver { + self.event_tx.subscribe() + } + + /// Get the authenticated username, if logged in. + pub fn username(&self) -> Option<&str> { + self.username.as_deref() + } + + /// Get the local identity key. + pub fn identity_key(&self) -> Option<&[u8]> { + self.identity_key.as_deref() + } + + /// Whether the client is connected. + pub fn is_connected(&self) -> bool { + self.rpc.is_some() + } + + /// Whether the client is authenticated. + pub fn is_authenticated(&self) -> bool { + self.session_token.is_some() + } + + /// Get a reference to the RPC client (for direct calls). + pub fn rpc(&self) -> Result<&quicproquo_rpc::client::RpcClient, SdkError> { + self.rpc.as_ref().ok_or(SdkError::NotConnected) + } + + /// Get a reference to the conversation store. + pub fn conversations(&self) -> Result<&ConversationStore, SdkError> { + self.conv_store + .as_ref() + .ok_or(SdkError::NotConnected) + } + + /// Disconnect from the server. + pub fn disconnect(&mut self) { + if let Some(rpc) = self.rpc.take() { + rpc.close(); + self.emit(ClientEvent::Disconnected { + reason: "client closed".into(), + }); + } + } + + fn emit(&self, event: ClientEvent) { + // Ignore send errors (no subscribers). + let _ = self.event_tx.send(event); + } +} + +impl Drop for QpqClient { + fn drop(&mut self) { + self.disconnect(); + } +} + +fn build_tls_config(accept_invalid_certs: bool) -> Result { + let builder = rustls::ClientConfig::builder(); + + if accept_invalid_certs { + let config = builder + .dangerous() + .with_custom_certificate_verifier(Arc::new(InsecureVerifier)) + .with_no_client_auth(); + Ok(config) + } else { + let roots = rustls::RootCertStore::empty(); + let config = builder + .with_root_certificates(roots) + .with_no_client_auth(); + Ok(config) + } +} + +/// A TLS verifier that accepts any certificate (for dev mode only). +#[derive(Debug)] +struct InsecureVerifier; + +impl rustls::client::danger::ServerCertVerifier for InsecureVerifier { + 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 { + vec![ + rustls::SignatureScheme::RSA_PKCS1_SHA256, + rustls::SignatureScheme::RSA_PKCS1_SHA384, + rustls::SignatureScheme::RSA_PKCS1_SHA512, + rustls::SignatureScheme::ECDSA_NISTP256_SHA256, + rustls::SignatureScheme::ECDSA_NISTP384_SHA384, + rustls::SignatureScheme::ED25519, + rustls::SignatureScheme::RSA_PSS_SHA256, + rustls::SignatureScheme::RSA_PSS_SHA384, + rustls::SignatureScheme::RSA_PSS_SHA512, + ] + } +} diff --git a/crates/quicproquo-sdk/src/config.rs b/crates/quicproquo-sdk/src/config.rs new file mode 100644 index 0000000..7feb20e --- /dev/null +++ b/crates/quicproquo-sdk/src/config.rs @@ -0,0 +1,44 @@ +//! Client configuration. + +use std::net::SocketAddr; +use std::path::PathBuf; + +/// Configuration for a `QpqClient` instance. +#[derive(Debug, Clone)] +pub struct ClientConfig { + /// Server address (host:port). + pub server_addr: SocketAddr, + + /// Server hostname for TLS SNI. + pub server_name: String, + + /// Path to the local conversation database. + pub db_path: PathBuf, + + /// Password for encrypting the local database (SQLCipher). + /// If `None`, the database is stored unencrypted. + pub db_password: Option, + + /// Path to the local state file (identity key, MLS state). + pub state_path: PathBuf, + + /// Whether to accept self-signed TLS certificates (dev mode only). + pub accept_invalid_certs: bool, + + /// ALPN protocol identifier for the RPC service. + pub alpn: Vec, +} + +impl Default for ClientConfig { + fn default() -> Self { + Self { + server_addr: "127.0.0.1:7000".parse().expect("valid addr"), + server_name: "localhost".to_string(), + db_path: PathBuf::from("conversations.db"), + db_password: None, + state_path: PathBuf::from("client-state.bin"), + accept_invalid_certs: false, + alpn: b"qpq/2".to_vec(), + } + } +} diff --git a/crates/quicproquo-sdk/src/conversation.rs b/crates/quicproquo-sdk/src/conversation.rs new file mode 100644 index 0000000..7265eca --- /dev/null +++ b/crates/quicproquo-sdk/src/conversation.rs @@ -0,0 +1,481 @@ +//! Conversation management — create DMs, groups, send and receive messages. +//! +//! This is the SDK-side conversation store (migrated from v1 client). + +use std::path::Path; + +use anyhow::Context; +use rusqlite::{params, Connection, OptionalExtension}; +use zeroize::Zeroizing; + +// ── Types ──────────────────────────────────────────────────────────────────── + +/// 16-byte conversation identifier. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ConversationId(pub [u8; 16]); + +impl ConversationId { + pub fn from_slice(s: &[u8]) -> Option { + if s.len() == 16 { + let mut buf = [0u8; 16]; + buf.copy_from_slice(s); + Some(Self(buf)) + } else { + None + } + } + + /// Derive a conversation ID from a group name via SHA-256 truncation. + pub fn from_group_name(name: &str) -> Self { + use sha2::{Digest, Sha256}; + let hash = Sha256::digest(name.as_bytes()); + let mut buf = [0u8; 16]; + buf.copy_from_slice(&hash[..16]); + Self(buf) + } + + pub fn hex(&self) -> String { + hex::encode(self.0) + } +} + +/// The kind of conversation. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ConversationKind { + /// 1:1 DM channel with a specific peer. + Dm { + peer_key: Vec, + peer_username: Option, + }, + /// Named group with N members. + Group { name: String }, +} + +/// A conversation with its metadata and MLS state. +#[derive(Clone, Debug)] +pub struct Conversation { + pub id: ConversationId, + pub kind: ConversationKind, + pub display_name: String, + /// Serialized MLS group (bincode). + pub mls_group_blob: Option>, + /// Serialized keystore (bincode HashMap). + pub keystore_blob: Option>, + /// Member identity keys. + pub member_keys: Vec>, + pub unread_count: u32, + pub last_activity_ms: u64, + pub created_at_ms: u64, + /// Whether this conversation uses hybrid (X25519 + ML-KEM-768) MLS keys. + pub is_hybrid: bool, + /// Highest server-side delivery sequence number seen. + pub last_seen_seq: u64, +} + +/// A stored message. +#[derive(Clone, Debug)] +pub struct StoredMessage { + pub conversation_id: ConversationId, + pub message_id: Option<[u8; 16]>, + pub sender_key: Vec, + pub sender_name: Option, + pub body: String, + pub msg_type: String, + pub ref_msg_id: Option<[u8; 16]>, + pub timestamp_ms: u64, + pub is_outgoing: bool, +} + +/// An entry in the offline outbox queue. +#[derive(Clone, Debug)] +pub struct OutboxEntry { + pub id: i64, + pub conversation_id: ConversationId, + pub recipient_key: Vec, + pub payload: Vec, + pub retry_count: u32, +} + +// ── ConversationStore ──────────────────────────────────────────────────────── + +/// SQLCipher-backed conversation and message store. +pub struct ConversationStore { + conn: Connection, +} + +impl ConversationStore { + /// Open or create the conversation database. + pub fn open(db_path: &Path, password: Option<&str>) -> anyhow::Result { + if let Some(parent) = db_path.parent() { + std::fs::create_dir_all(parent).ok(); + } + + let conn = Connection::open(db_path).context("open conversation db")?; + + if let Some(pw) = password { + let key = derive_db_key(pw, db_path)?; + let hex_key = Zeroizing::new(hex::encode(&*key)); + conn.pragma_update(None, "key", format!("x'{}'", &*hex_key)) + .context("set SQLCipher key")?; + } + + conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;") + .context("set pragmas")?; + Self::migrate(&conn)?; + Ok(Self { conn }) + } + + fn migrate(conn: &Connection) -> anyhow::Result<()> { + conn.execute_batch( + "CREATE TABLE IF NOT EXISTS conversations ( + id BLOB PRIMARY KEY, + kind TEXT NOT NULL, + display_name TEXT NOT NULL, + peer_key BLOB, + peer_username TEXT, + group_name TEXT, + mls_group_blob BLOB, + keystore_blob BLOB, + member_keys BLOB, + unread_count INTEGER NOT NULL DEFAULT 0, + last_activity_ms INTEGER NOT NULL DEFAULT 0, + created_at_ms INTEGER NOT NULL DEFAULT 0, + is_hybrid INTEGER NOT NULL DEFAULT 0, + last_seen_seq INTEGER NOT NULL DEFAULT 0 + ); + + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + conversation_id BLOB NOT NULL REFERENCES conversations(id), + message_id BLOB, + sender_key BLOB NOT NULL, + sender_name TEXT, + body TEXT NOT NULL, + msg_type TEXT NOT NULL, + ref_msg_id BLOB, + timestamp_ms INTEGER NOT NULL, + is_outgoing INTEGER NOT NULL DEFAULT 0 + ); + + CREATE INDEX IF NOT EXISTS idx_messages_conv + ON messages(conversation_id, timestamp_ms); + + CREATE TABLE IF NOT EXISTS outbox ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + conversation_id BLOB NOT NULL, + recipient_key BLOB NOT NULL, + payload BLOB NOT NULL, + created_at_ms INTEGER NOT NULL, + retry_count INTEGER NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'pending' + ); + CREATE INDEX IF NOT EXISTS idx_outbox_status + ON outbox(status, created_at_ms);", + ) + .context("migrate conversation db") + } + + /// Save or upsert a conversation. + pub fn save_conversation(&self, conv: &Conversation) -> anyhow::Result<()> { + let (kind_str, peer_key, peer_username, group_name) = match &conv.kind { + ConversationKind::Dm { peer_key, peer_username } => { + ("dm", Some(peer_key.as_slice()), peer_username.as_deref(), None) + } + ConversationKind::Group { name } => ("group", None, None, Some(name.as_str())), + }; + let member_keys_blob = + bincode::serialize(&conv.member_keys).context("serialize member_keys")?; + + self.conn.execute( + "INSERT INTO conversations + (id, kind, display_name, peer_key, peer_username, group_name, + mls_group_blob, keystore_blob, member_keys, unread_count, + last_activity_ms, created_at_ms, is_hybrid, last_seen_seq) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14) + ON CONFLICT(id) DO UPDATE SET + display_name = excluded.display_name, + mls_group_blob = excluded.mls_group_blob, + keystore_blob = excluded.keystore_blob, + member_keys = excluded.member_keys, + unread_count = excluded.unread_count, + last_activity_ms = excluded.last_activity_ms, + is_hybrid = excluded.is_hybrid, + last_seen_seq = excluded.last_seen_seq", + params![ + conv.id.0.as_slice(), + kind_str, + conv.display_name, + peer_key, + peer_username, + group_name, + conv.mls_group_blob, + conv.keystore_blob, + member_keys_blob, + conv.unread_count, + conv.last_activity_ms, + conv.created_at_ms, + conv.is_hybrid as i32, + conv.last_seen_seq as i64, + ], + )?; + Ok(()) + } + + /// Load a conversation by ID. + pub fn load_conversation(&self, id: &ConversationId) -> anyhow::Result> { + self.conn + .query_row( + "SELECT kind, display_name, peer_key, peer_username, group_name, + mls_group_blob, keystore_blob, member_keys, unread_count, + last_activity_ms, created_at_ms, is_hybrid, last_seen_seq + FROM conversations WHERE id = ?1", + params![id.0.as_slice()], + |row| row_to_conversation(id, row), + ) + .optional() + .context("load conversation") + } + + /// List all conversations, most recent first. + pub fn list_conversations(&self) -> anyhow::Result> { + let mut stmt = self.conn.prepare( + "SELECT id, kind, display_name, peer_key, peer_username, group_name, + mls_group_blob, keystore_blob, member_keys, unread_count, + last_activity_ms, created_at_ms, is_hybrid, last_seen_seq + FROM conversations ORDER BY last_activity_ms DESC", + )?; + let rows = stmt.query_map([], |row| { + let id_blob: Vec = row.get(0)?; + let id = ConversationId::from_slice(&id_blob).unwrap_or(ConversationId([0; 16])); + row_to_conversation_full(&id, row) + })?; + let mut convs = Vec::new(); + for row in rows { + convs.push(row?); + } + Ok(convs) + } + + /// Find a DM by peer identity key. + pub fn find_dm_by_peer(&self, peer_key: &[u8]) -> anyhow::Result> { + let id_blob: Option> = self + .conn + .query_row( + "SELECT id FROM conversations WHERE kind = 'dm' AND peer_key = ?1", + params![peer_key], + |row| row.get(0), + ) + .optional()?; + match id_blob { + Some(blob) => { + let id = ConversationId::from_slice(&blob) + .context("invalid conversation id")?; + self.load_conversation(&id) + } + None => Ok(None), + } + } + + /// Save a message. + pub fn save_message(&self, msg: &StoredMessage) -> anyhow::Result<()> { + self.conn.execute( + "INSERT INTO messages + (conversation_id, message_id, sender_key, sender_name, body, + msg_type, ref_msg_id, timestamp_ms, is_outgoing) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", + params![ + msg.conversation_id.0.as_slice(), + msg.message_id.as_ref().map(|id| id.as_slice()), + msg.sender_key, + msg.sender_name, + msg.body, + msg.msg_type, + msg.ref_msg_id.as_ref().map(|id| id.as_slice()), + msg.timestamp_ms, + msg.is_outgoing as i32, + ], + )?; + Ok(()) + } + + /// Load recent messages (newest first, then reversed to chronological). + pub fn load_recent_messages( + &self, + conv_id: &ConversationId, + limit: usize, + ) -> anyhow::Result> { + let mut stmt = self.conn.prepare( + "SELECT message_id, sender_key, sender_name, body, msg_type, + ref_msg_id, timestamp_ms, is_outgoing + FROM messages WHERE conversation_id = ?1 + ORDER BY timestamp_ms DESC LIMIT ?2", + )?; + let rows = stmt.query_map( + params![conv_id.0.as_slice(), limit.min(u32::MAX as usize) as u32], + |row| row_to_message(conv_id, row), + )?; + let mut msgs: Vec = rows.collect::>()?; + msgs.reverse(); + Ok(msgs) + } +} + +// ── Helpers ────────────────────────────────────────────────────────────────── + +fn derive_db_key(password: &str, db_path: &Path) -> anyhow::Result> { + use argon2::{Algorithm, Argon2, Params, Version}; + + let salt_path = db_path.with_extension("db-salt"); + let salt = if salt_path.exists() { + std::fs::read(&salt_path).context("read db salt")? + } else { + let mut salt = vec![0u8; 16]; + rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut salt); + std::fs::write(&salt_path, &salt).context("write db salt")?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&salt_path, std::fs::Permissions::from_mode(0o600)).ok(); + } + salt + }; + + let params = Params::new(19 * 1024, 2, 1, Some(32)) + .map_err(|e| anyhow::anyhow!("argon2 params: {e}"))?; + let argon2 = Argon2::new(Algorithm::Argon2id, Version::default(), params); + let mut key = Zeroizing::new([0u8; 32]); + argon2 + .hash_password_into(password.as_bytes(), &salt, &mut *key) + .map_err(|e| anyhow::anyhow!("db key derivation: {e}"))?; + Ok(key) +} + +fn row_to_conversation( + id: &ConversationId, + row: &rusqlite::Row<'_>, +) -> rusqlite::Result { + let kind_str: String = row.get(0)?; + let display_name: String = row.get(1)?; + let peer_key: Option> = row.get(2)?; + let peer_username: Option = row.get(3)?; + let group_name: Option = row.get(4)?; + let mls_group_blob: Option> = row.get(5)?; + let keystore_blob: Option> = row.get(6)?; + let member_keys_blob: Option> = row.get(7)?; + let unread_count: u32 = row.get(8)?; + let last_activity_ms: u64 = row.get(9)?; + let created_at_ms: u64 = row.get(10)?; + let is_hybrid_int: i32 = row.get(11)?; + let last_seen_seq: i64 = row.get(12)?; + + let kind = if kind_str == "dm" { + ConversationKind::Dm { + peer_key: peer_key.unwrap_or_default(), + peer_username, + } + } else { + ConversationKind::Group { + name: group_name.unwrap_or_default(), + } + }; + let member_keys: Vec> = member_keys_blob + .and_then(|b| bincode::deserialize(&b).ok()) + .unwrap_or_default(); + + Ok(Conversation { + id: id.clone(), + kind, + display_name, + mls_group_blob, + keystore_blob, + member_keys, + unread_count, + last_activity_ms, + created_at_ms, + is_hybrid: is_hybrid_int != 0, + last_seen_seq: last_seen_seq as u64, + }) +} + +fn row_to_conversation_full( + id: &ConversationId, + row: &rusqlite::Row<'_>, +) -> rusqlite::Result { + let kind_str: String = row.get(1)?; + let display_name: String = row.get(2)?; + let peer_key: Option> = row.get(3)?; + let peer_username: Option = row.get(4)?; + let group_name: Option = row.get(5)?; + let mls_group_blob: Option> = row.get(6)?; + let keystore_blob: Option> = row.get(7)?; + let member_keys_blob: Option> = row.get(8)?; + let unread_count: u32 = row.get(9)?; + let last_activity_ms: u64 = row.get(10)?; + let created_at_ms: u64 = row.get(11)?; + let is_hybrid_int: i32 = row.get(12)?; + let last_seen_seq: i64 = row.get(13)?; + + let kind = if kind_str == "dm" { + ConversationKind::Dm { + peer_key: peer_key.unwrap_or_default(), + peer_username, + } + } else { + ConversationKind::Group { + name: group_name.unwrap_or_default(), + } + }; + let member_keys: Vec> = member_keys_blob + .and_then(|b| bincode::deserialize(&b).ok()) + .unwrap_or_default(); + + Ok(Conversation { + id: id.clone(), + kind, + display_name, + mls_group_blob, + keystore_blob, + member_keys, + unread_count, + last_activity_ms, + created_at_ms, + is_hybrid: is_hybrid_int != 0, + last_seen_seq: last_seen_seq as u64, + }) +} + +fn to_16(v: &[u8]) -> Option<[u8; 16]> { + if v.len() == 16 { + let mut buf = [0u8; 16]; + buf.copy_from_slice(v); + Some(buf) + } else { + None + } +} + +fn row_to_message( + conv_id: &ConversationId, + row: &rusqlite::Row<'_>, +) -> rusqlite::Result { + let message_id: Option> = row.get(0)?; + let sender_key: Vec = row.get(1)?; + let sender_name: Option = row.get(2)?; + let body: String = row.get(3)?; + let msg_type: String = row.get(4)?; + let ref_msg_id: Option> = row.get(5)?; + let timestamp_ms: u64 = row.get(6)?; + let is_outgoing: i32 = row.get(7)?; + + Ok(StoredMessage { + conversation_id: conv_id.clone(), + message_id: message_id.as_deref().and_then(to_16), + sender_key, + sender_name, + body, + msg_type, + ref_msg_id: ref_msg_id.as_deref().and_then(to_16), + timestamp_ms, + is_outgoing: is_outgoing != 0, + }) +} diff --git a/crates/quicproquo-sdk/src/error.rs b/crates/quicproquo-sdk/src/error.rs new file mode 100644 index 0000000..1b82a71 --- /dev/null +++ b/crates/quicproquo-sdk/src/error.rs @@ -0,0 +1,29 @@ +//! SDK error types. + +/// Errors returned by SDK operations. +#[derive(Debug, thiserror::Error)] +pub enum SdkError { + #[error("not connected to server")] + NotConnected, + + #[error("not authenticated — call login() first")] + NotAuthenticated, + + #[error("authentication failed: {0}")] + AuthFailed(String), + + #[error("conversation not found: {0}")] + ConversationNotFound(String), + + #[error("crypto error: {0}")] + Crypto(String), + + #[error("RPC error: {0}")] + Rpc(#[from] quicproquo_rpc::error::RpcError), + + #[error("storage error: {0}")] + Storage(String), + + #[error("{0}")] + Other(#[from] anyhow::Error), +} diff --git a/crates/quicproquo-sdk/src/events.rs b/crates/quicproquo-sdk/src/events.rs new file mode 100644 index 0000000..47a95d8 --- /dev/null +++ b/crates/quicproquo-sdk/src/events.rs @@ -0,0 +1,56 @@ +//! Client event system — real-time notifications from the SDK. + +/// Events emitted by the SDK to the UI layer. +#[derive(Debug, Clone)] +pub enum ClientEvent { + /// Successfully connected to the server. + Connected, + + /// Disconnected from the server. + Disconnected { reason: String }, + + /// Authentication succeeded. + Authenticated { username: String }, + + /// A new message was received in a conversation. + MessageReceived { + conversation_id: [u8; 16], + sender_key: Vec, + sender_name: Option, + body: String, + timestamp_ms: u64, + }, + + /// A message was sent successfully. + MessageSent { + conversation_id: [u8; 16], + seq: u64, + }, + + /// A new conversation was created or discovered. + ConversationCreated { + conversation_id: [u8; 16], + display_name: String, + }, + + /// A member was added to a group conversation. + MemberAdded { + conversation_id: [u8; 16], + member_key: Vec, + }, + + /// A member was removed from a group conversation. + MemberRemoved { + conversation_id: [u8; 16], + member_key: Vec, + }, + + /// Server-push event received. + PushEvent { + event_type: u16, + payload: Vec, + }, + + /// An error occurred in the background. + Error { message: String }, +} diff --git a/crates/quicproquo-sdk/src/lib.rs b/crates/quicproquo-sdk/src/lib.rs new file mode 100644 index 0000000..88105ee --- /dev/null +++ b/crates/quicproquo-sdk/src/lib.rs @@ -0,0 +1,10 @@ +//! Client SDK for quicproquo v2. +//! +//! Provides `QpqClient` — a single entry point for connecting, authenticating, +//! sending/receiving messages, and subscribing to real-time events. + +pub mod client; +pub mod config; +pub mod conversation; +pub mod events; +pub mod error; diff --git a/crates/quicproquo-server/src/domain/auth.rs b/crates/quicproquo-server/src/domain/auth.rs new file mode 100644 index 0000000..d6bbece --- /dev/null +++ b/crates/quicproquo-server/src/domain/auth.rs @@ -0,0 +1,72 @@ +//! Authentication domain logic — OPAQUE registration and login. +//! +//! This module contains the pure business logic for OPAQUE auth, +//! extracted from `node_service/auth_ops.rs`. It operates on domain +//! types and the `Store` trait, with no dependency on Cap'n Proto or Protobuf. + +use std::sync::Arc; + +use dashmap::DashMap; +use opaque_ke::ServerSetup; +use quicproquo_core::opaque_auth::OpaqueSuite; + +use crate::auth::{AuthConfig, PendingLogin, SessionInfo}; +use crate::storage::{Store, StorageError}; + +use super::types::*; + +/// Shared state needed by auth operations. +pub struct AuthService { + pub store: Arc, + pub opaque_setup: Arc>, + pub pending_logins: Arc>, + pub sessions: Arc, SessionInfo>>, + pub auth_cfg: Arc, +} + +impl AuthService { + /// Validate a session token and return the caller's auth context. + pub fn validate_session(&self, token: &[u8]) -> Option { + let info = self.sessions.get(token)?; + if info.expires_at <= crate::auth::current_timestamp() { + self.sessions.remove(token); + return None; + } + Some(CallerAuth { + identity_key: info.identity_key.clone(), + token: token.to_vec(), + device_id: None, + }) + } + + /// Start OPAQUE registration. + pub fn register_start(&self, req: RegisterStartReq) -> Result { + use opaque_ke::ServerRegistration; + + let result = ServerRegistration::::start( + &self.opaque_setup, + opaque_ke::RegistrationRequest::deserialize(&req.request_bytes) + .map_err(|e| StorageError::Io(format!("bad registration request: {e}")))?, + req.username.as_bytes(), + ) + .map_err(|e| StorageError::Io(format!("OPAQUE register start: {e}")))?; + + let response_bytes = result.message.serialize().to_vec(); + Ok(RegisterStartResp { response_bytes }) + } + + /// Finish OPAQUE registration — persist user record and identity key. + pub fn register_finish(&self, req: RegisterFinishReq) -> Result { + let upload = opaque_ke::RegistrationUpload::::deserialize(&req.upload_bytes) + .map_err(|e| StorageError::Io(format!("bad registration upload: {e}")))?; + + let record = opaque_ke::ServerRegistration::::finish(upload); + let serialized = record.serialize().to_vec(); + + self.store.store_user_record(&req.username, serialized)?; + self.store + .store_user_identity_key(&req.username, req.identity_key)?; + + Ok(RegisterFinishResp { success: true }) + } +} diff --git a/crates/quicproquo-server/src/domain/delivery.rs b/crates/quicproquo-server/src/domain/delivery.rs new file mode 100644 index 0000000..cefdc86 --- /dev/null +++ b/crates/quicproquo-server/src/domain/delivery.rs @@ -0,0 +1,110 @@ +//! Delivery domain logic — enqueue, fetch, peek, ack. +//! +//! Pure business logic operating on `Store` trait and domain types. + +use std::sync::Arc; + +use dashmap::DashMap; +use tokio::sync::Notify; + +use crate::storage::Store; + +use super::types::*; + +/// Shared state needed by delivery operations. +pub struct DeliveryService { + pub store: Arc, + pub waiters: Arc, Arc>>, +} + +impl DeliveryService { + /// Enqueue a payload for delivery. + pub fn enqueue(&self, req: EnqueueReq) -> Result { + let ttl = if req.ttl_secs > 0 { + Some(req.ttl_secs) + } else { + None + }; + + let seq = self.store.enqueue( + &req.recipient_key, + &req.channel_id, + req.payload, + ttl, + )?; + + // Wake any long-polling waiter for this recipient. + if let Some(notify) = self.waiters.get(&req.recipient_key) { + notify.notify_one(); + } + + Ok(EnqueueResp { + seq, + delivery_proof: Vec::new(), // TODO: sign in Phase 2 + }) + } + + /// Fetch and drain queued messages. + pub fn fetch(&self, req: FetchReq) -> Result { + let messages = if req.limit > 0 { + self.store + .fetch_limited(&req.recipient_key, &req.channel_id, req.limit as usize)? + } else { + self.store.fetch(&req.recipient_key, &req.channel_id)? + }; + + Ok(FetchResp { + payloads: messages + .into_iter() + .map(|(seq, data)| Envelope { seq, data }) + .collect(), + }) + } + + /// Peek at messages without removing them. + pub fn peek(&self, req: PeekReq) -> Result { + let messages = self.store.peek( + &req.recipient_key, + &req.channel_id, + if req.limit > 0 { req.limit as usize } else { 0 }, + )?; + + Ok(PeekResp { + payloads: messages + .into_iter() + .map(|(seq, data)| Envelope { seq, data }) + .collect(), + }) + } + + /// Acknowledge messages up to a sequence number. + pub fn ack(&self, req: AckReq) -> Result<(), crate::storage::StorageError> { + self.store + .ack(&req.recipient_key, &req.channel_id, req.seq_up_to)?; + Ok(()) + } + + /// Batch enqueue to multiple recipients. + pub fn batch_enqueue( + &self, + req: BatchEnqueueReq, + ) -> Result { + let ttl = if req.ttl_secs > 0 { + Some(req.ttl_secs) + } else { + None + }; + + let mut seqs = Vec::with_capacity(req.recipient_keys.len()); + for rk in &req.recipient_keys { + let seq = self.store.enqueue(rk, &req.channel_id, req.payload.clone(), ttl)?; + seqs.push(seq); + + if let Some(notify) = self.waiters.get(rk) { + notify.notify_one(); + } + } + + Ok(BatchEnqueueResp { seqs }) + } +} diff --git a/crates/quicproquo-server/src/domain/mod.rs b/crates/quicproquo-server/src/domain/mod.rs new file mode 100644 index 0000000..caba330 --- /dev/null +++ b/crates/quicproquo-server/src/domain/mod.rs @@ -0,0 +1,10 @@ +//! Domain types and service logic — protocol-agnostic. +//! +//! These types define the server's business logic independently of any +//! serialization format (Cap'n Proto, Protobuf). RPC handlers translate +//! wire-format messages into these types, call service functions, and +//! translate the results back. + +pub mod types; +pub mod auth; +pub mod delivery; diff --git a/crates/quicproquo-server/src/domain/types.rs b/crates/quicproquo-server/src/domain/types.rs new file mode 100644 index 0000000..dff7246 --- /dev/null +++ b/crates/quicproquo-server/src/domain/types.rs @@ -0,0 +1,260 @@ +//! Plain Rust request/response types for server domain logic. +//! +//! No proto, no capnp — just Rust structs. + +// ── Auth ───────────────────────────────────────────────────────────────────── + +/// Caller authentication context (resolved from session token). +#[derive(Debug, Clone)] +pub struct CallerAuth { + /// Ed25519 identity key of the authenticated caller (32 bytes). + pub identity_key: Vec, + /// Session token bytes. + pub token: Vec, + /// Device ID (optional, for auditing). + pub device_id: Option>, +} + +/// OPAQUE registration start. +pub struct RegisterStartReq { + pub username: String, + pub request_bytes: Vec, +} + +pub struct RegisterStartResp { + pub response_bytes: Vec, +} + +/// OPAQUE registration finish. +pub struct RegisterFinishReq { + pub username: String, + pub upload_bytes: Vec, + pub identity_key: Vec, +} + +pub struct RegisterFinishResp { + pub success: bool, +} + +/// OPAQUE login start. +pub struct LoginStartReq { + pub username: String, + pub request_bytes: Vec, +} + +pub struct LoginStartResp { + pub response_bytes: Vec, +} + +/// OPAQUE login finish. +pub struct LoginFinishReq { + pub username: String, + pub finalization_bytes: Vec, + pub identity_key: Vec, +} + +pub struct LoginFinishResp { + pub session_token: Vec, +} + +// ── Delivery ───────────────────────────────────────────────────────────────── + +/// An envelope pairing a sequence number with an opaque payload. +#[derive(Debug, Clone)] +pub struct Envelope { + pub seq: u64, + pub data: Vec, +} + +pub struct EnqueueReq { + pub recipient_key: Vec, + pub payload: Vec, + pub channel_id: Vec, + pub ttl_secs: u32, +} + +pub struct EnqueueResp { + pub seq: u64, + pub delivery_proof: Vec, +} + +pub struct FetchReq { + pub recipient_key: Vec, + pub channel_id: Vec, + pub limit: u32, +} + +pub struct FetchResp { + pub payloads: Vec, +} + +pub struct PeekReq { + pub recipient_key: Vec, + pub channel_id: Vec, + pub limit: u32, +} + +pub struct PeekResp { + pub payloads: Vec, +} + +pub struct AckReq { + pub recipient_key: Vec, + pub channel_id: Vec, + pub seq_up_to: u64, +} + +pub struct BatchEnqueueReq { + pub recipient_keys: Vec>, + pub payload: Vec, + pub channel_id: Vec, + pub ttl_secs: u32, +} + +pub struct BatchEnqueueResp { + pub seqs: Vec, +} + +// ── Keys ───────────────────────────────────────────────────────────────────── + +pub struct UploadKeyPackageReq { + pub identity_key: Vec, + pub package: Vec, +} + +pub struct UploadKeyPackageResp { + pub fingerprint: Vec, +} + +pub struct FetchKeyPackageReq { + pub identity_key: Vec, +} + +pub struct FetchKeyPackageResp { + pub package: Vec, +} + +pub struct UploadHybridKeyReq { + pub identity_key: Vec, + pub hybrid_public_key: Vec, +} + +pub struct FetchHybridKeyReq { + pub identity_key: Vec, +} + +pub struct FetchHybridKeyResp { + pub hybrid_public_key: Vec, +} + +pub struct FetchHybridKeysReq { + pub identity_keys: Vec>, +} + +pub struct FetchHybridKeysResp { + pub keys: Vec>, +} + +// ── Channel ────────────────────────────────────────────────────────────────── + +pub struct CreateChannelReq { + pub peer_key: Vec, +} + +pub struct CreateChannelResp { + pub channel_id: Vec, + pub was_new: bool, +} + +// ── User ───────────────────────────────────────────────────────────────────── + +pub struct ResolveUserReq { + pub username: String, +} + +pub struct ResolveUserResp { + pub identity_key: Vec, + pub inclusion_proof: Vec, +} + +pub struct ResolveIdentityReq { + pub identity_key: Vec, +} + +pub struct ResolveIdentityResp { + pub username: String, +} + +// ── Blob ───────────────────────────────────────────────────────────────────── + +pub struct UploadBlobReq { + pub blob_hash: Vec, + pub chunk: Vec, + pub offset: u64, + pub total_size: u64, + pub mime_type: String, +} + +pub struct UploadBlobResp { + pub blob_id: Vec, +} + +pub struct DownloadBlobReq { + pub blob_id: Vec, + pub offset: u64, + pub length: u32, +} + +pub struct DownloadBlobResp { + pub chunk: Vec, + pub total_size: u64, + pub mime_type: String, +} + +// ── Device ─────────────────────────────────────────────────────────────────── + +pub struct RegisterDeviceReq { + pub device_id: Vec, + pub device_name: String, +} + +pub struct RegisterDeviceResp { + pub success: bool, +} + +pub struct DeviceInfo { + pub device_id: Vec, + pub device_name: String, + pub registered_at: u64, +} + +pub struct ListDevicesResp { + pub devices: Vec, +} + +pub struct RevokeDeviceReq { + pub device_id: Vec, +} + +pub struct RevokeDeviceResp { + pub success: bool, +} + +// ── P2P ────────────────────────────────────────────────────────────────────── + +pub struct PublishEndpointReq { + pub identity_key: Vec, + pub node_addr: Vec, +} + +pub struct ResolveEndpointReq { + pub identity_key: Vec, +} + +pub struct ResolveEndpointResp { + pub node_addr: Vec, +} + +pub struct HealthResp { + pub status: String, +} diff --git a/crates/quicproquo-server/src/main.rs b/crates/quicproquo-server/src/main.rs index f135b19..3c1edec 100644 --- a/crates/quicproquo-server/src/main.rs +++ b/crates/quicproquo-server/src/main.rs @@ -17,6 +17,7 @@ use tokio::task::LocalSet; mod auth; mod config; +pub mod domain; mod error_codes; mod federation; pub mod hooks; diff --git a/justfile b/justfile new file mode 100644 index 0000000..1980fd1 --- /dev/null +++ b/justfile @@ -0,0 +1,49 @@ +# quicproquo v2 — build commands + +# Default: build all workspace crates +build: + cargo build --workspace + +# Run all tests +test: + cargo test --workspace + +# Run core crypto tests only +test-core: + cargo test -p quicproquo-core + +# Build proto crate (triggers prost codegen) +proto: + cargo build -p quicproquo-proto + +# Build RPC framework +rpc: + cargo build -p quicproquo-rpc + +# Build SDK +sdk: + cargo build -p quicproquo-sdk + +# Build server +server: + cargo build -p quicproquo-server + +# Build client +client: + cargo build -p quicproquo-client + +# Check all with clippy +lint: + cargo clippy --workspace -- -D warnings + +# Format check +fmt: + cargo fmt --all -- --check + +# Format fix +fmt-fix: + cargo fmt --all + +# Clean build artifacts +clean: + cargo clean diff --git a/proto/qpq/v1/auth.proto b/proto/qpq/v1/auth.proto new file mode 100644 index 0000000..59c483f --- /dev/null +++ b/proto/qpq/v1/auth.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; +package qpq.v1; + +// OPAQUE registration + login (4 methods). +// Method IDs: 100-103. + +message OpaqueRegisterStartRequest { + string username = 1; + bytes request = 2; +} + +message OpaqueRegisterStartResponse { + bytes response = 1; +} + +message OpaqueRegisterFinishRequest { + string username = 1; + bytes upload = 2; + bytes identity_key = 3; +} + +message OpaqueRegisterFinishResponse { + bool success = 1; +} + +message OpaqueLoginStartRequest { + string username = 1; + bytes request = 2; +} + +message OpaqueLoginStartResponse { + bytes response = 1; +} + +message OpaqueLoginFinishRequest { + string username = 1; + bytes finalization = 2; + bytes identity_key = 3; +} + +message OpaqueLoginFinishResponse { + bytes session_token = 1; +} diff --git a/proto/qpq/v1/blob.proto b/proto/qpq/v1/blob.proto new file mode 100644 index 0000000..acb6ffa --- /dev/null +++ b/proto/qpq/v1/blob.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; +package qpq.v1; + +// Blob upload/download (2 methods). +// Method IDs: 600-601. + +message UploadBlobRequest { + bytes blob_hash = 1; + bytes chunk = 2; + uint64 offset = 3; + uint64 total_size = 4; + string mime_type = 5; +} + +message UploadBlobResponse { + bytes blob_id = 1; +} + +message DownloadBlobRequest { + bytes blob_id = 1; + uint64 offset = 2; + uint32 length = 3; +} + +message DownloadBlobResponse { + bytes chunk = 1; + uint64 total_size = 2; + string mime_type = 3; +} diff --git a/proto/qpq/v1/channel.proto b/proto/qpq/v1/channel.proto new file mode 100644 index 0000000..a395ce7 --- /dev/null +++ b/proto/qpq/v1/channel.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package qpq.v1; + +// Channel create (1 method). +// Method ID: 400. + +message CreateChannelRequest { + bytes peer_key = 1; +} + +message CreateChannelResponse { + bytes channel_id = 1; + bool was_new = 2; +} diff --git a/proto/qpq/v1/common.proto b/proto/qpq/v1/common.proto new file mode 100644 index 0000000..44d2162 --- /dev/null +++ b/proto/qpq/v1/common.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +package qpq.v1; + +// Common types shared across services. + +// Auth context included in authenticated RPC requests. +// In v2, this is carried as QUIC connection-level state (session token), +// not per-message. Included here for federation and internal use. +message Auth { + bytes access_token = 1; + bytes device_id = 2; +} + +// Account deletion. +message DeleteAccountRequest {} + +message DeleteAccountResponse { + bool success = 1; +} diff --git a/proto/qpq/v1/delivery.proto b/proto/qpq/v1/delivery.proto new file mode 100644 index 0000000..66d4b88 --- /dev/null +++ b/proto/qpq/v1/delivery.proto @@ -0,0 +1,72 @@ +syntax = "proto3"; +package qpq.v1; + +// Delivery service: enqueue, fetch, peek, ack, batch (6 methods). +// Method IDs: 200-205. + +message Envelope { + uint64 seq = 1; + bytes data = 2; +} + +message EnqueueRequest { + bytes recipient_key = 1; + bytes payload = 2; + bytes channel_id = 3; + uint32 ttl_secs = 4; +} + +message EnqueueResponse { + uint64 seq = 1; + bytes delivery_proof = 2; +} + +message FetchRequest { + bytes recipient_key = 1; + bytes channel_id = 2; + uint32 limit = 3; +} + +message FetchResponse { + repeated Envelope payloads = 1; +} + +message FetchWaitRequest { + bytes recipient_key = 1; + bytes channel_id = 2; + uint64 timeout_ms = 3; + uint32 limit = 4; +} + +message FetchWaitResponse { + repeated Envelope payloads = 1; +} + +message PeekRequest { + bytes recipient_key = 1; + bytes channel_id = 2; + uint32 limit = 3; +} + +message PeekResponse { + repeated Envelope payloads = 1; +} + +message AckRequest { + bytes recipient_key = 1; + bytes channel_id = 2; + uint64 seq_up_to = 3; +} + +message AckResponse {} + +message BatchEnqueueRequest { + repeated bytes recipient_keys = 1; + bytes payload = 2; + bytes channel_id = 3; + uint32 ttl_secs = 4; +} + +message BatchEnqueueResponse { + repeated uint64 seqs = 1; +} diff --git a/proto/qpq/v1/device.proto b/proto/qpq/v1/device.proto new file mode 100644 index 0000000..3915863 --- /dev/null +++ b/proto/qpq/v1/device.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; +package qpq.v1; + +// Device register/list/revoke (3 methods). +// Method IDs: 700-702. + +message RegisterDeviceRequest { + bytes device_id = 1; + string device_name = 2; +} + +message RegisterDeviceResponse { + bool success = 1; +} + +message ListDevicesRequest {} + +message ListDevicesResponse { + repeated Device devices = 1; +} + +message Device { + bytes device_id = 1; + string device_name = 2; + uint64 registered_at = 3; +} + +message RevokeDeviceRequest { + bytes device_id = 1; +} + +message RevokeDeviceResponse { + bool success = 1; +} diff --git a/proto/qpq/v1/federation.proto b/proto/qpq/v1/federation.proto new file mode 100644 index 0000000..1c45e62 --- /dev/null +++ b/proto/qpq/v1/federation.proto @@ -0,0 +1,65 @@ +syntax = "proto3"; +package qpq.v1; + +// Federation relay + proxy (6 methods). +// Method IDs: 900-905. + +message FederationAuth { + string origin = 1; +} + +message RelayEnqueueRequest { + bytes recipient_key = 1; + bytes payload = 2; + bytes channel_id = 3; + FederationAuth auth = 4; +} + +message RelayEnqueueResponse { + uint64 seq = 1; +} + +message RelayBatchEnqueueRequest { + repeated bytes recipient_keys = 1; + bytes payload = 2; + bytes channel_id = 3; + FederationAuth auth = 4; +} + +message RelayBatchEnqueueResponse { + repeated uint64 seqs = 1; +} + +message ProxyFetchKeyPackageRequest { + bytes identity_key = 1; + FederationAuth auth = 2; +} + +message ProxyFetchKeyPackageResponse { + bytes package = 1; +} + +message ProxyFetchHybridKeyRequest { + bytes identity_key = 1; + FederationAuth auth = 2; +} + +message ProxyFetchHybridKeyResponse { + bytes hybrid_public_key = 1; +} + +message ProxyResolveUserRequest { + string username = 1; + FederationAuth auth = 2; +} + +message ProxyResolveUserResponse { + bytes identity_key = 1; +} + +message FederationHealthRequest {} + +message FederationHealthResponse { + string status = 1; + string server_domain = 2; +} diff --git a/proto/qpq/v1/keys.proto b/proto/qpq/v1/keys.proto new file mode 100644 index 0000000..7515552 --- /dev/null +++ b/proto/qpq/v1/keys.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; +package qpq.v1; + +// Key package + hybrid key CRUD (5 methods). +// Method IDs: 300-304. + +message UploadKeyPackageRequest { + bytes identity_key = 1; + bytes package = 2; +} + +message UploadKeyPackageResponse { + bytes fingerprint = 1; +} + +message FetchKeyPackageRequest { + bytes identity_key = 1; +} + +message FetchKeyPackageResponse { + bytes package = 1; +} + +message UploadHybridKeyRequest { + bytes identity_key = 1; + bytes hybrid_public_key = 2; +} + +message UploadHybridKeyResponse {} + +message FetchHybridKeyRequest { + bytes identity_key = 1; +} + +message FetchHybridKeyResponse { + bytes hybrid_public_key = 1; +} + +message FetchHybridKeysRequest { + repeated bytes identity_keys = 1; +} + +message FetchHybridKeysResponse { + repeated bytes keys = 1; +} diff --git a/proto/qpq/v1/p2p.proto b/proto/qpq/v1/p2p.proto new file mode 100644 index 0000000..f686fb8 --- /dev/null +++ b/proto/qpq/v1/p2p.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; +package qpq.v1; + +// P2P endpoint publish/resolve + health (3 methods). +// Method IDs: 800-802. + +message PublishEndpointRequest { + bytes identity_key = 1; + bytes node_addr = 2; +} + +message PublishEndpointResponse {} + +message ResolveEndpointRequest { + bytes identity_key = 1; +} + +message ResolveEndpointResponse { + bytes node_addr = 1; +} + +message HealthRequest {} + +message HealthResponse { + string status = 1; +} diff --git a/proto/qpq/v1/push.proto b/proto/qpq/v1/push.proto new file mode 100644 index 0000000..15b38fe --- /dev/null +++ b/proto/qpq/v1/push.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; +package qpq.v1; + +// Server-push event types (sent on QUIC uni-streams). +// Event type IDs: 1000+. + +// Wrapper for a push event. +message PushEvent { + oneof event { + NewMessage new_message = 1; + TypingIndicator typing = 2; + PresenceUpdate presence = 3; + GroupMembershipChange membership = 4; + } +} + +message NewMessage { + bytes channel_id = 1; + bytes sender_key = 2; + uint64 seq = 3; + bytes payload = 4; + uint64 timestamp_ms = 5; +} + +message TypingIndicator { + bytes channel_id = 1; + bytes sender_key = 2; + bool is_typing = 3; +} + +message PresenceUpdate { + bytes identity_key = 1; + bool online = 2; + uint64 last_seen_ms = 3; +} + +message GroupMembershipChange { + bytes channel_id = 1; + bytes actor_key = 2; + bytes target_key = 3; + MembershipAction action = 4; +} + +enum MembershipAction { + MEMBERSHIP_ACTION_UNSPECIFIED = 0; + MEMBERSHIP_ACTION_ADDED = 1; + MEMBERSHIP_ACTION_REMOVED = 2; + MEMBERSHIP_ACTION_LEFT = 3; +} diff --git a/proto/qpq/v1/user.proto b/proto/qpq/v1/user.proto new file mode 100644 index 0000000..e7caddd --- /dev/null +++ b/proto/qpq/v1/user.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; +package qpq.v1; + +// User resolve + identity (2 methods). +// Method IDs: 500-501. + +message ResolveUserRequest { + string username = 1; +} + +message ResolveUserResponse { + bytes identity_key = 1; + bytes inclusion_proof = 2; +} + +message ResolveIdentityRequest { + bytes identity_key = 1; +} + +message ResolveIdentityResponse { + string username = 1; +}