From 02075907810f9af6b47a3ed6af1be173e8bf95fa Mon Sep 17 00:00:00 2001 From: adz Date: Wed, 18 Mar 2026 16:57:40 +0100 Subject: [PATCH 01/15] cargo: Update to p2panda v0.6.1 --- Cargo.lock | 2135 ++++++++++++++++++------------------ reflection-doc/Cargo.toml | 3 +- reflection-node/Cargo.toml | 19 +- 3 files changed, 1096 insertions(+), 1061 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb3a922..496dd07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "acto" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "148541f13c28e3e840354ee4d6c99046c10be2c81068bbd23b9e3a38f95a917e" +checksum = "598381761ee991bf2f1455f700380e2191fb370dc9df1ee764f348b7f089d8b6" dependencies = [ "parking_lot", "pin-project-lite", @@ -17,6 +17,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + [[package]] name = "aes" version = "0.8.4" @@ -28,6 +38,20 @@ dependencies = [ "cpufeatures 0.2.17", ] +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.12" @@ -67,9 +91,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -82,15 +106,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -117,9 +141,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "append-only-bytes" @@ -156,21 +180,21 @@ checksum = "2ccd462b64c3c72f1be8305905a85d85403d768e8690c9b8bd3b9009a5761679" [[package]] name = "ashpd" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618a409b91d5265798a99e3d1d0b226911605e581c4e7255e83c1e397b172bce" +checksum = "33a3c86f3fd70c0ffa500ed189abfa90b5a52398a45d5dc372fcc38ebeb7a645" dependencies = [ "async-fs 2.2.0", "async-net", "enumflags2", "futures-channel", "futures-util", - "rand 0.9.2", + "rand 0.9.4", "serde", "serde_repr", "tracing", "url", - "zbus 5.13.2", + "zbus 5.15.0", ] [[package]] @@ -207,28 +231,15 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-compat" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" -dependencies = [ - "futures-core", - "futures-io", - "once_cell", - "pin-project-lite", - "tokio", -] - [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-lite 2.6.1", "pin-project-lite", "slab", @@ -290,7 +301,7 @@ dependencies = [ "futures-lite 2.6.1", "parking", "polling 3.11.0", - "rustix 1.1.3", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -358,7 +369,7 @@ dependencies = [ "cfg-if", "event-listener 5.4.1", "futures-lite 2.6.1", - "rustix 1.1.3", + "rustix 1.1.4", ] [[package]] @@ -369,14 +380,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "async-signal" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io 2.6.0", "async-lock 3.4.2", @@ -384,7 +395,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.3", + "rustix 1.1.4", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -404,7 +415,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -442,18 +453,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "attohttpc" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" -dependencies = [ - "base64 0.22.1", - "http", - "log", - "url", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -466,17 +465,11 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ - "fastrand 2.3.0", + "fastrand 2.4.1", "gloo-timers", "tokio", ] -[[package]] -name = "base32" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" - [[package]] name = "base64" version = "0.21.7" @@ -503,9 +496,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] @@ -521,16 +514,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.3" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "cpufeatures 0.2.17", + "cpufeatures 0.3.0", ] [[package]] @@ -550,9 +543,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eb4cdd6cf1b31d671e9efe75c5d1ec614776856cefbe109ca373554a6d514f" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ "hybrid-array", ] @@ -608,14 +601,14 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -638,7 +631,7 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b01fe135c0bd16afe262b6dea349bd5ea30e6de50708cec639aae7c5c14cc7e4" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "cairo-sys-rs", "glib", "libc", @@ -666,9 +659,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "shlex", @@ -676,9 +669,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.6" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a" +checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" dependencies = [ "smallvec", "target-lexicon", @@ -704,14 +697,14 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -758,6 +751,12 @@ dependencies = [ "inout", ] +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + [[package]] name = "cobs" version = "0.3.0" @@ -769,9 +768,19 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] [[package]] name = "concurrent-queue" @@ -829,6 +838,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -864,9 +883,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "critical-section" @@ -925,13 +944,31 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "211f05e03c7d03754740fd9e585de910a095d6b99f8bcfffdef8319fa02a8331" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -950,16 +987,16 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "5.0.0-pre.1" +version = "5.0.0-pre.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f9200d1d13637f15a6acb71e758f64624048d85b31a5fdbfd8eca1e2687d0b7" +checksum = "335f1947f241137a14106b6f5acc5918a5ede29c9d71d3f2cb1678d5075d9fc3" dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.11.0-rc.10", + "digest 0.11.3", "fiat-crypto 0.3.0", - "rand_core 0.9.5", + "rand_core 0.10.1", "rustc_version", "serde", "subtle", @@ -974,7 +1011,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -998,7 +1035,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1009,14 +1046,14 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1028,9 +1065,29 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "data-encoding-macro" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" +dependencies = [ + "data-encoding", + "syn 2.0.117", +] [[package]] name = "der" @@ -1056,9 +1113,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -1082,7 +1139,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1103,7 +1160,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1113,7 +1170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1135,7 +1192,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.114", + "syn 2.0.117", "unicode-xid", ] @@ -1165,22 +1222,22 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-rc.10" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa94b64bfc6549e6e4b5a3216f22593224174083da7a90db47e951c4fb31725" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.11.0", + "block-buffer 0.12.0", "const-oid 0.10.2", - "crypto-common 0.2.0", + "crypto-common 0.2.2", ] [[package]] name = "dispatch2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -1194,41 +1251,26 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "dlopen2" -version = "0.5.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" dependencies = [ "libc", "once_cell", "winapi", ] -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - [[package]] name = "ed25519" version = "2.2.3" @@ -1247,7 +1289,7 @@ checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" dependencies = [ "pkcs8 0.11.0-rc.10", "serde", - "signature 3.0.0-rc.10", + "signature 3.0.0", ] [[package]] @@ -1267,25 +1309,25 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "3.0.0-pre.1" +version = "3.0.0-pre.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad207ed88a133091f83224265eac21109930db09bedcad05d5252f2af2de20a1" +checksum = "053618a4c3d3bc24f188aa660ae75a46eeab74ef07fb415c61431e5e7cd4749b" dependencies = [ - "curve25519-dalek 5.0.0-pre.1", + "curve25519-dalek 5.0.0-pre.6", "ed25519 3.0.0-rc.4", - "rand_core 0.9.5", + "rand_core 0.10.1", "serde", - "sha2 0.11.0-rc.2", - "signature 3.0.0-rc.10", + "sha2 0.11.0-rc.5", + "signature 3.0.0", "subtle", "zeroize", ] [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" dependencies = [ "serde", ] @@ -1335,7 +1377,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1346,7 +1388,7 @@ checksum = "3ed8956bd5c1f0415200516e78ff07ec9e16415ade83c056c230d7b7ea0d55b7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1358,7 +1400,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1379,23 +1421,23 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", ] [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "anstream", "anstyle", @@ -1468,18 +1510,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fastbloom" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" -dependencies = [ - "getrandom 0.3.4", - "libm", - "rand 0.9.2", - "siphasher", -] - [[package]] name = "fastrand" version = "1.9.0" @@ -1491,9 +1521,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fiat-crypto" @@ -1590,9 +1620,9 @@ checksum = "d8866fac38f53fc87fa3ae1b09ddd723e0482f8fa74323518b4c59df2c55a00a" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1605,9 +1635,9 @@ dependencies = [ [[package]] name = "futures-buffered" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e0e1f38ec07ba4abbde21eed377082f17ccb988be9d988a5adbf4bafc118fd" +checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" dependencies = [ "cordyceps", "diatomic-waker", @@ -1618,9 +1648,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1641,15 +1671,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1669,9 +1699,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -1694,7 +1724,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-core", "futures-io", "parking", @@ -1703,32 +1733,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1738,7 +1768,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1831,7 +1860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c1bce85c110ab718fd139e0cc89c51b63bd647b14a767e24bdfc77c83df79b" dependencies = [ "arref", - "heapless 0.9.2", + "heapless 0.9.3", "itertools 0.11.0", "loro-thunderdome", "proc-macro2", @@ -1858,25 +1887,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", + "js-sys", "libc", - "r-efi", - "rand_core 0.10.0", + "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", + "wasm-bindgen", ] [[package]] @@ -1899,6 +1928,16 @@ dependencies = [ "temp-dir", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gio" version = "0.21.5" @@ -1935,7 +1974,7 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "futures-channel", "futures-core", "futures-executor", @@ -1957,10 +1996,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" dependencies = [ "heck 0.5.0", - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2077,10 +2116,10 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ccfb5a14a3d941244815d5f8101fa12d4577b59cc47245778d8d907b0003e42" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2104,9 +2143,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -2178,6 +2217,12 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "hashlink" version = "0.10.0" @@ -2213,9 +2258,9 @@ dependencies = [ [[package]] name = "heapless" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" dependencies = [ "hash32 0.3.1", "stable_deref_trait", @@ -2255,26 +2300,25 @@ dependencies = [ ] [[package]] -name = "hickory-proto" -version = "0.25.2" +name = "hickory-net" +version = "0.26.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +checksum = "1e232f503c4cfe3f4ea6594971255ecab9f6a0080c4c8e0e17630cc701322aa4" dependencies = [ "async-trait", "bytes", "cfg-if", "data-encoding", - "enum-as-inner 0.6.1", "futures-channel", "futures-io", "futures-util", "h2", + "hickory-proto", "http", "idna", "ipnet", - "once_cell", - "rand 0.9.2", - "ring", + "jni", + "rand 0.10.1", "rustls", "thiserror 2.0.18", "tinyvec", @@ -2284,23 +2328,48 @@ dependencies = [ "url", ] +[[package]] +name = "hickory-proto" +version = "0.26.0-beta.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcca12171ce774c549f35510be702f4da00ef12ca486f0f2acb2ee96f2f5ca0f" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni", + "once_cell", + "prefix-trie", + "rand 0.10.1", + "ring", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "url", +] + [[package]] name = "hickory-resolver" -version = "0.25.2" +version = "0.26.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +checksum = "1e7d2c928fa078e6640f26cf1b537b212e1688829c3944780025c7084e8bbbf6" dependencies = [ "cfg-if", "futures-util", + "hickory-net", "hickory-proto", "ipconfig", + "ipnet", + "jni", "moka", + "ndk-context", "once_cell", "parking_lot", - "rand 0.9.2", + "rand 0.10.1", "resolv-conf", "rustls", "smallvec", + "system-configuration", "thiserror 2.0.18", "tokio", "tokio-rustls", @@ -2381,31 +2450,29 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.7" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -2413,19 +2480,17 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] @@ -2445,7 +2510,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -2477,12 +2542,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -2490,9 +2556,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -2503,9 +2569,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2517,15 +2583,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -2537,15 +2603,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -2587,35 +2653,14 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", ] -[[package]] -name = "igd-next" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" -dependencies = [ - "async-trait", - "attohttpc", - "bytes", - "futures", - "http", - "http-body-util", - "hyper", - "hyper-util", - "log", - "rand 0.9.2", - "tokio", - "url", - "xmltree", -] - [[package]] name = "im" version = "15.1.0" @@ -2633,12 +2678,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -2675,66 +2720,62 @@ dependencies = [ [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ - "socket2 0.5.10", + "socket2 0.6.3", "widestring", - "windows-sys 0.48.0", - "winreg", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", ] [[package]] name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.10" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" dependencies = [ - "memchr", "serde", ] [[package]] name = "iroh" -version = "0.96.1" +version = "0.98.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5236da4d5681f317ec393c8fe2b7e3d360d31c6bb40383991d0b7429ca5ad117" +checksum = "9881b221c7c645d90594cbd331012f7cccb914894288a6cf5538a9115f6d0f3e" dependencies = [ "backon", + "blake3", "bytes", "cfg_aliases", + "ctutils", "data-encoding", + "der 0.8.0-rc.10", "derive_more", - "ed25519-dalek 3.0.0-pre.1", + "ed25519-dalek 3.0.0-pre.6", "futures-util", - "getrandom 0.3.4", + "getrandom 0.4.2", "hickory-resolver", "http", - "igd-next", + "ipnet", "iroh-base", + "iroh-dns", "iroh-metrics", - "iroh-quinn", - "iroh-quinn-proto", - "iroh-quinn-udp", "iroh-relay", "n0-error", "n0-future", "n0-watcher", - "netdev", "netwatch", + "noq", + "noq-proto", + "noq-udp", "papaya", "pin-project", - "pkarr", "pkcs8 0.11.0-rc.10", - "portmapper", - "rand 0.9.2", + "portable-atomic", + "rand 0.10.1", "reqwest", "rustc-hash", "rustls", @@ -2742,9 +2783,8 @@ dependencies = [ "rustls-webpki", "serde", "smallvec", - "strum 0.27.2", + "strum", "swarm-discovery", - "sync_wrapper", "time", "tokio", "tokio-stream", @@ -2757,35 +2797,52 @@ dependencies = [ [[package]] name = "iroh-base" -version = "0.96.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c99d836a1c99e037e98d1bf3ef209c3a4df97555a00ce9510eb78eccdf5567" +checksum = "738865784637830fb14204ebd3047922db83bc1816a59027af29579b9c27bd99" dependencies = [ - "curve25519-dalek 5.0.0-pre.1", + "curve25519-dalek 5.0.0-pre.6", "data-encoding", + "data-encoding-macro", "derive_more", - "digest 0.11.0-rc.10", - "ed25519-dalek 3.0.0-pre.1", + "digest 0.11.3", + "ed25519-dalek 3.0.0-pre.6", + "getrandom 0.4.2", "n0-error", - "rand_core 0.9.5", + "rand 0.10.1", "serde", - "sha2 0.11.0-rc.2", + "sha2 0.11.0-rc.5", "url", "zeroize", "zeroize_derive", ] +[[package]] +name = "iroh-dns" +version = "0.98.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca474630d1e62ddef83149db6babe6a1055d901df9054349d31b22df99811b92" +dependencies = [ + "derive_more", + "iroh-base", + "n0-error", + "n0-future", + "simple-dns", + "strum", +] + [[package]] name = "iroh-gossip" -version = "0.96.0" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d04f83254c847ac61a9b2215b95a36d598d87af033ca12a546cd1c6a2e06dab" +checksum = "b349a9ab58e3b56cf41df693bc1812add192ad70ce7c8d0dbdc7d0319d71b11f" dependencies = [ "blake3", "bytes", + "constant_time_eq", "data-encoding", "derive_more", - "ed25519-dalek 3.0.0-pre.1", + "ed25519-dalek 3.0.0-pre.6", "futures-concurrency", "futures-lite 2.6.1", "futures-util", @@ -2798,7 +2855,7 @@ dependencies = [ "n0-error", "n0-future", "postcard", - "rand 0.9.2", + "rand 0.10.1", "serde", "tokio", "tokio-util", @@ -2807,13 +2864,14 @@ dependencies = [ [[package]] name = "iroh-metrics" -version = "0.38.2" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c946095f060e6e59b9ff30cc26c75cdb758e7fb0cde8312c89e2144654989fcb" +checksum = "761b45ba046134b11eb3e432fa501616b45c4bf3a30c21717578bc07aa6461dd" dependencies = [ "iroh-metrics-derive", "itoa", "n0-error", + "portable-atomic", "postcard", "ryu", "serde", @@ -2829,104 +2887,44 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", -] - -[[package]] -name = "iroh-quinn" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034ed21f34c657a123d39525d948c885aacba59508805e4dd67d71f022e7151b" -dependencies = [ - "bytes", - "cfg_aliases", - "iroh-quinn-proto", - "iroh-quinn-udp", - "pin-project-lite", - "rustc-hash", - "rustls", - "socket2 0.6.2", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", - "web-time", -] - -[[package]] -name = "iroh-quinn-proto" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de99ad8adc878ee0e68509ad256152ce23b8bbe45f5539d04e179630aca40a9" -dependencies = [ - "bytes", - "derive_more", - "enum-assoc", - "fastbloom", - "getrandom 0.3.4", - "identity-hash", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "sorted-index-buffer", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "iroh-quinn-udp" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f981dadd5a072a9e0efcd24bdcc388e570073f7e51b33505ceb1ef4668c80c86" -dependencies = [ - "cfg_aliases", - "libc", - "socket2 0.6.2", - "tracing", - "windows-sys 0.61.2", + "syn 2.0.117", ] [[package]] name = "iroh-relay" -version = "0.96.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd2b63e654b9dec799a73372cdc79b529ca6c7248c0c8de7da78a02e3a46f03c" +checksum = "4aa6e9a7277bfbb439739c52b57eb5f9288030983928412022b8e94a43d4d838" dependencies = [ "blake3", "bytes", "cfg_aliases", "data-encoding", "derive_more", - "getrandom 0.3.4", + "getrandom 0.4.2", "hickory-resolver", "http", "http-body-util", "hyper", "hyper-util", "iroh-base", + "iroh-dns", "iroh-metrics", - "iroh-quinn", - "iroh-quinn-proto", "lru", "n0-error", "n0-future", + "noq", + "noq-proto", "num_enum", "pin-project", - "pkarr", "postcard", - "rand 0.9.2", + "rand 0.10.1", "reqwest", "rustls", "rustls-pki-types", "serde", "serde_bytes", - "strum 0.27.2", + "strum", "tokio", "tokio-rustls", "tokio-util", @@ -2936,14 +2934,13 @@ dependencies = [ "vergen-gitcl", "webpki-roots", "ws_stream_wasm", - "z32", ] [[package]] name = "irpc" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bbc84aaeab13a6d7502bae4f40f2517b643924842e0230ea0bf807477cc208" +checksum = "26bacc8d71f54f16cb5ae82745cfca440ad8ecd09b4480d415b8d9dc78146432" dependencies = [ "futures-util", "irpc-derive", @@ -2957,13 +2954,13 @@ dependencies = [ [[package]] name = "irpc-derive" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58148196d2230183c9679431ac99b57e172000326d664e8456fa2cd27af6505a" +checksum = "4651422b9d7af09fa1437a5fabbd9e074162b502a1af7f5bae8b439eaf3e049f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2992,16 +2989,67 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -3016,7 +3064,7 @@ dependencies = [ "lazy_static", "linux-keyutils", "secret-service", - "security-framework", + "security-framework 2.11.1", "windows-sys 0.52.0", ] @@ -3031,9 +3079,9 @@ dependencies = [ [[package]] name = "leb128" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +checksum = "6cc46bac87ef8093eed6f272babb833b6443374399985ac8ed28471ee0918545" [[package]] name = "leb128fmt" @@ -3074,9 +3122,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.180" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -3086,13 +3134,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "libc", - "redox_syscall 0.7.0", + "plain", + "redox_syscall 0.7.5", ] [[package]] @@ -3137,11 +3186,11 @@ dependencies = [ [[package]] name = "linux-keyutils" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e" +checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "libc", ] @@ -3159,21 +3208,15 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "litrs" -version = "1.0.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "locale_config" @@ -3298,7 +3341,7 @@ dependencies = [ "pest_derive", "postcard", "pretty_assertions", - "rand 0.8.5", + "rand 0.8.6", "rustc-hash", "serde", "serde_columnar", @@ -3352,15 +3395,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c8ea186958094052b971fe7e322a934b034c3bf62f0458ccea04fcd687ba1" dependencies = [ "once_cell", - "rand 0.8.5", + "rand 0.8.6", "serde", ] [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] @@ -3373,9 +3416,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "lz4_flex" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" dependencies = [ "twox-hash", ] @@ -3446,9 +3489,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -3457,9 +3500,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.13" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -3491,7 +3534,7 @@ checksum = "03755949235714b2b307e5ae89dd8c1c2531fb127d9b8b7b4adf9c876cd3ed18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3526,11 +3569,17 @@ dependencies = [ "n0-future", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "netdev" -version = "0.40.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc9815643a243856e7bd84524e1ff739e901e846cfb06ad9627cd2b6d59bd737" +checksum = "e30af1a5073b82356d9317c18226826370b4288eba2f71c7e84e18bae51b3847" dependencies = [ "block2", "dispatch2", @@ -3539,13 +3588,13 @@ dependencies = [ "libc", "mac-addr", "netlink-packet-core", - "netlink-packet-route 0.25.1", + "netlink-packet-route 0.29.0", "netlink-sys", "objc2-core-foundation", "objc2-system-configuration", "once_cell", "plist", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3559,11 +3608,11 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef" +checksum = "df9854ea6ad14e3f4698a7f03b65bce0833dd2d81d594a0e4a984170537146b6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "libc", "log", "netlink-packet-core", @@ -3571,11 +3620,11 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" +checksum = "be8919612f6028ab4eacbbfe1234a9a43e3722c6e0915e7ff519066991905092" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "libc", "log", "netlink-packet-core", @@ -3610,15 +3659,14 @@ dependencies = [ [[package]] name = "netwatch" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "454b8c0759b2097581f25ed5180b4a1d14c324fde6d0734932a288e044d06232" +checksum = "6fc0d4b4134425d9834e591b1a6f807ea365c6d941d738942215564af5f28a97" dependencies = [ "atomic-waker", "bytes", "cfg_aliases", "derive_more", - "iroh-quinn-udp", "js-sys", "libc", "n0-error", @@ -3626,14 +3674,15 @@ dependencies = [ "n0-watcher", "netdev", "netlink-packet-core", - "netlink-packet-route 0.28.0", + "netlink-packet-route 0.30.0", "netlink-proto", "netlink-sys", + "noq-udp", "objc2-core-foundation", "objc2-system-configuration", "pin-project-lite", "serde", - "socket2 0.6.2", + "socket2 0.6.3", "time", "tokio", "tokio-util", @@ -3663,18 +3712,64 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" [[package]] -name = "ntimestamp" -version = "1.0.0" +name = "noq" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50f94c405726d3e0095e89e72f75ce7f6587b94a8bd8dc8054b73f65c0fd68c" +checksum = "4b969bd157c3bd3bab239a1a8b14f67f2033fa012770367fcbd5b42d71ae3548" dependencies = [ - "base32", - "document-features", - "getrandom 0.2.17", - "httpdate", - "js-sys", - "once_cell", - "serde", + "bytes", + "cfg_aliases", + "derive_more", + "noq-proto", + "noq-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "web-time", +] + +[[package]] +name = "noq-proto" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdec6f5039d98ee5377b2f532d495a555eb664c53161b1b5780dcaeac678b60e" +dependencies = [ + "aes-gcm", + "bytes", + "derive_more", + "enum-assoc", + "getrandom 0.4.2", + "identity-hash", + "lru-slab", + "rand 0.10.1", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "sorted-index-buffer", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "noq-udp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee91b05f4f3353290936ba1f3233518868fb4e2da99cb4c90d1f8cebb064e527" +dependencies = [ + "cfg_aliases", + "libc", + "socket2 0.6.3", + "tracing", + "windows-sys 0.61.2", ] [[package]] @@ -3721,7 +3816,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "serde", "smallvec", "zeroize", @@ -3738,9 +3833,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -3785,9 +3880,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -3795,14 +3890,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3836,9 +3931,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", ] @@ -3849,7 +3944,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "block2", "dispatch2", "libc", @@ -3868,7 +3963,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", ] @@ -3879,7 +3974,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "dispatch2", "libc", "objc2", @@ -3898,9 +3993,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ "critical-section", "portable-atomic", @@ -3930,26 +4025,31 @@ dependencies = [ "num", "num-bigint-dig", "openssl", - "rand 0.9.2", + "rand 0.9.4", "serde", "tracing", - "zbus 5.13.2", - "zbus_macros 5.13.2", + "zbus 5.15.0", + "zbus_macros 5.15.0", "zeroize", - "zvariant 5.9.2", + "zvariant 5.11.0", ] +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -3962,14 +4062,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -3987,16 +4093,37 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "p2panda" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5544dd10dd9b9eaeda24327888fe6bf75ed530e8c601d2406ddf4bf9f30f3c4e" +dependencies = [ + "futures-util", + "p2panda-core", + "p2panda-net", + "p2panda-store", + "p2panda-stream", + "p2panda-sync", + "pin-project", + "serde", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "p2panda-core" -version = "0.5.1" -source = "git+https://github.com/p2panda/p2panda?rev=a1308c116c1bd345f2cd6af7df41d2a6e0a4a682#a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27a463e79633277c88888f3b2e7bc6eb10e240e9b21a8aa262eb1aa814e4bf08" dependencies = [ "blake3", "ciborium", "ed25519-dalek 2.2.0", "hex", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_bytes", "thiserror 2.0.18", @@ -4004,13 +4131,15 @@ dependencies = [ [[package]] name = "p2panda-discovery" -version = "0.5.1" -source = "git+https://github.com/p2panda/p2panda?rev=a1308c116c1bd345f2cd6af7df41d2a6e0a4a682#a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b941f35c251f1e38ba985b6628ba57e3403806a58a5db4968be707728354e56" dependencies = [ "blake3", "futures-util", - "rand 0.9.2", - "rand_chacha 0.9.0", + "p2panda-core", + "p2panda-store", + "rand 0.10.1", "serde", "thiserror 2.0.18", "tokio", @@ -4018,8 +4147,9 @@ dependencies = [ [[package]] name = "p2panda-net" -version = "0.5.1" -source = "git+https://github.com/p2panda/p2panda?rev=a1308c116c1bd345f2cd6af7df41d2a6e0a4a682#a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e268db7d67957a6d9e38affaee7508f63e47cec3e8a6d66f7e9afaa7199623e" dependencies = [ "ciborium", "futures-channel", @@ -4033,8 +4163,8 @@ dependencies = [ "p2panda-store", "p2panda-sync", "ractor", - "rand 0.9.2", - "rand_chacha 0.9.0", + "rand 0.10.1", + "rand_chacha 0.10.0", "serde", "thiserror 2.0.18", "tokio", @@ -4045,36 +4175,36 @@ dependencies = [ [[package]] name = "p2panda-store" -version = "0.5.1" -source = "git+https://github.com/p2panda/p2panda?rev=a1308c116c1bd345f2cd6af7df41d2a6e0a4a682#a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ddf71909d86291bad9650b3f47f04e26997aa71e1c4077ba697dd53980095a" dependencies = [ - "ciborium", - "hex", "p2panda-core", + "serde", "sqlx", "thiserror 2.0.18", - "trait-variant", + "tokio", ] [[package]] name = "p2panda-stream" -version = "0.5.1" -source = "git+https://github.com/p2panda/p2panda?rev=a1308c116c1bd345f2cd6af7df41d2a6e0a4a682#a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a09be2ae329764c49d299e81da615852028f7e59c54bac7e43a4d08f5286d9cc" dependencies = [ - "ciborium", - "futures-channel", - "futures-util", + "futures-core", "p2panda-core", "p2panda-store", "pin-project", - "pin-utils", "thiserror 2.0.18", + "tokio", ] [[package]] name = "p2panda-sync" -version = "0.5.1" -source = "git+https://github.com/p2panda/p2panda?rev=a1308c116c1bd345f2cd6af7df41d2a6e0a4a682#a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dbb323a05aa5c10f668f2515a8755981aba36c5cdeed9f860ca86ddd86bee8" dependencies = [ "futures", "futures-util", @@ -4114,9 +4244,9 @@ dependencies = [ [[package]] name = "papaya" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92dd0b07c53a0a0c764db2ace8c541dc47320dad97c2200c2a637ab9dd2328f" +checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7" dependencies = [ "equivalent", "seize", @@ -4211,7 +4341,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4236,78 +4366,41 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-io", ] -[[package]] -name = "pkarr" -version = "5.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d346b545765a0ef58b6a7e160e17ddaa7427f439b7b9a287df6c88c9e04bf2" -dependencies = [ - "async-compat", - "base32", - "bytes", - "cfg_aliases", - "document-features", - "dyn-clone", - "ed25519-dalek 3.0.0-pre.1", - "futures-buffered", - "futures-lite 2.6.1", - "getrandom 0.3.4", - "log", - "lru", - "ntimestamp", - "reqwest", - "self_cell", - "serde", - "sha1_smol", - "simple-dns", - "thiserror 2.0.18", - "tokio", - "tracing", - "url", - "wasm-bindgen-futures", -] - [[package]] name = "pkcs1" version = "0.7.5" @@ -4341,15 +4434,21 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plist" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" dependencies = [ "base64 0.22.1", "indexmap", @@ -4384,44 +4483,29 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.5.2", "pin-project-lite", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] [[package]] -name = "portable-atomic" -version = "1.13.1" +name = "polyval" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] [[package]] -name = "portmapper" -version = "0.14.0" +name = "portable-atomic" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d2a8825353ace3285138da3378b1e21860d60351942f7aa3b99b13b41f80318" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" dependencies = [ - "base64 0.22.1", - "bytes", - "derive_more", - "futures-lite 2.6.1", - "futures-util", - "hyper-util", - "igd-next", - "iroh-metrics", - "libc", - "n0-error", - "netwatch", - "num_enum", - "rand 0.9.2", "serde", - "smallvec", - "socket2 0.6.2", - "time", - "tokio", - "tokio-util", - "tower-layer", - "tracing", - "url", ] [[package]] @@ -4446,14 +4530,14 @@ checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -4473,6 +4557,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prefix-trie" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -4490,7 +4585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4505,11 +4600,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -4523,18 +4618,18 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.38.4" +version = "0.39.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" dependencies = [ "memchr", ] [[package]] name = "quick_cache" -version = "0.6.18" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" +checksum = "d1c821816e9b928e20e92ed59bb3ac4aab321d16ca2316871c9fe7ca739cd477" dependencies = [ "ahash", "equivalent", @@ -4542,66 +4637,11 @@ dependencies = [ "parking_lot", ] -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2 0.6.2", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.6.2", - "tracing", - "windows-sys 0.60.2", -] - [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -4612,18 +4652,24 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "ractor" -version = "0.15.10" +version = "0.15.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6102314f700f3e8df466c49110830b18cbfc172f88f27a9d7383e455663b1be7" +checksum = "f12c86deb2af198b10a04c4fb3fba73baf3bb300df765a29272f0e5583da7510" dependencies = [ "bon", "dashmap", "futures", "js-sys", "once_cell", - "strum 0.26.3", + "strum", "tokio", "tokio_with_wasm", "tracing", @@ -4634,9 +4680,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -4645,9 +4691,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -4655,13 +4701,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.1", - "rand_core 0.10.0", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -4691,7 +4737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb" dependencies = [ "ppv-lite86", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -4714,9 +4760,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_xoshiro" @@ -4733,16 +4779,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", ] [[package]] name = "redox_syscall" -version = "0.7.0" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", ] [[package]] @@ -4776,7 +4822,8 @@ dependencies = [ "hex", "indexmap", "loro", - "rand 0.10.0", + "p2panda-core", + "rand 0.10.1", "reflection-node", "serde", "test-log", @@ -4789,17 +4836,10 @@ name = "reflection-node" version = "0.3.0" dependencies = [ "chrono", - "ciborium", - "hex", + "p2panda", "p2panda-core", - "p2panda-discovery", - "p2panda-net", "p2panda-store", - "p2panda-stream", - "p2panda-sync", - "rand_chacha 0.10.0", "serde", - "serde_bytes", "sqlx", "test-log", "thiserror 2.0.18", @@ -4833,15 +4873,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64 0.22.1", "bytes", @@ -4857,12 +4897,9 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", - "quinn", "rustls", "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", + "rustls-platform-verifier", "sync_wrapper", "tokio", "tokio-rustls", @@ -4875,7 +4912,6 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", ] [[package]] @@ -4920,9 +4956,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -4953,7 +4989,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -4962,22 +4998,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "log", "once_cell", @@ -4989,20 +5025,59 @@ dependencies = [ ] [[package]] -name = "rustls-pki-types" -version = "1.14.0" +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.7.0", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.7.0", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" -dependencies = [ - "web-time", - "zeroize", -] +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -5021,6 +5096,24 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -5046,7 +5139,7 @@ dependencies = [ "hkdf", "num", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "serde", "sha2 0.10.9", "zbus 3.15.2", @@ -5058,8 +5151,21 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.10.0", - "core-foundation", + "bitflags 2.11.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -5067,9 +5173,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -5085,17 +5191,11 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "self_cell" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" - [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "send_wrapper" @@ -5145,7 +5245,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5165,7 +5265,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5189,14 +5289,14 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -5243,13 +5343,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-rc.2" +version = "0.11.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924" +checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest 0.11.0-rc.10", + "digest 0.11.3", ] [[package]] @@ -5289,9 +5389,19 @@ dependencies = [ [[package]] name = "signature" -version = "3.0.0-rc.10" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] [[package]] name = "simdutf8" @@ -5305,15 +5415,9 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", ] -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - [[package]] name = "sized-chunks" version = "0.6.5" @@ -5357,22 +5461,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5424,7 +5518,7 @@ checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5520,7 +5614,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5543,7 +5637,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.114", + "syn 2.0.117", "tokio", "url", ] @@ -5556,7 +5650,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.1", "byteorder", "bytes", "chrono", @@ -5578,7 +5672,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.5", + "rand 0.8.6", "rsa", "serde", "sha1", @@ -5599,7 +5693,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.1", "byteorder", "chrono", "crc", @@ -5617,7 +5711,7 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_json", "sha2 0.10.9", @@ -5685,45 +5779,23 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", -] - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros 0.27.2", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.114", + "strum_macros", ] [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5734,14 +5806,14 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "swarm-discovery" -version = "0.5.0" +version = "0.6.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5ab62937edac8b23fa40e55a358ea1924245b17fc1eb20d14929c8f11be98d" +checksum = "cf5ccbd3c5abd6e7314768de12649c1b0a29bea38fca4370f9408340c0f364a6" dependencies = [ "acto", "hickory-proto", - "rand 0.9.2", - "socket2 0.6.2", + "rand 0.10.1", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -5760,9 +5832,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -5786,14 +5858,35 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] name = "system-deps" -version = "7.0.7" +version = "7.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" dependencies = [ "cfg-expr", "heck 0.5.0", @@ -5822,22 +5915,22 @@ checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964" [[package]] name = "tempfile" -version = "3.24.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "fastrand 2.3.0", - "getrandom 0.3.4", + "fastrand 2.4.1", + "getrandom 0.4.2", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] [[package]] name = "test-log" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d53ac171c92a39e4769491c4b4dde7022c60042254b5fc044ae409d34a24d4" +checksum = "2f46bf474f0a4afebf92f076d54fd5e63423d9438b8c278a3d2ccb0f47f7cdb3" dependencies = [ "env_logger", "test-log-macros", @@ -5845,14 +5938,24 @@ dependencies = [ ] [[package]] -name = "test-log-macros" -version = "0.2.19" +name = "test-log-core" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" +checksum = "37d4d41320b48bc4a211a9021678fcc0c99569b594ea31c93735b8e517102b4c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", +] + +[[package]] +name = "test-log-macros" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9beb9249a81e430dffd42400a49019bcf548444f1968ff23080a625de0d4d320" +dependencies = [ + "syn 2.0.117", + "test-log-core", ] [[package]] @@ -5881,7 +5984,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5892,7 +5995,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5940,9 +6043,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -5950,9 +6053,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -5965,16 +6068,16 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.2", + "socket2 0.6.3", "tokio-macros", "tracing", "windows-sys 0.61.2", @@ -5982,13 +6085,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6030,20 +6133,21 @@ dependencies = [ [[package]] name = "tokio-websockets" -version = "0.12.3" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b6348ebfaaecd771cecb69e832961d277f59845d4220a584701f72728152b7" +checksum = "dad543404f98bfc969aeb71994105c592acfc6c43323fddcd016bb208d1c65cb" dependencies = [ "base64 0.22.1", "bytes", "futures-core", "futures-sink", - "getrandom 0.3.4", + "getrandom 0.4.2", "http", "httparse", - "rand 0.9.2", + "rand 0.10.1", "ring", "rustls-pki-types", + "sha1_smol", "simdutf8", "tokio", "tokio-rustls", @@ -6071,22 +6175,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d01145a2c788d6aae4cd653afec1e8332534d7d783d01897cefcafe4428de992" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 0.7.14", + "winnow 1.0.3", ] [[package]] @@ -6097,9 +6201,9 @@ checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] @@ -6117,30 +6221,30 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 0.7.14", + "winnow 1.0.3", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 0.7.14", + "winnow 1.0.3", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tower" @@ -6159,20 +6263,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -6207,7 +6311,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6233,9 +6337,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -6249,17 +6353,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "trait-variant" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] - [[package]] name = "try-lock" version = "0.2.5" @@ -6274,9 +6367,9 @@ checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -6286,13 +6379,13 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uds_windows" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset 0.9.1", "tempfile", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -6303,9 +6396,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -6324,9 +6417,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -6334,6 +6427,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -6367,11 +6470,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", "serde_core", "wasm-bindgen", @@ -6455,6 +6558,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -6472,11 +6585,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -6485,7 +6598,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -6496,9 +6609,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -6509,23 +6622,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6533,22 +6642,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] @@ -6577,9 +6686,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -6594,7 +6703,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -6602,9 +6711,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ "js-sys", "wasm-bindgen", @@ -6620,11 +6729,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -6661,6 +6779,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -6720,7 +6847,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6731,7 +6858,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6750,6 +6877,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -6795,15 +6933,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -6837,30 +6966,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows-threading" version = "0.2.1" @@ -6882,12 +6994,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -6900,12 +7006,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -6918,24 +7018,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -6948,12 +7036,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -6966,12 +7048,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -6984,12 +7060,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -7002,12 +7072,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.5.40" @@ -7019,23 +7083,13 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -7045,6 +7099,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -7066,7 +7126,7 @@ dependencies = [ "heck 0.5.0", "indexmap", "prettyplease", - "syn 2.0.114", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -7082,7 +7142,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -7094,7 +7154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.1", "indexmap", "log", "serde", @@ -7126,9 +7186,9 @@ dependencies = [ [[package]] name = "wmi" -version = "0.18.1" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "746791db82f029aaefc774ccbb8e61306edba18ef2c8998337cadccc0b8067f7" +checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64" dependencies = [ "chrono", "futures", @@ -7141,9 +7201,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "ws_stream_wasm" @@ -7174,21 +7234,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "xml-rs" -version = "0.8.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" - -[[package]] -name = "xmltree" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" -dependencies = [ - "xml-rs", -] - [[package]] name = "xxhash-rust" version = "0.8.15" @@ -7203,9 +7248,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -7214,22 +7259,16 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "synstructure", ] -[[package]] -name = "z32" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f" - [[package]] name = "zbus" version = "3.15.2" @@ -7257,7 +7296,7 @@ dependencies = [ "nix", "once_cell", "ordered-stream", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_repr", "sha1", @@ -7273,9 +7312,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.13.2" +version = "5.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" +checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1" dependencies = [ "async-broadcast 0.7.2", "async-executor", @@ -7293,17 +7332,17 @@ dependencies = [ "hex", "libc", "ordered-stream", - "rustix 1.1.3", + "rustix 1.1.4", "serde", "serde_repr", "tracing", "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 0.7.14", - "zbus_macros 5.13.2", - "zbus_names 4.3.1", - "zvariant 5.9.2", + "winnow 1.0.3", + "zbus_macros 5.15.0", + "zbus_names 4.3.2", + "zvariant 5.11.0", ] [[package]] @@ -7322,17 +7361,17 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.13.2" +version = "5.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" +checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.114", - "zbus_names 4.3.1", - "zvariant 5.9.2", - "zvariant_utils 3.3.0", + "syn 2.0.117", + "zbus_names 4.3.2", + "zvariant 5.11.0", + "zvariant_utils 3.3.1", ] [[package]] @@ -7348,53 +7387,53 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow 0.7.14", - "zvariant 5.9.2", + "winnow 1.0.3", + "zvariant 5.11.0", ] [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "synstructure", ] @@ -7415,14 +7454,14 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -7431,9 +7470,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -7442,20 +7481,20 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "zmij" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zvariant" @@ -7473,17 +7512,17 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.9.2" +version = "5.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" +checksum = "1c1567a6ec68df868cbbfde844cfc6d81649fe5109a62b116b19fabd53e618ee" dependencies = [ "endi", "enumflags2", "serde", "url", - "winnow 0.7.14", - "zvariant_derive 5.9.2", - "zvariant_utils 3.3.0", + "winnow 1.0.3", + "zvariant_derive 5.11.0", + "zvariant_utils 3.3.1", ] [[package]] @@ -7501,15 +7540,15 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.9.2" +version = "5.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" +checksum = "c7d5b780599bbde114e39d9a0799577fad1ced5105d38515745f7b3099d8ceda" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.114", - "zvariant_utils 3.3.0", + "syn 2.0.117", + "zvariant_utils 3.3.1", ] [[package]] @@ -7525,13 +7564,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.3.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.114", - "winnow 0.7.14", + "syn 2.0.117", + "winnow 1.0.3", ] diff --git a/reflection-doc/Cargo.toml b/reflection-doc/Cargo.toml index d52206e..7ead161 100644 --- a/reflection-doc/Cargo.toml +++ b/reflection-doc/Cargo.toml @@ -13,7 +13,8 @@ gio = "0.21" glib = "0.21" hex = "0.4.3" indexmap = "2.13.0" -loro = "1.10.8" +loro = "1.12.0" +p2panda-core = "0.6.1" rand = "0.10.0" reflection-node = { path = "../reflection-node" } serde = { version = "1.0.228", features = ["derive"] } diff --git a/reflection-node/Cargo.toml b/reflection-node/Cargo.toml index 6d05e9a..3a89e38 100644 --- a/reflection-node/Cargo.toml +++ b/reflection-node/Cargo.toml @@ -12,21 +12,16 @@ authors = [ test_utils = [] [dependencies] -thiserror = "2.0.18" chrono = "0.4.43" -ciborium = "0.2.2" -p2panda-core = { git = "https://github.com/p2panda/p2panda", rev = "a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" } -p2panda-discovery = { git = "https://github.com/p2panda/p2panda", rev = "a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" } -p2panda-net = { git = "https://github.com/p2panda/p2panda", rev = "a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" } -p2panda-store = { git = "https://github.com/p2panda/p2panda", rev = "a1308c116c1bd345f2cd6af7df41d2a6e0a4a682", features = ["sqlite"], default-features = false } -p2panda-stream = { git = "https://github.com/p2panda/p2panda", rev = "a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" } -p2panda-sync = { git = "https://github.com/p2panda/p2panda", rev = "a1308c116c1bd345f2cd6af7df41d2a6e0a4a682" } +p2panda = "0.6.1" +p2panda-core = "0.6.1" +p2panda-store = "0.6.1" serde = { version = "1.0.228", features = ["derive"] } -serde_bytes = "0.11.19" sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite", "chrono"], default-features = false } -tokio = { version = "1.49.0", features = ["rt", "sync"] } +thiserror = "2.0.18" +tokio = { version = "1.52.3", features = ["rt", "sync"] } tokio-stream = "0.1.18" tracing = "0.1" + +[dev-dependencies] test-log = { version = "0.2.19", default-features = false, features = ["trace", "color"] } -hex = "0.4.3" -rand_chacha = "0.10.0" From 6b9f0cbc11990abaf41cd583b672b4f04b7afbd4 Mon Sep 17 00:00:00 2001 From: adz Date: Wed, 18 Mar 2026 16:57:40 +0100 Subject: [PATCH 02/15] node: p2panda high-level API v0.6.1 integration This patch integrates the new `p2panda` crate into the "backend" of Reflection. `p2panda` is the new all-in-one high-level Node API which should make usage of p2panda features easier, removing a bunch of code in `reflection-node` which is now handled in p2panda directly, such as: * Encoding / Decoding of operations and their message payloads * Make sure changes to the append-only log are strictly serialized, to avoid breaking consistency / integrity of data due to race conditions * Re-playing all operations on demand, this is required by Reflection to re-materialize all state for a document * Manage header extensions to include log ids and prune flags * Process local and remotely incoming operations ("ingest", "pruning") * SQLite storage handling for logs and operations * Mapping between logs and topics * De-duplicate messages in ephemeral streams by adding timestamp * Add public key / signatures to give provenance to ephemeral messages Some features implemented in `reflection-node` stay as they're adding functionality _on top_ of `p2panda`. Some of them might be moved there as well in the future, such as: * Author Presence: Indicate which authors have contributed to which topic and if they're currently online. Related issue: https://github.com/p2panda/p2panda/issues/1002 * Persisted Topics: Store to persist all previously used topics Unfortunately we need to remove one feature for now which is to change "connection modes", causing the network to be disabled / enabled. `reflection-node` handled that well in the past by only disabling the networking layer while keeping the event processing layer intact. We will have to do something similar in `p2panda` to offer this feature as well since the new API surface will not allow such low-level access anymore. See related issue: https://github.com/p2panda/p2panda/issues/1093 BREAKING CHANGES These changes introduce breaking changes to the core operation format (`previous` field was removed) and the header extensions changed which means that all previously created, persisted operations will _not_ be compatible anymore. THIS RESULTS IN LOOSING ALL PREVIOUSLY CREATED DOCUMENTS. Since `p2panda` is not stable yet this might happen again in the near future, though this was the most important and largest update and we're much closer to a stable API and protocol now. Future SQLite files will be written to a new path, the network id has also changed now. This prevents "old" nodes talking to "new" ones, similarily we allow users to retreive old documents from the database. Final remarks: Some minor changes in `reflection-doc` where also necessary, I specifically decided to make `p2panda-core` a concrete dependency there since too much functionality is used to justify re-exports from `reflection-node`. --- reflection-node/src/lib.rs | 15 +- reflection-node/src/network.rs | 131 ------ reflection-node/src/node.rs | 186 ++++++-- reflection-node/src/node_inner.rs | 148 ------ reflection-node/src/operation.rs | 72 --- reflection-node/src/operation_store.rs | 129 ------ reflection-node/src/persistent_operation.rs | 64 --- reflection-node/src/subscription.rs | 397 +++++++++++++++++ reflection-node/src/subscription_inner.rs | 469 -------------------- reflection-node/src/topic.rs | 130 ------ reflection-node/src/topic_store.rs | 97 +--- reflection-node/src/traits.rs | 17 + 12 files changed, 578 insertions(+), 1277 deletions(-) delete mode 100644 reflection-node/src/network.rs delete mode 100644 reflection-node/src/node_inner.rs delete mode 100644 reflection-node/src/operation.rs delete mode 100644 reflection-node/src/operation_store.rs delete mode 100644 reflection-node/src/persistent_operation.rs create mode 100644 reflection-node/src/subscription.rs delete mode 100644 reflection-node/src/subscription_inner.rs delete mode 100644 reflection-node/src/topic.rs create mode 100644 reflection-node/src/traits.rs diff --git a/reflection-node/src/lib.rs b/reflection-node/src/lib.rs index d1cddf8..6236477 100644 --- a/reflection-node/src/lib.rs +++ b/reflection-node/src/lib.rs @@ -1,17 +1,10 @@ mod author_tracker; -mod ephemerial_operation; -mod network; +mod database; +mod message; pub mod node; -mod node_inner; -mod operation; -mod operation_store; -mod subscription_inner; -pub mod topic; +pub mod subscription; mod topic_store; -mod utils; - -pub use p2panda_core; -pub use topic::SubscribableTopic; +pub mod traits; #[cfg(test)] mod tests { diff --git a/reflection-node/src/network.rs b/reflection-node/src/network.rs deleted file mode 100644 index c9aee3f..0000000 --- a/reflection-node/src/network.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::sync::LazyLock; - -use p2panda_net::Discovery; -use p2panda_net::discovery::DiscoveryError; -use thiserror::Error; -use tracing::error; - -use p2panda_core::Hash; -use p2panda_core::PrivateKey; -use p2panda_net::address_book::{AddressBook, AddressBookError}; -use p2panda_net::addrs::NodeInfo; -use p2panda_net::gossip::{Gossip, GossipError}; -use p2panda_net::iroh_endpoint::{Endpoint, EndpointAddr, EndpointError, RelayUrl}; -use p2panda_net::iroh_mdns::{MdnsDiscovery, MdnsDiscoveryError, MdnsDiscoveryMode}; - -use crate::operation::ReflectionExtensions; -use crate::operation_store::OperationStore; -use crate::topic_store::{LogId, TopicStore}; - -static RELAY_URL: LazyLock = LazyLock::new(|| { - "https://euc1-1.relay.n0.iroh-canary.iroh.link" - .parse() - .expect("valid relay URL") -}); - -static BOOTSTRAP_NODE: LazyLock = LazyLock::new(|| { - let endpoint_addr = EndpointAddr::new( - "9f63a15ab95959a992af96bf72fbc3e7dc98eeb4799f788bb07b20125053e795" - .parse() - .expect("valid bootstrap node id"), - ) - .with_relay_url(RELAY_URL.clone()); - NodeInfo::from(endpoint_addr).bootstrap() -}); - -pub type LogSync = p2panda_net::sync::LogSync< - p2panda_store::SqliteStore, - LogId, - ReflectionExtensions, - TopicStore, ->; -pub type LogSyncError = p2panda_net::sync::LogSyncError; - -#[derive(Error, Debug)] -pub enum NetworkError { - #[error(transparent)] - Gossip(#[from] GossipError), - #[error(transparent)] - LogSync(#[from] LogSyncError), - #[error(transparent)] - AddressBook(#[from] AddressBookError), - #[error(transparent)] - MdnsDiscovery(#[from] MdnsDiscoveryError), - #[error(transparent)] - Discovery(#[from] DiscoveryError), - #[error(transparent)] - Endpoint(#[from] EndpointError), -} - -#[allow(dead_code)] -pub struct Network { - pub(crate) mdns_discovery: MdnsDiscovery, - pub(crate) discovery: Discovery, - pub(crate) gossip: Gossip, - pub(crate) log_sync: LogSync, - pub(crate) endpoint: Endpoint, -} - -// FIXME: Endpoint, LogSync, MdnsDiscovery, and Gossip should implement debug -impl std::fmt::Debug for Network { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Network").finish() - } -} - -impl Network { - pub async fn new( - private_key: &PrivateKey, - network_id: &Hash, - topic_store: &TopicStore, - operation_store: &OperationStore, - ) -> Result { - let address_book = AddressBook::builder().spawn().await?; - - if cfg!(not(any(test, feature = "test_utils"))) - && let Err(error) = address_book.insert_node_info(BOOTSTRAP_NODE.clone()).await - { - error!("Failed to add bootstrap node to the address book: {error}"); - } - - let mut builder = Endpoint::builder(address_book.clone()) - .network_id(network_id.into()) - .private_key(private_key.clone()); - - if cfg!(not(any(test, feature = "test_utils"))) { - builder = builder.relay_url(RELAY_URL.clone()); - } - - let endpoint = builder.spawn().await?; - - let mdns_discovery = MdnsDiscovery::builder(address_book.clone(), endpoint.clone()) - .mode(MdnsDiscoveryMode::Active) - .spawn() - .await?; - - let discovery = Discovery::builder(address_book.clone(), endpoint.clone()) - .spawn() - .await?; - - let gossip = Gossip::builder(address_book.clone(), endpoint.clone()) - .spawn() - .await?; - - let log_sync = LogSync::builder( - operation_store.clone_inner(), - topic_store.clone(), - endpoint.clone(), - gossip.clone(), - ) - .spawn() - .await?; - - Ok(Network { - mdns_discovery, - discovery, - gossip, - log_sync, - endpoint, - }) - } -} diff --git a/reflection-node/src/node.rs b/reflection-node/src/node.rs index d1b2702..1519191 100644 --- a/reflection-node/src/node.rs +++ b/reflection-node/src/node.rs @@ -1,30 +1,47 @@ -use std::path::Path; -use std::sync::Arc; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, LazyLock}; use chrono::{DateTime, Utc}; -use p2panda_core::{Hash, PrivateKey}; -use p2panda_net::TopicId; +use p2panda::node::SpawnError; +use p2panda::{NetworkId, RelayUrl, SigningKey, VerifyingKey}; use thiserror::Error; +use tokio::sync::{Notify, RwLock}; use tracing::info; -use crate::network::NetworkError; -use crate::node_inner::NodeInner; -use crate::topic::{SubscribableTopic, Subscription, TopicError}; +use crate::database::{database_pool, run_migrations}; +use crate::subscription::{Subscription, SubscriptionError, SubscriptionInner}; pub use crate::topic_store::Author; -use crate::topic_store::StoreTopic; +use crate::topic_store::{StoreTopic, TopicStore}; +use crate::traits::SubscribableTopic; + +static RELAY_URL: LazyLock = LazyLock::new(|| { + "https://euc1-1.relay.n0.iroh-canary.iroh.link" + .parse() + .expect("valid relay URL") +}); + +static BOOTSTRAP_NODE_ID: LazyLock = LazyLock::new(|| { + "9f63a15ab95959a992af96bf72fbc3e7dc98eeb4799f788bb07b20125053e795" + .parse() + .expect("valid bootstrap node id") +}); #[derive(Debug, Error)] pub enum NodeError { #[error(transparent)] RuntimeStartup(#[from] std::io::Error), + #[error(transparent)] RuntimeSpawn(#[from] tokio::task::JoinError), + #[error(transparent)] Datebase(#[from] sqlx::Error), + #[error(transparent)] DatebaseMigration(#[from] sqlx::migrate::MigrateError), + #[error(transparent)] - Network(#[from] NetworkError), + NodeSpawn(#[from] SpawnError), } #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] @@ -36,8 +53,8 @@ pub enum ConnectionMode { } #[derive(Clone, Debug)] -pub struct Topic { - pub id: ID, +pub struct Topic { + pub id: p2panda_core::Topic, pub name: Option, pub last_accessed: Option>, pub authors: Vec, @@ -82,10 +99,14 @@ impl Node { ) }; - let db_file = db_location.map(|location| location.join("database.sqlite")); - let inner = runtime - .spawn(async move { NodeInner::new(network_id, private_key, db_file).await }) - .await??; + let inner = { + let network_id = network_id.into(); + let db_file = db_location.map(|location| location.join("database.sqlite")); + + runtime + .spawn(NodeInner::new(signing_key, network_id, db_file)) + .await?? + }; Ok(Self { inner: Arc::new(inner), @@ -97,30 +118,30 @@ impl Node { &self, connection_mode: ConnectionMode, ) -> Result<(), NodeError> { - let inner_clone = self.inner.clone(); + let inner = self.inner.clone(); self.runtime - .spawn(async move { inner_clone.set_connection_mode(connection_mode).await }) + .spawn(async move { inner.set_connection_mode(connection_mode).await }) .await??; Ok(()) } pub async fn shutdown(&self) -> Result<(), NodeError> { - let inner_clone = self.inner.clone(); + let inner = self.inner.clone(); self.runtime .spawn(async move { - inner_clone.shutdown().await; + inner.shutdown().await; }) .await?; Ok(()) } - pub async fn topics>(&self) -> Result>, TopicError> { - let inner_clone = self.inner.clone(); + pub async fn topics(&self) -> Result, SubscriptionError> { + let inner = self.inner.clone(); let topics = self .runtime - .spawn(async move { inner_clone.topic_store.topics().await }) + .spawn(async move { inner.topic_store.topics().await }) .await??; let topics = topics @@ -133,7 +154,7 @@ impl Node { authors, } = topic; Topic { - id: id.into(), + id, name, last_accessed, authors, @@ -144,30 +165,123 @@ impl Node { Ok(topics) } - pub async fn subscribe, T: SubscribableTopic + 'static>( + pub async fn subscribe( &self, - id: ID, - topic_handle: T, - ) -> Result, TopicError> { - let id: TopicId = id.into(); - let topic_handle = Arc::new(topic_handle); - let inner_clone = self.inner.clone(); + id: impl Into, + subscribable_topic: T, + ) -> Result, SubscriptionError> + where + T: SubscribableTopic + 'static, + { + let id = id.into(); + let subscribable_topic = Arc::new(subscribable_topic); + let inner = self.inner.clone(); let inner_subscription = self .runtime - .spawn(async move { inner_clone.subscribe(id, topic_handle).await }) + .spawn(async move { inner.subscribe(id, subscribable_topic).await }) .await??; let subscription = Subscription::new(self.runtime.clone(), inner_subscription).await; - info!("Subscribed to topic {}", hex::encode(id)); + info!(%id, "subscribed to topic"); Ok(subscription) } - pub async fn delete_topic>(&self, id: ID) -> Result<(), TopicError> { - let id: TopicId = id.into(); - let inner_clone = self.inner.clone(); + pub async fn delete_topic( + &self, + id: impl Into, + ) -> Result<(), SubscriptionError> { + let id = id.into(); + let inner = self.inner.clone(); self.runtime - .spawn(async move { inner_clone.delete_topic(id).await }) + .spawn(async move { inner.delete_topic(id).await }) .await? } } + +#[derive(Debug)] +pub(crate) struct NodeInner { + pub(crate) network: RwLock, + pub(crate) shutdown_notifier: Notify, + pub(crate) topic_store: TopicStore, + pub(crate) verifying_key: VerifyingKey, +} + +impl NodeInner { + pub async fn new( + signing_key: SigningKey, + network_id: impl Into, + db_file: Option, + ) -> Result { + let verifying_key = signing_key.verifying_key(); + + let pool = database_pool(db_file).await?; + run_migrations(&pool).await?; + + let topic_store = TopicStore::from_pool(pool.clone()); + + let mut builder = p2panda::Node::builder() + .network_id(network_id.into()) + .signing_key(signing_key) + .database_pool(pool); + + // Don't connect to any servers during testing. + if cfg!(not(any(test, feature = "test_utils"))) { + builder = builder + .bootstrap(*BOOTSTRAP_NODE_ID, RELAY_URL.clone()) + .relay_url(RELAY_URL.clone()); + } + + let node = builder.spawn().await?; + + Ok(Self { + network: RwLock::new(node), + shutdown_notifier: Notify::new(), + topic_store, + verifying_key, + }) + } + + pub async fn set_connection_mode( + &self, + _connection_mode: ConnectionMode, + ) -> Result<(), NodeError> { + // TODO: This is a no-op currently and requires work in `p2panda-net` upstream. + // See related issue: https://github.com/p2panda/p2panda/issues/1093 + Ok(()) + } + + pub async fn shutdown(&self) { + // Wake up all subscriptions that may still exist. + self.shutdown_notifier.notify_waiters(); + } + + pub async fn subscribe( + self: Arc, + id: impl Into, + subscribable_topic: Arc, + ) -> Result, SubscriptionError> + where + T: SubscribableTopic + 'static, + { + let id = id.into(); + + self.topic_store.add_topic(&id).await?; + + // Add ourselves as an author to the topic store. + self.topic_store + .add_author(&id, &self.verifying_key) + .await?; + + Ok(SubscriptionInner::new(self.clone(), id, subscribable_topic)) + } + + pub async fn delete_topic( + self: Arc, + id: impl Into, + ) -> Result<(), SubscriptionError> { + let id = id.into(); + self.topic_store.delete_topic(&id).await?; + Ok(()) + } +} diff --git a/reflection-node/src/node_inner.rs b/reflection-node/src/node_inner.rs deleted file mode 100644 index ba951f8..0000000 --- a/reflection-node/src/node_inner.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::path::PathBuf; -use std::sync::Arc; - -use crate::ephemerial_operation::EphemerialOperation; -use crate::network::{Network, NetworkError}; -use crate::node::{ConnectionMode, NodeError}; -use crate::operation_store::OperationStore; -use crate::subscription_inner::SubscriptionInner; -use crate::topic::{SubscribableTopic, TopicError}; -use crate::topic_store::TopicStore; -use crate::utils::CombinedMigrationSource; - -use p2panda_core::{Hash, PrivateKey}; -use p2panda_net::TopicId; -use p2panda_store::sqlite::store::migrations as operation_store_migrations; -use sqlx::{migrate::Migrator, sqlite}; -use tokio::sync::{Notify, RwLock}; -use tracing::info; - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub(crate) enum MessageType { - Ephemeral(EphemerialOperation), - AuthorEphemeral(EphemerialOperation), -} - -#[derive(Debug)] -pub struct NodeInner { - pub(crate) operation_store: OperationStore, - pub(crate) topic_store: TopicStore, - pub(crate) private_key: PrivateKey, - pub(crate) network_id: Hash, - pub(crate) network: RwLock>, - pub(crate) network_notifier: Notify, -} - -impl NodeInner { - pub async fn new( - network_id: Hash, - private_key: PrivateKey, - db_file: Option, - ) -> Result { - let connection_options = sqlx::sqlite::SqliteConnectOptions::new() - .shared_cache(true) - .create_if_missing(true); - let pool = if let Some(db_file) = db_file { - info!("Database file location: {db_file:?}"); - let connection_options = connection_options.filename(db_file); - sqlx::sqlite::SqlitePool::connect_with(connection_options).await? - } else { - let connection_options = connection_options.in_memory(true); - // FIXME: we need to set max connection to 1 for in memory sqlite DB. - // Probably has to do something with this issue: https://github.com/launchbadge/sqlx/issues/2510 - let pool_options = sqlite::SqlitePoolOptions::new().max_connections(1); - pool_options.connect_with(connection_options).await? - }; - - // Run migration for p2panda OperationStore and for the our TopicStore - Migrator::new(CombinedMigrationSource::new(vec![ - operation_store_migrations(), - sqlx::migrate!(), - ])) - .await? - .run(&pool) - .await?; - - let operation_store = OperationStore::new(pool.clone()); - let topic_store = TopicStore::new(pool); - - Ok(Self { - operation_store, - topic_store, - private_key, - network_id, - network: RwLock::new(None), - network_notifier: Notify::new(), - }) - } - - pub async fn set_connection_mode( - &self, - connection_mode: ConnectionMode, - ) -> Result<(), NetworkError> { - // Subscriptions will tear down the network subscription and drop the read lock, - // so that we can acquire the write lock and then shutdown the network. - self.network_notifier.notify_waiters(); - - let mut network_guard = self.network.write().await; - - match connection_mode { - ConnectionMode::None => { - *network_guard = None; - } - ConnectionMode::Bluetooth => { - unimplemented!("Bluetooth is currently not implemented") - } - ConnectionMode::Network => { - let network = Network::new( - &self.private_key, - &self.network_id, - &self.topic_store, - &self.operation_store, - ) - .await?; - - *network_guard = Some(network); - } - } - - Ok(()) - } - - pub async fn shutdown(&self) { - // Wake up all subscriptions that may still exist - self.network_notifier.notify_waiters(); - self.network.write().await.take(); - } - - pub async fn subscribe( - self: Arc, - id: TopicId, - subscribable_topic: Arc, - ) -> Result, TopicError> { - self.topic_store.add_topic(&id).await?; - // Add ourselves as an author to the topic store. - self.topic_store - .add_author(&id, &self.private_key.public_key()) - .await?; - let stored_operations = self - .topic_store - .operations_for_topic(&self.operation_store, &id) - .await?; - - for operation in stored_operations { - // Send all stored operation bytes to the app, - // it doesn't matter if the app already knows some or all of them - if let Some(body) = operation.body { - subscribable_topic.bytes_received(operation.header.public_key, body.to_bytes()); - } - } - - Ok(SubscriptionInner::new(self.clone(), id, subscribable_topic)) - } - - pub async fn delete_topic(self: Arc, id: TopicId) -> Result<(), TopicError> { - self.topic_store.delete_topic(&id).await?; - Ok(()) - } -} diff --git a/reflection-node/src/operation.rs b/reflection-node/src/operation.rs deleted file mode 100644 index beb5c99..0000000 --- a/reflection-node/src/operation.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::hash::Hash as StdHash; - -use p2panda_core::{Extension, Header, PruneFlag}; -use p2panda_net::TopicId; -use serde::{Deserialize, Serialize}; - -use crate::topic_store::LogId; - -/// Custom extensions for p2panda header. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct ReflectionExtensions { - /// If flag is true we can remove all previous operations in this log. - /// - /// This usually indicates that a "snapshot" has been inserted into the body of this operation, - /// containing all required state to reconstruct the full version including all previous edits - /// of this topic. - /// - /// In our case of a text-editor, this would be the encoded payload of a state-based CRDT. - #[serde( - rename = "p", - skip_serializing_if = "PruneFlag::is_not_set", - default = "PruneFlag::default" - )] - pub prune_flag: PruneFlag, - - /// Operations can be organised in separate logs. With a "log id" we can declare where this - /// operation belongs to. - /// - /// We organise two logs per author per topic, one for "short lived" / ephemeral deltas - /// (small text changes) and one for persisted snapshots (full topic history). These are two - /// distinct "log types". - #[serde(rename = "t")] - pub log_type: LogType, - - /// Identifier of the topic this operation relates to. - #[serde(rename = "d")] - pub topic: TopicId, -} - -#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, StdHash, Serialize, Deserialize)] -pub enum LogType { - Snapshot, - #[default] - Delta, -} - -impl Extension for ReflectionExtensions { - fn extract(header: &Header) -> Option { - Some(header.extensions.prune_flag.clone()) - } -} - -impl Extension for ReflectionExtensions { - fn extract(header: &Header) -> Option { - Some(header.extensions.log_type) - } -} - -impl Extension for ReflectionExtensions { - fn extract(header: &Header) -> Option { - Some(header.extensions.topic) - } -} - -impl Extension for ReflectionExtensions { - fn extract(header: &Header) -> Option { - let log_type: LogType = header.extension()?; - let id: TopicId = header.extension()?; - - Some(LogId::new(log_type, &id)) - } -} diff --git a/reflection-node/src/operation_store.rs b/reflection-node/src/operation_store.rs deleted file mode 100644 index 44b7219..0000000 --- a/reflection-node/src/operation_store.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::sync::Arc; -use std::time::{SystemTime, SystemTimeError}; - -use crate::topic_store::LogId; -use p2panda_core::{Body, Header, Operation, PrivateKey, PruneFlag}; -use p2panda_net::TopicId; -use p2panda_store::{ - LogStore, OperationStore as TraitOperationStore, SqliteStore, SqliteStoreError, -}; -use thiserror::Error; -use tokio::sync::Semaphore; - -use crate::operation::{LogType, ReflectionExtensions}; - -#[derive(Debug, Error)] -pub enum CreationError { - #[error(transparent)] - SytemTime(#[from] SystemTimeError), - #[error(transparent)] - Store(#[from] SqliteStoreError), -} - -#[derive(Debug)] -pub struct OperationStore { - inner: SqliteStore, - // FIXME: This makes sure we only create one operation at the time and not in parallel - // Since we would mess up the sequence of operations - semaphore_operation_store: Arc, -} - -impl OperationStore { - pub fn new(pool: sqlx::SqlitePool) -> Self { - Self { - inner: SqliteStore::new(pool), - semaphore_operation_store: Arc::new(Semaphore::new(1)), - } - } - - pub fn clone_inner(&self) -> SqliteStore { - self.inner.clone() - } - - pub fn inner(&self) -> &SqliteStore { - &self.inner - } - - /// Creates, signs and stores new operation in the author's append-only log. - /// - /// If no topic is specified we create a new operation in a new log. The resulting hash of the - /// header can be used to identify that new topic. - pub async fn create_operation( - &self, - private_key: &PrivateKey, - log_type: LogType, - topic: TopicId, - body: Option<&[u8]>, - prune_flag: bool, - ) -> Result, CreationError> { - let _permit = self - .semaphore_operation_store - .acquire() - .await - .expect("OperationStore semaphore not to be closed"); - - let body = body.map(Body::new); - let public_key = private_key.public_key(); - - let log_id = LogId::new(log_type, &topic); - let latest_operation = self.inner.latest_operation(&public_key, &log_id).await?; - - let (seq_num, backlink) = match latest_operation { - Some((header, _)) => (header.seq_num + 1, Some(header.hash())), - None => (0, None), - }; - - let timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH)? - .as_secs(); - - let extensions = ReflectionExtensions { - prune_flag: PruneFlag::new(prune_flag), - log_type, - topic, - }; - - let mut header = Header { - version: 1, - public_key, - signature: None, - payload_size: body.as_ref().map_or(0, |body| body.size()), - payload_hash: body.as_ref().map(|body| body.hash()), - timestamp, - seq_num, - backlink, - previous: vec![], - extensions, - }; - header.sign(private_key); - - let operation = Operation { - hash: header.hash(), - header, - body, - }; - - let mut inner_clone = self.clone_inner(); - inner_clone - .insert_operation( - operation.hash, - &operation.header, - operation.body.as_ref(), - operation.header.to_bytes().as_slice(), - &log_id, - ) - .await?; - - if prune_flag { - inner_clone - .delete_operations( - &operation.header.public_key, - &log_id, - operation.header.seq_num, - ) - .await?; - } - - Ok(operation) - } -} diff --git a/reflection-node/src/persistent_operation.rs b/reflection-node/src/persistent_operation.rs deleted file mode 100644 index dd90a21..0000000 --- a/reflection-node/src/persistent_operation.rs +++ /dev/null @@ -1,64 +0,0 @@ -use p2panda_core::{ - Body, Header, Operation, - cbor::{DecodeError, decode_cbor}, -}; -use thiserror::Error; - -use crate::topic::TopicId; -use crate::operation::ReflectionExtensions; - -type OperationWithRawHeader = (Header, Option, Vec); - -#[derive(Debug, Error)] -pub enum UnpackError { - #[error(transparent)] - Cbor(#[from] DecodeError), - #[error("Operation with invalid topic id")] - InvalidTopicId, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct PersistentOperation { - #[serde(with = "serde_bytes")] - header: Vec, - body: Option, -} - -impl PersistentOperation { - pub fn new(operation: Operation) -> Self { - Self { - header: operation.header.to_bytes(), - body: operation.body.map(|body| body.to_bytes().into()), - } - } - - pub fn from_serialized(header: Vec, body: Option>) -> Self { - Self { - header, - body: body.map(Into::into), - } - } - - /// Validates and unpacks the operation - pub fn validate_and_unpack( - self, - id: TopicId, - ) -> Result { - let PersistentOperation { header, body } = self; - - // The header is serialized by Header::to_bytes() as cbor - let header_deserialized: Header = decode_cbor(&header[..])?; - let body_deserialized = body.map(|body| Body::from(body.into_vec())); - - let Some(operation_id): Option = header_deserialized.extension() - else { - return Err(UnpackError::InvalidTopicId); - }; - - if operation_id != id { - return Err(UnpackError::InvalidTopicId); - } - - Ok((header_deserialized, body_deserialized, header)) - } -} diff --git a/reflection-node/src/subscription.rs b/reflection-node/src/subscription.rs new file mode 100644 index 0000000..68a4b05 --- /dev/null +++ b/reflection-node/src/subscription.rs @@ -0,0 +1,397 @@ +use std::mem::take; +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +use chrono::Utc; +use p2panda::node::CreateStreamError; +use p2panda::streams::{EphemeralStreamPublisher, StreamEvent, StreamFrom, StreamPublisher}; +use p2panda_core::Topic; +use thiserror::Error; +use tokio::sync::{RwLock, oneshot}; +use tokio::task::{AbortHandle, JoinError}; +use tokio_stream::StreamExt; +use tracing::{error, info}; + +use crate::author_tracker::AuthorTracker; +use crate::message::EphemeralMessage; +use crate::node::NodeInner; +use crate::traits::SubscribableTopic; + +#[derive(Debug, Error)] +pub enum SubscriptionError { + #[error(transparent)] + Runtime(#[from] JoinError), + + #[error(transparent)] + TopicStore(#[from] sqlx::Error), + + #[error(transparent)] + StreamPublish(#[from] p2panda::streams::PublishError), + + #[error(transparent)] + EphemeralStreamPublish(#[from] p2panda::streams::EphemeralPublishError), + + #[error("streams to publish data into network are not available due to a setup error")] + BrokenStream, +} + +pub struct Subscription { + inner: Arc>, + runtime: tokio::runtime::Handle, + network_monitor_task: AbortHandle, +} + +impl Drop for Subscription { + fn drop(&mut self) { + self.network_monitor_task.abort(); + } +} + +impl Subscription +where + T: SubscribableTopic + 'static, +{ + pub(crate) async fn new(runtime: tokio::runtime::Handle, inner: SubscriptionInner) -> Self { + let (ready_tx, ready_rx) = oneshot::channel(); + + // Spawn task to establish streams to publish and subscribe to messages, the same task will + // also await a shutdown signal to drop the streams. + let inner = Arc::new(inner); + let inner_clone = inner.clone(); + let network_monitor_task = runtime + .spawn(async move { + inner_clone.spawn_network_monitor(ready_tx).await; + }) + .abort_handle(); + + // Wait until streams with network have been established. + let _ = ready_rx.await; + + Subscription { + inner, + runtime, + network_monitor_task, + } + } + + pub async fn publish_delta(&self, data: Vec) -> Result<(), SubscriptionError> { + let inner = self.inner.clone(); + self.runtime + .spawn(async move { inner.publish_delta(data).await }) + .await? + } + + pub async fn publish_snapshot(&self, data: Vec) -> Result<(), SubscriptionError> { + let inner = self.inner.clone(); + self.runtime + .spawn(async move { inner.publish_snapshot(data).await }) + .await? + } + + pub async fn publish_ephemeral(&self, data: Vec) -> Result<(), SubscriptionError> { + let inner = self.inner.clone(); + self.runtime + .spawn(async move { inner.publish_ephemeral(data).await }) + .await? + } + + pub async fn unsubscribe(self) -> Result<(), SubscriptionError> { + self.network_monitor_task.abort(); + + let inner = self.inner.clone(); + self.runtime + .spawn(async move { inner.unsubscribe().await }) + .await??; + + info!("unsubscribed from topic {}", self.inner.id); + + Ok(()) + } + + /// Set the name for a given topic. + /// + /// This information will be written to the database. + pub async fn set_name(&self, name: Option) -> Result<(), SubscriptionError> { + let inner = self.inner.clone(); + self.runtime + .spawn(async move { inner.set_name(name).await }) + .await? + } +} + +pub(crate) struct SubscriptionInner { + tx: RwLock>>>, + ephemeral_tx: RwLock>>, + node: Arc, + id: Topic, + subscribable_topic: Arc, + author_tracker: Arc>, + abort_handles: RwLock>, +} + +impl Drop for SubscriptionInner { + fn drop(&mut self) { + for handle in self.abort_handles.get_mut() { + handle.abort(); + } + } +} + +impl SubscriptionInner +where + T: SubscribableTopic + 'static, +{ + pub fn new(node: Arc, id: Topic, subscribable_topic: Arc) -> Self { + let author_tracker = AuthorTracker::new(node.clone(), subscribable_topic.clone()); + + SubscriptionInner { + tx: RwLock::new(None), + ephemeral_tx: RwLock::new(None), + node, + id, + abort_handles: RwLock::new(Vec::new()), + subscribable_topic, + author_tracker, + } + } + + pub async fn spawn_network_monitor(&self, ready_signal: oneshot::Sender<()>) { + // Hold a read lock to the network, so that the network won't be dropped or shutdown. + let network_guard = self.node.network.read().await; + + let result = setup_streams( + &self.node, + network_guard.deref(), + self.id, + &self.subscribable_topic, + &self.author_tracker, + ) + .await; + + match result { + Ok((tx, ephemeral_tx, abort_handles)) => { + *self.tx.write().await = Some(tx); + *self.ephemeral_tx.write().await = Some(ephemeral_tx); + *self.abort_handles.write().await = abort_handles; + } + Err(error) => { + self.subscribable_topic.error(error.into()); + } + } + + drop(network_guard); + + // Inform caller that we're done with setting up the streams. They are ready now to be used + // for publishing and receiving messages. + let _ = ready_signal.send(()); + + // Wait until we've received signal from node to shut down. + let shutdown_notification = self.node.shutdown_notifier.notified(); + shutdown_notification.await; + + let _ = self.unsubscribe().await; + } + + pub async fn unsubscribe(&self) -> Result<(), SubscriptionError> { + let mut tx_guard = self.tx.write().await; + let mut ephemeral_tx_guard = self.ephemeral_tx.write().await; + let mut abort_handles_guard = self.abort_handles.write().await; + + let tx = take(tx_guard.deref_mut()); + let ephemeral_tx = take(ephemeral_tx_guard.deref_mut()); + let abort_handles = take(abort_handles_guard.deref_mut()); + + self.node + .topic_store + .set_last_accessed_for_topic(&self.id, Some(Utc::now())) + .await?; + + teardown_streams( + &self.id, + &self.author_tracker, + tx, + ephemeral_tx, + abort_handles, + ) + .await; + + Ok(()) + } + + pub async fn publish_delta(&self, data: Vec) -> Result<(), SubscriptionError> { + if let Some(tx) = self.tx.read().await.as_ref() { + info!("delta operation sent for topic with id {}", self.id); + tx.publish(data).await?; + } else { + return Err(SubscriptionError::BrokenStream); + } + + Ok(()) + } + + pub async fn publish_snapshot(&self, data: Vec) -> Result<(), SubscriptionError> { + if let Some(tx) = self.tx.read().await.as_ref() { + info!("snapshot saved for topic with id {}", self.id); + + // Append an operation to our log and set the prune flag to true. This will remove + // previous entries. + tx.prune(Some(data)).await?; + } else { + return Err(SubscriptionError::BrokenStream); + } + + Ok(()) + } + + pub async fn publish_ephemeral(&self, data: Vec) -> Result<(), SubscriptionError> { + if let Some(ephemeral_tx) = self.ephemeral_tx.read().await.as_ref() { + ephemeral_tx + .publish(EphemeralMessage::Application(data)) + .await?; + } else { + return Err(SubscriptionError::BrokenStream); + } + + Ok(()) + } + + pub async fn set_name(&self, name: Option) -> Result<(), SubscriptionError> { + self.node + .topic_store + .set_name_for_topic(&self.id, name) + .await?; + + Ok(()) + } +} + +async fn setup_streams( + node: &Arc, + network: &p2panda::Node, + id: Topic, + subscribable_topic: &Arc, + author_tracker: &Arc>, +) -> Result< + ( + StreamPublisher>, + EphemeralStreamPublisher, + Vec, + ), + CreateStreamError, +> +where + T: SubscribableTopic + 'static, +{ + let mut abort_handles = Vec::with_capacity(3); + + // 1. Handle incoming operations from eventually consistent topic stream. + + // Always start from re-playing _all_ operations in the beginning. This is due to Reflection not + // keeping materialised document state around and we need to repeat materialising the document + // at the beginning (in memory). + // + // This cost is acceptable since we're frequently pruning the log and the number of operations + // to process is rather small. + let stream_from = StreamFrom::Start; + + let (topic_tx, mut topic_rx) = network.stream_from::>(id, stream_from).await?; + + let node_clone = node.clone(); + let subscribable_topic_clone = subscribable_topic.clone(); + let abort_handle = tokio::spawn(async move { + while let Some(event) = topic_rx.next().await { + match event { + StreamEvent::Processed { operation, .. } => { + let author = operation.author(); + + // When we discover a new author we need to add them to our topic store. + if let Err(error) = node_clone.topic_store.add_author(&id, &author).await { + error!("can't store author to database: {error}"); + } + + // Forward the message payload up to the app layer. + subscribable_topic_clone.bytes_received(author, operation.message().to_owned()); + } + StreamEvent::DecodeFailed { error, .. } => { + error!("failed decoding incoming operation from stream: {error}"); + } + StreamEvent::ReplayFailed { error, .. } => { + error!("error occurred while replaying operation stream: {error}"); + } + StreamEvent::SyncStarted { .. } | StreamEvent::SyncEnded { .. } => { + // TODO: Handle sync events. + } + _ => (), + } + } + }) + .abort_handle(); + + abort_handles.push(abort_handle); + + // 2. Handle incoming messages from ephemeral topic stream. + + let (ephemeral_tx, mut ephemeral_rx) = network.ephemeral_stream::(id).await?; + + author_tracker + .set_topic_tx(Some(ephemeral_tx.clone())) + .await; + + let author_tracker_clone = author_tracker.clone(); + let subscribable_topic_clone = subscribable_topic.clone(); + let abort_handle = tokio::spawn(async move { + while let Some(message) = ephemeral_rx.next().await { + match message.body() { + EphemeralMessage::Application(bytes) => { + subscribable_topic_clone + .ephemeral_bytes_received(message.author(), bytes.to_owned()); + } + EphemeralMessage::AuthorTracker(tracker) => { + author_tracker_clone + .received(message.author(), tracker.to_owned()) + .await; + } + } + } + }) + .abort_handle(); + + abort_handles.push(abort_handle); + + // 3. Run task to track online status of authors. + + let author_tracker_clone = author_tracker.clone(); + let abort_handle = tokio::spawn(async move { + author_tracker_clone.spawn().await; + }) + .abort_handle(); + + abort_handles.push(abort_handle); + + info!("network streams set up for topic {}", id); + + Ok((topic_tx, ephemeral_tx, abort_handles)) +} + +async fn teardown_streams( + id: &Topic, + author_tracker: &Arc>, + tx: Option>>, + ephemeral_tx: Option>, + abort_handles: Vec, +) where + T: SubscribableTopic + 'static, +{ + for handle in abort_handles { + handle.abort(); + } + + author_tracker.set_topic_tx(None).await; + + if tx.is_some() { + info!("network streams torn down for topic {}", id); + } + + drop(tx); + drop(ephemeral_tx); +} diff --git a/reflection-node/src/subscription_inner.rs b/reflection-node/src/subscription_inner.rs deleted file mode 100644 index 931f2c7..0000000 --- a/reflection-node/src/subscription_inner.rs +++ /dev/null @@ -1,469 +0,0 @@ -use std::mem::take; -use std::ops::{Deref, DerefMut, Drop}; -use std::sync::Arc; - -use chrono::Utc; -use p2panda_core::Operation; -use p2panda_core::{ - Body, Header, - cbor::{decode_cbor, encode_cbor}, -}; -use p2panda_net::{TopicId, gossip::GossipHandle}; -use p2panda_stream::IngestExt; -use p2panda_sync::protocols::TopicLogSyncEvent as Event; -use tokio::{ - sync::{RwLock, mpsc}, - task::{AbortHandle, spawn}, -}; -use tokio_stream::{StreamExt, wrappers::ReceiverStream}; -use tracing::{error, info, warn}; - -use crate::author_tracker::{AuthorMessage, AuthorTracker}; -use crate::ephemerial_operation::EphemerialOperation; -use crate::network::Network; -use crate::node_inner::MessageType; -use crate::node_inner::NodeInner; -use crate::operation::{LogType, ReflectionExtensions}; -use crate::topic::{SubscribableTopic, SubscriptionError, TopicError}; - -pub type SyncHandle = - p2panda_net::sync::SyncHandle, Event>; - -pub struct SubscriptionInner { - ephemeral_tx: RwLock>, - tx: RwLock>, - pub(crate) node: Arc, - pub(crate) id: TopicId, - pub(crate) subscribable_topic: Arc, - author_tracker: Arc>, - abort_handles: RwLock>, -} - -impl Drop for SubscriptionInner { - fn drop(&mut self) { - for handle in self.abort_handles.get_mut() { - handle.abort(); - } - } -} - -impl SubscriptionInner { - pub fn new(node: Arc, id: TopicId, subscribable_topic: Arc) -> Self { - let author_tracker = AuthorTracker::new(node.clone(), subscribable_topic.clone()); - SubscriptionInner { - tx: RwLock::new(None), - ephemeral_tx: RwLock::new(None), - node, - id, - abort_handles: RwLock::new(Vec::new()), - subscribable_topic, - author_tracker, - } - } - - pub async fn spawn_network_monitor(&self) { - // We need to hold a read lock to the network, so that the network won't be dropped - // or shutdown. - let mut notify = Some(self.node.network_notifier.notified()); - let mut network_guard = Some(self.node.network.read().await); - - let (tx, ephemeral_tx, abort_handles) = - if let Some(network) = network_guard.as_ref().unwrap().deref() { - match setup_network( - &self.node, - network, - self.id, - &self.subscribable_topic, - &self.author_tracker, - ) - .await - { - Ok((sync_handle, gossip_handle, abort_handles)) => { - (Some(sync_handle), Some(gossip_handle), abort_handles) - } - Err(error) => { - self.subscribable_topic.error(error); - (None, None, Vec::new()) - } - } - } else { - (None, None, Vec::new()) - }; - - *self.tx.write().await = tx; - *self.ephemeral_tx.write().await = ephemeral_tx; - *self.abort_handles.write().await = abort_handles; - - loop { - if let Some(notify) = notify { - notify.await; - } - - let mut abort_handles_guard = self.abort_handles.write().await; - let mut tx_guard = self.tx.write().await; - let mut ephemeral_tx_guard = self.ephemeral_tx.write().await; - - let old_tx = take(tx_guard.deref_mut()); - let old_ephemeral_tx = take(ephemeral_tx_guard.deref_mut()); - let old_abort_handles = take(abort_handles_guard.deref_mut()); - - teardown_network( - &self.id, - &self.author_tracker, - old_tx, - old_ephemeral_tx, - old_abort_handles, - ) - .await; - // Release network lock and get a new one, so that the network can be change between them - network_guard.take(); - notify = Some(self.node.network_notifier.notified()); - network_guard = Some(self.node.network.read().await); - - let (tx, ephemeral_tx, abort_handles) = - if let Some(network) = network_guard.as_ref().unwrap().deref() { - match setup_network( - &self.node, - network, - self.id, - &self.subscribable_topic, - &self.author_tracker, - ) - .await - { - Ok((sync_handle, gossip_handle, abort_handles)) => { - (Some(sync_handle), Some(gossip_handle), abort_handles) - } - Err(error) => { - self.subscribable_topic.error(error); - (None, None, Vec::new()) - } - } - } else { - (None, None, Vec::new()) - }; - - *tx_guard = tx; - *ephemeral_tx_guard = ephemeral_tx; - *abort_handles_guard = abort_handles; - } - } - - pub async fn unsubscribe(&self) -> Result<(), TopicError> { - let mut tx_guard = self.tx.write().await; - let mut ephemeral_tx_guard = self.ephemeral_tx.write().await; - let mut abort_handles_guard = self.abort_handles.write().await; - - let tx = take(tx_guard.deref_mut()); - let ephemeral_tx = take(ephemeral_tx_guard.deref_mut()); - let abort_handles = take(abort_handles_guard.deref_mut()); - - self.node - .topic_store - .set_last_accessed_for_topic(&self.id, Some(Utc::now())) - .await?; - - teardown_network( - &self.id, - &self.author_tracker, - tx, - ephemeral_tx, - abort_handles, - ) - .await; - - Ok(()) - } - - pub async fn send_delta(&self, data: Vec) -> Result<(), TopicError> { - let operation = - // Append one operation to our "ephemeral" delta log. - self.node.operation_store - .create_operation( - &self.node.private_key, - LogType::Delta, - self.id, - Some(&data), - false, - ) - .await?; - - info!( - "Delta operation sent for topic with id {}", - hex::encode(self.id) - ); - - if let Some(tx) = self.tx.read().await.as_ref() { - tx.publish(operation).await?; - } - - Ok(()) - } - - pub async fn send_snapshot(&self, data: Vec) -> Result<(), TopicError> { - // Append an operation to our "snapshot" log and set the prune flag to - // true. This will remove previous snapshots. - // - // Snapshots are not broadcasted on the gossip overlay as they would be - // too large. Peers will sync them up when they join the topic. - self.node - .operation_store - .create_operation( - &self.node.private_key, - LogType::Snapshot, - self.id, - Some(&data), - true, - ) - .await?; - - // Append an operation to our "ephemeral" delta log and set the prune - // flag to true. - // - // This signals removing all previous "delta" operations now. This is - // some sort of garbage collection whenever we snapshot. Snapshots - // already contain all history, there is no need to keep duplicate - // "delta" data around. - let operation = self - .node - .operation_store - .create_operation(&self.node.private_key, LogType::Delta, self.id, None, true) - .await?; - - info!("Snapshot saved for topic with id {}", hex::encode(self.id)); - - if let Some(tx) = self.tx.read().await.as_ref() { - tx.publish(operation).await?; - } - - Ok(()) - } - - pub async fn send_ephemeral(&self, data: Vec) -> Result<(), TopicError> { - if let Some(ephemeral_tx) = self.ephemeral_tx.read().await.as_ref() { - let operation = EphemerialOperation::new(data, &self.node.private_key); - let bytes = encode_cbor(&MessageType::Ephemeral(operation))?; - ephemeral_tx.publish(bytes).await?; - } - - Ok(()) - } - - /// Set the name for a given topic - /// - /// This information will be written to the database - pub async fn set_name(&self, name: Option) -> Result<(), TopicError> { - self.node - .topic_store - .set_name_for_topic(&self.id, name) - .await?; - - Ok(()) - } -} - -async fn setup_network( - node: &Arc, - network: &Network, - id: TopicId, - subscribable_topic: &Arc, - author_tracker: &Arc>, -) -> Result<(SyncHandle, GossipHandle, Vec), SubscriptionError> { - let mut abort_handles = Vec::with_capacity(3); - - let stream = network.log_sync.stream(id, true).await?; - let mut topic_rx = stream.subscribe().await?; - let topic_tx = stream; - - let (persistent_tx, persistent_rx) = - mpsc::channel::<(Header, Option, Vec)>(128); - - let abort_handle = spawn(async move { - while let Some(event) = topic_rx.next().await { - let event = match event { - Ok(event) => event, - Err(error) => { - error!("Error while receiving sync message: {error}"); - continue; - } - }; - match event.event() { - Event::Operation(operation) => { - match validate_and_unpack(operation.as_ref().to_owned(), id) { - Ok(data) => { - persistent_tx.send(data).await.unwrap(); - } - Err(err) => { - error!("Failed to unpack operation: {err}"); - } - } - } - _ => { - // TODO: Handle sync events - } - } - } - }) - .abort_handle(); - - abort_handles.push(abort_handle); - - let ephemeral_stream = network.gossip.stream(id).await?; - let mut ephemeral_rx = ephemeral_stream.subscribe(); - let ephemeral_tx = ephemeral_stream; - - author_tracker.set_topic_tx(Some(ephemeral_tx)).await; - - let author_tracker_clone = author_tracker.clone(); - let subscribable_topic_clone = subscribable_topic.clone(); - let abort_handle = spawn(async move { - while let Some(bytes) = ephemeral_rx.next().await { - let bytes = match bytes { - Ok(bytes) => bytes, - Err(error) => { - error!("Error while receiving ephemeral message: {error}"); - continue; - } - }; - match decode_cbor(&bytes[..]) { - Ok(MessageType::Ephemeral(operation)) => { - if let Some((author, body)) = operation.validate_and_unpack() { - subscribable_topic_clone.ephemeral_bytes_received(author, body); - } else { - warn!("Got ephemeral operation with a bad signature"); - } - } - Ok(MessageType::AuthorEphemeral(operation)) => { - if let Some((author, body)) = operation.validate_and_unpack() { - match AuthorMessage::try_from(&body[..]) { - Ok(message) => { - author_tracker_clone.received(message, author).await; - } - Err(error) => { - warn!("Failed to deserialize AuthorMessage: {error}"); - } - } - } else { - warn!("Got internal ephemeral operation with a bad signature"); - } - } - Err(err) => { - error!("Failed to decode gossip message: {err}"); - } - } - } - }) - .abort_handle(); - - abort_handles.push(abort_handle); - - let stream = ReceiverStream::new(persistent_rx); - - // Ingest does multiple things for us: - // - // - Validate operation- and log integrity and authenticity - // - De-duplicate already known operations - // - Out-of-order buffering - // - Pruning when flag is set - // - Persist operation in store - let mut stream = stream - // NOTE(adz): The persisting part should happen later, we want to check the payload on - // application layer first. In general "ingest" does too much at once and is - // inflexible. Related issue: https://github.com/p2panda/p2panda/issues/696 - .ingest(node.operation_store.clone_inner(), 128) - .filter_map(|result| match result { - Ok(operation) => Some(operation), - Err(err) => { - error!("ingesting operation failed: {err}"); - None - } - }); - - let node = node.clone(); - let subscribable_topic_clone = subscribable_topic.clone(); - // Send checked and ingested operations for this topic to application layer. - let abort_handle = spawn(async move { - while let Some(operation) = stream.next().await { - // When we discover a new author we need to add them to our topic store. - if let Err(error) = node - .topic_store - .add_author(&id, &operation.header.public_key) - .await - { - error!("Can't store author to database: {error}"); - } - - // Forward the payload up to the app. - if let Some(body) = operation.body { - subscribable_topic_clone - .bytes_received(operation.header.public_key, body.to_bytes()); - } - } - }) - .abort_handle(); - - abort_handles.push(abort_handle); - let author_tracker_clone = author_tracker.clone(); - let abort_handle = spawn(async move { - author_tracker_clone.spawn().await; - }) - .abort_handle(); - - abort_handles.push(abort_handle); - - info!("Network subscription set up for topic {}", hex::encode(id)); - - let ephemeral_tx = network.gossip.stream(id).await?; - - Ok((topic_tx, ephemeral_tx, abort_handles)) -} - -async fn teardown_network( - id: &TopicId, - author_tracker: &Arc>, - tx: Option, - ephemeral_tx: Option, - abort_handles: Vec, -) { - for handle in abort_handles { - handle.abort(); - } - - author_tracker.set_topic_tx(None).await; - - if tx.is_some() { - info!( - "Network subscription torn down for topic {}", - hex::encode(id) - ); - } - drop(tx); - drop(ephemeral_tx); -} - -type OperationWithRawHeader = (Header, Option, Vec); - -#[derive(Debug, thiserror::Error)] -pub enum UnpackError { - #[error(transparent)] - Cbor(#[from] p2panda_core::cbor::DecodeError), - #[error("Operation with invalid topic id")] - InvalidTopicId, -} - -fn validate_and_unpack( - operation: p2panda_core::Operation, - id: TopicId, -) -> Result { - let p2panda_core::Operation:: { header, body, .. } = operation; - - let Some(operation_id): Option = header.extension() else { - return Err(UnpackError::InvalidTopicId); - }; - - if operation_id != id { - return Err(UnpackError::InvalidTopicId); - } - - Ok((header.clone(), body, header.to_bytes())) -} diff --git a/reflection-node/src/topic.rs b/reflection-node/src/topic.rs deleted file mode 100644 index 20a6a4f..0000000 --- a/reflection-node/src/topic.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::sync::Arc; - -use crate::operation::ReflectionExtensions; -use crate::operation_store::CreationError; - -use crate::network::LogSyncError; -use crate::subscription_inner::SubscriptionInner; -use p2panda_core::{Operation, PublicKey}; -use p2panda_sync::protocols::TopicLogSyncEvent; - -use p2panda_net::gossip::GossipError; -use thiserror::Error; -use tokio::sync::mpsc; -use tokio::task::{AbortHandle, JoinError}; -use tracing::info; - -pub type SyncHandleError = p2panda_net::sync::SyncHandleError< - Operation, - TopicLogSyncEvent, ->; - -#[derive(Debug, Error)] -pub enum TopicError { - #[error(transparent)] - TopicStore(#[from] sqlx::Error), - #[error(transparent)] - OperationStore(#[from] CreationError), - #[error(transparent)] - Encode(#[from] p2panda_core::cbor::EncodeError), - #[error(transparent)] - Publish(#[from] SyncHandleError), - #[error(transparent)] - PublishEphemeral(#[from] mpsc::error::SendError>), - #[error(transparent)] - Runtime(#[from] JoinError), -} - -#[derive(Debug, Error)] -pub enum SubscriptionError { - #[error(transparent)] - Gossip(#[from] GossipError), - #[error(transparent)] - LogSync(#[from] LogSyncError), - #[error(transparent)] - SyncHandle(#[from] SyncHandleError), -} - -pub trait SubscribableTopic: Sync + Send { - fn bytes_received(&self, author: PublicKey, data: Vec); - fn author_joined(&self, author: PublicKey); - fn author_left(&self, author: PublicKey); - fn ephemeral_bytes_received(&self, author: PublicKey, data: Vec); - fn error(&self, error: SubscriptionError); -} - -pub struct Subscription { - pub(crate) inner: Arc>, - pub(crate) runtime: tokio::runtime::Handle, - network_monitor_task: AbortHandle, -} - -impl Drop for Subscription { - fn drop(&mut self) { - self.network_monitor_task.abort(); - } -} - -impl Subscription { - pub(crate) async fn new(runtime: tokio::runtime::Handle, inner: SubscriptionInner) -> Self { - let inner = Arc::new(inner); - - let inner_clone = inner.clone(); - let network_monitor_task = runtime - .spawn(async move { - inner_clone.spawn_network_monitor().await; - }) - .abort_handle(); - - Subscription { - inner, - runtime, - network_monitor_task, - } - } - - pub async fn send_delta(&self, data: Vec) -> Result<(), TopicError> { - let inner = self.inner.clone(); - self.runtime - .spawn(async move { inner.send_delta(data).await }) - .await? - } - - pub async fn send_snapshot(&self, data: Vec) -> Result<(), TopicError> { - let inner = self.inner.clone(); - self.runtime - .spawn(async move { inner.send_snapshot(data).await }) - .await? - } - - pub async fn send_ephemeral(&self, data: Vec) -> Result<(), TopicError> { - let inner = self.inner.clone(); - self.runtime - .spawn(async move { inner.send_ephemeral(data).await }) - .await? - } - - pub async fn unsubscribe(self) -> Result<(), TopicError> { - let id = self.inner.id; - - self.network_monitor_task.abort(); - let inner = self.inner.clone(); - self.runtime - .spawn(async move { inner.unsubscribe().await }) - .await??; - - info!("Unsubscribed from topic {}", hex::encode(id)); - - Ok(()) - } - - /// Set the name for a given topic - /// - /// This information will be written to the database - pub async fn set_name(&self, name: Option) -> Result<(), TopicError> { - let inner = self.inner.clone(); - self.runtime - .spawn(async move { inner.set_name(name).await }) - .await? - } -} diff --git a/reflection-node/src/topic_store.rs b/reflection-node/src/topic_store.rs index b757abf..38e0931 100644 --- a/reflection-node/src/topic_store.rs +++ b/reflection-node/src/topic_store.rs @@ -1,23 +1,13 @@ use std::collections::HashMap; -use std::hash::Hash as StdHash; use chrono::{DateTime, Utc}; -use p2panda_core::PublicKey; -use p2panda_net::TopicId; -use p2panda_store::LogStore; -use p2panda_sync::protocols::Logs; -use p2panda_sync::traits::TopicMap; -use serde::{Deserialize, Serialize}; +use p2panda_core::{VerifyingKey, Topic}; use sqlx::{FromRow, Row}; -use tracing::error; - -use crate::operation::{LogType, ReflectionExtensions}; -use crate::operation_store::OperationStore; #[derive(Debug, FromRow)] pub struct StoreTopic { #[sqlx(try_from = "Vec")] - pub id: TopicId, + pub id: Topic, #[sqlx(default)] pub name: Option, pub last_accessed: Option>, @@ -148,7 +138,11 @@ impl TopicStore { Ok(()) } - pub async fn set_name_for_topic(&self, id: &TopicId, name: Option) -> sqlx::Result<()> { + pub async fn set_name_for_topic( + &self, + topic: &Topic, + name: Option, + ) -> sqlx::Result<()> { sqlx::query( " UPDATE topics @@ -157,7 +151,7 @@ impl TopicStore { ", ) .bind(name) - .bind(id.as_slice()) + .bind(topic.as_bytes().as_slice()) .execute(&self.pool) .await?; @@ -166,7 +160,7 @@ impl TopicStore { pub async fn set_last_accessed_for_topic( &self, - id: &TopicId, + topic: &Topic, last_accessed: Option>, ) -> sqlx::Result<()> { sqlx::query( @@ -177,81 +171,10 @@ impl TopicStore { ", ) .bind(last_accessed) - .bind(id.as_slice()) + .bind(topic.as_bytes().as_slice()) .execute(&self.pool) .await?; Ok(()) } - - pub async fn operations_for_topic( - &self, - operation_store: &OperationStore, - id: &TopicId, - ) -> sqlx::Result>> { - let operation_store = operation_store.inner(); - let authors = self.authors(id).await?; - - let log_ids = [ - LogId::new(LogType::Delta, id), - LogId::new(LogType::Snapshot, id), - ]; - - let mut result = Vec::new(); - - for author in authors.iter() { - for log_id in &log_ids { - let operations = match operation_store.get_log(author, log_id, None).await { - Ok(Some(operations)) => { - operations - .into_iter() - .map(|(header, body)| p2panda_core::Operation { - hash: header.hash(), - header, - body, - }) - } - Ok(None) => { - continue; - } - Err(error) => { - error!( - "Failed to load operation for {author} with log type {log_id:?}: {error}" - ); - continue; - } - }; - - result.extend(operations); - } - } - - Ok(result) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, StdHash, Serialize, Deserialize)] -pub struct LogId(LogType, TopicId); - -impl LogId { - pub fn new(log_type: LogType, topic: &TopicId) -> Self { - Self(log_type, *topic) - } -} - -impl TopicMap> for TopicStore { - type Error = sqlx::Error; - - async fn get(&self, topic: &TopicId) -> Result, Self::Error> { - let authors = self.authors(topic).await?; - - let log_ids = [ - LogId::new(LogType::Delta, topic), - LogId::new(LogType::Snapshot, topic), - ]; - Ok(authors - .into_iter() - .map(|author| (author, log_ids.to_vec())) - .collect()) - } } diff --git a/reflection-node/src/traits.rs b/reflection-node/src/traits.rs new file mode 100644 index 0000000..68398d3 --- /dev/null +++ b/reflection-node/src/traits.rs @@ -0,0 +1,17 @@ +use p2panda::node::CreateStreamError; +use p2panda_core::VerifyingKey; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SubscriptionError { + #[error(transparent)] + CreateStream(#[from] CreateStreamError), +} + +pub trait SubscribableTopic: Sync + Send { + fn bytes_received(&self, author: VerifyingKey, data: Vec); + fn author_joined(&self, author: VerifyingKey); + fn author_left(&self, author: VerifyingKey); + fn ephemeral_bytes_received(&self, author: VerifyingKey, data: Vec); + fn error(&self, error: SubscriptionError); +} From 605ff31c8618037cb5bce1f7edc0e3b8ce37a638 Mon Sep 17 00:00:00 2001 From: adz Date: Wed, 18 Mar 2026 16:57:40 +0100 Subject: [PATCH 03/15] node: Refactor AuthorTracker for new Node API --- reflection-node/src/author_tracker.rs | 95 ++++++++------------- reflection-node/src/ephemerial_operation.rs | 34 -------- reflection-node/src/message.rs | 15 ++++ 3 files changed, 52 insertions(+), 92 deletions(-) delete mode 100644 reflection-node/src/ephemerial_operation.rs create mode 100644 reflection-node/src/message.rs diff --git a/reflection-node/src/author_tracker.rs b/reflection-node/src/author_tracker.rs index f665f2b..9d223ab 100644 --- a/reflection-node/src/author_tracker.rs +++ b/reflection-node/src/author_tracker.rs @@ -1,53 +1,44 @@ use std::collections::HashMap; use std::ops::DerefMut; use std::sync::Arc; -use std::time::{Duration, Instant, SystemTime}; +use std::time::{Duration, Instant}; -use crate::ephemerial_operation::EphemerialOperation; -use crate::node_inner::MessageType; -use crate::node_inner::NodeInner; -use crate::topic::SubscribableTopic; use chrono::Utc; -use p2panda_core::cbor::{DecodeError, decode_cbor, encode_cbor}; -use p2panda_core::{PrivateKey, PublicKey}; -use p2panda_net::gossip::GossipHandle; +use p2panda::streams::EphemeralStreamPublisher; +use p2panda_core::VerifyingKey; +use serde::{Deserialize, Serialize}; use tokio::sync::{Mutex, RwLock}; -use tracing::error; +use tracing::{error, warn}; + +use crate::message::EphemeralMessage; +use crate::node::NodeInner; +use crate::traits::SubscribableTopic; const OFFLINE_TIMEOUT: Duration = Duration::from_secs(60); -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub enum AuthorMessage { +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum AuthorTrackerMessage { Hello, Ping, Bye, } -impl std::fmt::Display for AuthorMessage { +impl std::fmt::Display for AuthorTrackerMessage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - AuthorMessage::Hello => write!(f, "Hello message"), - AuthorMessage::Ping => write!(f, "Ping message"), - AuthorMessage::Bye => write!(f, "Bye message"), + AuthorTrackerMessage::Hello => write!(f, "Hello message"), + AuthorTrackerMessage::Ping => write!(f, "Ping message"), + AuthorTrackerMessage::Bye => write!(f, "Bye message"), } } } -impl TryFrom<&[u8]> for AuthorMessage { - type Error = DecodeError; - - fn try_from(value: &[u8]) -> Result { - let (res, _): (AuthorMessage, SystemTime) = decode_cbor(value)?; - - Ok(res) - } -} - pub struct AuthorTracker { - last_ping: Mutex>, + last_ping: Mutex>, subscribable_topic: Arc, node: Arc, - tx: RwLock>, + tx: RwLock>>, } impl AuthorTracker { @@ -60,14 +51,16 @@ impl AuthorTracker { }) } - pub async fn set_topic_tx(&self, tx: Option) { + pub async fn set_topic_tx(&self, tx: Option>) { let mut tx_guard = self.tx.write().await; + // Send good bye message to the network if let Some(tx) = tx_guard.as_ref() { - send_message(&self.node.private_key, tx, AuthorMessage::Bye).await; + send_message(tx, AuthorTrackerMessage::Bye).await; } - // Set all authors that the tracker has seen to offline, authors the tracker hasn't seen are already offline + // Set all authors that the tracker has seen to offline, authors the tracker hasn't seen + // are already offline let old_authors = std::mem::take(self.last_ping.lock().await.deref_mut()); for author in old_authors.into_keys() { self.subscribable_topic.author_left(author); @@ -87,21 +80,21 @@ impl AuthorTracker { pub async fn received(&self, message: AuthorMessage, author: PublicKey) { match message { - AuthorMessage::Hello => { + AuthorTrackerMessage::Hello => { self.join(author).await; } - AuthorMessage::Ping => { + AuthorTrackerMessage::Ping => { self.ping(author).await; } - AuthorMessage::Bye => { + AuthorTrackerMessage::Bye => { self.left(author).await; } } } - async fn send(&self, message: AuthorMessage) { + async fn send(&self, message: AuthorTrackerMessage) { if let Some(tx) = self.tx.read().await.as_ref() { - send_message(&self.node.private_key, tx, message).await; + send_message(tx, message).await; } } @@ -112,7 +105,7 @@ impl AuthorTracker { // Send a ping to the network to ensure that the new author knows we exist // Normally we send a ping every `OFFLINE_TIMEOUT / 2` - self.send(AuthorMessage::Ping).await; + self.send(AuthorTrackerMessage::Ping).await; } async fn ping(&self, author: PublicKey) { @@ -133,7 +126,7 @@ impl AuthorTracker { pub async fn spawn(&self) { // Send a hello to the network so other authors know we joined the topic - self.send(AuthorMessage::Hello).await; + self.send(AuthorTrackerMessage::Hello).await; let mut interval = tokio::time::interval(OFFLINE_TIMEOUT / 2); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); @@ -144,7 +137,7 @@ impl AuthorTracker { interval.tick().await; // Send a ping to the network so that we won't be marked as offline - self.send(AuthorMessage::Ping).await; + self.send(AuthorTrackerMessage::Ping).await; let mut expired = Vec::new(); self.last_ping.lock().await.retain(|author, instant| { if instant.elapsed() > OFFLINE_TIMEOUT { @@ -174,25 +167,11 @@ impl AuthorTracker { } } -async fn send_message(private_key: &PrivateKey, tx: &GossipHandle, message: AuthorMessage) { - // FIXME: We need to add the current time to the message, - // because iroh doesn't broadcast twice the same message message. - let author_message = match encode_cbor(&(&message, SystemTime::now())) { - Ok(result) => result, - Err(error) => { - error!("Failed to encode {message} as CBOR: {error}"); - return; - } - }; - let operation = EphemerialOperation::new(author_message, private_key); - let bytes = match encode_cbor(&MessageType::AuthorEphemeral(operation)) { - Ok(result) => result, - Err(error) => { - error!("Failed to encode {message} as CBOR: {error}"); - return; - } - }; - if let Err(error) = tx.publish(bytes).await { - error!("Failed to sent {message} to the network: {error}"); +async fn send_message( + tx: &EphemeralStreamPublisher, + message: AuthorTrackerMessage, +) { + if let Err(err) = tx.publish(EphemeralMessage::AuthorTracker(message)).await { + warn!("error occurred when sending ephemeral message: {err}"); } } diff --git a/reflection-node/src/ephemerial_operation.rs b/reflection-node/src/ephemerial_operation.rs deleted file mode 100644 index 1fd6aa3..0000000 --- a/reflection-node/src/ephemerial_operation.rs +++ /dev/null @@ -1,34 +0,0 @@ -use p2panda_core::identity::{PrivateKey, PublicKey, Signature}; - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct EphemerialOperation { - #[serde(with = "serde_bytes")] - body: Vec, - author: PublicKey, - signature: Signature, -} - -impl EphemerialOperation { - pub fn new(body: Vec, author: &PrivateKey) -> Self { - Self { - signature: author.sign(&body), - body, - author: author.public_key(), - } - } - - /// Validates the signature and unpacks the operation - pub fn validate_and_unpack(self) -> Option<(PublicKey, Vec)> { - let EphemerialOperation { - body, - author, - signature, - } = self; - - if self.author.verify(&body[..], &signature) { - Some((author, body)) - } else { - None - } - } -} diff --git a/reflection-node/src/message.rs b/reflection-node/src/message.rs new file mode 100644 index 0000000..ed32603 --- /dev/null +++ b/reflection-node/src/message.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +use crate::author_tracker::AuthorTrackerMessage; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "t", content = "c")] +pub(crate) enum EphemeralMessage { + /// Custom message to be forwarded to the application-layer. + #[serde(rename = "app")] + Application(Vec), + + /// Message used to track online status of authors. + #[serde(rename = "author_tracker")] + AuthorTracker(AuthorTrackerMessage), +} From ea4306bdb11c5844a830f33ec948382209353ca7 Mon Sep 17 00:00:00 2001 From: adz Date: Wed, 18 Mar 2026 16:57:40 +0100 Subject: [PATCH 04/15] node: Move sqlx methods from utils to database mod --- reflection-node/src/database.rs | 73 +++++++++++++++++++++++++++++++++ reflection-node/src/utils.rs | 33 --------------- 2 files changed, 73 insertions(+), 33 deletions(-) create mode 100644 reflection-node/src/database.rs delete mode 100644 reflection-node/src/utils.rs diff --git a/reflection-node/src/database.rs b/reflection-node/src/database.rs new file mode 100644 index 0000000..ef55390 --- /dev/null +++ b/reflection-node/src/database.rs @@ -0,0 +1,73 @@ +use std::path::PathBuf; +use std::pin::Pin; + +use p2panda_store::sqlite::migrations as p2panda_migrations; +use sqlx::error::BoxDynError; +use sqlx::migrate::{Migration, MigrationSource, Migrator}; +use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions}; +use tracing::info; + +/// Establishes a SQLite connection pool. +/// +/// If no database path is given the database is created in memory. +pub async fn database_pool(db_file: Option) -> Result { + let connection_options = SqliteConnectOptions::new() + .shared_cache(true) + .create_if_missing(true); + + let pool = if let Some(db_file) = db_file { + info!("database file location: {db_file:?}"); + let connection_options = connection_options.filename(db_file); + SqlitePool::connect_with(connection_options).await? + } else { + let connection_options = connection_options.in_memory(true); + // FIXME: we need to set max connection to 1 for in memory sqlite DB. Probably has to + // do something with this issue: https://github.com/launchbadge/sqlx/issues/2510 + let pool_options = SqlitePoolOptions::new().max_connections(1); + pool_options.connect_with(connection_options).await? + }; + + Ok(pool) +} + +/// Run migration for p2panda and for the our TopicStore. +pub async fn run_migrations(pool: &SqlitePool) -> Result<(), sqlx::migrate::MigrateError> { + Migrator::new(CombinedMigrationSource::new(vec![ + p2panda_migrations(), + sqlx::migrate!(), + ])) + .await? + .run(pool) + .await?; + + Ok(()) +} + +type BoxFuture<'a, T> = Pin + Send + 'a>>; + +/// Combine multiple `sqlx::migrate::Migrator` into a single `sqlx::migrate::MigrationSource` +/// +/// See for more details: https://github.com/launchbadge/sqlx/discussions/3407 +#[derive(Debug)] +pub struct CombinedMigrationSource { + migrators: Vec, +} + +impl CombinedMigrationSource { + pub fn new(migrators: Vec) -> CombinedMigrationSource { + Self { migrators } + } +} + +impl<'s> MigrationSource<'s> for CombinedMigrationSource { + fn resolve(self) -> BoxFuture<'s, Result, BoxDynError>> { + Box::pin(async move { + Ok(self + .migrators + .iter() + .flat_map(|migrator| migrator.iter()) + .cloned() + .collect()) + }) + } +} diff --git a/reflection-node/src/utils.rs b/reflection-node/src/utils.rs deleted file mode 100644 index a298933..0000000 --- a/reflection-node/src/utils.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::pin::Pin; - -use sqlx::error::BoxDynError; -use sqlx::migrate::{Migration, MigrationSource, Migrator}; - -type BoxFuture<'a, T> = Pin + Send + 'a>>; - -/// Combine multiple `sqlx::migrate::Migrator` into a single `sqlx::migrate::MigrationSource` -/// -/// See for more details: https://github.com/launchbadge/sqlx/discussions/3407 -#[derive(Debug)] -pub struct CombinedMigrationSource { - migrators: Vec, -} - -impl CombinedMigrationSource { - pub fn new(migrators: Vec) -> CombinedMigrationSource { - Self { migrators } - } -} - -impl<'s> MigrationSource<'s> for CombinedMigrationSource { - fn resolve(self) -> BoxFuture<'s, Result, BoxDynError>> { - Box::pin(async move { - Ok(self - .migrators - .iter() - .flat_map(|migrator| migrator.iter()) - .cloned() - .collect()) - }) - } -} From 30af9428eebe8dc262d6464e3dbd1db774d9bf9a Mon Sep 17 00:00:00 2001 From: adz Date: Wed, 18 Mar 2026 16:57:40 +0100 Subject: [PATCH 05/15] core: Rename types after changes in p2panda-core --- reflection-app/src/application.rs | 12 ++-- reflection-app/src/secret.rs | 42 +++++------ reflection-doc/src/author.rs | 26 +++---- reflection-doc/src/authors.rs | 16 ++--- reflection-doc/src/document.rs | 24 +++---- reflection-doc/src/documents.rs | 12 ++-- reflection-doc/src/lib.rs | 70 +++++++++---------- reflection-doc/src/service.rs | 27 +++---- .../20250418140035_create_tables.sql | 19 ++--- ...840_rename_public_key_to_verifying_key.sql | 2 + reflection-node/src/author_tracker.rs | 12 ++-- reflection-node/src/lib.rs | 29 ++++---- reflection-node/src/node.rs | 4 +- reflection-node/src/topic_store.rs | 56 +++++++-------- 14 files changed, 171 insertions(+), 180 deletions(-) create mode 100644 reflection-node/migrations/20260521094840_rename_public_key_to_verifying_key.sql diff --git a/reflection-app/src/application.rs b/reflection-app/src/application.rs index 92dcb67..e31a354 100644 --- a/reflection-app/src/application.rs +++ b/reflection-app/src/application.rs @@ -22,7 +22,7 @@ use adw::prelude::*; use adw::subclass::prelude::*; use gettextrs::gettext; use gtk::{gdk, gio, glib, glib::Properties, glib::clone}; -use reflection_doc::{document::DocumentId, identity::PrivateKey, service::Service}; +use reflection_doc::{document::DocumentId, identity::SigningKey, service::Service}; use std::{cell::RefCell, fs}; use thiserror::Error; use tracing::error; @@ -258,15 +258,15 @@ impl ReflectionApplication { } async fn create_service(&self) -> Result { - let private_key = secret::get_or_create_identity().await?; + let signing_key = secret::get_or_create_identity().await?; let mut data_path = glib::user_data_dir(); data_path.push("Reflection"); - data_path.push(private_key.public_key().to_string()); + data_path.push(signing_key.verifying_key().to_string()); fs::create_dir_all(&data_path)?; let data_dir = gio::File::for_path(data_path); - let service = Service::new(&private_key, Some(&data_dir)); + let service = Service::new(&signing_key, Some(&data_dir)); service.startup().await?; Ok(service) @@ -406,8 +406,8 @@ impl ReflectionApplication { } async fn new_temporary_identity(&self) { - let private_key = PrivateKey::new(); - let service = Service::new(&private_key, None); + let signing_key = SigningKey::generate(); + let service = Service::new(&signing_key, None); if let Err(error) = service.startup().await { let error = error.into(); diff --git a/reflection-app/src/secret.rs b/reflection-app/src/secret.rs index 26b6222..0e6a836 100644 --- a/reflection-app/src/secret.rs +++ b/reflection-app/src/secret.rs @@ -23,7 +23,7 @@ use tracing::info; #[cfg(target_os = "linux")] use crate::APP_ID; -use reflection_doc::identity::{IdentityError, PrivateKey}; +use reflection_doc::identity::{IdentityError, SigningKey}; #[cfg(target_os = "linux")] const XDG_SCHEMA: &str = "xdg:schema"; @@ -52,60 +52,60 @@ pub enum Error { } #[cfg(target_os = "linux")] -pub async fn get_or_create_identity() -> Result { +pub async fn get_or_create_identity() -> Result { let keyring = oo7::Keyring::new().await?; keyring.unlock().await?; - let private_key: PrivateKey = + let signing_key: SigningKey = if let Some(item) = keyring.search_items(&attributes()).await?.first() { item.unlock().await?; - let private_key = PrivateKey::try_from(item.secret().await?.as_bytes())?; - info!("Found existing identity: {}", private_key.public_key()); + let signing_key = SigningKey::try_from(item.secret().await?.as_bytes())?; + info!("Found existing identity: {}", signing_key.verifying_key()); - private_key + signing_key } else { - let private_key = PrivateKey::new(); + let signing_key = SigningKey::generate(); keyring - .create_item("Reflection", &attributes(), private_key.as_bytes(), true) + .create_item("Reflection", &attributes(), signing_key.as_bytes(), true) .await?; info!( "No existing identity found. Create new identity: {}", - private_key.public_key() + signing_key.verifying_key() ); - private_key + signing_key }; - Ok(private_key) + Ok(signing_key) } #[cfg(target_os = "macos")] -pub async fn get_or_create_identity() -> Result { +pub async fn get_or_create_identity() -> Result { let entry = keyring::Entry::new("Reflection Identity", "default user")?; - let private_key: PrivateKey = match entry.get_password() { + let signing_key: SigningKey = match entry.get_password() { Ok(password) => { - let private_key = PrivateKey::try_from( + let signing_key = SigningKey::try_from( Base64Engine .decode(password) .expect("Failed to decode base64 secret from keyring") .as_slice(), )?; - info!("Found existing identity: {}", private_key.public_key()); - private_key + info!("Found existing identity: {}", signing_key.verifying_key()); + signing_key } Err(keyring::Error::NoEntry) => { - let private_key = PrivateKey::new(); - entry.set_password(&Base64Engine.encode(private_key.as_bytes()))?; + let signing_key = SigningKey::generate(); + entry.set_password(&Base64Engine.encode(signing_key.as_bytes()))?; info!( "No existing identity found. Create new identity: {}", - private_key.public_key() + signing_key.verifying_key() ); - private_key + signing_key } Err(e) => return Err(Error::Service(e)), }; - Ok(private_key) + Ok(signing_key) } diff --git a/reflection-doc/src/author.rs b/reflection-doc/src/author.rs index ae4b80a..ccaed65 100644 --- a/reflection-doc/src/author.rs +++ b/reflection-doc/src/author.rs @@ -5,7 +5,7 @@ use glib::Properties; use glib::prelude::*; use glib::subclass::prelude::*; -use crate::identity::PublicKey; +use crate::identity::VerifyingKey; pub const COLORS: [(&str, &str); 14] = [ ("Yellow", "#faf387"), @@ -78,8 +78,8 @@ mod imp { #[property(name = "emoji", get = Self::emoji, type = String)] #[property(name = "color", get = Self::color, type = String)] #[property(name = "hex-color", get = Self::hex_color, type = String)] - #[property(get, set, construct_only, type = PublicKey)] - public_key: OnceLock, + #[property(get, set, construct_only, type = VerifyingKey)] + verifying_key: OnceLock, #[property(get, set, construct_only)] pub last_seen: Mutex>, #[property(get, default = true)] @@ -100,7 +100,7 @@ mod imp { impl Author { fn name(&self) -> String { - let bytes = self.public_key.get().unwrap().as_bytes(); + let bytes = self.verifying_key.get().unwrap().as_bytes(); let selector_color = bytes[..(bytes.len() / 2)] .iter() .fold(0u8, |acc, b| acc ^ b) as usize @@ -113,7 +113,7 @@ mod imp { } fn emoji(&self) -> String { - let bytes = self.public_key.get().unwrap().as_bytes(); + let bytes = self.verifying_key.get().unwrap().as_bytes(); let selector_emoji = bytes[(bytes.len() / 2)..] .iter() .fold(0u8, |acc, b| acc ^ b) as usize @@ -122,7 +122,7 @@ mod imp { } fn color(&self) -> String { - let bytes = self.public_key.get().unwrap().as_bytes(); + let bytes = self.verifying_key.get().unwrap().as_bytes(); let selector_color = bytes[..(bytes.len() / 2)] .iter() .fold(0u8, |acc, b| acc ^ b) as usize @@ -131,7 +131,7 @@ mod imp { } fn hex_color(&self) -> String { - let bytes = self.public_key.get().unwrap().as_bytes(); + let bytes = self.verifying_key.get().unwrap().as_bytes(); let selector_color = bytes[..(bytes.len() / 2)] .iter() .fold(0u8, |acc, b| acc ^ b) as usize @@ -145,24 +145,24 @@ glib::wrapper! { pub struct Author(ObjectSubclass); } impl Author { - pub(crate) fn new(public_key: &PublicKey) -> Self { + pub(crate) fn new(verifying_key: &VerifyingKey) -> Self { glib::Object::builder() - .property("public-key", public_key) + .property("verifying-key", verifying_key) .build() } - pub(crate) fn with_state(public_key: &PublicKey, last_seen: Option<&glib::DateTime>) -> Self { + pub(crate) fn with_state(verifying_key: &VerifyingKey, last_seen: Option<&glib::DateTime>) -> Self { glib::Object::builder() - .property("public-key", public_key) + .property("verifying-key", verifying_key) .property("last-seen", last_seen) .build() } pub(crate) fn for_this_device( - public_key: &PublicKey, + verifying_key: &VerifyingKey, last_seen: Option<&glib::DateTime>, ) -> Self { - let obj = Self::with_state(public_key, last_seen); + let obj = Self::with_state(verifying_key, last_seen); obj.imp().is_this_device.set(true); obj diff --git a/reflection-doc/src/authors.rs b/reflection-doc/src/authors.rs index 0f58a3b..f0d2df6 100644 --- a/reflection-doc/src/authors.rs +++ b/reflection-doc/src/authors.rs @@ -6,14 +6,14 @@ use glib::subclass::prelude::*; use indexmap::IndexMap; use crate::author::Author; -use crate::identity::PublicKey; +use crate::identity::VerifyingKey; mod imp { use super::*; #[derive(Default)] pub struct Authors { - pub(super) list: RwLock>, + pub(super) list: RwLock>, } #[glib::object_subclass] @@ -70,9 +70,9 @@ impl Authors { assert_eq!(list.len(), 1); for author in authors { - let public_key = author.public_key(); - if !list.contains_key(&public_key) { - list.insert(public_key, author); + let verifying_key = author.verifying_key(); + if !list.contains_key(&verifying_key) { + list.insert(verifying_key, author); } } @@ -80,7 +80,7 @@ impl Authors { self.items_changed(1, 0, authors_len as u32); } - pub(crate) fn add_this_device(&self, author_key: PublicKey) { + pub(crate) fn add_this_device(&self, author_key: VerifyingKey) { let mut list = self.imp().list.write().unwrap(); let now = glib::DateTime::now_utc().ok(); @@ -92,7 +92,7 @@ impl Authors { self.items_changed(0, 0, 1); } - pub(crate) fn add(&self, author_key: PublicKey) -> Author { + pub(crate) fn add(&self, author_key: VerifyingKey) -> Author { let mut list = self.imp().list.write().unwrap(); let entry = list.entry(author_key); let index = entry.index(); @@ -108,7 +108,7 @@ impl Authors { author } - pub(crate) fn author(&self, author_key: &PublicKey) -> Option { + pub(crate) fn author(&self, author_key: &VerifyingKey) -> Option { let list = self.imp().list.read().unwrap(); list.get(author_key).cloned() } diff --git a/reflection-doc/src/document.rs b/reflection-doc/src/document.rs index d758142..f1d0a54 100644 --- a/reflection-doc/src/document.rs +++ b/reflection-doc/src/document.rs @@ -18,7 +18,7 @@ use tracing::error; use crate::author::Author; use crate::authors::Authors; -use crate::identity::PublicKey; +use crate::identity::VerifyingKey; use crate::service::Service; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, glib::Boxed)] @@ -414,7 +414,7 @@ mod imp { } fn setup_loro_document(&self) { - let public_key = self.obj().service().private_key().public_key(); + let verifying_key = self.obj().service().signing_key().verifying_key(); let obj = self.obj(); let doc = LoroDoc::new(); // The peer id represents the identity of the author applying local changes (that's @@ -428,7 +428,7 @@ mod imp { // this should not really be a problem, but it would be nice if the Loro API would // change some day. let mut buf = [0u8; 8]; - buf[..8].copy_from_slice(&public_key.0.as_bytes()[..8]); + buf[..8].copy_from_slice(&verifying_key.0.as_bytes()[..8]); u64::from_be_bytes(buf) }) .expect("set peer id for new document"); @@ -688,7 +688,7 @@ mod imp { // Add ourself to the list of authors self.authors - .add_this_device(self.obj().service().private_key().public_key()); + .add_this_device(self.obj().service().signing_key().verifying_key()); } } } @@ -900,19 +900,19 @@ unsafe impl Sync for Document {} struct DocumentHandle(glib::WeakRef); impl SubscribableTopic for DocumentHandle { - fn bytes_received(&self, author: p2panda_core::PublicKey, data: Vec) { + fn bytes_received(&self, author: p2panda_core::VerifyingKey, data: Vec) { if let Some(document) = self.0.upgrade() { document.main_context().invoke(move || { document.imp().on_remote_message(data); - document.authors().add(PublicKey(author)); + document.authors().add(VerifyingKey(author)); }); } } - fn author_joined(&self, author: p2panda_core::PublicKey) { + fn author_joined(&self, author: p2panda_core::VerifyingKey) { if let Some(document) = self.0.upgrade() { document.main_context().invoke(move || { - let author = document.authors().add(PublicKey(author)); + let author = document.authors().add(VerifyingKey(author)); author.set_online(true); // When a new author joins we need to send ephemeral messages again document.imp().brodcast_ephemeral(); @@ -920,20 +920,20 @@ impl SubscribableTopic for DocumentHandle { } } - fn author_left(&self, author: p2panda_core::PublicKey) { + fn author_left(&self, author: p2panda_core::VerifyingKey) { if let Some(document) = self.0.upgrade() { document.main_context().invoke(move || { - let author = document.authors().add(PublicKey(author)); + let author = document.authors().add(VerifyingKey(author)); author.set_online(false); }); } } - fn ephemeral_bytes_received(&self, author: p2panda_core::PublicKey, data: Vec) { + fn ephemeral_bytes_received(&self, author: p2panda_core::VerifyingKey, data: Vec) { if let Some(document) = self.0.upgrade() { document.main_context().invoke(move || { if let Ok(data) = decode_cbor(&data[..]) - && let Some(author) = document.authors().author(&PublicKey(author)) + && let Some(author) = document.authors().author(&VerifyingKey(author)) { document.imp().handle_ephemeral_data(author, data); } diff --git a/reflection-doc/src/documents.rs b/reflection-doc/src/documents.rs index d9dc609..c321d1b 100644 --- a/reflection-doc/src/documents.rs +++ b/reflection-doc/src/documents.rs @@ -5,7 +5,7 @@ use gio::subclass::prelude::ListModelImpl; use glib::subclass::prelude::*; use indexmap::IndexMap; -use crate::identity::PublicKey; +use crate::identity::VerifyingKey; use crate::service::StartupError; use crate::{ author::Author, @@ -68,7 +68,7 @@ impl Documents { } pub(crate) async fn load(&self, service: &Service) -> Result<(), StartupError> { - let public_key = service.private_key().public_key(); + let verifying_key = service.signing_key().verifying_key(); let documents = service.node().topics::().await?; @@ -85,17 +85,17 @@ impl Documents { .authors .iter() .map(|author| { - let author_public_key = PublicKey(author.public_key); - if author_public_key == public_key { + let author_verifying_key = VerifyingKey(author.verifying_key); + if author_verifying_key == verifying_key { let last_seen = author.last_seen.and_then(|last_seen| { glib::DateTime::from_unix_utc(last_seen.timestamp()).ok() }); - Author::for_this_device(&PublicKey(author.public_key), last_seen.as_ref()) + Author::for_this_device(&VerifyingKey(author.verifying_key), last_seen.as_ref()) } else { let last_seen = author.last_seen.and_then(|last_seen| { glib::DateTime::from_unix_utc(last_seen.timestamp()).ok() }); - Author::with_state(&author_public_key, last_seen.as_ref()) + Author::with_state(&author_verifying_key, last_seen.as_ref()) } }) .collect(); diff --git a/reflection-doc/src/lib.rs b/reflection-doc/src/lib.rs index 8fabd3a..f320cae 100644 --- a/reflection-doc/src/lib.rs +++ b/reflection-doc/src/lib.rs @@ -12,22 +12,22 @@ pub mod identity { use std::fmt; #[derive(Clone, Debug, glib::Boxed)] - #[boxed_type(name = "ReflectionPrivateKey", nullable)] - pub struct PrivateKey(pub(crate) p2panda_core::PrivateKey); + #[boxed_type(name = "ReflectionSigningKey", nullable)] + pub struct SigningKey(pub(crate) p2panda_core::SigningKey); - impl Default for PrivateKey { + impl Default for SigningKey { fn default() -> Self { - Self::new() + Self::generate() } } - impl PrivateKey { - pub fn new() -> PrivateKey { - PrivateKey(p2panda_core::PrivateKey::new()) + impl SigningKey { + pub fn generate() -> SigningKey { + SigningKey(p2panda_core::SigningKey::generate()) } - pub fn public_key(&self) -> PublicKey { - PublicKey(self.0.public_key()) + pub fn verifying_key(&self) -> VerifyingKey { + VerifyingKey(self.0.verifying_key()) } pub fn as_bytes(&self) -> &[u8] { @@ -35,43 +35,43 @@ pub mod identity { } } - impl TryFrom<&[u8]> for PrivateKey { + impl TryFrom<&[u8]> for SigningKey { type Error = p2panda_core::IdentityError; fn try_from(value: &[u8]) -> Result { - Ok(PrivateKey(p2panda_core::PrivateKey::try_from(value)?)) + Ok(SigningKey(p2panda_core::SigningKey::try_from(value)?)) } } - impl<'a> From<&'a PrivateKey> for &'a [u8] { - fn from(value: &PrivateKey) -> &[u8] { + impl<'a> From<&'a SigningKey> for &'a [u8] { + fn from(value: &SigningKey) -> &[u8] { value.0.as_bytes().as_slice() } } - impl fmt::Display for PrivateKey { + impl fmt::Display for SigningKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } #[derive(Clone, Debug, PartialEq, Hash, Eq, glib::Boxed)] - #[boxed_type(name = "ReflectionPublicKey", nullable)] - pub struct PublicKey(pub(crate) p2panda_core::PublicKey); + #[boxed_type(name = "ReflectionVerifyingKey", nullable)] + pub struct VerifyingKey(pub(crate) p2panda_core::VerifyingKey); - impl fmt::Display for PublicKey { + impl fmt::Display for VerifyingKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } - impl<'a> From<&'a PublicKey> for &'a [u8] { - fn from(value: &PublicKey) -> &[u8] { + impl<'a> From<&'a VerifyingKey> for &'a [u8] { + fn from(value: &VerifyingKey) -> &[u8] { value.0.as_bytes().as_slice() } } - impl PublicKey { + impl VerifyingKey { pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes().as_slice() } @@ -81,7 +81,7 @@ pub mod identity { #[cfg(test)] mod tests { use crate::document::DocumentId; - use crate::identity::PrivateKey; + use crate::identity::SigningKey; use crate::service::Service; #[test_log::test(glib::async_test)] @@ -90,8 +90,8 @@ mod tests { let context = glib::MainContext::ref_thread_default(); - let private_key = PrivateKey::new(); - let service = Service::new(&private_key, None); + let signing_key = SigningKey::generate(); + let service = Service::new(&signing_key, None); service.startup().await.unwrap(); let document = service.join_document_with_main_context(&DocumentId::new(), &context); @@ -109,16 +109,16 @@ mod tests { let context = glib::MainContext::ref_thread_default(); - let private_key = PrivateKey::new(); - let service = Service::new(&private_key, None); + let signing_key = SigningKey::generate(); + let service = Service::new(&signing_key, None); service.startup().await.unwrap(); let document = service.join_document_with_main_context(&DocumentId::new(), &context); document.subscribe().await; let id = document.id(); - let private_key2 = PrivateKey::new(); - let service2 = Service::new(&private_key2, None); + let signing_key2 = SigningKey::generate(); + let service2 = Service::new(&signing_key2, None); service2.startup().await.unwrap(); let document2 = service2.join_document_with_main_context(&id, &context); @@ -150,16 +150,16 @@ mod tests { let context = glib::MainContext::ref_thread_default(); - let private_key = PrivateKey::new(); - let service = Service::new(&private_key, None); + let signing_key = SigningKey::generate(); + let service = Service::new(&signing_key, None); service.startup().await.unwrap(); let document = service.join_document_with_main_context(&DocumentId::new(), &context); document.subscribe().await; let id = document.id(); - let private_key2 = PrivateKey::new(); - let service2 = Service::new(&private_key2, None); + let signing_key2 = SigningKey::generate(); + let service2 = Service::new(&signing_key2, None); service2.startup().await.unwrap(); let document2 = service2.join_document_with_main_context(&id, &context); @@ -201,8 +201,8 @@ mod tests { let context = glib::MainContext::ref_thread_default(); - let private_key = PrivateKey::new(); - let service = Service::new(&private_key, None); + let signing_key = SigningKey::generate(); + let service = Service::new(&signing_key, None); service.startup().await.unwrap(); let document = service.join_document_with_main_context(&DocumentId::new(), &context); @@ -210,8 +210,8 @@ mod tests { document.subscribe().await; - let private_key2 = PrivateKey::new(); - let service2 = Service::new(&private_key2, None); + let signing_key2 = SigningKey::generate(); + let service2 = Service::new(&signing_key2, None); service2.startup().await.unwrap(); let document2 = service2.join_document_with_main_context(&id, &context); diff --git a/reflection-doc/src/service.rs b/reflection-doc/src/service.rs index 6b737f3..6e04975 100644 --- a/reflection-doc/src/service.rs +++ b/reflection-doc/src/service.rs @@ -7,16 +7,9 @@ use std::sync::{Mutex, OnceLock}; use thiserror::Error; use tracing::error; -use crate::identity::PrivateKey; -use crate::{ - document::{Document, DocumentId}, - documents::Documents, -}; -use reflection_node::{ - node, - node::{Node, NodeError}, - topic::TopicError, -}; +use crate::document::{Document, DocumentId}; +use crate::documents::Documents; +use crate::identity::SigningKey; #[derive(Error, Debug)] pub enum StartupError { @@ -53,8 +46,8 @@ mod imp { #[properties(wrapper_type = super::Service)] pub struct Service { pub node: OnceLock, - #[property(get, set, construct_only, type = PrivateKey)] - pub private_key: OnceLock, + #[property(get, set, construct_only, type = SigningKey)] + pub signing_key: OnceLock, #[property(get, set, construct_only, nullable, type = Option)] pub data_dir: OnceLock>, #[property(get)] @@ -135,9 +128,9 @@ glib::wrapper! { } impl Service { - pub fn new(private_key: &PrivateKey, data_dir: Option<&gio::File>) -> Self { + pub fn new(signing_key: &SigningKey, data_dir: Option<&gio::File>) -> Self { glib::Object::builder() - .property("private-key", private_key) + .property("signing-key", signing_key) .property("data-dir", data_dir) .build() } @@ -171,10 +164,10 @@ impl Service { } pub async fn startup(&self) -> Result<(), StartupError> { - let private_key = self.private_key().0; - let network_id = Hash::new(b"reflection"); + let signing_key = self.signing_key().0; + let network_id = Hash::digest(b"reflection"); let path = self.data_dir().and_then(|data_dir| data_dir.path()); - let node = Node::new(private_key, network_id, path.as_deref()).await?; + let node = Node::new(signing_key, network_id, path.as_deref()).await?; self.imp() .node diff --git a/reflection-node/migrations/20250418140035_create_tables.sql b/reflection-node/migrations/20250418140035_create_tables.sql index 186eb4c..fd8b812 100644 --- a/reflection-node/migrations/20250418140035_create_tables.sql +++ b/reflection-node/migrations/20250418140035_create_tables.sql @@ -1,13 +1,14 @@ CREATE TABLE IF NOT EXISTS authors ( - public_key TEXT NOT NULL, - topic_id TEXT NOT NULL, - last_seen INTEGER, - UNIQUE(public_key, topic_id), - FOREIGN KEY(topic_id) REFERENCES topics(id) ON DELETE CASCADE + public_key TEXT NOT NULL, + topic_id TEXT NOT NULL, + last_seen INTEGER, + + UNIQUE(public_key, topic_id), + FOREIGN KEY(topic_id) REFERENCES topics(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS topics ( - id TEXT NOT NULL PRIMARY KEY, - name TEXT, - last_accessed INTEGER -); \ No newline at end of file + id TEXT NOT NULL PRIMARY KEY, + name TEXT, + last_accessed INTEGER +); diff --git a/reflection-node/migrations/20260521094840_rename_public_key_to_verifying_key.sql b/reflection-node/migrations/20260521094840_rename_public_key_to_verifying_key.sql new file mode 100644 index 0000000..60a372e --- /dev/null +++ b/reflection-node/migrations/20260521094840_rename_public_key_to_verifying_key.sql @@ -0,0 +1,2 @@ +ALTER TABLE authors +RENAME COLUMN public_key to verifying_key; diff --git a/reflection-node/src/author_tracker.rs b/reflection-node/src/author_tracker.rs index 9d223ab..cf0e541 100644 --- a/reflection-node/src/author_tracker.rs +++ b/reflection-node/src/author_tracker.rs @@ -67,7 +67,7 @@ impl AuthorTracker { self.set_last_seen(author).await; } - let this_author = self.node.private_key.public_key(); + let this_author = self.node.verifying_key; if tx.is_some() { self.subscribable_topic.author_joined(this_author); } else { @@ -78,7 +78,7 @@ impl AuthorTracker { *tx_guard = tx; } - pub async fn received(&self, message: AuthorMessage, author: PublicKey) { + pub async fn received(&self, author: VerifyingKey, message: AuthorTrackerMessage) { match message { AuthorTrackerMessage::Hello => { self.join(author).await; @@ -98,7 +98,7 @@ impl AuthorTracker { } } - async fn join(&self, author: PublicKey) { + async fn join(&self, author: VerifyingKey) { self.last_ping.lock().await.insert(author, Instant::now()); self.subscribable_topic.author_joined(author); self.set_last_seen(author).await; @@ -108,7 +108,7 @@ impl AuthorTracker { self.send(AuthorTrackerMessage::Ping).await; } - async fn ping(&self, author: PublicKey) { + async fn ping(&self, author: VerifyingKey) { let old = self.last_ping.lock().await.insert(author, Instant::now()); // If this is a new author emit author join @@ -118,7 +118,7 @@ impl AuthorTracker { self.set_last_seen(author).await; } - async fn left(&self, author: PublicKey) { + async fn left(&self, author: VerifyingKey) { self.last_ping.lock().await.remove(&author); self.subscribable_topic.author_left(author); self.set_last_seen(author).await; @@ -155,7 +155,7 @@ impl AuthorTracker { } } - async fn set_last_seen(&self, author: PublicKey) { + async fn set_last_seen(&self, author: VerifyingKey) { if let Err(error) = self .node .topic_store diff --git a/reflection-node/src/lib.rs b/reflection-node/src/lib.rs index 6236477..c8741e2 100644 --- a/reflection-node/src/lib.rs +++ b/reflection-node/src/lib.rs @@ -10,9 +10,7 @@ pub mod traits; mod tests { use std::sync::Arc; - use p2panda_core::Hash; - use p2panda_core::PrivateKey; - use p2panda_core::PublicKey; + use p2panda_core::{Hash, SigningKey, Topic, VerifyingKey}; use tokio::sync::{Mutex, mpsc}; use crate::node::ConnectionMode; @@ -22,9 +20,9 @@ mod tests { #[tokio::test] #[test_log::test] async fn create_topic() { - let private_key = PrivateKey::new(); - let network_id = Hash::new(b"reflection"); - let node = Node::new(private_key, network_id, None).await.unwrap(); + let signing_key = SigningKey::generate(); + let network_id = Hash::digest(b"reflection"); + let node = Node::new(signing_key, network_id, None).await.unwrap(); let id: [u8; 32] = [0; 32]; let _sub = node.subscribe(id, TestTopic::new()).await; @@ -57,22 +55,25 @@ mod tests { } impl SubscribableTopic for TestTopic { - fn bytes_received(&self, _author: PublicKey, data: Vec) { + fn bytes_received(&self, _author: VerifyingKey, data: Vec) { self.tx.send(data).unwrap(); } - fn author_joined(&self, _author: PublicKey) {} - fn author_left(&self, _author: PublicKey) {} - fn ephemeral_bytes_received(&self, _author: PublicKey, _data: Vec) {} - fn error(&self, _error: crate::topic::SubscriptionError) {} + fn author_joined(&self, _author: VerifyingKey) {} + fn author_left(&self, _author: VerifyingKey) {} + fn ephemeral_bytes_received(&self, _author: VerifyingKey, _data: Vec) {} + fn error(&self, _error: SubscriptionError) {} } #[tokio::test] #[test_log::test] async fn subscribe_topic() { - let private_key = PrivateKey::new(); - let network_id = Hash::new(b"reflection"); - let node = Node::new(private_key, network_id, None).await.unwrap(); + let network_id = Hash::digest(b"reflection"); + let topic_id: Topic = [1; 32].into(); + + let node = Node::new(SigningKey::generate(), network_id, None) + .await + .unwrap(); node.set_connection_mode(ConnectionMode::Network) .await .unwrap(); diff --git a/reflection-node/src/node.rs b/reflection-node/src/node.rs index 1519191..0a2755e 100644 --- a/reflection-node/src/node.rs +++ b/reflection-node/src/node.rs @@ -85,8 +85,8 @@ pub struct Node { impl Node { pub async fn new( - private_key: PrivateKey, - network_id: Hash, + signing_key: SigningKey, + network_id: impl Into, db_location: Option<&Path>, ) -> Result { let runtime = if let Ok(handle) = tokio::runtime::Handle::try_current() { diff --git a/reflection-node/src/topic_store.rs b/reflection-node/src/topic_store.rs index 38e0931..0e7a583 100644 --- a/reflection-node/src/topic_store.rs +++ b/reflection-node/src/topic_store.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use chrono::{DateTime, Utc}; -use p2panda_core::{VerifyingKey, Topic}; +use p2panda_core::{Topic, VerifyingKey}; use sqlx::{FromRow, Row}; #[derive(Debug, FromRow)] @@ -17,7 +17,7 @@ pub struct StoreTopic { #[derive(Debug, Clone)] pub struct Author { - pub public_key: PublicKey, + pub verifying_key: VerifyingKey, pub last_seen: Option>, } @@ -27,43 +27,32 @@ pub struct TopicStore { } impl TopicStore { - pub fn new(pool: sqlx::SqlitePool) -> Self { + pub fn from_pool(pool: sqlx::SqlitePool) -> Self { Self { pool } } - async fn authors(&self, id: &TopicId) -> sqlx::Result> { - let list = sqlx::query("SELECT public_key FROM authors WHERE topic_id = ?") - .bind(id.as_slice()) - .fetch_all(&self.pool) - .await?; - - Ok(list - .iter() - .filter_map(|row| PublicKey::try_from(row.get::<&[u8], _>("public_key")).ok()) - .collect()) - } - pub async fn topics(&self) -> sqlx::Result> { let mut topics: Vec = sqlx::query_as("SELECT id, name, last_accessed FROM topics") .fetch_all(&self.pool) .await?; - let authors = sqlx::query("SELECT public_key, topic_id, last_seen FROM authors") + let authors = sqlx::query("SELECT verifying_key, topic_id, last_seen FROM authors") .fetch_all(&self.pool) .await?; let mut authors_per_topic = authors.iter().fold(HashMap::new(), |mut acc, row| { - let Ok(id) = TopicId::try_from(row.get::<&[u8], _>("topic_id")) else { + let Ok(id) = Topic::try_from(row.get::<&[u8], _>("topic_id")) else { return acc; }; - let Ok(public_key) = PublicKey::try_from(row.get::<&[u8], _>("public_key")) else { + let Ok(verifying_key) = VerifyingKey::try_from(row.get::<&[u8], _>("verifying_key")) + else { return acc; }; let Ok(last_seen) = row.try_get::>, _>("last_seen") else { return acc; }; acc.entry(id).or_insert_with(Vec::new).push(Author { - public_key, + verifying_key, last_seen, }); acc @@ -78,40 +67,45 @@ impl TopicStore { Ok(topics) } - pub async fn add_topic(&self, id: &TopicId) -> sqlx::Result<()> { - // The id is the primary key in the table therefore ignore insertion when the topic exists already + pub async fn add_topic(&self, topic: &Topic) -> sqlx::Result<()> { + // The id is the primary key in the table therefore ignore insertion when the topic exists + // already sqlx::query( " INSERT OR IGNORE INTO topics ( id ) VALUES ( ? ) ", ) - .bind(id.as_slice()) + .bind(topic.as_bytes().as_slice()) .execute(&self.pool) .await?; Ok(()) } - pub async fn delete_topic(&self, id: &TopicId) -> sqlx::Result<()> { + pub async fn delete_topic(&self, topic: &Topic) -> sqlx::Result<()> { sqlx::query("DELETE FROM topics WHERE id = ?") - .bind(id.as_slice()) + .bind(topic.as_bytes().as_slice()) .execute(&self.pool) .await?; Ok(()) } - pub async fn add_author(&self, id: &TopicId, public_key: &PublicKey) -> sqlx::Result<()> { + pub async fn add_author( + &self, + topic: &Topic, + verifying_key: &VerifyingKey, + ) -> sqlx::Result<()> { // The author/id pair is required to be unique therefore ignore if the insertion fails sqlx::query( " - INSERT OR IGNORE INTO authors ( public_key, topic_id ) + INSERT OR IGNORE INTO authors ( verifying_key, topic_id ) VALUES ( ?, ? ) ", ) - .bind(public_key.as_bytes().as_slice()) - .bind(id.as_slice()) + .bind(verifying_key.as_bytes().as_slice()) + .bind(topic.as_bytes().as_slice()) .execute(&self.pool) .await?; @@ -120,18 +114,18 @@ impl TopicStore { pub async fn set_last_seen_for_author( &self, - public_key: PublicKey, + verifying_key: VerifyingKey, last_seen: Option>, ) -> sqlx::Result<()> { sqlx::query( " UPDATE authors SET last_seen = ? - WHERE public_key = ? + WHERE verifying_key = ? ", ) .bind(last_seen) - .bind(public_key.as_bytes().as_slice()) + .bind(verifying_key.as_bytes().as_slice()) .execute(&self.pool) .await?; From e289ae2fd1b78dbb4a30153c792e8e537fe5b363 Mon Sep 17 00:00:00 2001 From: adz Date: Wed, 18 Mar 2026 16:57:40 +0100 Subject: [PATCH 06/15] node: Update tests after API changes --- reflection-node/src/lib.rs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/reflection-node/src/lib.rs b/reflection-node/src/lib.rs index c8741e2..8364212 100644 --- a/reflection-node/src/lib.rs +++ b/reflection-node/src/lib.rs @@ -13,9 +13,8 @@ mod tests { use p2panda_core::{Hash, SigningKey, Topic, VerifyingKey}; use tokio::sync::{Mutex, mpsc}; - use crate::node::ConnectionMode; - use crate::node::Node; - use crate::topic::SubscribableTopic; + use crate::node::{ConnectionMode, Node}; + use crate::traits::{SubscribableTopic, SubscriptionError}; #[tokio::test] #[test_log::test] @@ -26,10 +25,10 @@ mod tests { let id: [u8; 32] = [0; 32]; let _sub = node.subscribe(id, TestTopic::new()).await; - let topics = node.topics::<[u8; 32]>().await.unwrap(); + let topics = node.topics().await.unwrap(); assert_eq!(topics.len(), 1); - assert_eq!(topics.first().unwrap().id, id); + assert_eq!(topics.first().unwrap().id, id.into()); node.shutdown().await.unwrap(); } @@ -80,16 +79,11 @@ mod tests { let test_topic = TestTopic::new(); - let id: [u8; 32] = [0; 32]; - let subscription = node.subscribe(id, test_topic).await.unwrap(); - - let topics = node.topics::<[u8; 32]>().await.unwrap(); - assert_eq!(topics.len(), 1); - assert_eq!(topics.first().unwrap().id, id); + let subscription = node.subscribe(topic_id, test_topic).await.unwrap(); - let private_key2 = PrivateKey::new(); - let network_id2 = Hash::new(b"reflection"); - let node2 = Node::new(private_key2, network_id2, None).await.unwrap(); + let node2 = Node::new(SigningKey::generate(), network_id, None) + .await + .unwrap(); node2 .set_connection_mode(ConnectionMode::Network) .await @@ -97,15 +91,17 @@ mod tests { let test_topic2 = TestTopic::new(); - let _subscription2 = node2.subscribe(id, test_topic2.clone()).await.unwrap(); + let _subscription2 = node2 + .subscribe(topic_id, test_topic2.clone()) + .await + .unwrap(); - let topics2 = node2.topics::<[u8; 32]>().await.unwrap(); - assert_eq!(topics2.len(), 1); - assert_eq!(topics2.first().unwrap().id, id); + // TODO: Need to sleep here to make sure tx already exists. + tokio::time::sleep(std::time::Duration::from_secs(3)).await; let test_snapshot = "test".as_bytes().to_vec(); subscription - .send_snapshot(test_snapshot.clone()) + .publish_snapshot(test_snapshot.clone()) .await .unwrap(); From 6ee8d4d5fdcf4e1356f5c6f6e2fbf9ccf461b022 Mon Sep 17 00:00:00 2001 From: adz Date: Wed, 18 Mar 2026 16:57:40 +0100 Subject: [PATCH 07/15] docs: Adjust to new -node API changes --- reflection-doc/src/author.rs | 5 ++++- reflection-doc/src/document.rs | 28 +++++++++++++++++++--------- reflection-doc/src/documents.rs | 11 +++++++---- reflection-doc/src/lib.rs | 6 +++--- reflection-doc/src/service.rs | 10 +++++++--- 5 files changed, 40 insertions(+), 20 deletions(-) diff --git a/reflection-doc/src/author.rs b/reflection-doc/src/author.rs index ccaed65..70d2f1d 100644 --- a/reflection-doc/src/author.rs +++ b/reflection-doc/src/author.rs @@ -151,7 +151,10 @@ impl Author { .build() } - pub(crate) fn with_state(verifying_key: &VerifyingKey, last_seen: Option<&glib::DateTime>) -> Self { + pub(crate) fn with_state( + verifying_key: &VerifyingKey, + last_seen: Option<&glib::DateTime>, + ) -> Self { glib::Object::builder() .property("verifying-key", verifying_key) .property("last-seen", last_seen) diff --git a/reflection-doc/src/document.rs b/reflection-doc/src/document.rs index f1d0a54..a93d013 100644 --- a/reflection-doc/src/document.rs +++ b/reflection-doc/src/document.rs @@ -9,11 +9,9 @@ use glib::{Properties, clone}; pub use hex::FromHexError; use loro::{ExportMode, LoroDoc, LoroText, event::Diff}; use p2panda_core::cbor::{decode_cbor, encode_cbor}; -use reflection_node::p2panda_core; -use reflection_node::topic::{ - SubscribableTopic, Subscription as TopicSubscription, - SubscriptionError as TopicSubscriptionError, -}; +use p2panda_core::{self, Topic}; +use reflection_node::subscription::Subscription as TopicSubscription; +use reflection_node::traits::{SubscribableTopic, SubscriptionError as TopicSubscriptionError}; use tracing::error; use crate::author::Author; @@ -37,6 +35,18 @@ impl From<[u8; 32]> for DocumentId { } } +impl From for DocumentId { + fn from(value: Topic) -> Self { + Self(value.to_bytes()) + } +} + +impl From for Topic { + fn from(value: DocumentId) -> Self { + Topic::from(value.0) + } +} + impl fmt::Display for DocumentId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.to_hex()) @@ -301,7 +311,7 @@ mod imp { #[weak] subscription, async move { - if let Err(error) = subscription.send_ephemeral(cursor_bytes).await { + if let Err(error) = subscription.publish_ephemeral(cursor_bytes).await { error!("Failed to send cursor position: {}", error); } } @@ -498,7 +508,7 @@ mod imp { subscription, async move { // Broadcast a "text delta" to all peers - if let Err(error) = subscription.send_delta(delta_bytes).await { + if let Err(error) = subscription.publish_delta(delta_bytes).await { error!( "Failed to send delta of document to the network: {}", error @@ -831,7 +841,7 @@ impl Document { .export(ExportMode::Snapshot) .expect("encoded crdt snapshot"); - if let Err(error) = subscription.send_snapshot(snapshot_bytes).await { + if let Err(error) = subscription.publish_snapshot(snapshot_bytes).await { error!( "Failed to send snapshot of document to the network: {}", error @@ -875,7 +885,7 @@ impl Document { .expect("crdt_doc to be set") .export(ExportMode::Snapshot) .expect("encoded crdt snapshot"); - if let Err(error) = subscription.send_snapshot(snapshot_bytes).await { + if let Err(error) = subscription.publish_snapshot(snapshot_bytes).await { error!( "Failed to send snapshot of document to the network: {}", error diff --git a/reflection-doc/src/documents.rs b/reflection-doc/src/documents.rs index c321d1b..3c59bb6 100644 --- a/reflection-doc/src/documents.rs +++ b/reflection-doc/src/documents.rs @@ -70,7 +70,7 @@ impl Documents { pub(crate) async fn load(&self, service: &Service) -> Result<(), StartupError> { let verifying_key = service.signing_key().verifying_key(); - let documents = service.node().topics::().await?; + let documents = service.node().topics().await?; let mut list = self.imp().list.write().unwrap(); assert!(list.is_empty()); @@ -90,7 +90,10 @@ impl Documents { let last_seen = author.last_seen.and_then(|last_seen| { glib::DateTime::from_unix_utc(last_seen.timestamp()).ok() }); - Author::for_this_device(&VerifyingKey(author.verifying_key), last_seen.as_ref()) + Author::for_this_device( + &VerifyingKey(author.verifying_key), + last_seen.as_ref(), + ) } else { let last_seen = author.last_seen.and_then(|last_seen| { glib::DateTime::from_unix_utc(last_seen.timestamp()).ok() @@ -102,14 +105,14 @@ impl Documents { let obj = Document::with_state( service, - Some(&document.id), + Some(&document.id.into()), document.name.as_deref(), last_accessed.as_ref(), ); obj.authors().load(authors); - list.insert(document.id, obj); + list.insert(document.id.into(), obj); } drop(list); diff --git a/reflection-doc/src/lib.rs b/reflection-doc/src/lib.rs index f320cae..9653789 100644 --- a/reflection-doc/src/lib.rs +++ b/reflection-doc/src/lib.rs @@ -5,11 +5,11 @@ pub mod documents; pub mod service; pub mod identity { + use std::fmt; use std::hash::Hash; - use reflection_node::p2panda_core; - pub use reflection_node::p2panda_core::identity::IdentityError; - use std::fmt; + use p2panda_core; + pub use p2panda_core::identity::IdentityError; #[derive(Clone, Debug, glib::Boxed)] #[boxed_type(name = "ReflectionSigningKey", nullable)] diff --git a/reflection-doc/src/service.rs b/reflection-doc/src/service.rs index 6e04975..6f54f1e 100644 --- a/reflection-doc/src/service.rs +++ b/reflection-doc/src/service.rs @@ -1,9 +1,13 @@ +use std::sync::{Mutex, OnceLock}; + use gio::prelude::{FileExt, ListModelExtManual, NetworkMonitorExt}; use glib::object::ObjectExt; use glib::subclass::prelude::*; use glib::{Properties, clone}; -use reflection_node::p2panda_core::Hash; -use std::sync::{Mutex, OnceLock}; +use p2panda_core::Hash; +use reflection_node::node; +use reflection_node::node::{Node, NodeError}; +use reflection_node::subscription::SubscriptionError; use thiserror::Error; use tracing::error; @@ -16,7 +20,7 @@ pub enum StartupError { #[error(transparent)] Node(#[from] NodeError), #[error(transparent)] - Topic(#[from] TopicError), + Topic(#[from] SubscriptionError), } #[derive(Debug, Copy, Clone, PartialEq, Eq, glib::Enum, Default)] From 6039a839c66c927039293795fe8d6f7a03201480 Mon Sep 17 00:00:00 2001 From: adz Date: Thu, 21 May 2026 12:02:52 +0200 Subject: [PATCH 08/15] db: New SQLite file name to keep old dbs around This will assure that users have a chance to recover documents from past Reflection versions and move them into this new version with a migration script, etc. --- reflection-node/src/node.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reflection-node/src/node.rs b/reflection-node/src/node.rs index 0a2755e..7dfae20 100644 --- a/reflection-node/src/node.rs +++ b/reflection-node/src/node.rs @@ -14,6 +14,8 @@ pub use crate::topic_store::Author; use crate::topic_store::{StoreTopic, TopicStore}; use crate::traits::SubscribableTopic; +static DATABASE_FILE: &str = "database-v2.sqlite"; + static RELAY_URL: LazyLock = LazyLock::new(|| { "https://euc1-1.relay.n0.iroh-canary.iroh.link" .parse() @@ -101,7 +103,7 @@ impl Node { let inner = { let network_id = network_id.into(); - let db_file = db_location.map(|location| location.join("database.sqlite")); + let db_file = db_location.map(|location| location.join(DATABASE_FILE)); runtime .spawn(NodeInner::new(signing_key, network_id, db_file)) From 33fb9192dc7ed30c6bd25303702c75c005eebcf4 Mon Sep 17 00:00:00 2001 From: adz Date: Thu, 21 May 2026 12:55:03 +0200 Subject: [PATCH 09/15] node: Remove p2panda-core as dependency --- Cargo.lock | 1 - reflection-node/Cargo.toml | 1 - reflection-node/src/author_tracker.rs | 2 +- reflection-node/src/lib.rs | 2 +- reflection-node/src/node.rs | 10 +++++----- reflection-node/src/subscription.rs | 2 +- reflection-node/src/topic_store.rs | 2 +- reflection-node/src/traits.rs | 2 +- 8 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 496dd07..3c5b0e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4837,7 +4837,6 @@ version = "0.3.0" dependencies = [ "chrono", "p2panda", - "p2panda-core", "p2panda-store", "serde", "sqlx", diff --git a/reflection-node/Cargo.toml b/reflection-node/Cargo.toml index 3a89e38..486a864 100644 --- a/reflection-node/Cargo.toml +++ b/reflection-node/Cargo.toml @@ -14,7 +14,6 @@ test_utils = [] [dependencies] chrono = "0.4.43" p2panda = "0.6.1" -p2panda-core = "0.6.1" p2panda-store = "0.6.1" serde = { version = "1.0.228", features = ["derive"] } sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite", "chrono"], default-features = false } diff --git a/reflection-node/src/author_tracker.rs b/reflection-node/src/author_tracker.rs index cf0e541..349baa8 100644 --- a/reflection-node/src/author_tracker.rs +++ b/reflection-node/src/author_tracker.rs @@ -4,8 +4,8 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use chrono::Utc; +use p2panda::VerifyingKey; use p2panda::streams::EphemeralStreamPublisher; -use p2panda_core::VerifyingKey; use serde::{Deserialize, Serialize}; use tokio::sync::{Mutex, RwLock}; use tracing::{error, warn}; diff --git a/reflection-node/src/lib.rs b/reflection-node/src/lib.rs index 8364212..7839be2 100644 --- a/reflection-node/src/lib.rs +++ b/reflection-node/src/lib.rs @@ -10,7 +10,7 @@ pub mod traits; mod tests { use std::sync::Arc; - use p2panda_core::{Hash, SigningKey, Topic, VerifyingKey}; + use p2panda::{Hash, SigningKey, Topic, VerifyingKey}; use tokio::sync::{Mutex, mpsc}; use crate::node::{ConnectionMode, Node}; diff --git a/reflection-node/src/node.rs b/reflection-node/src/node.rs index 7dfae20..6461bda 100644 --- a/reflection-node/src/node.rs +++ b/reflection-node/src/node.rs @@ -56,7 +56,7 @@ pub enum ConnectionMode { #[derive(Clone, Debug)] pub struct Topic { - pub id: p2panda_core::Topic, + pub id: p2panda::Topic, pub name: Option, pub last_accessed: Option>, pub authors: Vec, @@ -169,7 +169,7 @@ impl Node { pub async fn subscribe( &self, - id: impl Into, + id: impl Into, subscribable_topic: T, ) -> Result, SubscriptionError> where @@ -191,7 +191,7 @@ impl Node { pub async fn delete_topic( &self, - id: impl Into, + id: impl Into, ) -> Result<(), SubscriptionError> { let id = id.into(); let inner = self.inner.clone(); @@ -260,7 +260,7 @@ impl NodeInner { pub async fn subscribe( self: Arc, - id: impl Into, + id: impl Into, subscribable_topic: Arc, ) -> Result, SubscriptionError> where @@ -280,7 +280,7 @@ impl NodeInner { pub async fn delete_topic( self: Arc, - id: impl Into, + id: impl Into, ) -> Result<(), SubscriptionError> { let id = id.into(); self.topic_store.delete_topic(&id).await?; diff --git a/reflection-node/src/subscription.rs b/reflection-node/src/subscription.rs index 68a4b05..ee43989 100644 --- a/reflection-node/src/subscription.rs +++ b/reflection-node/src/subscription.rs @@ -3,9 +3,9 @@ use std::ops::{Deref, DerefMut}; use std::sync::Arc; use chrono::Utc; +use p2panda::Topic; use p2panda::node::CreateStreamError; use p2panda::streams::{EphemeralStreamPublisher, StreamEvent, StreamFrom, StreamPublisher}; -use p2panda_core::Topic; use thiserror::Error; use tokio::sync::{RwLock, oneshot}; use tokio::task::{AbortHandle, JoinError}; diff --git a/reflection-node/src/topic_store.rs b/reflection-node/src/topic_store.rs index 0e7a583..9373ee1 100644 --- a/reflection-node/src/topic_store.rs +++ b/reflection-node/src/topic_store.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use chrono::{DateTime, Utc}; -use p2panda_core::{Topic, VerifyingKey}; +use p2panda::{Topic, VerifyingKey}; use sqlx::{FromRow, Row}; #[derive(Debug, FromRow)] diff --git a/reflection-node/src/traits.rs b/reflection-node/src/traits.rs index 68398d3..5c04949 100644 --- a/reflection-node/src/traits.rs +++ b/reflection-node/src/traits.rs @@ -1,5 +1,5 @@ +use p2panda::VerifyingKey; use p2panda::node::CreateStreamError; -use p2panda_core::VerifyingKey; use thiserror::Error; #[derive(Debug, Error)] From 058079a82ec4bdd67d00566c84d0d45f6712cc5a Mon Sep 17 00:00:00 2001 From: adz Date: Thu, 21 May 2026 13:10:52 +0200 Subject: [PATCH 10/15] node: Log error and sync started/ended events --- reflection-node/src/subscription.rs | 54 +++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/reflection-node/src/subscription.rs b/reflection-node/src/subscription.rs index ee43989..8bf1d3e 100644 --- a/reflection-node/src/subscription.rs +++ b/reflection-node/src/subscription.rs @@ -10,7 +10,7 @@ use thiserror::Error; use tokio::sync::{RwLock, oneshot}; use tokio::task::{AbortHandle, JoinError}; use tokio_stream::StreamExt; -use tracing::{error, info}; +use tracing::{error, info, warn}; use crate::author_tracker::AuthorTracker; use crate::message::EphemeralMessage; @@ -285,6 +285,7 @@ where let mut abort_handles = Vec::with_capacity(3); // 1. Handle incoming operations from eventually consistent topic stream. + // ====================================================================== // Always start from re-playing _all_ operations in the beginning. This is due to Reflection not // keeping materialised document state around and we need to repeat materialising the document @@ -312,14 +313,59 @@ where // Forward the message payload up to the app layer. subscribable_topic_clone.bytes_received(author, operation.message().to_owned()); } + StreamEvent::SyncStarted { + remote_node_id, + session_id, + incoming_operations, + outgoing_operations, + incoming_bytes, + outgoing_bytes, + .. + } => { + info!( + %session_id, + remote_node_id = &remote_node_id.to_string()[0..8], + "sync started w. {} incoming ({} bytes) and {} outgoing operations ({} bytes)", + incoming_operations, + incoming_bytes, + outgoing_operations, + outgoing_bytes, + ); + } + StreamEvent::SyncEnded { + remote_node_id, + session_id, + error, + .. + } => { + match error { + Some(error) => { + warn!( + %session_id, + remote_node_id = &remote_node_id.to_string()[0..8], + "sync failed with error {error}", + ); + } + None => { + info!( + %session_id, + remote_node_id = &remote_node_id.to_string()[0..8], + "sync ended", + ); + }, + } + } StreamEvent::DecodeFailed { error, .. } => { error!("failed decoding incoming operation from stream: {error}"); } StreamEvent::ReplayFailed { error, .. } => { error!("error occurred while replaying operation stream: {error}"); } - StreamEvent::SyncStarted { .. } | StreamEvent::SyncEnded { .. } => { - // TODO: Handle sync events. + StreamEvent::ProcessingFailed { event, error, .. } => { + error!("error occurred while processing operation {}: {error}", event.header().hash()); + } + StreamEvent::AckFailed { error, .. } => { + error!("error occurred while acking event: {error}"); } _ => (), } @@ -330,6 +376,7 @@ where abort_handles.push(abort_handle); // 2. Handle incoming messages from ephemeral topic stream. + // ======================================================== let (ephemeral_tx, mut ephemeral_rx) = network.ephemeral_stream::(id).await?; @@ -359,6 +406,7 @@ where abort_handles.push(abort_handle); // 3. Run task to track online status of authors. + // ============================================== let author_tracker_clone = author_tracker.clone(); let abort_handle = tokio::spawn(async move { From d4f9cc4c2e2ff8c27e3a8849789228a39a4b75b9 Mon Sep 17 00:00:00 2001 From: adz Date: Thu, 21 May 2026 14:13:38 +0200 Subject: [PATCH 11/15] node: More logging for processed events --- reflection-node/src/subscription.rs | 6 ++++++ reflection-node/src/traits.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/reflection-node/src/subscription.rs b/reflection-node/src/subscription.rs index 8bf1d3e..0f81aa2 100644 --- a/reflection-node/src/subscription.rs +++ b/reflection-node/src/subscription.rs @@ -305,6 +305,12 @@ where StreamEvent::Processed { operation, .. } => { let author = operation.author(); + info!( + author = &author.to_string()[0..8], + "processed operation with id {}", + operation.id() + ); + // When we discover a new author we need to add them to our topic store. if let Err(error) = node_clone.topic_store.add_author(&id, &author).await { error!("can't store author to database: {error}"); diff --git a/reflection-node/src/traits.rs b/reflection-node/src/traits.rs index 5c04949..1a1c69a 100644 --- a/reflection-node/src/traits.rs +++ b/reflection-node/src/traits.rs @@ -10,8 +10,8 @@ pub enum SubscriptionError { pub trait SubscribableTopic: Sync + Send { fn bytes_received(&self, author: VerifyingKey, data: Vec); + fn ephemeral_bytes_received(&self, author: VerifyingKey, data: Vec); fn author_joined(&self, author: VerifyingKey); fn author_left(&self, author: VerifyingKey); - fn ephemeral_bytes_received(&self, author: VerifyingKey, data: Vec); fn error(&self, error: SubscriptionError); } From bc4cc4e860854b008902e04c466308f1504f91a7 Mon Sep 17 00:00:00 2001 From: adz Date: Thu, 21 May 2026 14:15:13 +0200 Subject: [PATCH 12/15] p2panda: Change network name to v2 This ensures that we're not able to sync with Reflection users of an older version. --- reflection-doc/src/service.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reflection-doc/src/service.rs b/reflection-doc/src/service.rs index 6f54f1e..e705c38 100644 --- a/reflection-doc/src/service.rs +++ b/reflection-doc/src/service.rs @@ -15,6 +15,8 @@ use crate::document::{Document, DocumentId}; use crate::documents::Documents; use crate::identity::SigningKey; +static NETWORK_NAME: &[u8] = b"reflection-v2"; + #[derive(Error, Debug)] pub enum StartupError { #[error(transparent)] @@ -169,7 +171,7 @@ impl Service { pub async fn startup(&self) -> Result<(), StartupError> { let signing_key = self.signing_key().0; - let network_id = Hash::digest(b"reflection"); + let network_id = Hash::digest(NETWORK_NAME); let path = self.data_dir().and_then(|data_dir| data_dir.path()); let node = Node::new(signing_key, network_id, path.as_deref()).await?; From ffb067ca7e366c48b549564a0c8cba70c2864adf Mon Sep 17 00:00:00 2001 From: adz Date: Thu, 21 May 2026 14:23:19 +0200 Subject: [PATCH 13/15] cursors: Handle timestamp from Node API The new p2panda Node API brings timestamp out-of-the-box from ephemeral streams, we don't need to provide them ourselves anymore. --- reflection-doc/src/author.rs | 4 ++-- reflection-doc/src/document.rs | 32 +++++++++++++++++++++-------- reflection-node/src/lib.rs | 3 ++- reflection-node/src/subscription.rs | 7 +++++-- reflection-node/src/traits.rs | 2 +- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/reflection-doc/src/author.rs b/reflection-doc/src/author.rs index 70d2f1d..f41bb06 100644 --- a/reflection-doc/src/author.rs +++ b/reflection-doc/src/author.rs @@ -86,7 +86,7 @@ mod imp { pub is_online: Cell, #[property(get)] pub is_this_device: Cell, - pub last_cursor_update: Mutex>, + pub last_cursor_update: Mutex>, } #[glib::object_subclass] @@ -181,7 +181,7 @@ impl Author { self.notify_is_online(); } - pub(crate) fn is_new_cursor_position(&self, timestamp: std::time::SystemTime) -> bool { + pub(crate) fn is_new_cursor_position(&self, timestamp: u64) -> bool { let mut last_cursor_update = self.imp().last_cursor_update.lock().unwrap(); if last_cursor_update.is_none() || timestamp >= last_cursor_update.unwrap() { diff --git a/reflection-doc/src/document.rs b/reflection-doc/src/document.rs index a93d013..1f07d81 100644 --- a/reflection-doc/src/document.rs +++ b/reflection-doc/src/document.rs @@ -99,11 +99,15 @@ impl DocumentId { } #[derive(Debug, serde::Serialize, serde::Deserialize)] -enum EphemerialData { +#[serde(tag = "t", content = "d")] +enum EphemeralData { + #[serde(rename = "cursor")] Cursor { + #[serde(rename = "i")] insert_cursor: Option, + + #[serde(rename = "s")] selection_bound: Option, - timestamp: std::time::SystemTime, }, } @@ -292,10 +296,9 @@ mod imp { } pub fn brodcast_ephemeral(&self) { - let cursor_data = EphemerialData::Cursor { + let cursor_data = EphemeralData::Cursor { insert_cursor: self.insert_cursor.read().unwrap().clone(), selection_bound: self.selection_bound.read().unwrap().clone(), - timestamp: std::time::SystemTime::now(), }; let cursor_bytes = match encode_cbor(&cursor_data) { @@ -599,12 +602,16 @@ mod imp { self.crdt_doc.set(doc).unwrap(); } - pub(super) fn handle_ephemeral_data(&self, author: Author, data: EphemerialData) { + pub(super) fn handle_ephemeral_data( + &self, + author: Author, + timestamp: u64, + data: EphemeralData, + ) { match data { - EphemerialData::Cursor { + EphemeralData::Cursor { insert_cursor, selection_bound, - timestamp, } => { let doc = self.crdt_doc.get().expect("crdt_doc to be set"); @@ -939,13 +946,20 @@ impl SubscribableTopic for DocumentHandle { } } - fn ephemeral_bytes_received(&self, author: p2panda_core::VerifyingKey, data: Vec) { + fn ephemeral_bytes_received( + &self, + author: p2panda_core::VerifyingKey, + timestamp: u64, + data: Vec, + ) { if let Some(document) = self.0.upgrade() { document.main_context().invoke(move || { if let Ok(data) = decode_cbor(&data[..]) && let Some(author) = document.authors().author(&VerifyingKey(author)) { - document.imp().handle_ephemeral_data(author, data); + document + .imp() + .handle_ephemeral_data(author, timestamp, data); } }); } diff --git a/reflection-node/src/lib.rs b/reflection-node/src/lib.rs index 7839be2..b1e60b1 100644 --- a/reflection-node/src/lib.rs +++ b/reflection-node/src/lib.rs @@ -60,7 +60,8 @@ mod tests { fn author_joined(&self, _author: VerifyingKey) {} fn author_left(&self, _author: VerifyingKey) {} - fn ephemeral_bytes_received(&self, _author: VerifyingKey, _data: Vec) {} + fn ephemeral_bytes_received(&self, _author: VerifyingKey, _timestamp: u64, _data: Vec) { + } fn error(&self, _error: SubscriptionError) {} } diff --git a/reflection-node/src/subscription.rs b/reflection-node/src/subscription.rs index 0f81aa2..6adb826 100644 --- a/reflection-node/src/subscription.rs +++ b/reflection-node/src/subscription.rs @@ -396,8 +396,11 @@ where while let Some(message) = ephemeral_rx.next().await { match message.body() { EphemeralMessage::Application(bytes) => { - subscribable_topic_clone - .ephemeral_bytes_received(message.author(), bytes.to_owned()); + subscribable_topic_clone.ephemeral_bytes_received( + message.author(), + message.timestamp(), + bytes.to_owned(), + ); } EphemeralMessage::AuthorTracker(tracker) => { author_tracker_clone diff --git a/reflection-node/src/traits.rs b/reflection-node/src/traits.rs index 1a1c69a..1745d42 100644 --- a/reflection-node/src/traits.rs +++ b/reflection-node/src/traits.rs @@ -10,7 +10,7 @@ pub enum SubscriptionError { pub trait SubscribableTopic: Sync + Send { fn bytes_received(&self, author: VerifyingKey, data: Vec); - fn ephemeral_bytes_received(&self, author: VerifyingKey, data: Vec); + fn ephemeral_bytes_received(&self, author: VerifyingKey, timestamp: u64, data: Vec); fn author_joined(&self, author: VerifyingKey); fn author_left(&self, author: VerifyingKey); fn error(&self, error: SubscriptionError); From b59f8119c446949f20459c1307df6d87936df473 Mon Sep 17 00:00:00 2001 From: adz Date: Mon, 25 May 2026 12:29:37 +0200 Subject: [PATCH 14/15] node: Expose more errors from -node, renaming There's more stream processing related errors we want to expose to -docs from p2panda aka -node. To avoid confusion around two SubscriptionError types I've renamed one of them which was anyhow more "publishing" related. I'm considering renaming more types in a subsequent patch to make the PubSub relationship clearer. --- reflection-doc/src/service.rs | 5 ++-- reflection-node/src/node.rs | 19 ++++++------- reflection-node/src/subscription.rs | 44 ++++++++++++++++++----------- reflection-node/src/traits.rs | 22 +++++++++++++++ 4 files changed, 60 insertions(+), 30 deletions(-) diff --git a/reflection-doc/src/service.rs b/reflection-doc/src/service.rs index e705c38..deab014 100644 --- a/reflection-doc/src/service.rs +++ b/reflection-doc/src/service.rs @@ -7,7 +7,7 @@ use glib::{Properties, clone}; use p2panda_core::Hash; use reflection_node::node; use reflection_node::node::{Node, NodeError}; -use reflection_node::subscription::SubscriptionError; +use reflection_node::subscription::StoreError; use thiserror::Error; use tracing::error; @@ -21,8 +21,9 @@ static NETWORK_NAME: &[u8] = b"reflection-v2"; pub enum StartupError { #[error(transparent)] Node(#[from] NodeError), + #[error(transparent)] - Topic(#[from] SubscriptionError), + Store(#[from] StoreError), } #[derive(Debug, Copy, Clone, PartialEq, Eq, glib::Enum, Default)] diff --git a/reflection-node/src/node.rs b/reflection-node/src/node.rs index 6461bda..b6b36ea 100644 --- a/reflection-node/src/node.rs +++ b/reflection-node/src/node.rs @@ -9,7 +9,7 @@ use tokio::sync::{Notify, RwLock}; use tracing::info; use crate::database::{database_pool, run_migrations}; -use crate::subscription::{Subscription, SubscriptionError, SubscriptionInner}; +use crate::subscription::{StoreError, Subscription, SubscriptionInner}; pub use crate::topic_store::Author; use crate::topic_store::{StoreTopic, TopicStore}; use crate::traits::SubscribableTopic; @@ -37,10 +37,10 @@ pub enum NodeError { RuntimeSpawn(#[from] tokio::task::JoinError), #[error(transparent)] - Datebase(#[from] sqlx::Error), + Database(#[from] sqlx::Error), #[error(transparent)] - DatebaseMigration(#[from] sqlx::migrate::MigrateError), + DatabaseMigration(#[from] sqlx::migrate::MigrateError), #[error(transparent)] NodeSpawn(#[from] SpawnError), @@ -139,7 +139,7 @@ impl Node { Ok(()) } - pub async fn topics(&self) -> Result, SubscriptionError> { + pub async fn topics(&self) -> Result, StoreError> { let inner = self.inner.clone(); let topics = self .runtime @@ -171,7 +171,7 @@ impl Node { &self, id: impl Into, subscribable_topic: T, - ) -> Result, SubscriptionError> + ) -> Result, StoreError> where T: SubscribableTopic + 'static, { @@ -189,10 +189,7 @@ impl Node { Ok(subscription) } - pub async fn delete_topic( - &self, - id: impl Into, - ) -> Result<(), SubscriptionError> { + pub async fn delete_topic(&self, id: impl Into) -> Result<(), StoreError> { let id = id.into(); let inner = self.inner.clone(); self.runtime @@ -262,7 +259,7 @@ impl NodeInner { self: Arc, id: impl Into, subscribable_topic: Arc, - ) -> Result, SubscriptionError> + ) -> Result, StoreError> where T: SubscribableTopic + 'static, { @@ -281,7 +278,7 @@ impl NodeInner { pub async fn delete_topic( self: Arc, id: impl Into, - ) -> Result<(), SubscriptionError> { + ) -> Result<(), StoreError> { let id = id.into(); self.topic_store.delete_topic(&id).await?; Ok(()) diff --git a/reflection-node/src/subscription.rs b/reflection-node/src/subscription.rs index 6adb826..8c27a11 100644 --- a/reflection-node/src/subscription.rs +++ b/reflection-node/src/subscription.rs @@ -18,13 +18,10 @@ use crate::node::NodeInner; use crate::traits::SubscribableTopic; #[derive(Debug, Error)] -pub enum SubscriptionError { +pub enum PublishError { #[error(transparent)] Runtime(#[from] JoinError), - #[error(transparent)] - TopicStore(#[from] sqlx::Error), - #[error(transparent)] StreamPublish(#[from] p2panda::streams::PublishError), @@ -35,6 +32,15 @@ pub enum SubscriptionError { BrokenStream, } +#[derive(Debug, Error)] +pub enum StoreError { + #[error(transparent)] + Runtime(#[from] JoinError), + + #[error(transparent)] + Database(#[from] sqlx::Error), +} + pub struct Subscription { inner: Arc>, runtime: tokio::runtime::Handle, @@ -74,28 +80,28 @@ where } } - pub async fn publish_delta(&self, data: Vec) -> Result<(), SubscriptionError> { + pub async fn publish_delta(&self, data: Vec) -> Result<(), PublishError> { let inner = self.inner.clone(); self.runtime .spawn(async move { inner.publish_delta(data).await }) .await? } - pub async fn publish_snapshot(&self, data: Vec) -> Result<(), SubscriptionError> { + pub async fn publish_snapshot(&self, data: Vec) -> Result<(), PublishError> { let inner = self.inner.clone(); self.runtime .spawn(async move { inner.publish_snapshot(data).await }) .await? } - pub async fn publish_ephemeral(&self, data: Vec) -> Result<(), SubscriptionError> { + pub async fn publish_ephemeral(&self, data: Vec) -> Result<(), PublishError> { let inner = self.inner.clone(); self.runtime .spawn(async move { inner.publish_ephemeral(data).await }) .await? } - pub async fn unsubscribe(self) -> Result<(), SubscriptionError> { + pub async fn unsubscribe(self) -> Result<(), StoreError> { self.network_monitor_task.abort(); let inner = self.inner.clone(); @@ -111,7 +117,7 @@ where /// Set the name for a given topic. /// /// This information will be written to the database. - pub async fn set_name(&self, name: Option) -> Result<(), SubscriptionError> { + pub async fn set_name(&self, name: Option) -> Result<(), StoreError> { let inner = self.inner.clone(); self.runtime .spawn(async move { inner.set_name(name).await }) @@ -192,7 +198,7 @@ where let _ = self.unsubscribe().await; } - pub async fn unsubscribe(&self) -> Result<(), SubscriptionError> { + pub async fn unsubscribe(&self) -> Result<(), StoreError> { let mut tx_guard = self.tx.write().await; let mut ephemeral_tx_guard = self.ephemeral_tx.write().await; let mut abort_handles_guard = self.abort_handles.write().await; @@ -218,18 +224,18 @@ where Ok(()) } - pub async fn publish_delta(&self, data: Vec) -> Result<(), SubscriptionError> { + pub async fn publish_delta(&self, data: Vec) -> Result<(), PublishError> { if let Some(tx) = self.tx.read().await.as_ref() { info!("delta operation sent for topic with id {}", self.id); tx.publish(data).await?; } else { - return Err(SubscriptionError::BrokenStream); + return Err(PublishError::BrokenStream); } Ok(()) } - pub async fn publish_snapshot(&self, data: Vec) -> Result<(), SubscriptionError> { + pub async fn publish_snapshot(&self, data: Vec) -> Result<(), PublishError> { if let Some(tx) = self.tx.read().await.as_ref() { info!("snapshot saved for topic with id {}", self.id); @@ -237,25 +243,25 @@ where // previous entries. tx.prune(Some(data)).await?; } else { - return Err(SubscriptionError::BrokenStream); + return Err(PublishError::BrokenStream); } Ok(()) } - pub async fn publish_ephemeral(&self, data: Vec) -> Result<(), SubscriptionError> { + pub async fn publish_ephemeral(&self, data: Vec) -> Result<(), PublishError> { if let Some(ephemeral_tx) = self.ephemeral_tx.read().await.as_ref() { ephemeral_tx .publish(EphemeralMessage::Application(data)) .await?; } else { - return Err(SubscriptionError::BrokenStream); + return Err(PublishError::BrokenStream); } Ok(()) } - pub async fn set_name(&self, name: Option) -> Result<(), SubscriptionError> { + pub async fn set_name(&self, name: Option) -> Result<(), StoreError> { self.node .topic_store .set_name_for_topic(&self.id, name) @@ -363,15 +369,19 @@ where } StreamEvent::DecodeFailed { error, .. } => { error!("failed decoding incoming operation from stream: {error}"); + subscribable_topic_clone.error(error.into()); } StreamEvent::ReplayFailed { error, .. } => { error!("error occurred while replaying operation stream: {error}"); + subscribable_topic_clone.error(crate::traits::SubscriptionError::ReplayStream(error)); } StreamEvent::ProcessingFailed { event, error, .. } => { error!("error occurred while processing operation {}: {error}", event.header().hash()); + subscribable_topic_clone.error(error.into()); } StreamEvent::AckFailed { error, .. } => { error!("error occurred while acking event: {error}"); + subscribable_topic_clone.error(crate::traits::SubscriptionError::AckedMessage(error)); } _ => (), } diff --git a/reflection-node/src/traits.rs b/reflection-node/src/traits.rs index 1745d42..83ad9b3 100644 --- a/reflection-node/src/traits.rs +++ b/reflection-node/src/traits.rs @@ -1,11 +1,33 @@ +use std::sync::Arc; + use p2panda::VerifyingKey; use p2panda::node::CreateStreamError; +use p2panda::processor::ProcessorError; +use p2panda::streams::{AckedError, DecodeError, ReplayError}; use thiserror::Error; #[derive(Debug, Error)] pub enum SubscriptionError { + /// Broken / closed communication channel with the internal actor in `p2panda-net` prevented + /// creation of stream. This can be due to the actor crashing. #[error(transparent)] CreateStream(#[from] CreateStreamError), + + /// Topic stream could not re-play events due to an internal error. + #[error("{0}")] + ReplayStream(Arc), + + /// Acknowledgment of event failed due to critical error. + #[error("{0}")] + AckedMessage(Arc), + + /// Application payload could not be deserialized. + #[error(transparent)] + DecodeMessage(#[from] DecodeError), + + /// Operation failed during event processing of the system-level pipeline. + #[error(transparent)] + StreamProcessor(#[from] ProcessorError), } pub trait SubscribableTopic: Sync + Send { From 3aa8885b1b20f316596e055028955dfe34e96bb2 Mon Sep 17 00:00:00 2001 From: adz Date: Mon, 25 May 2026 13:25:58 +0200 Subject: [PATCH 15/15] node: Rename API structs and methods Topic is used already all over the place in p2panda and indicates the identifier of the topic itself (32 byte string). A stream on top of a topic is a "topic stream", we can subscribe to it ("topic subscription") and publish into it, following a PubSub pattern. Reflection "enhances" these types with "tracking", that is, we know which topics have been used and which authors are connected to it and when they are online. This hopefully clarifies the types a bit better and stays consistent with naming in p2panda to avoid confusion: Topic -> TrackedTopic Author -> TrackedAuthor Subscription -> TopicStream SubscribableTopic -> TopicSubscription --- reflection-doc/src/document.rs | 13 ++- reflection-doc/src/documents.rs | 4 +- reflection-doc/src/service.rs | 19 ++--- reflection-node/src/author_tracker.rs | 6 +- .../src/{message.rs => ephemeral_message.rs} | 0 reflection-node/src/lib.rs | 78 +++++++++-------- reflection-node/src/node.rs | 83 ++++++++++--------- reflection-node/src/topic_store.rs | 16 ++-- .../src/{subscription.rs => topic_stream.rs} | 80 +++++++++--------- reflection-node/src/traits.rs | 15 +++- 10 files changed, 169 insertions(+), 145 deletions(-) rename reflection-node/src/{message.rs => ephemeral_message.rs} (100%) rename reflection-node/src/{subscription.rs => topic_stream.rs} (89%) diff --git a/reflection-doc/src/document.rs b/reflection-doc/src/document.rs index 1f07d81..b0f776a 100644 --- a/reflection-doc/src/document.rs +++ b/reflection-doc/src/document.rs @@ -10,8 +10,7 @@ pub use hex::FromHexError; use loro::{ExportMode, LoroDoc, LoroText, event::Diff}; use p2panda_core::cbor::{decode_cbor, encode_cbor}; use p2panda_core::{self, Topic}; -use reflection_node::subscription::Subscription as TopicSubscription; -use reflection_node::traits::{SubscribableTopic, SubscriptionError as TopicSubscriptionError}; +use reflection_node::{TopicStream, TopicSubscription, TopicSubscriptionError}; use tracing::error; use crate::author::Author; @@ -144,7 +143,7 @@ mod imp { #[property(get, construct_only)] id: OnceCell, #[property(name = "subscribed", get = Self::subscribed, type = bool)] - pub(super) subscription: RwLock>>>, + pub(super) subscription: RwLock>>>, #[property(get = Self::service, set = Self::set_service, construct_only, type = Service)] service: glib::WeakRef, #[property(get)] @@ -662,7 +661,7 @@ mod imp { } } - pub(super) fn subscription(&self) -> Option>> { + pub(super) fn subscription(&self) -> Option>> { self.subscription.read().unwrap().clone() } } @@ -814,7 +813,7 @@ impl Document { } let handle = DocumentHandle(self.downgrade()); - match self.service().node().subscribe(self.id(), handle).await { + match self.service().node().stream(self.id(), handle).await { Ok(subscription) => { self.imp() .subscription @@ -916,7 +915,7 @@ unsafe impl Sync for Document {} struct DocumentHandle(glib::WeakRef); -impl SubscribableTopic for DocumentHandle { +impl TopicSubscription for DocumentHandle { fn bytes_received(&self, author: p2panda_core::VerifyingKey, data: Vec) { if let Some(document) = self.0.upgrade() { document.main_context().invoke(move || { @@ -968,7 +967,7 @@ impl SubscribableTopic for DocumentHandle { fn error(&self, error: TopicSubscriptionError) { if let Some(document) = self.0.upgrade() { document.main_context().invoke(move || { - error!("Network error received for subscribed document: {error}"); + error!("error received for subscribed document: {error}"); }); } } diff --git a/reflection-doc/src/documents.rs b/reflection-doc/src/documents.rs index 3c59bb6..dd56a75 100644 --- a/reflection-doc/src/documents.rs +++ b/reflection-doc/src/documents.rs @@ -105,14 +105,14 @@ impl Documents { let obj = Document::with_state( service, - Some(&document.id.into()), + Some(&document.topic.into()), document.name.as_deref(), last_accessed.as_ref(), ); obj.authors().load(authors); - list.insert(document.id.into(), obj); + list.insert(document.topic.into(), obj); } drop(list); diff --git a/reflection-doc/src/service.rs b/reflection-doc/src/service.rs index deab014..cc18830 100644 --- a/reflection-doc/src/service.rs +++ b/reflection-doc/src/service.rs @@ -5,9 +5,8 @@ use glib::object::ObjectExt; use glib::subclass::prelude::*; use glib::{Properties, clone}; use p2panda_core::Hash; -use reflection_node::node; -use reflection_node::node::{Node, NodeError}; -use reflection_node::subscription::StoreError; +use reflection_node::TopicStreamError; +use reflection_node::{Node, NodeError}; use thiserror::Error; use tracing::error; @@ -23,7 +22,7 @@ pub enum StartupError { Node(#[from] NodeError), #[error(transparent)] - Store(#[from] StoreError), + TopicStream(#[from] TopicStreamError), } #[derive(Debug, Copy, Clone, PartialEq, Eq, glib::Enum, Default)] @@ -36,12 +35,12 @@ pub enum ConnectionMode { Network, } -impl From for node::ConnectionMode { +impl From for reflection_node::ConnectionMode { fn from(value: ConnectionMode) -> Self { match value { - ConnectionMode::None => node::ConnectionMode::None, - ConnectionMode::Bluetooth => node::ConnectionMode::Bluetooth, - ConnectionMode::Network => node::ConnectionMode::Network, + ConnectionMode::None => reflection_node::ConnectionMode::None, + ConnectionMode::Bluetooth => reflection_node::ConnectionMode::Bluetooth, + ConnectionMode::Network => reflection_node::ConnectionMode::Network, } } } @@ -88,9 +87,9 @@ mod imp { monitor.is_network_available() }; let connection_mode = (*self.connection_mode.lock().unwrap()).into(); - let wants_network = connection_mode == node::ConnectionMode::Network; + let wants_network = connection_mode == reflection_node::ConnectionMode::Network; let real_connection_mode = if !network_available && wants_network { - node::ConnectionMode::None + reflection_node::ConnectionMode::None } else { connection_mode }; diff --git a/reflection-node/src/author_tracker.rs b/reflection-node/src/author_tracker.rs index 349baa8..6109a24 100644 --- a/reflection-node/src/author_tracker.rs +++ b/reflection-node/src/author_tracker.rs @@ -10,9 +10,9 @@ use serde::{Deserialize, Serialize}; use tokio::sync::{Mutex, RwLock}; use tracing::{error, warn}; -use crate::message::EphemeralMessage; +use crate::ephemeral_message::EphemeralMessage; use crate::node::NodeInner; -use crate::traits::SubscribableTopic; +use crate::traits::TopicSubscription; const OFFLINE_TIMEOUT: Duration = Duration::from_secs(60); @@ -41,7 +41,7 @@ pub struct AuthorTracker { tx: RwLock>>, } -impl AuthorTracker { +impl AuthorTracker { pub fn new(node: Arc, subscribable_topic: Arc) -> Arc { Arc::new(Self { last_ping: Mutex::new(HashMap::new()), diff --git a/reflection-node/src/message.rs b/reflection-node/src/ephemeral_message.rs similarity index 100% rename from reflection-node/src/message.rs rename to reflection-node/src/ephemeral_message.rs diff --git a/reflection-node/src/lib.rs b/reflection-node/src/lib.rs index b1e60b1..84dd770 100644 --- a/reflection-node/src/lib.rs +++ b/reflection-node/src/lib.rs @@ -1,10 +1,23 @@ +//! Peer-to-peer, local-first networking and sync "backend" of Reflection based on p2panda. +//! +//! Some features implemented in `reflection-node` add functionality on top of p2panda: +//! +//! - Author Presence: Indicate which authors have contributed to which topic and if they're +//! currently online. +//! - Persisted Topics: Store to persist all previously used topics. mod author_tracker; mod database; -mod message; -pub mod node; -pub mod subscription; +mod ephemeral_message; +mod node; mod topic_store; -pub mod traits; +mod topic_stream; +mod traits; + +#[doc(hidden)] // FIXME: We're currently not supporting this feature. +pub use node::ConnectionMode; +pub use node::{Node, NodeError, TrackedAuthor, TrackedTopic}; +pub use topic_stream::{PublishError, TopicStream, TopicStreamError}; +pub use traits::{TopicSubscription, TopicSubscriptionError}; #[cfg(test)] mod tests { @@ -14,35 +27,18 @@ mod tests { use tokio::sync::{Mutex, mpsc}; use crate::node::{ConnectionMode, Node}; - use crate::traits::{SubscribableTopic, SubscriptionError}; - - #[tokio::test] - #[test_log::test] - async fn create_topic() { - let signing_key = SigningKey::generate(); - let network_id = Hash::digest(b"reflection"); - let node = Node::new(signing_key, network_id, None).await.unwrap(); - - let id: [u8; 32] = [0; 32]; - let _sub = node.subscribe(id, TestTopic::new()).await; - let topics = node.topics().await.unwrap(); - - assert_eq!(topics.len(), 1); - assert_eq!(topics.first().unwrap().id, id.into()); - - node.shutdown().await.unwrap(); - } + use crate::traits::{TopicSubscription, TopicSubscriptionError}; #[derive(Clone)] - struct TestTopic { + struct TestDocument { tx: mpsc::UnboundedSender>, rx: Arc>>>, } - impl TestTopic { + impl TestDocument { fn new() -> Self { let (tx, rx) = mpsc::unbounded_channel::>(); - TestTopic { + TestDocument { tx, rx: Arc::new(Mutex::new(rx)), } @@ -53,7 +49,7 @@ mod tests { } } - impl SubscribableTopic for TestTopic { + impl TopicSubscription for TestDocument { fn bytes_received(&self, _author: VerifyingKey, data: Vec) { self.tx.send(data).unwrap(); } @@ -62,7 +58,24 @@ mod tests { fn author_left(&self, _author: VerifyingKey) {} fn ephemeral_bytes_received(&self, _author: VerifyingKey, _timestamp: u64, _data: Vec) { } - fn error(&self, _error: SubscriptionError) {} + fn error(&self, _error: TopicSubscriptionError) {} + } + + #[tokio::test] + #[test_log::test] + async fn create_topic() { + let signing_key = SigningKey::generate(); + let network_id = Hash::digest(b"reflection"); + let node = Node::new(signing_key, network_id, None).await.unwrap(); + + let id: [u8; 32] = [0; 32]; + let _sub = node.stream(id, TestDocument::new()).await; + let topics = node.topics().await.unwrap(); + + assert_eq!(topics.len(), 1); + assert_eq!(topics.first().unwrap().topic, id.into()); + + node.shutdown().await.unwrap(); } #[tokio::test] @@ -78,9 +91,9 @@ mod tests { .await .unwrap(); - let test_topic = TestTopic::new(); + let test_topic = TestDocument::new(); - let subscription = node.subscribe(topic_id, test_topic).await.unwrap(); + let subscription = node.stream(topic_id, test_topic).await.unwrap(); let node2 = Node::new(SigningKey::generate(), network_id, None) .await @@ -90,12 +103,9 @@ mod tests { .await .unwrap(); - let test_topic2 = TestTopic::new(); + let test_topic2 = TestDocument::new(); - let _subscription2 = node2 - .subscribe(topic_id, test_topic2.clone()) - .await - .unwrap(); + let _subscription2 = node2.stream(topic_id, test_topic2.clone()).await.unwrap(); // TODO: Need to sleep here to make sure tx already exists. tokio::time::sleep(std::time::Duration::from_secs(3)).await; diff --git a/reflection-node/src/node.rs b/reflection-node/src/node.rs index b6b36ea..b31c9ce 100644 --- a/reflection-node/src/node.rs +++ b/reflection-node/src/node.rs @@ -9,10 +9,10 @@ use tokio::sync::{Notify, RwLock}; use tracing::info; use crate::database::{database_pool, run_migrations}; -use crate::subscription::{StoreError, Subscription, SubscriptionInner}; -pub use crate::topic_store::Author; -use crate::topic_store::{StoreTopic, TopicStore}; -use crate::traits::SubscribableTopic; +pub use crate::topic_store::TrackedAuthor; +use crate::topic_store::{TopicRow, TrackedTopicStore}; +use crate::topic_stream::{TopicStream, TopicStreamError, TopicStreamInner}; +use crate::traits::TopicSubscription; static DATABASE_FILE: &str = "database-v2.sqlite"; @@ -55,11 +55,11 @@ pub enum ConnectionMode { } #[derive(Clone, Debug)] -pub struct Topic { - pub id: p2panda::Topic, +pub struct TrackedTopic { + pub topic: p2panda::Topic, pub name: Option, pub last_accessed: Option>, - pub authors: Vec, + pub authors: Vec, } #[derive(Debug)] @@ -139,7 +139,7 @@ impl Node { Ok(()) } - pub async fn topics(&self) -> Result, StoreError> { + pub async fn topics(&self) -> Result, TopicStreamError> { let inner = self.inner.clone(); let topics = self .runtime @@ -149,14 +149,14 @@ impl Node { let topics = topics .into_iter() .map(|topic| { - let StoreTopic { + let TopicRow { id, name, last_accessed, authors, } = topic; - Topic { - id, + TrackedTopic { + topic: id, name, last_accessed, authors, @@ -167,33 +167,36 @@ impl Node { Ok(topics) } - pub async fn subscribe( + pub async fn stream( &self, - id: impl Into, - subscribable_topic: T, - ) -> Result, StoreError> + topic: impl Into, + subscription: T, + ) -> Result, TopicStreamError> where - T: SubscribableTopic + 'static, + T: TopicSubscription + 'static, { - let id = id.into(); - let subscribable_topic = Arc::new(subscribable_topic); + let topic = topic.into(); + let subscription = Arc::new(subscription); let inner = self.inner.clone(); let inner_subscription = self .runtime - .spawn(async move { inner.subscribe(id, subscribable_topic).await }) + .spawn(async move { inner.stream(topic, subscription).await }) .await??; - let subscription = Subscription::new(self.runtime.clone(), inner_subscription).await; - info!(%id, "subscribed to topic"); + let subscription = TopicStream::new(self.runtime.clone(), inner_subscription).await; + info!(%topic, "subscribed to topic"); Ok(subscription) } - pub async fn delete_topic(&self, id: impl Into) -> Result<(), StoreError> { - let id = id.into(); + pub async fn delete_topic( + &self, + topic: impl Into, + ) -> Result<(), TopicStreamError> { + let topic = topic.into(); let inner = self.inner.clone(); self.runtime - .spawn(async move { inner.delete_topic(id).await }) + .spawn(async move { inner.delete_topic(topic).await }) .await? } } @@ -202,7 +205,7 @@ impl Node { pub(crate) struct NodeInner { pub(crate) network: RwLock, pub(crate) shutdown_notifier: Notify, - pub(crate) topic_store: TopicStore, + pub(crate) topic_store: TrackedTopicStore, pub(crate) verifying_key: VerifyingKey, } @@ -217,7 +220,7 @@ impl NodeInner { let pool = database_pool(db_file).await?; run_migrations(&pool).await?; - let topic_store = TopicStore::from_pool(pool.clone()); + let topic_store = TrackedTopicStore::from_pool(pool.clone()); let mut builder = p2panda::Node::builder() .network_id(network_id.into()) @@ -255,32 +258,36 @@ impl NodeInner { self.shutdown_notifier.notify_waiters(); } - pub async fn subscribe( + pub async fn stream( self: Arc, - id: impl Into, + topic: impl Into, subscribable_topic: Arc, - ) -> Result, StoreError> + ) -> Result, TopicStreamError> where - T: SubscribableTopic + 'static, + T: TopicSubscription + 'static, { - let id = id.into(); + let topic = topic.into(); - self.topic_store.add_topic(&id).await?; + self.topic_store.add_topic(&topic).await?; // Add ourselves as an author to the topic store. self.topic_store - .add_author(&id, &self.verifying_key) + .add_author(&topic, &self.verifying_key) .await?; - Ok(SubscriptionInner::new(self.clone(), id, subscribable_topic)) + Ok(TopicStreamInner::new( + self.clone(), + topic, + subscribable_topic, + )) } pub async fn delete_topic( self: Arc, - id: impl Into, - ) -> Result<(), StoreError> { - let id = id.into(); - self.topic_store.delete_topic(&id).await?; + topic: impl Into, + ) -> Result<(), TopicStreamError> { + let topic = topic.into(); + self.topic_store.delete_topic(&topic).await?; Ok(()) } } diff --git a/reflection-node/src/topic_store.rs b/reflection-node/src/topic_store.rs index 9373ee1..dd0f2e3 100644 --- a/reflection-node/src/topic_store.rs +++ b/reflection-node/src/topic_store.rs @@ -5,34 +5,34 @@ use p2panda::{Topic, VerifyingKey}; use sqlx::{FromRow, Row}; #[derive(Debug, FromRow)] -pub struct StoreTopic { +pub struct TopicRow { #[sqlx(try_from = "Vec")] pub id: Topic, #[sqlx(default)] pub name: Option, pub last_accessed: Option>, #[sqlx(skip)] - pub authors: Vec, + pub authors: Vec, } #[derive(Debug, Clone)] -pub struct Author { +pub struct TrackedAuthor { pub verifying_key: VerifyingKey, pub last_seen: Option>, } #[derive(Clone, Debug)] -pub struct TopicStore { +pub struct TrackedTopicStore { pool: sqlx::SqlitePool, } -impl TopicStore { +impl TrackedTopicStore { pub fn from_pool(pool: sqlx::SqlitePool) -> Self { Self { pool } } - pub async fn topics(&self) -> sqlx::Result> { - let mut topics: Vec = + pub async fn topics(&self) -> sqlx::Result> { + let mut topics: Vec = sqlx::query_as("SELECT id, name, last_accessed FROM topics") .fetch_all(&self.pool) .await?; @@ -51,7 +51,7 @@ impl TopicStore { let Ok(last_seen) = row.try_get::>, _>("last_seen") else { return acc; }; - acc.entry(id).or_insert_with(Vec::new).push(Author { + acc.entry(id).or_insert_with(Vec::new).push(TrackedAuthor { verifying_key, last_seen, }); diff --git a/reflection-node/src/subscription.rs b/reflection-node/src/topic_stream.rs similarity index 89% rename from reflection-node/src/subscription.rs rename to reflection-node/src/topic_stream.rs index 8c27a11..7fe3e3b 100644 --- a/reflection-node/src/subscription.rs +++ b/reflection-node/src/topic_stream.rs @@ -13,9 +13,9 @@ use tokio_stream::StreamExt; use tracing::{error, info, warn}; use crate::author_tracker::AuthorTracker; -use crate::message::EphemeralMessage; +use crate::ephemeral_message::EphemeralMessage; use crate::node::NodeInner; -use crate::traits::SubscribableTopic; +use crate::traits::TopicSubscription; #[derive(Debug, Error)] pub enum PublishError { @@ -33,7 +33,7 @@ pub enum PublishError { } #[derive(Debug, Error)] -pub enum StoreError { +pub enum TopicStreamError { #[error(transparent)] Runtime(#[from] JoinError), @@ -41,23 +41,23 @@ pub enum StoreError { Database(#[from] sqlx::Error), } -pub struct Subscription { - inner: Arc>, +pub struct TopicStream { + inner: Arc>, runtime: tokio::runtime::Handle, network_monitor_task: AbortHandle, } -impl Drop for Subscription { +impl Drop for TopicStream { fn drop(&mut self) { self.network_monitor_task.abort(); } } -impl Subscription +impl TopicStream where - T: SubscribableTopic + 'static, + T: TopicSubscription + 'static, { - pub(crate) async fn new(runtime: tokio::runtime::Handle, inner: SubscriptionInner) -> Self { + pub(crate) async fn new(runtime: tokio::runtime::Handle, inner: TopicStreamInner) -> Self { let (ready_tx, ready_rx) = oneshot::channel(); // Spawn task to establish streams to publish and subscribe to messages, the same task will @@ -73,7 +73,7 @@ where // Wait until streams with network have been established. let _ = ready_rx.await; - Subscription { + TopicStream { inner, runtime, network_monitor_task, @@ -101,7 +101,7 @@ where .await? } - pub async fn unsubscribe(self) -> Result<(), StoreError> { + pub async fn unsubscribe(self) -> Result<(), TopicStreamError> { self.network_monitor_task.abort(); let inner = self.inner.clone(); @@ -109,7 +109,7 @@ where .spawn(async move { inner.unsubscribe().await }) .await??; - info!("unsubscribed from topic {}", self.inner.id); + info!("unsubscribed from topic {}", self.inner.topic); Ok(()) } @@ -117,7 +117,7 @@ where /// Set the name for a given topic. /// /// This information will be written to the database. - pub async fn set_name(&self, name: Option) -> Result<(), StoreError> { + pub async fn set_name(&self, name: Option) -> Result<(), TopicStreamError> { let inner = self.inner.clone(); self.runtime .spawn(async move { inner.set_name(name).await }) @@ -125,17 +125,17 @@ where } } -pub(crate) struct SubscriptionInner { +pub(crate) struct TopicStreamInner { tx: RwLock>>>, ephemeral_tx: RwLock>>, node: Arc, - id: Topic, - subscribable_topic: Arc, + topic: Topic, + subscription: Arc, author_tracker: Arc>, abort_handles: RwLock>, } -impl Drop for SubscriptionInner { +impl Drop for TopicStreamInner { fn drop(&mut self) { for handle in self.abort_handles.get_mut() { handle.abort(); @@ -143,20 +143,20 @@ impl Drop for SubscriptionInner { } } -impl SubscriptionInner +impl TopicStreamInner where - T: SubscribableTopic + 'static, + T: TopicSubscription + 'static, { - pub fn new(node: Arc, id: Topic, subscribable_topic: Arc) -> Self { - let author_tracker = AuthorTracker::new(node.clone(), subscribable_topic.clone()); + pub fn new(node: Arc, topic: Topic, subscription: Arc) -> Self { + let author_tracker = AuthorTracker::new(node.clone(), subscription.clone()); - SubscriptionInner { + TopicStreamInner { tx: RwLock::new(None), ephemeral_tx: RwLock::new(None), node, - id, + topic, abort_handles: RwLock::new(Vec::new()), - subscribable_topic, + subscription, author_tracker, } } @@ -168,8 +168,8 @@ where let result = setup_streams( &self.node, network_guard.deref(), - self.id, - &self.subscribable_topic, + self.topic, + &self.subscription, &self.author_tracker, ) .await; @@ -181,7 +181,7 @@ where *self.abort_handles.write().await = abort_handles; } Err(error) => { - self.subscribable_topic.error(error.into()); + self.subscription.error(error.into()); } } @@ -198,7 +198,7 @@ where let _ = self.unsubscribe().await; } - pub async fn unsubscribe(&self) -> Result<(), StoreError> { + pub async fn unsubscribe(&self) -> Result<(), TopicStreamError> { let mut tx_guard = self.tx.write().await; let mut ephemeral_tx_guard = self.ephemeral_tx.write().await; let mut abort_handles_guard = self.abort_handles.write().await; @@ -209,11 +209,11 @@ where self.node .topic_store - .set_last_accessed_for_topic(&self.id, Some(Utc::now())) + .set_last_accessed_for_topic(&self.topic, Some(Utc::now())) .await?; teardown_streams( - &self.id, + &self.topic, &self.author_tracker, tx, ephemeral_tx, @@ -226,7 +226,7 @@ where pub async fn publish_delta(&self, data: Vec) -> Result<(), PublishError> { if let Some(tx) = self.tx.read().await.as_ref() { - info!("delta operation sent for topic with id {}", self.id); + info!("delta operation sent for topic with id {}", self.topic); tx.publish(data).await?; } else { return Err(PublishError::BrokenStream); @@ -237,7 +237,7 @@ where pub async fn publish_snapshot(&self, data: Vec) -> Result<(), PublishError> { if let Some(tx) = self.tx.read().await.as_ref() { - info!("snapshot saved for topic with id {}", self.id); + info!("snapshot saved for topic with id {}", self.topic); // Append an operation to our log and set the prune flag to true. This will remove // previous entries. @@ -261,10 +261,10 @@ where Ok(()) } - pub async fn set_name(&self, name: Option) -> Result<(), StoreError> { + pub async fn set_name(&self, name: Option) -> Result<(), TopicStreamError> { self.node .topic_store - .set_name_for_topic(&self.id, name) + .set_name_for_topic(&self.topic, name) .await?; Ok(()) @@ -286,7 +286,7 @@ async fn setup_streams( CreateStreamError, > where - T: SubscribableTopic + 'static, + T: TopicSubscription + 'static, { let mut abort_handles = Vec::with_capacity(3); @@ -373,7 +373,7 @@ where } StreamEvent::ReplayFailed { error, .. } => { error!("error occurred while replaying operation stream: {error}"); - subscribable_topic_clone.error(crate::traits::SubscriptionError::ReplayStream(error)); + subscribable_topic_clone.error(crate::traits::TopicSubscriptionError::ReplayStream(error)); } StreamEvent::ProcessingFailed { event, error, .. } => { error!("error occurred while processing operation {}: {error}", event.header().hash()); @@ -381,7 +381,7 @@ where } StreamEvent::AckFailed { error, .. } => { error!("error occurred while acking event: {error}"); - subscribable_topic_clone.error(crate::traits::SubscriptionError::AckedMessage(error)); + subscribable_topic_clone.error(crate::traits::TopicSubscriptionError::AckedMessage(error)); } _ => (), } @@ -441,13 +441,13 @@ where } async fn teardown_streams( - id: &Topic, + topic: &Topic, author_tracker: &Arc>, tx: Option>>, ephemeral_tx: Option>, abort_handles: Vec, ) where - T: SubscribableTopic + 'static, + T: TopicSubscription + 'static, { for handle in abort_handles { handle.abort(); @@ -456,7 +456,7 @@ async fn teardown_streams( author_tracker.set_topic_tx(None).await; if tx.is_some() { - info!("network streams torn down for topic {}", id); + info!("network streams torn down for topic {}", topic); } drop(tx); diff --git a/reflection-node/src/traits.rs b/reflection-node/src/traits.rs index 83ad9b3..2aba5a6 100644 --- a/reflection-node/src/traits.rs +++ b/reflection-node/src/traits.rs @@ -7,7 +7,7 @@ use p2panda::streams::{AckedError, DecodeError, ReplayError}; use thiserror::Error; #[derive(Debug, Error)] -pub enum SubscriptionError { +pub enum TopicSubscriptionError { /// Broken / closed communication channel with the internal actor in `p2panda-net` prevented /// creation of stream. This can be due to the actor crashing. #[error(transparent)] @@ -30,10 +30,19 @@ pub enum SubscriptionError { StreamProcessor(#[from] ProcessorError), } -pub trait SubscribableTopic: Sync + Send { +pub trait TopicSubscription: Sync + Send { + /// Received bytes from eventually consistent topic stream (sync protocol). fn bytes_received(&self, author: VerifyingKey, data: Vec); + + /// Received bytes from ephemeral stream (gossip overlay). fn ephemeral_bytes_received(&self, author: VerifyingKey, timestamp: u64, data: Vec); + + /// Author went online. fn author_joined(&self, author: VerifyingKey); + + /// Author went offline or timed-out. fn author_left(&self, author: VerifyingKey); - fn error(&self, error: SubscriptionError); + + /// Error occurred in topic subscription. + fn error(&self, error: TopicSubscriptionError); }